mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Update hooks and add content for room page
This commit is contained in:
@@ -2,12 +2,25 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
// Constants
|
||||
import { BASE_URL } from '../constants/path';
|
||||
import { searchQuery } from '../helpers/utils';
|
||||
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config';
|
||||
|
||||
export const useFetch = (
|
||||
// Utils
|
||||
import { searchQuery } from '../helpers/utils';
|
||||
|
||||
/**
|
||||
*
|
||||
* @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,
|
||||
phoneNum: string,
|
||||
columnSearch: string,
|
||||
keyWord: string,
|
||||
tempSortBy: string,
|
||||
tempOrderBy: string,
|
||||
reload?: boolean,
|
||||
@@ -35,7 +48,7 @@ export const useFetch = (
|
||||
: DEFAULT_ORDER_BY;
|
||||
|
||||
// Query search
|
||||
const query = searchQuery(phoneNum, sortBy, orderBy);
|
||||
const query = searchQuery(columnSearch, keyWord, sortBy, orderBy);
|
||||
|
||||
try {
|
||||
const response = await fetch(BASE_URL + path + '?' + query);
|
||||
@@ -56,6 +69,8 @@ export const useFetch = (
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [path, reload, phoneNum, tempOrderBy, tempSortBy]);
|
||||
}, [path, reload, columnSearch, keyWord, tempOrderBy, tempSortBy]);
|
||||
return { data, isPending, errorMsg };
|
||||
};
|
||||
|
||||
export { useFetch };
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { useState, useEffect, useCallback, ChangeEvent } from 'react';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
ERROR,
|
||||
VALUE,
|
||||
getPropValues,
|
||||
isObject,
|
||||
isRequired,
|
||||
} from '../helpers/utils';
|
||||
import { getPropValues, isObject, isRequired } from '../helpers/utils';
|
||||
|
||||
// Types
|
||||
import { TKeyValue, TValidator } from '../globals/types';
|
||||
import { ERROR, VALUE } from '../constants/variables';
|
||||
|
||||
/**
|
||||
* Custom hooks to validate your Form...
|
||||
@@ -36,11 +31,11 @@ const useForm = (
|
||||
useEffect(() => {
|
||||
setInitialErrorState(initialValue);
|
||||
setDisable(true);
|
||||
setDirty(getPropValues(stateSchema));
|
||||
|
||||
// If initial value true, setValues again from stateSchema
|
||||
// and enabled button
|
||||
if (initialValue) {
|
||||
setValues({});
|
||||
setValues(getPropValues(stateSchema, VALUE));
|
||||
setDisable(false);
|
||||
}
|
||||
@@ -116,10 +111,19 @@ const useForm = (
|
||||
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setIsDirty(true);
|
||||
|
||||
let error = '';
|
||||
const name = (event.target! as HTMLInputElement).name;
|
||||
const value = (event.target! as HTMLInputElement).value;
|
||||
let value: string | boolean = '';
|
||||
|
||||
const error = validateFormFields(name, value);
|
||||
if ((event.target! as HTMLInputElement).type === 'checkbox') {
|
||||
value = (event.target! as HTMLInputElement).checked;
|
||||
} else {
|
||||
value = (event.target! as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
error = validateFormFields(name, value)!;
|
||||
}
|
||||
|
||||
setValues((prevState) => ({ ...prevState, [name]: value }));
|
||||
setErrors((prevState) => ({ ...prevState, [name]: error }));
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* Custom hook to call handler when click outside element
|
||||
* @param handler Handler function
|
||||
* @param listeningCapturing A boolean value indicating that events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. If not specified, defaults to false.
|
||||
* @returns Return a mutable ref object
|
||||
*/
|
||||
export const useOutsideClick = (
|
||||
handler: () => void,
|
||||
listeningCapturing = true,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledRoom = styled.main`
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
const Room = () => {
|
||||
return (
|
||||
<StyledRoom>
|
||||
<p>Room page</p>
|
||||
</StyledRoom>
|
||||
);
|
||||
};
|
||||
|
||||
export default Room;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { forwardRef, useEffect } from 'react';
|
||||
|
||||
// Components
|
||||
import Dialog from '../../components/Dialog';
|
||||
import RoomForm from './Form';
|
||||
|
||||
// Interfaces
|
||||
import { IDialogProps } from '../../globals/interfaces';
|
||||
|
||||
// Types
|
||||
import { TRoom } from '../../globals/types';
|
||||
|
||||
const RoomDialog = forwardRef((props, ref) => {
|
||||
const dialogRef = ref as React.MutableRefObject<
|
||||
HTMLDialogElement | undefined
|
||||
>;
|
||||
// prettier-ignore
|
||||
const {
|
||||
onClose,
|
||||
setReload,
|
||||
reload,
|
||||
data,
|
||||
isAdd
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogRef.current) {
|
||||
dialogRef.current.addEventListener('click', (e: MouseEvent) => {
|
||||
const dialogDimensions = dialogRef.current!.getBoundingClientRect();
|
||||
if (
|
||||
e.clientX < dialogDimensions.left ||
|
||||
e.clientX > dialogDimensions.right ||
|
||||
e.clientY < dialogDimensions.top ||
|
||||
e.clientY > dialogDimensions.bottom
|
||||
) {
|
||||
dialogRef.current!.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [dialogRef]);
|
||||
|
||||
return (
|
||||
<Dialog title={'Add room'} onClose={onClose} ref={dialogRef}>
|
||||
<RoomForm
|
||||
onClose={onClose!}
|
||||
reload={reload!}
|
||||
setReload={setReload!}
|
||||
room={data}
|
||||
isAdd={isAdd!}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}) as React.FC<IDialogProps<TRoom>>;
|
||||
|
||||
export default RoomDialog;
|
||||
@@ -0,0 +1,334 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
// Styled
|
||||
import Input from '../../commons/styles/Input.ts';
|
||||
import TextArea from '../../commons/styles/TextArea.ts';
|
||||
|
||||
// Components
|
||||
import Form from '../../components/Form/index.tsx';
|
||||
import FormRow from '../../components/FormRow/index.tsx';
|
||||
import Button from '../../commons/styles/Button.ts';
|
||||
|
||||
// Types
|
||||
import {
|
||||
TKeyValue,
|
||||
TRoom,
|
||||
TStateSchema,
|
||||
TValidator,
|
||||
} from '../../globals/types.ts';
|
||||
|
||||
// Hooks
|
||||
import useForm from '../../hooks/useForm.ts';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
isValidNumber,
|
||||
isValidString,
|
||||
skipCheck,
|
||||
} from '../../helpers/validators.ts';
|
||||
import { addValidator, getValueFromObj } from '../../helpers/utils.ts';
|
||||
import { sendRequest } from '../../helpers/sendRequest.ts';
|
||||
|
||||
// Constants
|
||||
import { STATUS_CODE } from '../../constants/statusCode.ts';
|
||||
import {
|
||||
ADD_SUCCESS,
|
||||
EDIT_SUCCESS,
|
||||
errorMsg,
|
||||
} from '../../constants/messages.ts';
|
||||
import { ROOM_PATH } from '../../constants/path.ts';
|
||||
|
||||
const FormBtn = styled(Button)`
|
||||
width: 100%;
|
||||
|
||||
&:disabled,
|
||||
&[disabled] {
|
||||
background-color: var(--disabled-btn-color);
|
||||
}
|
||||
`;
|
||||
|
||||
interface IRoomFormProp {
|
||||
onClose: () => void;
|
||||
reload: boolean;
|
||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
room?: TRoom | null;
|
||||
isAdd: boolean;
|
||||
}
|
||||
|
||||
const RoomForm = ({
|
||||
onClose,
|
||||
reload,
|
||||
setReload,
|
||||
room,
|
||||
isAdd,
|
||||
}: IRoomFormProp) => {
|
||||
const [reset, setReset] = useState(true);
|
||||
|
||||
if (isAdd) {
|
||||
// If this is add form, reset value
|
||||
room = null;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
const initialValue: string = isAdd
|
||||
? ''
|
||||
: room!
|
||||
&& room.id;
|
||||
|
||||
const {
|
||||
idValue,
|
||||
nameValue,
|
||||
amountValue,
|
||||
priceValue,
|
||||
discountValue,
|
||||
statusValue,
|
||||
descriptionValue,
|
||||
} = getValueFromObj<TRoom>(room);
|
||||
|
||||
// Define your state schema
|
||||
const stateSchema: TStateSchema = {
|
||||
id: { value: idValue || '' },
|
||||
name: { value: nameValue || '', error: '' },
|
||||
amount: { value: amountValue || '', error: '' },
|
||||
price: { value: priceValue || '', error: '' },
|
||||
discount: { value: discountValue || '', error: '' },
|
||||
status: { value: statusValue || '', error: '' },
|
||||
description: { value: descriptionValue || '', error: '' },
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const stateValidatorSchema: TValidator = {
|
||||
name: addValidator({
|
||||
validatorFunc: isValidString,
|
||||
prop: 'name'
|
||||
}),
|
||||
amount: addValidator({
|
||||
validatorFunc: isValidNumber,
|
||||
prop: 'amount',
|
||||
}),
|
||||
price: addValidator({
|
||||
validatorFunc: isValidNumber,
|
||||
prop: 'price',
|
||||
}),
|
||||
discount: addValidator({
|
||||
validatorFunc: isValidNumber,
|
||||
prop: 'discount'
|
||||
}),
|
||||
status: addValidator({
|
||||
validatorFunc: skipCheck,
|
||||
prop: 'status',
|
||||
required: false,
|
||||
}),
|
||||
description: addValidator({
|
||||
validatorFunc: isValidString,
|
||||
prop: 'description'
|
||||
}),
|
||||
};
|
||||
|
||||
// Submit form
|
||||
const onSubmitForm = async (state: TKeyValue) => {
|
||||
try {
|
||||
if (isAdd) {
|
||||
// Add request
|
||||
const response = await sendRequest(
|
||||
ROOM_PATH,
|
||||
JSON.stringify(state),
|
||||
'POST',
|
||||
);
|
||||
|
||||
if (response.statusCode === STATUS_CODE.CREATE) {
|
||||
toast.success(ADD_SUCCESS);
|
||||
|
||||
onResetForm();
|
||||
} else {
|
||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
||||
}
|
||||
} else {
|
||||
// Edit request
|
||||
const response = await sendRequest(
|
||||
ROOM_PATH + `/${room!.id}`,
|
||||
JSON.stringify(state),
|
||||
'PUT',
|
||||
);
|
||||
|
||||
if (response.statusCode == STATUS_CODE.OK) {
|
||||
toast.success(EDIT_SUCCESS);
|
||||
} else {
|
||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
||||
}
|
||||
}
|
||||
// Reload table data
|
||||
setReload(!reload);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
// Close and reset form
|
||||
const closeAndReset = () => {
|
||||
onClose();
|
||||
onResetForm();
|
||||
};
|
||||
|
||||
console.log(initialValue);
|
||||
|
||||
// prettier-ignore
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
dirty,
|
||||
handleOnChange,
|
||||
handleOnSubmit,
|
||||
disable } =
|
||||
useForm(
|
||||
stateSchema,
|
||||
stateValidatorSchema,
|
||||
onSubmitForm,
|
||||
initialValue
|
||||
);
|
||||
|
||||
// prettier-ignore
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
amount,
|
||||
price,
|
||||
discount,
|
||||
status,
|
||||
description
|
||||
} = values;
|
||||
|
||||
// Clear form value when this is a add form
|
||||
useEffect(() => {
|
||||
if (isAdd) {
|
||||
Object.keys(values).forEach((key) => (values[key] = ''));
|
||||
}
|
||||
}, [isAdd]); // eslint-disable-line
|
||||
|
||||
// Reset form
|
||||
const onResetForm = () => {
|
||||
setReset(!reset);
|
||||
Object.keys(values).forEach((key) => (values[key] = ''));
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleOnSubmit}>
|
||||
<Input type="hidden" name="id" value={id as string} />
|
||||
<FormRow
|
||||
label="Name"
|
||||
error={
|
||||
// prettier-ignore
|
||||
errors.name && dirty.name
|
||||
? (errors.name as string)
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
name="name"
|
||||
value={name as string}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow
|
||||
label="Amount"
|
||||
// prettier-ignore
|
||||
error={
|
||||
errors.amount && dirty.amount
|
||||
? (errors.amount as string)
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
name="amount"
|
||||
value={amount as string}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow
|
||||
label="Price"
|
||||
error={
|
||||
// prettier-ignore
|
||||
errors.price && dirty.price
|
||||
? (errors.price as string)
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
name="price"
|
||||
value={price as string}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow
|
||||
label="Discount"
|
||||
error={
|
||||
// prettier-ignore
|
||||
errors.roomId && dirty.roomId
|
||||
? (errors.roomId as string)
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
name="discount"
|
||||
value={discount as string}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow label="Status">
|
||||
<Input
|
||||
type="checkbox"
|
||||
name="status"
|
||||
checked={status as boolean}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow
|
||||
label="Description"
|
||||
error={
|
||||
// prettier-ignore
|
||||
errors.description && dirty.description
|
||||
? (errors.description as string)
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<TextArea
|
||||
name="description"
|
||||
rows={3}
|
||||
value={description as string}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<Form.Action>
|
||||
<FormBtn type="submit" name="submit" disabled={disable}>
|
||||
{
|
||||
// prettier-ignore
|
||||
isAdd
|
||||
? 'Add'
|
||||
: 'Edit'
|
||||
}
|
||||
</FormBtn>
|
||||
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
||||
Close
|
||||
</FormBtn>
|
||||
</Form.Action>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomForm;
|
||||
@@ -0,0 +1,198 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
// Components
|
||||
import { HiSquare2Stack } from 'react-icons/hi2';
|
||||
import { HiTrash } from 'react-icons/hi';
|
||||
import { StyledOperationTable } from './styled';
|
||||
import Menus from '../../components/Menus/Menus';
|
||||
import Table from '../../components/Table';
|
||||
import Direction from '../../commons/styles/Direction';
|
||||
import Message from '../../components/Message';
|
||||
import Search from '../../components/Search';
|
||||
import SortBy from '../../components/SortBy';
|
||||
import OrderBy from '../../components/OrderBy';
|
||||
|
||||
// Types
|
||||
import { TRoom } from '../../globals/types';
|
||||
|
||||
// Constants
|
||||
import { useFetch } from '../../hooks/useFetch';
|
||||
import { ROOM_PATH } from '../../constants/path';
|
||||
import { STATUS_CODE } from '../../constants/statusCode';
|
||||
import { CONFIRM_DELETE, DELETE_SUCCESS } from '../../constants/messages';
|
||||
import { ROOM_PAGE } from '../../constants/variables';
|
||||
|
||||
// Styled
|
||||
import Spinner from '../../commons/styles/Spinner';
|
||||
|
||||
// Utils
|
||||
import { sendRequest } from '../../helpers/sendRequest';
|
||||
|
||||
interface IRoomRow {
|
||||
room: TRoom;
|
||||
openFormDialog: () => void;
|
||||
setRoom: React.Dispatch<React.SetStateAction<TRoom | null>>;
|
||||
reload: boolean;
|
||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const RoomRow = ({
|
||||
room,
|
||||
openFormDialog,
|
||||
setRoom,
|
||||
reload,
|
||||
setReload,
|
||||
}: IRoomRow) => {
|
||||
const handleOnEdit = (room: TRoom) => {
|
||||
setRoom(room);
|
||||
openFormDialog();
|
||||
};
|
||||
|
||||
const handleOnDelete = async (room: TRoom) => {
|
||||
if (confirm(CONFIRM_DELETE)) {
|
||||
const response = await sendRequest(
|
||||
ROOM_PATH + `/${room.id}`,
|
||||
null,
|
||||
'DELETE',
|
||||
);
|
||||
|
||||
if (response.statusCode === STATUS_CODE.OK) {
|
||||
toast.success(DELETE_SUCCESS);
|
||||
|
||||
// Reload table
|
||||
setReload(!reload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { id, name, amount, price, discount, status } = room;
|
||||
|
||||
// prettier-ignore
|
||||
const statusText = status
|
||||
? 'Valid'
|
||||
: 'Invalid';
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<div>{id}</div>
|
||||
<div>{name}</div>
|
||||
<div>{amount}</div>
|
||||
<div>{price}</div>
|
||||
<div>{discount}</div>
|
||||
<div>{statusText}</div>
|
||||
|
||||
<Menus.Menu>
|
||||
<Menus.Toggle id={id} />
|
||||
|
||||
<Menus.List id={id}>
|
||||
<Menus.Button
|
||||
icon={<HiSquare2Stack />}
|
||||
onClick={() => handleOnEdit(room)}
|
||||
>
|
||||
Edit
|
||||
</Menus.Button>
|
||||
<Menus.Button icon={<HiTrash />} onClick={() => handleOnDelete(room)}>
|
||||
Delete
|
||||
</Menus.Button>
|
||||
</Menus.List>
|
||||
</Menus.Menu>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
interface IRoomTable {
|
||||
reload: boolean;
|
||||
setReload: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
openFormDialog: () => void;
|
||||
setRoom?: React.Dispatch<React.SetStateAction<TRoom | null>>;
|
||||
}
|
||||
|
||||
const RoomTable = ({
|
||||
reload,
|
||||
setReload,
|
||||
openFormDialog,
|
||||
setRoom,
|
||||
}: IRoomTable) => {
|
||||
const [nameSearch, setNameSearch] = useState('');
|
||||
const [searchParams] = useSearchParams();
|
||||
const [rooms, setRooms] = useState<TRoom[]>([]);
|
||||
const sortByValue = searchParams.get('sortBy')
|
||||
? searchParams.get('sortBy')!
|
||||
: '';
|
||||
const orderByValue = searchParams.get('orderBy')
|
||||
? searchParams.get('orderBy')!
|
||||
: '';
|
||||
|
||||
const { data, isPending, errorMsg } = useFetch(
|
||||
'rooms',
|
||||
'name',
|
||||
nameSearch,
|
||||
sortByValue,
|
||||
orderByValue,
|
||||
reload,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setRooms(data);
|
||||
} else {
|
||||
setRooms([]);
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
console.error(errorMsg);
|
||||
}
|
||||
}, [data, errorMsg]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Direction>
|
||||
<StyledOperationTable>
|
||||
<OrderBy options={ROOM_PAGE.ORDERBY_OPTIONS} />
|
||||
|
||||
<SortBy options={ROOM_PAGE.SORTBY_OPTIONS} />
|
||||
<Search
|
||||
setValueSearch={setNameSearch}
|
||||
setPlaceHolder="Search by name..."
|
||||
/>
|
||||
</StyledOperationTable>
|
||||
|
||||
{isPending && <Spinner />}
|
||||
|
||||
{rooms.length ? (
|
||||
<Menus>
|
||||
<Table columns="10% 20% 20% 20% 10% 10% 5%">
|
||||
<Table.Header>
|
||||
<div>Id</div>
|
||||
<div>Name</div>
|
||||
<div>Amount</div>
|
||||
<div>Price</div>
|
||||
<div>Discount</div>
|
||||
<div>Status</div>
|
||||
</Table.Header>
|
||||
<Table.Body<TRoom>
|
||||
data={rooms}
|
||||
render={(room: TRoom) => (
|
||||
<RoomRow
|
||||
room={room}
|
||||
key={room.id}
|
||||
reload={reload}
|
||||
setReload={setReload}
|
||||
openFormDialog={openFormDialog}
|
||||
setRoom={setRoom!}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
</Menus>
|
||||
) : (
|
||||
!isPending && <Message>No data to show here!</Message>
|
||||
)}
|
||||
</Direction>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomTable;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
// Components
|
||||
import Direction from '../../commons/styles/Direction';
|
||||
import Button from '../../commons/styles/Button';
|
||||
import RoomTable from './Table';
|
||||
|
||||
// Styled
|
||||
import { StyledRoom, Title } from './styled';
|
||||
import RoomDialog from './Dialog';
|
||||
|
||||
// Types
|
||||
import { TRoom } from '../../globals/types';
|
||||
|
||||
const Room = () => {
|
||||
const dialogRef = useRef<HTMLDialogElement>();
|
||||
const [reload, setReload] = useState(true);
|
||||
const [room, setRoom] = useState<TRoom | null>(null);
|
||||
const [isAdd, setIsAdd] = useState(false);
|
||||
|
||||
const openFormDialog = (isAddForm: boolean = false) => {
|
||||
setIsAdd(isAddForm);
|
||||
dialogRef.current?.showModal();
|
||||
};
|
||||
|
||||
const closeFormDialog = () => {
|
||||
dialogRef.current?.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledRoom>
|
||||
<Direction type="horizontal">
|
||||
<Title>List Room</Title>
|
||||
<Button onClick={() => openFormDialog(true)}>Add room</Button>
|
||||
</Direction>
|
||||
|
||||
<RoomTable
|
||||
reload={reload}
|
||||
setReload={setReload}
|
||||
openFormDialog={() => openFormDialog()}
|
||||
setRoom={setRoom}
|
||||
/>
|
||||
</StyledRoom>
|
||||
|
||||
<RoomDialog
|
||||
onClose={closeFormDialog}
|
||||
ref={dialogRef}
|
||||
setReload={setReload}
|
||||
reload={reload}
|
||||
data={room}
|
||||
isAdd={isAdd}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Room;
|
||||
@@ -0,0 +1,24 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledRoom = styled.main`
|
||||
padding: 20px;
|
||||
padding-bottom: 100px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
`;
|
||||
|
||||
const Title = styled.h2`
|
||||
font-size: var(--fs-md);
|
||||
color: var(--dark-text);
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
|
||||
const StyledOperationTable = styled.div`
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
export { StyledRoom, Title, StyledOperationTable };
|
||||
@@ -6,17 +6,19 @@ import UserForm from './Form';
|
||||
|
||||
// Interfaces
|
||||
import { IDialogProps } from '../../globals/interfaces';
|
||||
import { TUser } from '../../globals/types';
|
||||
|
||||
const UserDialog = forwardRef((props, ref) => {
|
||||
const dialogRef = ref as React.MutableRefObject<
|
||||
HTMLDialogElement | undefined
|
||||
>;
|
||||
|
||||
// prettier-ignore
|
||||
const {
|
||||
onClose,
|
||||
setReload,
|
||||
reload,
|
||||
user,
|
||||
data,
|
||||
isAdd
|
||||
} = props;
|
||||
|
||||
@@ -42,11 +44,11 @@ const UserDialog = forwardRef((props, ref) => {
|
||||
onClose={onClose!}
|
||||
reload={reload!}
|
||||
setReload={setReload!}
|
||||
user={user}
|
||||
user={data}
|
||||
isAdd={isAdd!}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}) as React.FC<IDialogProps>;
|
||||
}) as React.FC<IDialogProps<TUser>>;
|
||||
|
||||
export default UserDialog;
|
||||
|
||||
@@ -24,12 +24,12 @@ import useForm from '../../hooks/useForm';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
isValidAddress,
|
||||
isValidString,
|
||||
isValidNumber,
|
||||
isValidPhoneNumber,
|
||||
isValidString,
|
||||
isValidName,
|
||||
} from '../../helpers/validators';
|
||||
import { addValidator, getValueUser } from '../../helpers/utils.ts';
|
||||
import { addValidator, getValueFromObj } from '../../helpers/utils.ts';
|
||||
import { sendRequest } from '../../helpers/sendRequest.ts';
|
||||
|
||||
// Constants
|
||||
@@ -66,26 +66,57 @@ const UserForm = ({
|
||||
isAdd,
|
||||
}: IUserFormProp) => {
|
||||
const [reset, setReset] = useState(true);
|
||||
|
||||
if (isAdd) {
|
||||
// If this is add form, reset value
|
||||
user = null;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
const initialValue: string = isAdd
|
||||
? ''
|
||||
: user!
|
||||
&& user.id;
|
||||
|
||||
const {
|
||||
idValue,
|
||||
nameValue,
|
||||
identifiedCodeValue,
|
||||
phoneValue,
|
||||
roomIdValue,
|
||||
addressValue,
|
||||
} = getValueFromObj<TUser>(user);
|
||||
|
||||
// Define your state schema
|
||||
// prettier-ignore
|
||||
const stateSchema: TStateSchema = {
|
||||
id: { value: getValueUser(user, 'id') },
|
||||
name: { value: getValueUser(user, 'name'), error: '' },
|
||||
identifiedCode: { value: getValueUser(user, 'identifiedCode'), error: '' },
|
||||
phone: { value: getValueUser(user, 'phone'), error: '' },
|
||||
roomId: { value: getValueUser(user, 'roomId'), error: '' },
|
||||
address: { value: getValueUser(user, 'address'), error: '' },
|
||||
id: { value: idValue || '' },
|
||||
name: {
|
||||
value: nameValue || '',
|
||||
error: '' ,
|
||||
},
|
||||
identifiedCode: {
|
||||
value: identifiedCodeValue || '',
|
||||
error: '',
|
||||
},
|
||||
phone: {
|
||||
value: phoneValue || '',
|
||||
error: '',
|
||||
},
|
||||
roomId: {
|
||||
value: roomIdValue || '',
|
||||
error: '' ,
|
||||
},
|
||||
address: {
|
||||
value: addressValue || '',
|
||||
error: ''
|
||||
},
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const stateValidatorSchema: TValidator = {
|
||||
name: addValidator({
|
||||
validatorFunc: isValidString,
|
||||
validatorFunc: isValidName,
|
||||
prop: 'full name'
|
||||
}),
|
||||
identifiedCode: addValidator({
|
||||
@@ -101,7 +132,7 @@ const UserForm = ({
|
||||
prop: 'room number'
|
||||
}),
|
||||
address: addValidator({
|
||||
validatorFunc: isValidAddress,
|
||||
validatorFunc: isValidString,
|
||||
prop: 'address'
|
||||
}),
|
||||
};
|
||||
@@ -122,6 +153,8 @@ const UserForm = ({
|
||||
} else {
|
||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
||||
}
|
||||
|
||||
onResetForm();
|
||||
} else {
|
||||
// Edit request
|
||||
const response = await sendRequest(
|
||||
@@ -138,7 +171,6 @@ const UserForm = ({
|
||||
}
|
||||
// Reload table data
|
||||
setReload(!reload);
|
||||
onResetForm();
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message);
|
||||
@@ -280,7 +312,12 @@ const UserForm = ({
|
||||
|
||||
<Form.Action>
|
||||
<FormBtn type="submit" name="submit" disabled={disable}>
|
||||
Add
|
||||
{
|
||||
// prettier-ignore
|
||||
isAdd
|
||||
? 'Add'
|
||||
: 'Edit'
|
||||
}
|
||||
</FormBtn>
|
||||
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
||||
Close
|
||||
|
||||
@@ -121,6 +121,7 @@ const UserTable = ({
|
||||
|
||||
const { data, isPending, errorMsg } = useFetch(
|
||||
'users',
|
||||
'phone',
|
||||
phoneSearch,
|
||||
sortByValue,
|
||||
orderByValue,
|
||||
@@ -146,7 +147,10 @@ const UserTable = ({
|
||||
<OrderBy options={USER_PAGE.ORDERBY_OPTIONS} />
|
||||
|
||||
<SortBy options={USER_PAGE.SORTBY_OPTIONS} />
|
||||
<Search setPhoneSearch={setPhoneSearch} />
|
||||
<Search
|
||||
setValueSearch={setPhoneSearch}
|
||||
setPlaceHolder="Search by phone..."
|
||||
/>
|
||||
</StyledOperationTable>
|
||||
|
||||
{isPending && <Spinner />}
|
||||
|
||||
@@ -48,7 +48,7 @@ const User = () => {
|
||||
ref={dialogRef}
|
||||
setReload={setReload}
|
||||
reload={reload}
|
||||
user={user}
|
||||
data={user}
|
||||
isAdd={isAdd}
|
||||
/>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user