mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Update method for user pages and format code
This commit is contained in:
@@ -1,9 +1,25 @@
|
|||||||
import styled, { css } from 'styled-components';
|
import styled, { RuleSet, css } from 'styled-components';
|
||||||
|
|
||||||
type TButtonStyle = 'primary' | 'secondary';
|
interface IVariations {
|
||||||
|
[key: string]: RuleSet<object>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variations: IVariations = {
|
||||||
|
primary: css`
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--light-text);
|
||||||
|
`,
|
||||||
|
secondary: css`
|
||||||
|
background-color: var(--secondary-btn-color);
|
||||||
|
`,
|
||||||
|
danger: css`
|
||||||
|
background-color: var(--danger-btn-color);
|
||||||
|
color: var(--light-text);
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
interface IButtonStyle {
|
interface IButtonStyle {
|
||||||
styled?: TButtonStyle;
|
variations?: keyof IVariations;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = styled.button<IButtonStyle>`
|
const Button = styled.button<IButtonStyle>`
|
||||||
@@ -16,22 +32,16 @@ const Button = styled.button<IButtonStyle>`
|
|||||||
|
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
|
|
||||||
${(props) =>
|
${(props) => variations[props.variations!]}
|
||||||
props.styled === 'primary' &&
|
|
||||||
css`
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: var(--light-text);
|
|
||||||
`}
|
|
||||||
|
|
||||||
${(props) =>
|
&:disabled,
|
||||||
props.styled === 'secondary' &&
|
&[disabled] {
|
||||||
css`
|
cursor: no-drop;
|
||||||
background-color: var(--secondary-btn-color);
|
}
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Button.defaultProps = {
|
Button.defaultProps = {
|
||||||
styled: 'primary',
|
variations: 'primary',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Button;
|
export default Button;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
// Styled
|
||||||
|
import { StyledConfirmDelete } from './styled';
|
||||||
|
|
||||||
|
// Component
|
||||||
|
import Button from '@commonStyle/Button';
|
||||||
|
|
||||||
|
interface IConfirmMessage {
|
||||||
|
message: string;
|
||||||
|
disabled: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCloseModal?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmMessage = memo(
|
||||||
|
({ message, disabled, onConfirm, onCloseModal }: IConfirmMessage) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledConfirmDelete>
|
||||||
|
<p>{message}</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button variations="danger" disabled={disabled} onClick={onConfirm}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variations="secondary"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onCloseModal}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</StyledConfirmDelete>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ConfirmMessage;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const StyledConfirmDelete = styled.div`
|
||||||
|
width: 450px;
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
padding-inline: 50px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledConfirmDelete };
|
||||||
@@ -5,19 +5,26 @@ import { StyledSearch } from './styled';
|
|||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useDebounce } from '@hook/useDebounce';
|
import { useDebounce } from '@hook/useDebounce';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { Nullable } from '@type/common';
|
||||||
|
|
||||||
interface ISearch {
|
interface ISearch {
|
||||||
setPlaceHolder: string;
|
setPlaceHolder: string;
|
||||||
setValueSearch: (phone: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Search = ({ setValueSearch, setPlaceHolder }: ISearch) => {
|
const Search = ({ setPlaceHolder }: ISearch) => {
|
||||||
const [query, setQuery] = useState('');
|
const field = 'search';
|
||||||
const debounceValue = useDebounce<string>(query, 700);
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const [query, setQuery] = useState<Nullable<string>>(null);
|
||||||
|
const debounceValue = useDebounce<Nullable<string>>(query, 700);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValueSearch(debounceValue);
|
if (debounceValue !== null) {
|
||||||
}, [debounceValue, setValueSearch]);
|
searchParams.set(field, debounceValue);
|
||||||
|
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}
|
||||||
|
}, [debounceValue, searchParams, setSearchParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSearch
|
<StyledSearch
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const Toast = ({
|
|||||||
Toast.defaultProps = {
|
Toast.defaultProps = {
|
||||||
position: 'top-center',
|
position: 'top-center',
|
||||||
gutter: 12,
|
gutter: 12,
|
||||||
containerStyle: { margin: '8px', zIndex: 1 },
|
containerStyle: { margin: '8px', zIndex: 2000 },
|
||||||
success: {
|
success: {
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const ROOM_PAGE = {
|
|||||||
label: 'Sort by name',
|
label: 'Sort by name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'finalPrice',
|
value: 'price',
|
||||||
label: 'Sort by price',
|
label: 'Sort by price',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -59,6 +59,7 @@ const INIT_VALUE_ROOM_FORM = {
|
|||||||
|
|
||||||
const FORM = {
|
const FORM = {
|
||||||
EDIT: 'edit',
|
EDIT: 'edit',
|
||||||
|
DELETE: 'delete',
|
||||||
USER: 'user-form',
|
USER: 'user-form',
|
||||||
ROOM: 'room-form',
|
ROOM: 'room-form',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const TIME_OUT_SEC = 1;
|
|
||||||
const DEFAULT_SORT_BY = 'id';
|
const DEFAULT_SORT_BY = 'id';
|
||||||
const DEFAULT_ORDER_BY = 'asc';
|
const DEFAULT_ORDER_BY = 'asc';
|
||||||
|
const supabaseUrl = 'https://pjqujsjzzdrlrgqbxepe.supabase.co'
|
||||||
|
const supabaseKey = import.meta.env.VITE_SUPABASE_KEY
|
||||||
|
|
||||||
export { TIME_OUT_SEC, DEFAULT_SORT_BY, DEFAULT_ORDER_BY };
|
export { DEFAULT_SORT_BY, DEFAULT_ORDER_BY, supabaseKey, supabaseUrl };
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
const ADD_SUCCESS = 'Add success';
|
const ADD_SUCCESS = 'Add success';
|
||||||
const EDIT_SUCCESS = 'Edit success';
|
const UPDATE_SUCCESS = 'Update 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,
|
||||||
EDIT_SUCCESS,
|
UPDATE_SUCCESS,
|
||||||
CONFIRM_MESSAGE,
|
CONFIRM_MESSAGE,
|
||||||
CONFIRM_DELETE,
|
CONFIRM_DELETE,
|
||||||
DELETE_SUCCESS,
|
DELETE_SUCCESS,
|
||||||
CHECKOUT_SUCCESS,
|
CHECKOUT_SUCCESS,
|
||||||
DENIED_ACTION
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ 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 = 'http://localhost:3000/';
|
|
||||||
export const USER_PATH = 'users';
|
export const USER_PATH = 'users';
|
||||||
export const ROOM_PATH = 'rooms';
|
export const ROOM_PATH = 'rooms';
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
const STATUS_CODE = {
|
|
||||||
OK: 200,
|
|
||||||
CREATE: 201,
|
|
||||||
NOT_FOUND: 404,
|
|
||||||
};
|
|
||||||
|
|
||||||
const RESPONSE_MESSAGE = {
|
|
||||||
UPDATE_SUCCESS: 'Update success',
|
|
||||||
};
|
|
||||||
|
|
||||||
export { STATUS_CODE, RESPONSE_MESSAGE };
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createClient } from '@supabase/supabase-js'
|
|
||||||
import { Database } from '@type/supabase';
|
|
||||||
const supabaseUrl = 'https://pjqujsjzzdrlrgqbxepe.supabase.co'
|
|
||||||
const supabaseKey = import.meta.env.VITE_SUPABASE_KEY
|
|
||||||
const supabase = createClient<Database>(supabaseUrl, supabaseKey!)
|
|
||||||
|
|
||||||
export default supabase;
|
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
// Constants
|
|
||||||
import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert value to currency format with value
|
* Convert value to currency format with value
|
||||||
* @param value Value need to be converted
|
* @param value Value need to be converted
|
||||||
@@ -13,14 +10,4 @@ const formatCurrency = (value: number): string => {
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export { formatCurrency };
|
||||||
* 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, formatCurrency, REQUIRED_FIELD_ERROR };
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
// Constants
|
|
||||||
import { BASE_URL } from '@constant/path';
|
|
||||||
|
|
||||||
// Types
|
|
||||||
import { IResponse } from '@type/responses';
|
|
||||||
|
|
||||||
type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 <T>(
|
|
||||||
path: string,
|
|
||||||
method: TMethodRequest = 'GET',
|
|
||||||
body?: BodyInit
|
|
||||||
): Promise<IResponse<T>> => {
|
|
||||||
const response = await fetch(BASE_URL + path, {
|
|
||||||
method,
|
|
||||||
body,
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = (await response.json()) as T;
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
msg: response.statusText,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
const isValidRegex = (regex: RegExp, value: string): boolean => {
|
const isValidRegex = (regex: RegExp, value: string): boolean => {
|
||||||
return regex.test(value);
|
return regex.test(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check value is valid discount or not
|
* Check value is valid discount or not
|
||||||
@@ -17,7 +17,6 @@ const isValidDiscount = (value: number): boolean => {
|
|||||||
return value >= 0 && value <= 100;
|
return value >= 0 && value <= 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check value is valid string or not
|
* Check value is valid string or not
|
||||||
* @param value Value need to be checked
|
* @param value Value need to be checked
|
||||||
@@ -27,18 +26,4 @@ const isValidString = (value: string): boolean => {
|
|||||||
return value.trim().length >= 5;
|
return value.trim().length >= 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export { isValidRegex, isValidString, isValidDiscount };
|
||||||
* Check object is empty or not
|
|
||||||
* @param obj Object need to be check
|
|
||||||
* @returns A boolean indicating whether or not the argument has valid
|
|
||||||
*/
|
|
||||||
const isEmptyObj = (obj: object) => {
|
|
||||||
return Object.keys(obj).length === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
isValidRegex,
|
|
||||||
isValidString,
|
|
||||||
isValidDiscount,
|
|
||||||
isEmptyObj,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { createRoom as createRoomFn } from '@service/roomServices';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { ADD_SUCCESS } from '@constant/messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create room on database
|
||||||
|
* @returns The boolean of isCreating and create room function
|
||||||
|
*/
|
||||||
|
const useCreateRoom = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutate: createRoom, isPending: isCreating } = useMutation({
|
||||||
|
mutationFn: createRoomFn,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(ADD_SUCCESS);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['rooms'] });
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isCreating, createRoom };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useCreateRoom };
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { deleteRoom as deleteRoomFn } from '@service/roomServices';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import * as messages from '@constant/messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the room from database
|
||||||
|
* @returns The boolean of status deleting and deleteRoom function
|
||||||
|
*/
|
||||||
|
const useDeleteRoom = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { isPending: isDeleting, mutate: deleteRoom } = useMutation({
|
||||||
|
mutationFn: deleteRoomFn,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(messages.DELETE_SUCCESS);
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['rooms'],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isDeleting, deleteRoom };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useDeleteRoom };
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { getAllRooms } from "@service/roomServices";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch room from database
|
||||||
|
* @returns The status of loading rooms from database and data of rooms
|
||||||
|
*/
|
||||||
|
const useRooms = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const sortByValue = searchParams.get('sortBy') || 'id';
|
||||||
|
const orderByValue = searchParams.get('orderBy') || 'asc';
|
||||||
|
const phoneSearch = searchParams.get('search') || ''
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
data: rooms,
|
||||||
|
error,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ['rooms', sortByValue, orderByValue, phoneSearch],
|
||||||
|
queryFn: () => getAllRooms(sortByValue, orderByValue, phoneSearch),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isLoading, rooms };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useRooms };
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { updateRoom as updateRoomFn } from '@service/roomServices';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { UPDATE_SUCCESS } from '@constant/messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update room from database
|
||||||
|
* @returns The status of updating and updateRoom function
|
||||||
|
*/
|
||||||
|
const useUpdateRoom = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutate: updateRoom, isPending: isUpdating } = useMutation({
|
||||||
|
mutationFn: updateRoomFn,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(UPDATE_SUCCESS);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['rooms'] });
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isUpdating, updateRoom };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useUpdateRoom };
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { ForwardedRef, useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
// Types
|
|
||||||
import { Nullable } from '@type/common';
|
|
||||||
|
|
||||||
const useForwardRef = <T>(
|
|
||||||
ref: ForwardedRef<T>,
|
|
||||||
initialValue: Nullable<T> = null
|
|
||||||
) => {
|
|
||||||
const targetRef = useRef<T>(initialValue);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ref) return;
|
|
||||||
|
|
||||||
if (typeof ref === 'function') {
|
|
||||||
ref(targetRef.current);
|
|
||||||
} else {
|
|
||||||
ref.current = targetRef.current;
|
|
||||||
}
|
|
||||||
}, [ref]);
|
|
||||||
|
|
||||||
return targetRef;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { useForwardRef };
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { createUser as createUserFn } from '@service/userServices';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { ADD_SUCCESS } from '@constant/messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create user in database
|
||||||
|
* @returns The status of creating users and createUser function
|
||||||
|
*/
|
||||||
|
const useCreateUser = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutate: createUser, isPending: isCreating } = useMutation({
|
||||||
|
mutationFn: createUserFn,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(ADD_SUCCESS);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isCreating, createUser };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useCreateUser };
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { updateUser as updateUserFn } from '@service/userServices';
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
import { UPDATE_SUCCESS } from '@constant/messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user from database
|
||||||
|
* @returns The status of updating user and updateUser function
|
||||||
|
*/
|
||||||
|
const useUpdateUser = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutate: updateUser, isPending: isUpdating } = useMutation({
|
||||||
|
mutationFn: updateUserFn,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(UPDATE_SUCCESS);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isUpdating, updateUser };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useUpdateUser };
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { getAllUsers } from "@service/userServices";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data of users from database
|
||||||
|
* @returns The status of loading users and data of users
|
||||||
|
*/
|
||||||
|
const useUsers = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const sortByValue = searchParams.get('sortBy') || 'id';
|
||||||
|
const orderByValue = searchParams.get('orderBy') || 'asc';
|
||||||
|
const phoneSearch = searchParams.get('search') || ''
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
data: users,
|
||||||
|
error,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ['users', sortByValue, orderByValue, phoneSearch],
|
||||||
|
queryFn: () => getAllUsers(sortByValue, orderByValue, phoneSearch),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isLoading, users };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useUsers };
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useCallback } from 'react';
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Menus from '@component/Menus';
|
import Menus from '@component/Menus';
|
||||||
@@ -21,24 +20,18 @@ import { StyledOperationTable } from './styled';
|
|||||||
import Direction from '@commonStyle/Direction';
|
import Direction from '@commonStyle/Direction';
|
||||||
import Spinner from '@commonStyle/Spinner';
|
import Spinner from '@commonStyle/Spinner';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useRooms } from '@hook/rooms/useRooms';
|
||||||
|
|
||||||
interface IRoomTable {
|
const RoomTable = () => {
|
||||||
reload: boolean;
|
|
||||||
setReload: Dispatch<SetStateAction<boolean>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoomTable = ({ reload, setReload }: IRoomTable) => {
|
|
||||||
const columnName = ['Id', 'Name', 'Price', 'Status'];
|
const columnName = ['Id', 'Name', 'Price', 'Status'];
|
||||||
const [nameSearch, setNameSearch] = useState('');
|
const { isLoading, rooms } = useRooms();
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const [rooms, setRooms] = useState<IRoom[]>([]);
|
const renderRoomRow = useCallback(
|
||||||
const sortByValue = searchParams.get('sortBy')
|
(room: IRoom) => <RoomRow room={room} key={room.id} />,
|
||||||
? searchParams.get('sortBy')!
|
[]
|
||||||
: '';
|
);
|
||||||
const orderByValue = searchParams.get('orderBy')
|
|
||||||
? searchParams.get('orderBy')!
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Direction>
|
<Direction>
|
||||||
@@ -46,33 +39,20 @@ const RoomTable = ({ reload, setReload }: IRoomTable) => {
|
|||||||
<OrderBy options={ORDERBY_OPTIONS} />
|
<OrderBy options={ORDERBY_OPTIONS} />
|
||||||
|
|
||||||
<SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />
|
<SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />
|
||||||
<Search
|
<Search setPlaceHolder="Search by name..." />
|
||||||
setValueSearch={setNameSearch}
|
|
||||||
setPlaceHolder="Search by name..."
|
|
||||||
/>
|
|
||||||
</StyledOperationTable>
|
</StyledOperationTable>
|
||||||
|
|
||||||
{isPending && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
|
|
||||||
{rooms.length ? (
|
{rooms && rooms.length ? (
|
||||||
<Menus>
|
<Menus>
|
||||||
<Table columns="10% 40% 20% 20% 5%">
|
<Table columns="10% 40% 20% 20% 5%">
|
||||||
<Table.Header headerColumn={columnName}/>
|
<Table.Header headerColumn={columnName} />
|
||||||
<Table.Body<IRoom>
|
<Table.Body<IRoom> data={rooms} render={renderRoomRow} />
|
||||||
data={rooms}
|
|
||||||
render={(room: IRoom) => (
|
|
||||||
<RoomRow
|
|
||||||
room={room}
|
|
||||||
key={room.id}
|
|
||||||
reload={reload}
|
|
||||||
setReload={setReload}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Table>
|
</Table>
|
||||||
</Menus>
|
</Menus>
|
||||||
) : (
|
) : (
|
||||||
!isPending && <Message>No data to show here!</Message>
|
!isLoading && <Message>No data to show here!</Message>
|
||||||
)}
|
)}
|
||||||
</Direction>
|
</Direction>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
import { Dispatch, SetStateAction, useEffect } from 'react';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
// Hooks
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import Button from '@commonStyle/Button.ts';
|
import Button from '@commonStyle/Button.ts';
|
||||||
import Input from '@commonStyle/Input.ts';
|
import Input from '@commonStyle/Input.ts';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Nullable } from '@type/common.ts';
|
|
||||||
import { IRoom } from '@type/rooms.ts';
|
import { IRoom } from '@type/rooms.ts';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts';
|
|
||||||
import {
|
import {
|
||||||
INVALID_DISCOUNT,
|
|
||||||
INVALID_FIELD,
|
INVALID_FIELD,
|
||||||
REQUIRED_FIELD_ERROR,
|
REQUIRED_FIELD_ERROR,
|
||||||
} from '@constant/formValidateMessage.ts';
|
} from '@constant/formValidateMessage.ts';
|
||||||
import { INIT_VALUE_ROOM_FORM, REGEX } from '../../constants/commons.ts';
|
import { REGEX } from '../../constants/commons.ts';
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import {
|
import { isValidRegex, isValidString } from '@helper/validators.ts';
|
||||||
isValidDiscount,
|
|
||||||
isValidRegex,
|
|
||||||
isValidString,
|
|
||||||
} from '@helper/validators.ts';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import FormRow from '@component/LabelControl/index.tsx';
|
import FormRow from '@component/LabelControl/index.tsx';
|
||||||
import Form from '@component/Form/index.tsx';
|
import Form from '@component/Form/index.tsx';
|
||||||
|
|
||||||
// Services
|
// Hooks
|
||||||
import { addRoom, updateRoom } from '@service/roomServices.ts';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useCreateRoom } from '@hook/rooms/useCreateRoom.ts';
|
||||||
|
import { useUpdateRoom } from '@hook/rooms/useUpdateRoom.ts';
|
||||||
|
|
||||||
const FormBtn = styled(Button)`
|
const FormBtn = styled(Button)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -48,65 +38,51 @@ const FormBtn = styled(Button)`
|
|||||||
|
|
||||||
interface IRoomFormProp {
|
interface IRoomFormProp {
|
||||||
onCloseModal?: () => void;
|
onCloseModal?: () => void;
|
||||||
reload?: boolean;
|
room?: IRoom;
|
||||||
setReload?: Dispatch<SetStateAction<boolean>>;
|
|
||||||
roomEdit?: Nullable<IRoom>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomForm = ({
|
const RoomForm = ({ onCloseModal, room }: IRoomFormProp) => {
|
||||||
onCloseModal,
|
const { isCreating, createRoom } = useCreateRoom();
|
||||||
reload,
|
const { isUpdating, updateRoom } = useUpdateRoom();
|
||||||
setReload,
|
const isLoading = isCreating || isUpdating;
|
||||||
roomEdit,
|
const { id: editId, ...editValues } = { ...room };
|
||||||
}: IRoomFormProp) => {
|
const formMethods = useForm<IRoom>({
|
||||||
const formMethods = useForm<IRoom>();
|
defaultValues: editId
|
||||||
|
? editValues
|
||||||
|
: {},
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isDirty, isValid, isSubmitting },
|
formState: { errors, isDirty, isValid },
|
||||||
trigger,
|
trigger,
|
||||||
} = formMethods;
|
} = formMethods;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (roomEdit) {
|
|
||||||
reset(roomEdit);
|
|
||||||
} else {
|
|
||||||
reset(INIT_VALUE_ROOM_FORM);
|
|
||||||
}
|
|
||||||
}, [roomEdit, reset]);
|
|
||||||
|
|
||||||
// Submit form
|
// Submit form
|
||||||
const onSubmit = async (room: IRoom) => {
|
const onSubmit = async (newRoom: IRoom) => {
|
||||||
// Calculate final price
|
if (!editId) {
|
||||||
room.finalPrice = room.price - (room.price * room.discount) / 100;
|
|
||||||
|
|
||||||
if (!roomEdit) {
|
|
||||||
// Add request
|
// Add request
|
||||||
const response = await addRoom(room);
|
createRoom(newRoom, {
|
||||||
|
onSuccess: () => {
|
||||||
if (response) {
|
reset();
|
||||||
toast.success(ADD_SUCCESS);
|
onCloseModal?.();
|
||||||
}
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Edit request
|
// Edit request
|
||||||
const response = await updateRoom(room);
|
newRoom.id = editId!;
|
||||||
|
updateRoom(newRoom, {
|
||||||
if (response) {
|
onSuccess: () => {
|
||||||
toast.success(EDIT_SUCCESS);
|
reset();
|
||||||
}
|
onCloseModal?.();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload table data
|
|
||||||
setReload!(!reload);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
onCloseModal!();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit(onSubmit)}>
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Input type="hidden" id="id" {...register('id')} />
|
|
||||||
<FormRow label="Name" error={errors?.name?.message}>
|
<FormRow label="Name" error={errors?.name?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -114,7 +90,8 @@ const RoomForm = ({
|
|||||||
{...register('name', {
|
{...register('name', {
|
||||||
required: REQUIRED_FIELD_ERROR,
|
required: REQUIRED_FIELD_ERROR,
|
||||||
validate: {
|
validate: {
|
||||||
checkValidRoomName: (value) => isValidString(value) || INVALID_FIELD,
|
checkValidRoomName: (value) =>
|
||||||
|
isValidString(value) || INVALID_FIELD,
|
||||||
},
|
},
|
||||||
onChange: () => trigger('name'),
|
onChange: () => trigger('name'),
|
||||||
})}
|
})}
|
||||||
@@ -130,41 +107,27 @@ const RoomForm = ({
|
|||||||
required: REQUIRED_FIELD_ERROR,
|
required: REQUIRED_FIELD_ERROR,
|
||||||
validate: {
|
validate: {
|
||||||
checkPrice: (v) =>
|
checkPrice: (v) =>
|
||||||
isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) || INVALID_FIELD,
|
isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) ||
|
||||||
|
INVALID_FIELD,
|
||||||
},
|
},
|
||||||
onChange: () => trigger('price'),
|
onChange: () => trigger('price'),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<FormRow label="discount" error={errors?.discount?.message}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
id="phone"
|
|
||||||
{...register('discount', {
|
|
||||||
valueAsNumber: true,
|
|
||||||
required: REQUIRED_FIELD_ERROR,
|
|
||||||
validate: {
|
|
||||||
checkDiscount: (v) => isValidDiscount(v) || INVALID_DISCOUNT,
|
|
||||||
},
|
|
||||||
onChange: () => trigger('discount'),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<Form.Action>
|
<Form.Action>
|
||||||
<FormBtn
|
<FormBtn
|
||||||
type="submit"
|
type="submit"
|
||||||
name="submit"
|
name="submit"
|
||||||
disabled={!isDirty || !isValid || isSubmitting}
|
disabled={!isDirty || !isValid || isLoading}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!roomEdit
|
!editId
|
||||||
? 'Add'
|
? 'Add'
|
||||||
: 'Save'
|
: 'Save'
|
||||||
}
|
}
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={onCloseModal}>
|
<FormBtn type="button" variations="secondary" onClick={onCloseModal}>
|
||||||
Close
|
Close
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
</Form.Action>
|
</Form.Action>
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { Dispatch, SetStateAction } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
import { ROOM_PATH } from '@constant/path';
|
|
||||||
import { STATUS_CODE } from '@constant/responseStatus';
|
|
||||||
import { CONFIRM_DELETE, DELETE_SUCCESS } from '@constant/messages';
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { sendRequest } from '@helper/sendRequest';
|
|
||||||
import { formatCurrency } from '@helper/helper';
|
import { formatCurrency } from '@helper/helper';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
@@ -20,38 +13,47 @@ import Table from '@component/Table';
|
|||||||
import Menus from '@component/Menus';
|
import Menus from '@component/Menus';
|
||||||
import { RiEditBoxFill } from 'react-icons/ri';
|
import { RiEditBoxFill } from 'react-icons/ri';
|
||||||
import { HiTrash } from 'react-icons/hi';
|
import { HiTrash } from 'react-icons/hi';
|
||||||
|
import ConfirmMessage from '@component/ConfirmMessage';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import { useDeleteRoom } from '@hook/rooms/useDeleteRoom';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { FORM } from '@constant/commons';
|
||||||
|
|
||||||
interface IRoomRow {
|
interface IRoomRow {
|
||||||
room: IRoom;
|
room: IRoom;
|
||||||
reload: boolean;
|
|
||||||
setReload: Dispatch<SetStateAction<boolean>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomRow = ({ room, reload, setReload }: IRoomRow) => {
|
const RoomRow = ({ room }: IRoomRow) => {
|
||||||
const handleDelete = async (room: IRoom) => {
|
const { id, name, price, status } = room;
|
||||||
if (confirm(CONFIRM_DELETE)) {
|
const { isDeleting, deleteRoom } = useDeleteRoom();
|
||||||
const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE');
|
|
||||||
|
|
||||||
if (response.statusCode === STATUS_CODE.OK) {
|
|
||||||
toast.success(DELETE_SUCCESS);
|
|
||||||
|
|
||||||
// Reload table
|
|
||||||
setReload(!reload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { id, name, finalPrice, status } = room;
|
|
||||||
|
|
||||||
const statusText = status
|
const statusText = status
|
||||||
? 'Unavailable'
|
? 'Unavailable'
|
||||||
: 'Available';
|
: 'Available';
|
||||||
|
const formattedPrice = useMemo(() => formatCurrency(price), [price]);
|
||||||
|
const renderEditBtn = useCallback(
|
||||||
|
(onCloseModal: () => void) => (
|
||||||
|
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}>
|
||||||
|
Edit
|
||||||
|
</Menus.Button>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const renderDeleteBtn = useCallback(
|
||||||
|
(onCloseModal: () => void) => (
|
||||||
|
<Menus.Button icon={<HiTrash />} onClick={onCloseModal}>
|
||||||
|
Delete
|
||||||
|
</Menus.Button>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<div>{id}</div>
|
<div>{id}</div>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
<div>{formatCurrency(finalPrice)}</div>
|
<div>{formattedPrice}</div>
|
||||||
<div>{statusText}</div>
|
<div>{statusText}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -61,24 +63,25 @@ const RoomRow = ({ room, reload, setReload }: IRoomRow) => {
|
|||||||
|
|
||||||
<Menus.List id={id.toString()}>
|
<Menus.List id={id.toString()}>
|
||||||
<Modal.Open
|
<Modal.Open
|
||||||
modalName="edit"
|
modalName={FORM.EDIT}
|
||||||
renderChildren={(onCloseModal) => (
|
renderChildren={renderEditBtn}
|
||||||
<Menus.Button onClick={onCloseModal} icon={<RiEditBoxFill />}>
|
/>
|
||||||
Edit
|
<Modal.Open
|
||||||
</Menus.Button>
|
modalName={FORM.DELETE}
|
||||||
)}
|
renderChildren={renderDeleteBtn}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Menus.Button
|
|
||||||
icon={<HiTrash />}
|
|
||||||
onClick={() => handleDelete(room)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Menus.Button>
|
|
||||||
</Menus.List>
|
</Menus.List>
|
||||||
|
|
||||||
<Modal.Window name="edit" title="Edit Room">
|
<Modal.Window name={FORM.EDIT} title="Edit Room">
|
||||||
<RoomForm roomEdit={room} setReload={setReload} reload={reload} />
|
<RoomForm room={room} />
|
||||||
|
</Modal.Window>
|
||||||
|
|
||||||
|
<Modal.Window name={FORM.DELETE} title="Delete Room">
|
||||||
|
<ConfirmMessage
|
||||||
|
disabled={isDeleting}
|
||||||
|
message={`Are you sure to delete ${name}?`}
|
||||||
|
onConfirm={() => deleteRoom(id)}
|
||||||
|
/>
|
||||||
</Modal.Window>
|
</Modal.Window>
|
||||||
</Menus.Menu>
|
</Menus.Menu>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import RoomTable from './RomTable';
|
import RoomTable from './RomTable';
|
||||||
import RoomForm from './RoomForm';
|
import RoomForm from './RoomForm';
|
||||||
|
import Modal from '@component/Modal';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { StyledRoom, Title } from './styled';
|
import { StyledRoom, Title } from './styled';
|
||||||
import Direction from '@commonStyle/Direction.ts';
|
import Direction from '@commonStyle/Direction.ts';
|
||||||
import Button from '@commonStyle/Button';
|
import Button from '@commonStyle/Button';
|
||||||
|
|
||||||
// Types
|
|
||||||
import Modal from '@component/Modal';
|
|
||||||
|
|
||||||
const Room = () => {
|
const Room = () => {
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledRoom>
|
<StyledRoom>
|
||||||
@@ -29,12 +23,12 @@ const Room = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Modal.Window name="room-form" title="Add form">
|
<Modal.Window name="room-form" title="Add form">
|
||||||
<RoomForm setReload={setReload} reload={reload} />
|
<RoomForm />
|
||||||
</Modal.Window>
|
</Modal.Window>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Direction>
|
</Direction>
|
||||||
|
|
||||||
<RoomTable reload={reload} setReload={setReload} />
|
<RoomTable />
|
||||||
</StyledRoom>
|
</StyledRoom>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import {
|
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import { useCreateUser } from '@hook/users/useCreateUser.ts';
|
||||||
|
import { useUpdateUser } from '@hook/users/useUpdateUser.ts';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import Input from '@commonStyle/Input.ts';
|
import Input from '@commonStyle/Input.ts';
|
||||||
@@ -16,139 +9,73 @@ import Input from '@commonStyle/Input.ts';
|
|||||||
// Components
|
// Components
|
||||||
import Form from '@component/Form/index.tsx';
|
import Form from '@component/Form/index.tsx';
|
||||||
import FormRow from '@component/LabelControl/index.tsx';
|
import FormRow from '@component/LabelControl/index.tsx';
|
||||||
import Select, { ISelectOptions } from '@component/Select/index.tsx';
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { isEmptyObj, isValidRegex } from '@helper/validators.ts';
|
import { isValidRegex } from '@helper/validators.ts';
|
||||||
|
|
||||||
// Constants
|
|
||||||
import { ADD_SUCCESS, EDIT_SUCCESS } from '@constant/messages.ts';
|
|
||||||
import {
|
import {
|
||||||
INVALID_FIELD,
|
INVALID_FIELD,
|
||||||
INVALID_PHONE,
|
INVALID_PHONE,
|
||||||
REQUIRED_FIELD_ERROR,
|
REQUIRED_FIELD_ERROR,
|
||||||
} from '@constant/formValidateMessage.ts';
|
} from '@constant/formValidateMessage.ts';
|
||||||
import { INIT_VALUE_USER_FORM, REGEX } from '@constant/commons.ts';
|
import { REGEX } from '@constant/commons.ts';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { FormBtn } from './styled.ts';
|
import { FormBtn } from './styled.ts';
|
||||||
|
|
||||||
// Services
|
|
||||||
import { getAllRoom, updateRoomStatus } from '@service/roomServices.ts';
|
|
||||||
import { createUser, updateUser } from '@service/userServices.ts';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Nullable } from '@type/common.ts';
|
|
||||||
import { IUser } from '@type/users.ts';
|
import { IUser } from '@type/users.ts';
|
||||||
import { IRoom } from '@type/rooms.ts';
|
|
||||||
|
|
||||||
interface IUserFormProp {
|
interface IUserFormProp {
|
||||||
onCloseModal?: () => void;
|
onCloseModal?: () => void;
|
||||||
reload: boolean;
|
user?: IUser;
|
||||||
setReload: Dispatch<SetStateAction<boolean>>;
|
|
||||||
user?: Nullable<IUser>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
|
const UserForm = ({ onCloseModal, user }: IUserFormProp) => {
|
||||||
const formMethods = useForm<IUser>();
|
const { isCreating, createUser } = useCreateUser();
|
||||||
|
const { isUpdating, updateUser } = useUpdateUser();
|
||||||
|
const isLoading = isCreating || isUpdating;
|
||||||
|
const {id: editId, ...editValues} = {...user};
|
||||||
|
|
||||||
|
const formMethods = useForm<IUser>({
|
||||||
|
defaultValues: editId
|
||||||
|
? editValues
|
||||||
|
: {}
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isDirty, isValid, isSubmitting },
|
formState: { errors, isDirty, isValid },
|
||||||
trigger,
|
trigger,
|
||||||
} = formMethods;
|
} = formMethods;
|
||||||
const [rooms, setRooms] = useState<IRoom[]>([]);
|
|
||||||
const [options, setOptions] = useState<ISelectOptions[]>();
|
|
||||||
|
|
||||||
// Init value when edit form and load options
|
|
||||||
useEffect(() => {
|
|
||||||
const load = async () => {
|
|
||||||
const options: ISelectOptions[] = [];
|
|
||||||
const tempUser = !user ? { roomId: 0 } : { ...user };
|
|
||||||
|
|
||||||
// Load and set default options room
|
|
||||||
if (rooms.length > 0) {
|
|
||||||
// Init first options
|
|
||||||
options.push({
|
|
||||||
label: '---Select---',
|
|
||||||
value: '0',
|
|
||||||
});
|
|
||||||
|
|
||||||
rooms.forEach((item) => {
|
|
||||||
if (!item.status || tempUser?.roomId === item.id)
|
|
||||||
options.push({
|
|
||||||
label: item.name!,
|
|
||||||
value: item.id!.toString(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.length > 0) {
|
|
||||||
setOptions(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEmptyObj(tempUser)) {
|
|
||||||
// Init value
|
|
||||||
reset(tempUser);
|
|
||||||
} else {
|
|
||||||
reset(INIT_VALUE_USER_FORM);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
load();
|
|
||||||
}, [reset, user, rooms]);
|
|
||||||
|
|
||||||
// Submit form
|
// Submit form
|
||||||
const onSubmit = useCallback(
|
const onSubmit =
|
||||||
async (newUser: IUser) => {
|
(newUser: IUser) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// Add request
|
// Add request
|
||||||
const response = await createUser(newUser);
|
createUser(newUser, {
|
||||||
|
onSuccess: () => {
|
||||||
if (response) {
|
reset();
|
||||||
toast.success(ADD_SUCCESS);
|
onCloseModal?.();
|
||||||
}
|
},
|
||||||
|
});
|
||||||
// Update room status
|
|
||||||
updateRoomStatus(newUser.roomId, true);
|
|
||||||
} else {
|
} else {
|
||||||
// Edit request
|
// Edit request
|
||||||
const response = await updateUser(newUser);
|
newUser.id = editId!;
|
||||||
|
updateUser(newUser, {
|
||||||
if (response) {
|
onSuccess: () => {
|
||||||
toast.success(EDIT_SUCCESS);
|
reset();
|
||||||
}
|
onCloseModal?.();
|
||||||
|
}
|
||||||
// Update room status
|
});
|
||||||
updateRoomStatus(user!.roomId, true, newUser.roomId);
|
|
||||||
}
|
|
||||||
// Reload table data
|
|
||||||
setReload(!reload);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
onCloseModal!();
|
|
||||||
},
|
|
||||||
[onCloseModal, reload, reset, setReload, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load all rooms
|
|
||||||
useEffect(() => {
|
|
||||||
const loadRoom = async () => {
|
|
||||||
const rooms = await getAllRoom();
|
|
||||||
|
|
||||||
if (rooms) {
|
|
||||||
setRooms(rooms);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadRoom();
|
|
||||||
}, [onSubmit]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...formMethods}>
|
<FormProvider {...formMethods}>
|
||||||
<Form onSubmit={handleSubmit(onSubmit)}>
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Input type="hidden" id="id" {...register('id')} />
|
|
||||||
<FormRow label="Full Name" error={errors?.name?.message}>
|
<FormRow label="Full Name" error={errors?.name?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -164,25 +91,6 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
|
|||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<FormRow
|
|
||||||
label="Identified Code"
|
|
||||||
error={errors?.identifiedCode?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
id="identifiedCode"
|
|
||||||
{...register('identifiedCode', {
|
|
||||||
required: REQUIRED_FIELD_ERROR,
|
|
||||||
validate: {
|
|
||||||
checkIdentifiedCode: (v) =>
|
|
||||||
isValidRegex(new RegExp(REGEX.NUMBER), v.toString()) ||
|
|
||||||
INVALID_FIELD,
|
|
||||||
},
|
|
||||||
onChange: () => trigger('identifiedCode'),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<FormRow label="Phone" error={errors?.phone?.message}>
|
<FormRow label="Phone" error={errors?.phone?.message}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -198,32 +106,19 @@ const UserForm = ({ onCloseModal, reload, setReload, user }: IUserFormProp) => {
|
|||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<FormRow label="Room">
|
|
||||||
{options && options.length > 1 ? (
|
|
||||||
<Select
|
|
||||||
id="roomId"
|
|
||||||
options={options!}
|
|
||||||
ariaLabel="RoomId"
|
|
||||||
optionsConfigForm={{
|
|
||||||
valueAsNumber: true,
|
|
||||||
onChange: () => trigger('roomId'),
|
|
||||||
validate: (v) => v !== 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p>No room available!</p>
|
|
||||||
)}
|
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<Form.Action>
|
<Form.Action>
|
||||||
<FormBtn
|
<FormBtn
|
||||||
type="submit"
|
type="submit"
|
||||||
name="submit"
|
name="submit"
|
||||||
disabled={!isDirty || !isValid || isSubmitting}
|
disabled={!isDirty || !isValid || isLoading}
|
||||||
>
|
>
|
||||||
{!user ? 'Add' : 'Save'}
|
{
|
||||||
|
!user
|
||||||
|
? 'Add'
|
||||||
|
: 'Save'
|
||||||
|
}
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={onCloseModal}>
|
<FormBtn type="button" variations="secondary" onClick={onCloseModal}>
|
||||||
Close
|
Close
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
</Form.Action>
|
</Form.Action>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Dispatch, SetStateAction } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { RiEditBoxFill } from 'react-icons/ri';
|
import { RiEditBoxFill } from 'react-icons/ri';
|
||||||
import Modal from '@component/Modal';
|
import Modal from '@component/Modal';
|
||||||
@@ -13,11 +11,9 @@ import { IUser } from '@type/users';
|
|||||||
|
|
||||||
interface IUserRow {
|
interface IUserRow {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
reload: boolean;
|
|
||||||
setReload: Dispatch<SetStateAction<boolean>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserRow = ({ user, reload, setReload }: IUserRow) => {
|
const UserRow = ({ user }: IUserRow) => {
|
||||||
const { id, name, phone } = user;
|
const { id, name, phone } = user;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -42,7 +38,7 @@ const UserRow = ({ user, reload, setReload }: IUserRow) => {
|
|||||||
</Menus.List>
|
</Menus.List>
|
||||||
|
|
||||||
<Modal.Window name="edit" title="Edit user">
|
<Modal.Window name="edit" title="Edit user">
|
||||||
<UserForm user={user} setReload={setReload} reload={reload} />
|
<UserForm user={user} />
|
||||||
</Modal.Window>
|
</Modal.Window>
|
||||||
</Menus.Menu>
|
</Menus.Menu>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useCallback } from 'react';
|
||||||
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Menus from '@component/Menus';
|
import Menus from '@component/Menus';
|
||||||
@@ -21,50 +20,21 @@ import Direction from '@commonStyle/Direction';
|
|||||||
import { StyledOperationTable } from './styled';
|
import { StyledOperationTable } from './styled';
|
||||||
import Spinner from '@commonStyle/Spinner';
|
import Spinner from '@commonStyle/Spinner';
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
// Hooks
|
||||||
import { getAllUsers } from '@service/userServices';
|
import { useUsers } from '@hook/users/useUsers';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
interface IUserTable {
|
const UserTable = () => {
|
||||||
reload: boolean;
|
|
||||||
setReload: Dispatch<SetStateAction<boolean>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserTable = ({ reload, setReload }: IUserTable) => {
|
|
||||||
const columnName = ['Id', 'Name', 'Phone'];
|
const columnName = ['Id', 'Name', 'Phone'];
|
||||||
|
const { isLoading, users } = useUsers();
|
||||||
const [phoneSearch, setPhoneSearch] = useState('');
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const sortByValue = searchParams.get('sortBy')
|
|
||||||
? searchParams.get('sortBy')!
|
|
||||||
: 'id';
|
|
||||||
const orderByValue = searchParams.get('orderBy')
|
|
||||||
? searchParams.get('orderBy')!
|
|
||||||
: 'asc';
|
|
||||||
|
|
||||||
const {
|
|
||||||
isLoading,
|
|
||||||
data: users,
|
|
||||||
error,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ['cabins', sortByValue, orderByValue, phoneSearch],
|
|
||||||
queryFn: () => getAllUsers(sortByValue, orderByValue, phoneSearch),
|
|
||||||
});
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderUserRow = useCallback(
|
const renderUserRow = useCallback(
|
||||||
(user: IUser) => (
|
(user: IUser) => (
|
||||||
<UserRow
|
<UserRow
|
||||||
user={user}
|
user={user}
|
||||||
key={user.id}
|
key={user.id}
|
||||||
reload={reload}
|
|
||||||
setReload={setReload}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[reload, setReload]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -74,10 +44,7 @@ const UserTable = ({ reload, setReload }: IUserTable) => {
|
|||||||
<OrderBy options={ORDERBY_OPTIONS} />
|
<OrderBy options={ORDERBY_OPTIONS} />
|
||||||
|
|
||||||
<SortBy options={USER_PAGE.SORTBY_OPTIONS} />
|
<SortBy options={USER_PAGE.SORTBY_OPTIONS} />
|
||||||
<Search
|
<Search setPlaceHolder="Search by phone..." />
|
||||||
setValueSearch={setPhoneSearch}
|
|
||||||
setPlaceHolder="Search by phone..."
|
|
||||||
/>
|
|
||||||
</StyledOperationTable>
|
</StyledOperationTable>
|
||||||
|
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import UserTable from './UserTable';
|
import UserTable from './UserTable';
|
||||||
|
|
||||||
@@ -17,7 +15,6 @@ import { FORM } from '@constant/commons';
|
|||||||
|
|
||||||
const User = () => {
|
const User = () => {
|
||||||
const TITLE = 'Add user';
|
const TITLE = 'Add user';
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -33,12 +30,12 @@ const User = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Modal.Window name={FORM.USER} title={TITLE}>
|
<Modal.Window name={FORM.USER} title={TITLE}>
|
||||||
<UserForm setReload={setReload} reload={reload} />
|
<UserForm />
|
||||||
</Modal.Window>
|
</Modal.Window>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Direction>
|
</Direction>
|
||||||
|
|
||||||
<UserTable reload={reload} setReload={setReload} />
|
<UserTable />
|
||||||
</StyledUser>
|
</StyledUser>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,173 +1,80 @@
|
|||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Nullable } from '@type/common';
|
|
||||||
import { IResponse } from '@type/responses';
|
|
||||||
import { IRoom } from '@type/rooms';
|
import { IRoom } from '@type/rooms';
|
||||||
|
|
||||||
// Helpers
|
// Services
|
||||||
import { sendRequest } from '@helper/sendRequest';
|
import supabase from './supabaseService';
|
||||||
import { errorMsg } from '@helper/helper';
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { STATUS_CODE, RESPONSE_MESSAGE } from '@constant/responseStatus';
|
const ROOMS_TABLE = 'rooms';
|
||||||
import { ROOM_PATH } from '@constant/path';
|
const ERROR_FETCHING = "Can't fetch room data!";
|
||||||
|
const ERROR_UPDATE_ROOM = "Can't update room!";
|
||||||
|
const ERROR_CREATE_ROOM = "Can't create room!";
|
||||||
|
const ERROR_DELETE_ROOM = "Can't delete room!";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all rooms from server
|
* Get all rooms from database
|
||||||
* @returns Return all rooms in server
|
* @returns Return all rooms in database
|
||||||
*/
|
*/
|
||||||
const getAllRoom = async (): Promise<Nullable<IRoom[]>> => {
|
const getAllRooms = async (
|
||||||
try {
|
sortBy: string,
|
||||||
const response = await sendRequest<IRoom[]>(ROOM_PATH);
|
orderBy: string,
|
||||||
|
roomName: string
|
||||||
|
): Promise<IRoom[]> => {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from(ROOMS_TABLE)
|
||||||
|
.select('*')
|
||||||
|
.order(sortBy, { ascending: orderBy === 'asc' })
|
||||||
|
.like('name', `%${roomName}%`);
|
||||||
|
|
||||||
if (response.statusCode === STATUS_CODE.OK) {
|
if (error) {
|
||||||
const rooms = response.data!;
|
console.error(error.message);
|
||||||
|
throw new Error(ERROR_FETCHING);
|
||||||
return rooms;
|
|
||||||
} else {
|
|
||||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get room by id
|
|
||||||
* @param roomId The id room need to be get
|
|
||||||
* @returns Return the room object depend on room id
|
|
||||||
*/
|
|
||||||
const getRoom = async (roomId: number): Promise<Nullable<IRoom>> => {
|
|
||||||
try {
|
|
||||||
const response = await sendRequest<IRoom>(ROOM_PATH + '/' + roomId);
|
|
||||||
|
|
||||||
if (response.statusCode !== STATUS_CODE.OK) {
|
|
||||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
const rooms = response.data!;
|
|
||||||
|
|
||||||
return rooms;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update room into server
|
* Update room into database
|
||||||
* @param room Room object need to be updated
|
* @param room Room object need to be updated
|
||||||
* @returns The response object
|
|
||||||
*/
|
*/
|
||||||
const updateRoom = async (room: IRoom): Promise<Nullable<IResponse<IRoom>>> => {
|
const updateRoom = async (room: IRoom): Promise<void> => {
|
||||||
try {
|
const { error } = await supabase.from(ROOMS_TABLE).update(room).eq("id", room.id);
|
||||||
const response = await sendRequest<IRoom>(
|
|
||||||
ROOM_PATH + '/' + room.id,
|
|
||||||
'PUT',
|
|
||||||
JSON.stringify(room)
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
if(error) {
|
||||||
} catch (error: unknown) {
|
console.error(error.message);
|
||||||
if (error instanceof Error) {
|
throw new Error(ERROR_UPDATE_ROOM);
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update room status
|
* Add room to database
|
||||||
* @param roomId The id room need to be updated
|
|
||||||
* @param status Status of room
|
|
||||||
* @param roomIdNew The new id room need to be updated
|
|
||||||
* @returns Return the response object
|
|
||||||
*/
|
|
||||||
const updateRoomStatus = async (
|
|
||||||
roomId: number,
|
|
||||||
status: boolean,
|
|
||||||
roomIdNew?: number
|
|
||||||
): Promise<Nullable<IResponse<IRoom>>> => {
|
|
||||||
try {
|
|
||||||
if (!roomIdNew) {
|
|
||||||
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',
|
|
||||||
JSON.stringify({ status: status })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update old room status;
|
|
||||||
const resOldRoom = await sendRequest<IRoom>(
|
|
||||||
ROOM_PATH + '/' + roomId,
|
|
||||||
'PATCH',
|
|
||||||
JSON.stringify({ status: !status })
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
resNewRoom.statusCode === STATUS_CODE.OK &&
|
|
||||||
resOldRoom.statusCode === STATUS_CODE.OK
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
statusCode: STATUS_CODE.OK,
|
|
||||||
msg: RESPONSE_MESSAGE.UPDATE_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add room to server
|
|
||||||
* @param room The room object need to be add
|
* @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>>> => {
|
const createRoom = async (room: IRoom): Promise<void> => {
|
||||||
try {
|
// Set default status
|
||||||
// Set default status room
|
room.status = false;
|
||||||
room.status = false;
|
|
||||||
|
|
||||||
const response = await sendRequest<IRoom>(
|
const { error } = await supabase.from(ROOMS_TABLE).insert([room]);
|
||||||
ROOM_PATH,
|
|
||||||
'POST',
|
|
||||||
JSON.stringify(room)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode !== STATUS_CODE.CREATE) {
|
if(error) {
|
||||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
console.error(error.message);
|
||||||
}
|
throw new Error(ERROR_CREATE_ROOM);
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getRoom, updateRoom, updateRoomStatus, getAllRoom, addRoom };
|
/**
|
||||||
|
* Delete room in database
|
||||||
|
* @param idRoom The id of room need to delete
|
||||||
|
*/
|
||||||
|
const deleteRoom = async(idRoom: number) => {
|
||||||
|
const { error } = await supabase.from(ROOMS_TABLE).delete().eq('id', idRoom);
|
||||||
|
|
||||||
|
if(error) {
|
||||||
|
console.error(error.message);
|
||||||
|
throw new Error(ERROR_DELETE_ROOM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getAllRooms, updateRoom, createRoom, deleteRoom };
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createClient } from "@supabase/supabase-js";
|
||||||
|
import { Database } from "@type/supabase";
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { supabaseKey, supabaseUrl } from "@constant/config";
|
||||||
|
|
||||||
|
const supabase = createClient<Database>(supabaseUrl, supabaseKey!);
|
||||||
|
|
||||||
|
export default supabase;
|
||||||
@@ -1,96 +1,47 @@
|
|||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
import { USER_PATH } from '@constant/path';
|
|
||||||
import { STATUS_CODE } from '@constant/responseStatus';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Nullable } from '@type/common';
|
|
||||||
import { IResponse } from '@type/responses';
|
|
||||||
import { IUser } from '@type/users';
|
import { IUser } from '@type/users';
|
||||||
|
|
||||||
// Helpers
|
// Services
|
||||||
import { sendRequest } from '@helper/sendRequest';
|
import supabase from './supabaseService';
|
||||||
import { errorMsg } from '@helper/helper';
|
|
||||||
import supabase from '@constant/supabaseConfig';
|
|
||||||
|
|
||||||
const USERS_TABLE = 'users';
|
const USERS_TABLE = 'users';
|
||||||
const ERROR_FETCHING = "Users can't be loaded!";
|
const ERROR_FETCHING = "Users can't be loaded!";
|
||||||
|
const ERROR_CREATE_USER = "Can't create user!";
|
||||||
|
const ERROR_UPDATE_USER = "Can't update user!";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create user to the server
|
* Create user to the database
|
||||||
* @param user The user object need to be created
|
* @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>>> => {
|
const createUser = async (user: IUser): Promise<void> => {
|
||||||
// try {
|
const { error } = await supabase.from(USERS_TABLE).insert([user]);
|
||||||
// const response = await sendRequest<IUser>(
|
|
||||||
// USER_PATH,
|
|
||||||
// 'POST',
|
|
||||||
// JSON.stringify(user)
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (response.statusCode !== STATUS_CODE.CREATE) {
|
if(error) {
|
||||||
// throw new Error(errorMsg(response.statusCode, response.msg));
|
console.error(error.message);
|
||||||
// }
|
throw new Error(ERROR_CREATE_USER);
|
||||||
|
}
|
||||||
// return response;
|
|
||||||
// } catch (error: unknown) {
|
|
||||||
// if (error instanceof Error) {
|
|
||||||
// toast.error(error.message);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the user to the server
|
* Update the user to the database
|
||||||
* @param user The user object need to be updated
|
* @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>>> => {
|
const updateUser = async (user: IUser): Promise<void> => {
|
||||||
// try {
|
const { error } = await supabase.from(USERS_TABLE).update(user).eq("id", user.id);
|
||||||
// const response = await sendRequest<IUser>(
|
|
||||||
// USER_PATH + '/' + user.id,
|
|
||||||
// 'PUT',
|
|
||||||
// JSON.stringify(user)
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (response.statusCode !== STATUS_CODE.OK) {
|
if(error) {
|
||||||
// throw new Error(errorMsg(response.statusCode, response.msg));
|
console.error(error.message);
|
||||||
// }
|
throw new Error(ERROR_UPDATE_USER);
|
||||||
|
}
|
||||||
// return response;
|
|
||||||
// } catch (error: unknown) {
|
|
||||||
// if (error instanceof Error) {
|
|
||||||
// toast.error(error.message);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkout user
|
* Return data of users from database
|
||||||
* @param user The user need to be checkout
|
* @param sortBy Sort by column
|
||||||
* @returns The IResponse object if checkout success or not
|
* @param orderBy Order by ascending or descending
|
||||||
|
* @param phoneSearch The phone need to be search
|
||||||
|
* @returns The data of users from database
|
||||||
*/
|
*/
|
||||||
const checkOutUser = async (
|
|
||||||
user: IUser
|
|
||||||
): Promise<Nullable<IResponse<IUser>>> => {
|
|
||||||
// const tempUser = user;
|
|
||||||
|
|
||||||
// if (tempUser) {
|
|
||||||
// tempUser.roomId = 0;
|
|
||||||
// const resUpdateUser = await updateUser(tempUser);
|
|
||||||
|
|
||||||
// return resUpdateUser;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllUsers = async (
|
const getAllUsers = async (
|
||||||
sortBy: string,
|
sortBy: string,
|
||||||
orderBy: string,
|
orderBy: string,
|
||||||
@@ -103,10 +54,11 @@ const getAllUsers = async (
|
|||||||
.like('phone', `%${phoneSearch}%`);
|
.like('phone', `%${phoneSearch}%`);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
console.error(error.message);
|
||||||
throw new Error(ERROR_FETCHING);
|
throw new Error(ERROR_FETCHING);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { updateUser, checkOutUser, createUser, getAllUsers };
|
export { updateUser, createUser, getAllUsers };
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
--secondary-btn-color: #d1d1d1;
|
--secondary-btn-color: #d1d1d1;
|
||||||
--disabled-btn-color: #7f82a6;
|
--disabled-btn-color: #7f82a6;
|
||||||
|
--danger-btn-color: #ff4f4f;
|
||||||
|
|
||||||
--hover-background-color: #f3f4f6;
|
--hover-background-color: #f3f4f6;
|
||||||
--hover-dark-background-color: #d0d0d0;
|
--hover-dark-background-color: #d0d0d0;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
interface IResponse<T> {
|
|
||||||
statusCode: number;
|
|
||||||
msg: string;
|
|
||||||
data?: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { IResponse };
|
|
||||||
@@ -2,8 +2,6 @@ interface IRoom {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
price: number;
|
price: number;
|
||||||
discount: number;
|
|
||||||
finalPrice: number;
|
|
||||||
status: boolean;
|
status: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user