Merge pull request #30 from Nez27/feat/add-user-rooms-to-global-state

Add user and rooms to global state
This commit is contained in:
Loi Phan
2023-11-23 17:14:52 +07:00
committed by GitHub
15 changed files with 346 additions and 37 deletions
+56 -13
View File
@@ -13,30 +13,73 @@ import NotFound from './pages/NotFound';
// Constants // Constants
import * as PATH from './constants/path'; import * as PATH from './constants/path';
import Toast from './components/Toast'; import Toast from './components/Toast';
import {
UserRoomAvailableContext,
initialState,
reducer,
} from '@context/UserRoomAvailableContext';
import { useEffect, useMemo, useReducer } from 'react';
// Services
import { getUserNotBooked } from '@service/userServices';
import { getRoomsAvailable } from '@service/roomServices';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
staleTime: 60 * 1000, staleTime: 60 * 1000,
} },
} },
}); });
function App() { function App() {
const [{ usersAvailable, roomsAvailable }, dispatch] = useReducer(
reducer,
initialState
);
// Init list user and room available
useEffect(() => {
const load = async () => {
const tempUser = await getUserNotBooked();
if (tempUser) {
dispatch({ type: 'initUser', payload: tempUser });
}
const tempRoom = await getRoomsAvailable();
if (tempRoom) {
dispatch({ type: 'initRoom', payload: tempRoom });
}
};
load();
}, []);
const store = useMemo(() => {
return { roomsAvailable, usersAvailable, dispatch };
}, [roomsAvailable, usersAvailable]);
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<StyleSheetManager shouldForwardProp={shouldForwardProp}> <StyleSheetManager shouldForwardProp={shouldForwardProp}>
<BrowserRouter> <UserRoomAvailableContext.Provider value={store}>
<Routes> <BrowserRouter>
<Route element={<AppLayout />}> <Routes>
<Route index element={<Navigate replace to={PATH.USER} />} /> <Route element={<AppLayout />}>
<Route path={PATH.DASHBOARD} element={<Dashboard />} /> <Route
<Route path={PATH.USER} element={<User />} /> index
<Route path={PATH.ROOM} element={<Room />} /> element={<Navigate replace to={PATH.DASHBOARD} />}
</Route> />
<Route path={PATH.OTHER_PATH} element={<NotFound />} /> <Route path={PATH.DASHBOARD} element={<Dashboard />} />
</Routes> <Route path={PATH.USER} element={<User />} />
</BrowserRouter> <Route path={PATH.ROOM} element={<Room />} />
</Route>
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
</Routes>
</BrowserRouter>
</UserRoomAvailableContext.Provider>
</StyleSheetManager> </StyleSheetManager>
<Toast /> <Toast />
@@ -0,0 +1,139 @@
import { IDataState } from '@type/common';
import { Dispatch, createContext } from 'react';
interface IUserRoomState {
usersAvailable: IDataState[];
roomsAvailable: IDataState[];
}
type TAction =
| 'initRoom'
| 'initUser'
| 'addRoom'
| 'removeRoom'
| 'addUser'
| 'removeUser'
| 'updateUserName'
| 'updateRoomName';
interface IAction {
type: TAction;
payload: IDataState[];
}
interface IUserRoomAvailableContext {
roomsAvailable?: IDataState[];
usersAvailable?: IDataState[];
dispatch?: Dispatch<IAction>;
}
const UserRoomAvailableContext = createContext<IUserRoomAvailableContext>({});
const initialState: IUserRoomState = {
usersAvailable: [],
roomsAvailable: [],
};
const reducer = (state: IUserRoomState, action: IAction) => {
switch (action.type) {
case 'initRoom':
return {
...state,
roomsAvailable: action.payload,
};
case 'initUser':
return {
...state,
usersAvailable: action.payload,
};
case 'addUser': {
const tempArr = state.usersAvailable;
const itemExist = state.usersAvailable.find(
(item) => item.id === action.payload[0].id
);
if (!itemExist) {
tempArr.push(action.payload[0]);
}
return {
...state,
usersAvailable: tempArr,
};
}
case 'updateUserName': {
const tempArr = state.usersAvailable;
if (action.payload[0].name) {
const indexItemUpdate = tempArr.findIndex(
(item) => item.id === action.payload[0].id
);
tempArr[indexItemUpdate].name = action.payload[0].name;
}
return {
...state,
usersAvailable: tempArr,
};
}
case 'removeUser':
return {
...state,
usersAvailable: state.usersAvailable.filter(
(item) => item.id !== action.payload[0].id
),
};
case 'addRoom': {
const tempArr = state.roomsAvailable;
const itemExist = state.roomsAvailable.find(
(item) => item.id === action.payload[0].id
);
if (!itemExist) {
tempArr.push(action.payload[0]);
}
return {
...state,
roomsAvailable: tempArr,
};
}
case 'updateRoomName': {
const tempArr = state.roomsAvailable;
if (action.payload[0].name) {
const indexItemUpdate = tempArr.findIndex(
(item) => item.id === action.payload[0].id
);
tempArr[indexItemUpdate].name = action.payload[0].name;
}
return {
...state,
roomsAvailable: tempArr,
};
}
case 'removeRoom':
return {
...state,
roomsAvailable: state.roomsAvailable.filter(
(item) => item.id !== action.payload[0].id
),
};
default:
throw new Error('Action unknown');
}
};
export { UserRoomAvailableContext, reducer, initialState };
@@ -7,18 +7,28 @@ import { createRoom as createRoomFn } from '@service/roomServices';
// Constants // Constants
import { ADD_SUCCESS } from '@constant/messages'; import { ADD_SUCCESS } from '@constant/messages';
// Hooks
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
/** /**
* Create room on database * Create room on database
* @returns The boolean of isCreating and create room function * @returns The boolean of isCreating and create room function
*/ */
const useCreateRoom = () => { const useCreateRoom = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { dispatch } = useUserRoomAvailable();
const { mutate: createRoom, isPending: isCreating } = useMutation({ const { mutate: createRoom, isPending: isCreating } = useMutation({
mutationFn: createRoomFn, mutationFn: createRoomFn,
onSuccess: () => { onSuccess: (room) => {
toast.success(ADD_SUCCESS); toast.success(ADD_SUCCESS);
queryClient.invalidateQueries({ queryKey: ['rooms'] }); queryClient.invalidateQueries({ queryKey: ['rooms'] });
// Add room available to global state
dispatch!({
type: 'addRoom',
payload: [{ id: room.id, name: room.name }],
});
}, },
onError: (err) => toast.error(err.message), onError: (err) => toast.error(err.message),
}); });
@@ -7,18 +7,28 @@ import { updateRoom as updateRoomFn } from '@service/roomServices';
// Constants // Constants
import { UPDATE_SUCCESS } from '@constant/messages'; import { UPDATE_SUCCESS } from '@constant/messages';
// Hooks
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
/** /**
* Update room from database * Update room from database
* @returns The status of updating and updateRoom function * @returns The status of updating and updateRoom function
*/ */
const useUpdateRoom = () => { const useUpdateRoom = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { dispatch } = useUserRoomAvailable();
const { mutate: updateRoom, isPending: isUpdating } = useMutation({ const { mutate: updateRoom, isPending: isUpdating } = useMutation({
mutationFn: updateRoomFn, mutationFn: updateRoomFn,
onSuccess: () => { onSuccess: (room) => {
toast.success(UPDATE_SUCCESS); toast.success(UPDATE_SUCCESS);
queryClient.invalidateQueries({ queryKey: ['rooms'] }); queryClient.invalidateQueries({ queryKey: ['rooms'] });
// Update name room in global state
dispatch!({
type: 'updateRoomName',
payload: [{ id: room.id, name: room.name }],
});
}, },
onError: (err) => toast.error(err.message), onError: (err) => toast.error(err.message),
}); });
@@ -0,0 +1,18 @@
import { useContext } from 'react';
// Contexts
import { UserRoomAvailableContext } from '@context/UserRoomAvailableContext';
const useUserRoomAvailable = () => {
const context = useContext(UserRoomAvailableContext);
if (context === undefined) {
throw new Error(
'UserRoomAvailableContext was used outside of UserRoomAvailableProvider'
);
}
return context;
};
export { useUserRoomAvailable };
@@ -7,18 +7,28 @@ import { createUser as createUserFn } from '@service/userServices';
// Constants // Constants
import { ADD_SUCCESS } from '@constant/messages'; import { ADD_SUCCESS } from '@constant/messages';
// Hooks
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
/** /**
* Create user in database * Create user in database
* @returns The status of creating users and createUser function * @returns The status of creating users and createUser function
*/ */
const useCreateUser = () => { const useCreateUser = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { dispatch } = useUserRoomAvailable();
const { mutate: createUser, isPending: isCreating } = useMutation({ const { mutate: createUser, isPending: isCreating } = useMutation({
mutationFn: createUserFn, mutationFn: createUserFn,
onSuccess: () => { onSuccess: (user) => {
toast.success(ADD_SUCCESS); toast.success(ADD_SUCCESS);
queryClient.invalidateQueries({ queryKey: ['users'] }); queryClient.invalidateQueries({ queryKey: ['users'] });
// Add user to global statement
dispatch!({
type: 'addUser',
payload: [{ id: user.id, name: user.name }],
});
}, },
onError: (err) => toast.error(err.message), onError: (err) => toast.error(err.message),
}); });
@@ -7,18 +7,28 @@ import { updateUser as updateUserFn } from '@service/userServices';
// Messages // Messages
import { UPDATE_SUCCESS } from '@constant/messages'; import { UPDATE_SUCCESS } from '@constant/messages';
// Hooks
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
/** /**
* Update user from database * Update user from database
* @returns The status of updating user and updateUser function * @returns The status of updating user and updateUser function
*/ */
const useUpdateUser = () => { const useUpdateUser = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { dispatch } = useUserRoomAvailable();
const { mutate: updateUser, isPending: isUpdating } = useMutation({ const { mutate: updateUser, isPending: isUpdating } = useMutation({
mutationFn: updateUserFn, mutationFn: updateUserFn,
onSuccess: () => { onSuccess: (user) => {
toast.success(UPDATE_SUCCESS); toast.success(UPDATE_SUCCESS);
queryClient.invalidateQueries({ queryKey: ['users'] }); queryClient.invalidateQueries({ queryKey: ['users'] });
// Update name in global state
dispatch!({
type: 'updateUserName',
payload: [{ id: user.id, name: user.name }],
});
}, },
onError: (err) => toast.error(err.message), onError: (err) => toast.error(err.message),
}); });
+8
View File
@@ -1,10 +1,18 @@
import styled from 'styled-components'; import styled from 'styled-components';
// Hooks
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
const StyledDashboard = styled.main` const StyledDashboard = styled.main`
padding: 20px; padding: 20px;
`; `;
const Dashboard = () => { const Dashboard = () => {
const { roomsAvailable, usersAvailable } = useUserRoomAvailable();
console.log('Rooms Available: ', roomsAvailable);
console.log('Users available: ', usersAvailable);
return ( return (
<StyledDashboard> <StyledDashboard>
<p>This page currently still develop. Please try again later!</p> <p>This page currently still develop. Please try again later!</p>
+6 -1
View File
@@ -14,13 +14,18 @@ interface IUserRow {
} }
const UserRow = ({ user }: IUserRow) => { const UserRow = ({ user }: IUserRow) => {
const { id, name, phone } = user; const { id, name, phone, isBooked } = user;
return ( return (
<Table.Row> <Table.Row>
<div>{id}</div> <div>{id}</div>
<div>{name}</div> <div>{name}</div>
<div>{phone}</div> <div>{phone}</div>
<div>{
isBooked
? 'Yes'
: 'No'
}</div>
<div> <div>
<Modal> <Modal>
<Menus.Menu> <Menus.Menu>
@@ -24,7 +24,7 @@ import Spinner from '@commonStyle/Spinner';
import { useUsers } from '@hook/users/useUsers'; import { useUsers } from '@hook/users/useUsers';
const UserTable = () => { const UserTable = () => {
const columnName = ['Id', 'Name', 'Phone']; const columnName = ['Id', 'Name', 'Phone', 'Is Booked'];
const { isLoading, users } = useUsers(); const { isLoading, users } = useUsers();
const renderUserRow = useCallback( const renderUserRow = useCallback(
@@ -51,7 +51,7 @@ const UserTable = () => {
{users && users.length ? ( {users && users.length ? (
<Menus> <Menus>
<Table columns="10% 40% 35% 15%"> <Table columns="10% 35% 30% 15% 10%">
<Table.Header headerColumn={columnName} /> <Table.Header headerColumn={columnName} />
<Table.Body<IUser> data={users} render={renderUserRow} /> <Table.Body<IUser> data={users} render={renderUserRow} />
</Table> </Table>
+31 -9
View File
@@ -1,5 +1,6 @@
// Types // Types
import { IRoom } from '@type/rooms'; import { IRoom } from '@type/rooms';
import { IDataState } from '@type/common';
// Services // Services
import supabase from './supabaseService'; import supabase from './supabaseService';
@@ -38,32 +39,39 @@ const getAllRooms = async (
* Update room into database * Update room into database
* @param room Room object need to be updated * @param room Room object need to be updated
*/ */
const updateRoom = async (room: IRoom): Promise<void> => { const updateRoom = async (room: IRoom): Promise<IRoom> => {
const { error } = await supabase const { data, error } = await supabase
.from(ROOMS_TABLE) .from(ROOMS_TABLE)
.update(room) .update(room)
.eq('id', room.id); .eq('id', room.id)
.select()
.single();
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_UPDATE_ROOM); throw new Error(ERROR_UPDATE_ROOM);
} }
return data;
}; };
/** /**
* Add room to database * Add room to database
* @param room The room object need to be add * @param room The room object need to be add
*/ */
const createRoom = async (room: IRoom): Promise<void> => { const createRoom = async (room: IRoom): Promise<IRoom> => {
// Set default status const { data, error } = await supabase
room.status = false; .from(ROOMS_TABLE)
.insert(room)
const { error } = await supabase.from(ROOMS_TABLE).insert([room]); .select()
.single();
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_CREATE_ROOM); throw new Error(ERROR_CREATE_ROOM);
} }
return data;
}; };
/** /**
@@ -79,4 +87,18 @@ const deleteRoom = async (idRoom: number) => {
} }
}; };
export { getAllRooms, updateRoom, createRoom, deleteRoom }; const getRoomsAvailable = async (): Promise<IDataState[]> => {
const { data, error } = await supabase
.from(ROOMS_TABLE)
.select('id, name')
.eq('status', false);
if (error) {
console.error(error.message);
throw new Error(ERROR_FETCHING);
}
return data;
};
export { getAllRooms, updateRoom, createRoom, deleteRoom, getRoomsAvailable };
+31 -6
View File
@@ -1,5 +1,6 @@
// Types // Types
import { IUser } from '@type/users'; import { IUser } from '@type/users';
import { IDataState } from '@type/common';
// Services // Services
import supabase from './supabaseService'; import supabase from './supabaseService';
@@ -13,29 +14,39 @@ const ERROR_UPDATE_USER = "Can't update user!";
* Create user to the database * Create user to the database
* @param user The user object need to be created * @param user The user object need to be created
*/ */
const createUser = async (user: IUser): Promise<void> => { const createUser = async (user: IUser): Promise<IUser> => {
const { error } = await supabase.from(USERS_TABLE).insert([user]); const { data, error } = await supabase
.from(USERS_TABLE)
.insert(user)
.select()
.single();
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_CREATE_USER); throw new Error(ERROR_CREATE_USER);
} }
return data;
}; };
/** /**
* Update the user to the database * Update the user to the database
* @param user The user object need to be updated * @param user The user object need to be updated
*/ */
const updateUser = async (user: IUser): Promise<void> => { const updateUser = async (user: IUser): Promise<IUser> => {
const { error } = await supabase const { data, error } = await supabase
.from(USERS_TABLE) .from(USERS_TABLE)
.update(user) .update(user)
.eq('id', user.id); .eq('id', user.id)
.select()
.single();
if (error) { if (error) {
console.error(error.message); console.error(error.message);
throw new Error(ERROR_UPDATE_USER); throw new Error(ERROR_UPDATE_USER);
} }
return data;
}; };
/** /**
@@ -64,4 +75,18 @@ const getAllUsers = async (
return data; return data;
}; };
export { updateUser, createUser, getAllUsers }; const getUserNotBooked = async (): Promise<IDataState[]> => {
const { data, error } = await supabase
.from(USERS_TABLE)
.select('id, name')
.eq('isBooked', false);
if (error) {
console.error(error.message);
throw new Error(ERROR_FETCHING);
}
return data;
};
export { updateUser, createUser, getAllUsers, getUserNotBooked };
+6 -1
View File
@@ -1,3 +1,8 @@
type Nullable<T> = T | null; type Nullable<T> = T | null;
export type { Nullable }; interface IDataState {
id: number;
name: string;
}
export type { Nullable, IDataState };
+4 -1
View File
@@ -65,7 +65,7 @@ export interface Database {
id?: number id?: number
name: string name: string
price: number price: number
status: boolean status?: boolean
} }
Update: { Update: {
id?: number id?: number
@@ -78,16 +78,19 @@ export interface Database {
users: { users: {
Row: { Row: {
id: number id: number
isBooked: boolean
name: string name: string
phone: string phone: string
} }
Insert: { Insert: {
id?: number id?: number
isBooked?: boolean
name: string name: string
phone: string phone: string
} }
Update: { Update: {
id?: number id?: number
isBooked?: boolean
name?: string name?: string
phone?: string phone?: string
} }
+1
View File
@@ -2,6 +2,7 @@ interface IUser {
id: number; id: number;
name: string; name: string;
phone: string; phone: string;
isBooked: boolean;
} }
export type { IUser }; export type { IUser };