Implement supabase and update user service

This commit is contained in:
2023-11-17 17:46:17 +07:00
parent c08f2490fe
commit 9a9617bb16
15 changed files with 462 additions and 572 deletions
+11 -2
View File
@@ -1,6 +1,7 @@
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import isPropValid from '@emotion/is-prop-valid';
import { StyleSheetManager } from 'styled-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// Components
import AppLayout from './components/AppLayout';
@@ -13,9 +14,17 @@ import NotFound from './pages/NotFound';
import * as PATH from './constants/path';
import Toast from './components/Toast';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
}
}
});
function App() {
return (
<>
<QueryClientProvider client={queryClient}>
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
<BrowserRouter>
<Routes>
@@ -31,7 +40,7 @@ function App() {
</StyleSheetManager>
<Toast />
</>
</QueryClientProvider>
);
}
@@ -8,18 +8,10 @@ const USER_PAGE = {
value: 'name',
label: 'Sort by name',
},
{
value: 'identifiedCode',
label: 'Sort by identified code',
},
{
value: 'phone',
label: 'Sort by phone',
},
{
value: 'roomId',
label: 'Sort by room',
},
],
};
+1 -1
View File
@@ -2,6 +2,6 @@ export const DASHBOARD = '/dashboard';
export const USER = '/user';
export const ROOM = '/room';
export const OTHER_PATH = '*';
export const BASE_URL = 'https://hotel-management-api.loiphan.com/';
export const BASE_URL = 'http://localhost:3000/';
export const USER_PATH = 'users';
export const ROOM_PATH = 'rooms';
@@ -0,0 +1,7 @@
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 -42
View File
@@ -1,47 +1,6 @@
// Constants
import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage';
/**
* 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
) => {
const phoneParams = keySearch
? `${columnSearch}_like=` + keySearch
: '';
const sortParams = sort
? '_sort=' + sort
: '';
const orderParams = order
? '_order=' + order
: '';
const finalParam = [phoneParams, sortParams, orderParams];
let query = '';
let isFirstParam = true;
finalParam.forEach((param) => {
if (param) {
if (isFirstParam) {
query = query.concat('', param);
isFirstParam = false;
} else {
query = query.concat('&', param);
}
}
});
return query;
};
/**
* Convert value to currency format with value
* @param value Value need to be converted
@@ -64,4 +23,4 @@ const errorMsg = (errorCode: number, msg: string) => {
return `Error code: ${errorCode}. Message: ${msg}`;
};
export { errorMsg, searchQuery, formatCurrency, REQUIRED_FIELD_ERROR };
export { errorMsg, formatCurrency, REQUIRED_FIELD_ERROR };
-77
View File
@@ -1,77 +0,0 @@
import { useEffect, useState } from 'react';
// Constants
import { BASE_URL } from '@constant/path';
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '@constant/config';
// Helpers
import { searchQuery } from '@helper/helper';
// Types
import { Nullable } from '@type/common';
/**
* The function use to fetch data on server API.
* @param path The path of url
* @param columnSearch Column need to search
* @param keyWord The key word to search
* @param tempSortBy Sort by
* @param tempOrderBy Order by
* @param reload Fetch again
* @returns data: A data after fetch, isPending: A boolean indicating whether or not the progress of fetch data is done, errorMsg: A error message from the server.
*/
const useFetch = (
path: string,
columnSearch: string = '',
keyWord: string = '',
tempSortBy?: string,
tempOrderBy?: string,
reload?: boolean
) => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
const [errorFetchMsg, setErrorFetchMsg] = useState<Nullable<string>>(null);
useEffect(() => {
// Clear data
setData(null);
const fetchData = async () => {
setIsPending(true);
// Set default value
const sortBy = tempSortBy
? tempSortBy
: DEFAULT_SORT_BY;
const orderBy = tempOrderBy
? tempOrderBy
: DEFAULT_ORDER_BY;
// Query search
const query = searchQuery(columnSearch, keyWord, sortBy, orderBy);
try {
const response = await fetch(BASE_URL + path + '?' + query);
const json = await response.json();
if (!response.ok) {
throw new Error(
`Error code: ${response.status} \n Messages: ${response.text}`
);
}
setIsPending(false);
setData(json);
setErrorFetchMsg(null);
} catch (error) {
setErrorFetchMsg(`Could not fetch data.\n ${error}`);
setIsPending(false);
}
};
fetchData();
}, [path, reload, columnSearch, keyWord, tempOrderBy, tempSortBy]);
return { data, isPending, errorFetchMsg };
};
export { useFetch };
+1 -23
View File
@@ -14,7 +14,6 @@ import RoomRow from './RoomRow';
import { IRoom } from '@type/rooms';
// Constants
import { useFetch } from '@hook/useFetch';
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@constant/commons';
// Styled
@@ -39,28 +38,7 @@ const RoomTable = ({ reload, setReload }: IRoomTable) => {
const orderByValue = searchParams.get('orderBy')
? searchParams.get('orderBy')!
: '';
const { data, isPending, errorFetchMsg } = useFetch(
'rooms',
'name',
nameSearch,
sortByValue,
orderByValue,
reload
);
useEffect(() => {
if (data) {
setRooms(data);
} else {
setRooms([]);
}
if (errorFetchMsg) {
console.error(errorFetchMsg);
}
}, [data, errorFetchMsg]);
return (
<>
<Direction>
+1 -45
View File
@@ -1,21 +1,12 @@
import toast from 'react-hot-toast';
import { Dispatch, SetStateAction } from 'react';
// Components
import { RiEditBoxFill } from 'react-icons/ri';
import { IoExit } from 'react-icons/io5';
import Modal from '@component/Modal';
import Table from '@component/Table';
import Menus from '@component/Menus';
import UserForm from './UserForm';
// Constants
import { STATUS_CODE } from '@constant/responseStatus';
import { CONFIRM_MESSAGE, DENIED_ACTION } from '@constant/messages';
// Services
import { updateRoomStatus } from '@service/roomServices';
import { checkOutUser } from '@service/userServices';
// Types
import { IUser } from '@type/users';
@@ -27,42 +18,13 @@ interface IUserRow {
}
const UserRow = ({ user, reload, setReload }: IUserRow) => {
const handleCheckOut = async (user: IUser) => {
if (user.roomId !== 0) {
if (confirm(CONFIRM_MESSAGE)) {
const resUpdateStatus = await updateRoomStatus(user.roomId, false);
const resCheckoutUser = await checkOutUser(user);
if (
resCheckoutUser?.statusCode === STATUS_CODE.OK &&
resUpdateStatus?.statusCode === STATUS_CODE.OK
) {
toast.success('Check out complete!');
// Reload table
setReload(!reload);
}
}
} else {
toast.error(DENIED_ACTION);
}
};
const { id, name, identifiedCode, phone, roomId } = user;
const { id, name, phone } = user;
return (
<Table.Row>
<div>{id}</div>
<div>{name}</div>
<div>{identifiedCode}</div>
<div>{phone}</div>
<div>
{
roomId
? roomId
: 'None'
}
</div>
<div>
<Modal>
<Menus.Menu>
@@ -77,12 +39,6 @@ const UserRow = ({ user, reload, setReload }: IUserRow) => {
</Menus.Button>
)}
/>
<Menus.Button
icon={<IoExit />}
onClick={() => handleCheckOut(user)}
>
Check out
</Menus.Button>
</Menus.List>
<Modal.Window name="edit" title="Edit user">
+37 -42
View File
@@ -1,5 +1,5 @@
import { useSearchParams } from 'react-router-dom';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
// Components
import Menus from '@component/Menus';
@@ -13,9 +13,6 @@ import UserRow from './UserRow';
// Types
import { IUser } from '@type/users';
// Hooks
import { useFetch } from '@hook/useFetch';
// Constants
import { ORDERBY_OPTIONS, USER_PAGE } from '@constant/commons';
@@ -24,44 +21,52 @@ import Direction from '@commonStyle/Direction';
import { StyledOperationTable } from './styled';
import Spinner from '@commonStyle/Spinner';
import { useQuery } from '@tanstack/react-query';
import { getAllUsers } from '@service/userServices';
import toast from 'react-hot-toast';
interface IUserTable {
reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>;
}
const UserTable = ({ reload, setReload }: IUserTable) => {
const columnName = ['Id', 'Name', 'Identified Code', 'Phone', 'Room Id'];
const [users, setUsers] = useState<IUser[]>([]);
const columnName = ['Id', 'Name', 'Phone'];
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 { data, isPending, errorFetchMsg } = useFetch(
'users',
'phone',
phoneSearch,
sortByValue,
orderByValue,
reload
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(
(user: IUser) => (
<UserRow
user={user}
key={user.id}
reload={reload}
setReload={setReload}
/>
),
[reload, setReload]
);
useEffect(() => {
if (data) {
setUsers(data);
} else {
setUsers([]);
}
if (errorFetchMsg) {
console.error(errorFetchMsg);
}
}, [data, errorFetchMsg, setUsers]);
return (
<>
<Direction>
@@ -75,27 +80,17 @@ const UserTable = ({ reload, setReload }: IUserTable) => {
/>
</StyledOperationTable>
{isPending && <Spinner />}
{isLoading && <Spinner />}
{users.length ? (
{users && users.length ? (
<Menus>
<Table columns="10% 30% 20% 20% 10% 5%">
<Table columns="10% 40% 35% 15%">
<Table.Header headerColumn={columnName} />
<Table.Body<IUser>
data={users}
render={(user: IUser) => (
<UserRow
user={user}
key={user.id}
reload={reload}
setReload={setReload}
/>
)}
/>
<Table.Body<IUser> data={users} render={renderUserRow} />
</Table>
</Menus>
) : (
!isPending && <Message>No data to show here!</Message>
!isLoading && <Message>No data to show here!</Message>
)}
</Direction>
</>
+63 -39
View File
@@ -12,6 +12,10 @@ import { IUser } from '@type/users';
// Helpers
import { sendRequest } from '@helper/sendRequest';
import { errorMsg } from '@helper/helper';
import supabase from '@constant/supabaseConfig';
const USERS_TABLE = 'users';
const ERROR_FETCHING = "Users can't be loaded!";
/**
* Create user to the server
@@ -19,26 +23,26 @@ import { errorMsg } from '@helper/helper';
* @returns The IResponse object if success or null
*/
const createUser = async (user: IUser): Promise<Nullable<IResponse<IUser>>> => {
try {
const response = await sendRequest<IUser>(
USER_PATH,
'POST',
JSON.stringify(user)
);
// try {
// 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));
}
// 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 response;
// } catch (error: unknown) {
// if (error instanceof Error) {
// toast.error(error.message);
// }
// }
return null;
}
};
/**
* Update the user to the server
@@ -46,23 +50,23 @@ const createUser = async (user: IUser): Promise<Nullable<IResponse<IUser>>> => {
* @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,
'PUT',
JSON.stringify(user)
);
// try {
// const response = await sendRequest<IUser>(
// USER_PATH + '/' + user.id,
// 'PUT',
// JSON.stringify(user)
// );
if (response.statusCode !== STATUS_CODE.OK) {
throw new Error(errorMsg(response.statusCode, response.msg));
}
// if (response.statusCode !== STATUS_CODE.OK) {
// throw new Error(errorMsg(response.statusCode, response.msg));
// }
return response;
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(error.message);
}
}
// return response;
// } catch (error: unknown) {
// if (error instanceof Error) {
// toast.error(error.message);
// }
// }
return null;
};
@@ -72,17 +76,37 @@ const updateUser = async (user: IUser): Promise<Nullable<IResponse<IUser>>> => {
* @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 checkOutUser = async (
user: IUser
): Promise<Nullable<IResponse<IUser>>> => {
// const tempUser = user;
if (tempUser) {
tempUser.roomId = 0;
const resUpdateUser = await updateUser(tempUser);
// if (tempUser) {
// tempUser.roomId = 0;
// const resUpdateUser = await updateUser(tempUser);
return resUpdateUser;
}
// return resUpdateUser;
// }
return null;
};
export { updateUser, checkOutUser, createUser };
const getAllUsers = async (
sortBy: string,
orderBy: string,
phoneSearch: string
): Promise<IUser[]> => {
const { data, error } = await supabase
.from(USERS_TABLE)
.select('*')
.order(sortBy, { ascending: orderBy === 'asc' })
.like('phone', `%${phoneSearch}%`);
if (error) {
throw new Error(ERROR_FETCHING);
}
return data;
};
export { updateUser, checkOutUser, createUser, getAllUsers };
+110
View File
@@ -0,0 +1,110 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export interface Database {
public: {
Tables: {
bookings: {
Row: {
amount: number
endDate: string
id: number
roomId: number
startDate: string
status: boolean
userId: number
}
Insert: {
amount: number
endDate: string
id?: number
roomId: number
startDate: string
status: boolean
userId: number
}
Update: {
amount?: number
endDate?: string
id?: number
roomId?: number
startDate?: string
status?: boolean
userId?: number
}
Relationships: [
{
foreignKeyName: "bookings_roomId_fkey"
columns: ["roomId"]
isOneToOne: false
referencedRelation: "rooms"
referencedColumns: ["id"]
},
{
foreignKeyName: "bookings_userId_fkey"
columns: ["userId"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
rooms: {
Row: {
id: number
name: string
price: number
status: boolean
}
Insert: {
id?: number
name: string
price: number
status: boolean
}
Update: {
id?: number
name?: string
price?: number
status?: boolean
}
Relationships: []
}
users: {
Row: {
id: number
name: string
phone: string
}
Insert: {
id?: number
name: string
phone: string
}
Update: {
id?: number
name?: string
phone?: string
}
Relationships: []
}
}
Views: {
[_ in never]: never
}
Functions: {
[_ in never]: never
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
}
}
}
-2
View File
@@ -1,9 +1,7 @@
interface IUser {
id: number;
name: string;
identifiedCode: string;
phone: string;
roomId: number;
}
export type { IUser };