mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Add user and rooms to global state
This commit is contained in:
@@ -13,30 +13,66 @@ import NotFound from './pages/NotFound';
|
||||
// Constants
|
||||
import * as PATH from './constants/path';
|
||||
import Toast from './components/Toast';
|
||||
import {
|
||||
UserRoomAvailableContext,
|
||||
initialState,
|
||||
reducer,
|
||||
} from '@context/UserRoomAvailableContext';
|
||||
import { useEffect, useReducer } from 'react';
|
||||
import { getUserNotBooked } from '@service/userServices';
|
||||
import { getRoomsAvailable } from '@service/roomServices';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route index element={<Navigate replace to={PATH.USER} />} />
|
||||
<Route path={PATH.DASHBOARD} element={<Dashboard />} />
|
||||
<Route path={PATH.USER} element={<User />} />
|
||||
<Route path={PATH.ROOM} element={<Room />} />
|
||||
</Route>
|
||||
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
<UserRoomAvailableContext.Provider
|
||||
value={{ roomsAvailable, usersAvailable, dispatch }}
|
||||
>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route index element={<Navigate replace to={PATH.DASHBOARD} />} />
|
||||
<Route path={PATH.DASHBOARD} element={<Dashboard />} />
|
||||
<Route path={PATH.USER} element={<User />} />
|
||||
<Route path={PATH.ROOM} element={<Room />} />
|
||||
</Route>
|
||||
<Route path={PATH.OTHER_PATH} element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</UserRoomAvailableContext.Provider>
|
||||
</StyleSheetManager>
|
||||
|
||||
<Toast />
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
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 };
|
||||
@@ -6,6 +6,7 @@ import { createRoom as createRoomFn } from '@service/roomServices';
|
||||
|
||||
// Constants
|
||||
import { ADD_SUCCESS } from '@constant/messages';
|
||||
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
|
||||
|
||||
/**
|
||||
* Create room on database
|
||||
@@ -13,12 +14,19 @@ import { ADD_SUCCESS } from '@constant/messages';
|
||||
*/
|
||||
const useCreateRoom = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dispatch } = useUserRoomAvailable();
|
||||
|
||||
const { mutate: createRoom, isPending: isCreating } = useMutation({
|
||||
mutationFn: createRoomFn,
|
||||
onSuccess: () => {
|
||||
onSuccess: (room) => {
|
||||
toast.success(ADD_SUCCESS);
|
||||
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),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { updateRoom as updateRoomFn } from '@service/roomServices';
|
||||
|
||||
// Constants
|
||||
import { UPDATE_SUCCESS } from '@constant/messages';
|
||||
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
|
||||
|
||||
/**
|
||||
* Update room from database
|
||||
@@ -13,12 +14,19 @@ import { UPDATE_SUCCESS } from '@constant/messages';
|
||||
*/
|
||||
const useUpdateRoom = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dispatch } = useUserRoomAvailable();
|
||||
|
||||
const { mutate: updateRoom, isPending: isUpdating } = useMutation({
|
||||
mutationFn: updateRoomFn,
|
||||
onSuccess: () => {
|
||||
onSuccess: (room) => {
|
||||
toast.success(UPDATE_SUCCESS);
|
||||
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),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { UserRoomAvailableContext } from '@context/UserRoomAvailableContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
const useUserRoomAvailable = () => {
|
||||
const context = useContext(UserRoomAvailableContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
'UserRoomAvailableContext was used outside of UserRoomAvailableProvider'
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export { useUserRoomAvailable };
|
||||
@@ -6,6 +6,7 @@ import { createUser as createUserFn } from '@service/userServices';
|
||||
|
||||
// Constants
|
||||
import { ADD_SUCCESS } from '@constant/messages';
|
||||
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
|
||||
|
||||
/**
|
||||
* Create user in database
|
||||
@@ -13,12 +14,19 @@ import { ADD_SUCCESS } from '@constant/messages';
|
||||
*/
|
||||
const useCreateUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dispatch } = useUserRoomAvailable();
|
||||
|
||||
const { mutate: createUser, isPending: isCreating } = useMutation({
|
||||
mutationFn: createUserFn,
|
||||
onSuccess: () => {
|
||||
onSuccess: (user) => {
|
||||
toast.success(ADD_SUCCESS);
|
||||
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),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { updateUser as updateUserFn } from '@service/userServices';
|
||||
|
||||
// Messages
|
||||
import { UPDATE_SUCCESS } from '@constant/messages';
|
||||
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
|
||||
|
||||
/**
|
||||
* Update user from database
|
||||
@@ -13,12 +14,19 @@ import { UPDATE_SUCCESS } from '@constant/messages';
|
||||
*/
|
||||
const useUpdateUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dispatch } = useUserRoomAvailable();
|
||||
|
||||
const { mutate: updateUser, isPending: isUpdating } = useMutation({
|
||||
mutationFn: updateUserFn,
|
||||
onSuccess: () => {
|
||||
onSuccess: (user) => {
|
||||
toast.success(UPDATE_SUCCESS);
|
||||
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),
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useUserRoomAvailable } from '@hook/useUserRoomAvailable';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledDashboard = styled.main`
|
||||
@@ -5,6 +6,11 @@ const StyledDashboard = styled.main`
|
||||
`;
|
||||
|
||||
const Dashboard = () => {
|
||||
const { roomsAvailable, usersAvailable } = useUserRoomAvailable();
|
||||
|
||||
console.log('Rooms Available: ', roomsAvailable);
|
||||
console.log('Users available: ', usersAvailable);
|
||||
|
||||
return (
|
||||
<StyledDashboard>
|
||||
<p>This page currently still develop. Please try again later!</p>
|
||||
|
||||
@@ -14,13 +14,18 @@ interface IUserRow {
|
||||
}
|
||||
|
||||
const UserRow = ({ user }: IUserRow) => {
|
||||
const { id, name, phone } = user;
|
||||
const { id, name, phone, isBooked } = user;
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<div>{id}</div>
|
||||
<div>{name}</div>
|
||||
<div>{phone}</div>
|
||||
<div>{
|
||||
isBooked
|
||||
? 'Yes'
|
||||
: 'No'
|
||||
}</div>
|
||||
<div>
|
||||
<Modal>
|
||||
<Menus.Menu>
|
||||
|
||||
@@ -24,7 +24,7 @@ import Spinner from '@commonStyle/Spinner';
|
||||
import { useUsers } from '@hook/users/useUsers';
|
||||
|
||||
const UserTable = () => {
|
||||
const columnName = ['Id', 'Name', 'Phone'];
|
||||
const columnName = ['Id', 'Name', 'Phone', 'Is Booked'];
|
||||
const { isLoading, users } = useUsers();
|
||||
|
||||
const renderUserRow = useCallback(
|
||||
@@ -51,7 +51,7 @@ const UserTable = () => {
|
||||
|
||||
{users && users.length ? (
|
||||
<Menus>
|
||||
<Table columns="10% 40% 35% 15%">
|
||||
<Table columns="10% 35% 30% 15% 10%">
|
||||
<Table.Header headerColumn={columnName} />
|
||||
<Table.Body<IUser> data={users} render={renderUserRow} />
|
||||
</Table>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IRoom } from '@type/rooms';
|
||||
|
||||
// Services
|
||||
import supabase from './supabaseService';
|
||||
import { IDataState } from '@type/common';
|
||||
|
||||
// Constants
|
||||
const ROOMS_TABLE = 'rooms';
|
||||
@@ -34,47 +35,70 @@ const getAllRooms = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update room into database
|
||||
* @param room Room object need to be updated
|
||||
*/
|
||||
const updateRoom = async (room: IRoom): Promise<void> => {
|
||||
const { error } = await supabase.from(ROOMS_TABLE).update(room).eq("id", room.id);
|
||||
const updateRoom = async (room: IRoom): Promise<IRoom> => {
|
||||
const { data, error } = await supabase
|
||||
.from(ROOMS_TABLE)
|
||||
.update(room)
|
||||
.eq('id', room.id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
throw new Error(ERROR_UPDATE_ROOM);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add room to database
|
||||
* @param room The room object need to be add
|
||||
*/
|
||||
const createRoom = async (room: IRoom): Promise<void> => {
|
||||
// Set default status
|
||||
room.status = false;
|
||||
const createRoom = async (room: IRoom): Promise<IRoom> => {
|
||||
const { data, error } = await supabase
|
||||
.from(ROOMS_TABLE)
|
||||
.insert(room)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
const { error } = await supabase.from(ROOMS_TABLE).insert([room]);
|
||||
|
||||
if(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
throw new Error(ERROR_CREATE_ROOM);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete room in database
|
||||
* @param idRoom The id of room need to delete
|
||||
*/
|
||||
const deleteRoom = async(idRoom: number) => {
|
||||
const deleteRoom = async (idRoom: number) => {
|
||||
const { error } = await supabase.from(ROOMS_TABLE).delete().eq('id', idRoom);
|
||||
|
||||
if(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
throw new Error(ERROR_DELETE_ROOM);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 };
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IUser } from '@type/users';
|
||||
|
||||
// Services
|
||||
import supabase from './supabaseService';
|
||||
import { IDataState } from '@type/common';
|
||||
|
||||
const USERS_TABLE = 'users';
|
||||
const ERROR_FETCHING = "Users can't be loaded!";
|
||||
@@ -13,26 +14,39 @@ const ERROR_UPDATE_USER = "Can't update user!";
|
||||
* Create user to the database
|
||||
* @param user The user object need to be created
|
||||
*/
|
||||
const createUser = async (user: IUser): Promise<void> => {
|
||||
const { error } = await supabase.from(USERS_TABLE).insert([user]);
|
||||
const createUser = async (user: IUser): Promise<IUser> => {
|
||||
const { data, error } = await supabase
|
||||
.from(USERS_TABLE)
|
||||
.insert(user)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
throw new Error(ERROR_CREATE_USER);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the user to the database
|
||||
* @param user The user object need to be updated
|
||||
*/
|
||||
const updateUser = async (user: IUser): Promise<void> => {
|
||||
const { error } = await supabase.from(USERS_TABLE).update(user).eq("id", user.id);
|
||||
const updateUser = async (user: IUser): Promise<IUser> => {
|
||||
const { data, error } = await supabase
|
||||
.from(USERS_TABLE)
|
||||
.update(user)
|
||||
.eq('id', user.id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
throw new Error(ERROR_UPDATE_USER);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,4 +75,18 @@ const getAllUsers = async (
|
||||
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 };
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
type Nullable<T> = T | null;
|
||||
|
||||
export type { Nullable };
|
||||
interface IDataState {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type { Nullable, IDataState };
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface Database {
|
||||
id?: number
|
||||
name: string
|
||||
price: number
|
||||
status: boolean
|
||||
status?: boolean
|
||||
}
|
||||
Update: {
|
||||
id?: number
|
||||
@@ -78,16 +78,19 @@ export interface Database {
|
||||
users: {
|
||||
Row: {
|
||||
id: number
|
||||
isBooked: boolean
|
||||
name: string
|
||||
phone: string
|
||||
}
|
||||
Insert: {
|
||||
id?: number
|
||||
isBooked?: boolean
|
||||
name: string
|
||||
phone: string
|
||||
}
|
||||
Update: {
|
||||
id?: number
|
||||
isBooked?: boolean
|
||||
name?: string
|
||||
phone?: string
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ interface IUser {
|
||||
id: number;
|
||||
name: string;
|
||||
phone: string;
|
||||
isBooked: boolean;
|
||||
}
|
||||
|
||||
export type { IUser };
|
||||
|
||||
Reference in New Issue
Block a user