diff --git a/package.json b/package.json index f64cf39..219ad38 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.1.3", + "mapbox-gl": "^2.11.0", "jwt-decode": "^3.1.2", "moment": "^2.29.4", "react": "^18.2.0", diff --git a/src/App.js b/src/App.js index 89571cf..0dfb39e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,21 @@ import { - BrowserRouter as Router, Navigate, Route, Routes + BrowserRouter as Router, + Navigate, + Route, + Routes, } from "react-router-dom"; import "./App.css"; import { RequireAuth } from "./components"; +import { + Client, + Dashboard, + HostAdmin, + Login, + ManagerAdmin, + Signup, + ManagerRoom, +} from "./pages"; import RoleBasedGuard from "./guards/RoleBasedGuard"; -import { Client, Dashboard, HostAdmin, Login, ManagerAdmin, Signup } from "./pages"; import { SYSTEM_ADMIN, HOST, CLIENT } from "./constants/index"; function App() { @@ -15,31 +26,33 @@ function App() { } /> } /> } /> + } /> + + + + } + > - - - }> - - - - - - }> - - - - - - }> - + + + + } + > + + + + } + > ); diff --git a/src/components/card/card.jsx b/src/components/card/card.jsx new file mode 100644 index 0000000..47c8219 --- /dev/null +++ b/src/components/card/card.jsx @@ -0,0 +1,41 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardActions from "@mui/material/CardActions"; +import CardContent from "@mui/material/CardContent"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; + +const bull = ( + + • + +); + +export default function BasicCard(props) { + return ( + + + + {props.inTitle} + + + {props.title} + + + {props.underTitle} + + + {props.content} +
a benevolent smile +
+
+ + + +
+ ); +} diff --git a/src/components/snackbars/index.jsx b/src/components/snackbars/index.jsx new file mode 100644 index 0000000..152b23f --- /dev/null +++ b/src/components/snackbars/index.jsx @@ -0,0 +1,26 @@ +import * as React from "react"; +import Button from "@mui/material/Button"; +import Snackbar from "@mui/material/Snackbar"; +import IconButton from "@mui/material/IconButton"; +import CloseIcon from "@mui/icons-material/Close"; + +export default function SimpleSnackbar(props) { + const [open, setOpen] = React.useState(false); + React.useEffect(() => { + setOpen(props.open); + }, [props]); + const handleClose = () => { + setOpen(false); + }; + + return ( +
+ +
+ ); +} diff --git a/src/pages/index.js b/src/pages/index.js index dddba0e..0291beb 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,6 +1,7 @@ export { default as Login } from "./login"; export { default as Signup } from "./Signup"; export { default as HostAdmin } from "./HostAdmin"; +export { default as ManagerRoom } from "./manager_room"; export { default as ManagerAdmin } from "./ManagerAdmin"; export { default as Dashboard } from "./Dashboard"; export { default as Client } from "./Client"; diff --git a/src/pages/manager_room/create.jsx b/src/pages/manager_room/create.jsx new file mode 100644 index 0000000..06ca354 --- /dev/null +++ b/src/pages/manager_room/create.jsx @@ -0,0 +1,214 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import Modal from "@mui/material/Modal"; +import Button from "@mui/material/Button"; +import TextField from "@mui/material/TextField"; +import MenuItem from "@mui/material/MenuItem"; +import { roomAPI } from "../../services/room.api"; +import SimpleSnackbar from "../../components/snackbars"; +const currencies = [ + { + value: "available", + label: "Available", + }, + { + value: "full", + label: "Full", + }, + { + value: "pending", + label: "Pending", + }, + { + value: "maintain", + label: "Maintain", + }, +]; +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + bgcolor: "background.paper", + border: "2px solid #000", + boxShadow: 24, + pt: 2, + px: 4, + pb: 3, +}; + +export default function CreateModal(props) { + const [open, setOpen] = React.useState(false); + const [snackOpen, setsnackOpen] = React.useState(false); + const [masage, setmasage] = React.useState(""); + const [name, setname] = React.useState(""); + const [address, setaddress] = React.useState(""); + const [capacity, setcapacity] = React.useState(0); + const [price, setprice] = React.useState(0); + const [lng, setlng] = React.useState(0); + const [lat, setlat] = React.useState(0); + const [status, setstatus] = React.useState("false"); + const [description, setdescription] = React.useState(""); + const [image, setimage] = React.useState(""); + const [rating, setrating] = React.useState(0); + React.useEffect(() => { + setOpen(props.show); + }, [props]); + const handleClose = () => { + setOpen(false); + }; + const CreateRoom = async () => { + if ( + name !== "" && + address !== "" && + lng && + lat && + status !== "" && + image !== "" && + rating + ) { + const newRoom = await roomAPI.create({ + name, + address, + capacity, + price, + status, + description, + image, + rating, + location: { lng, lat }, + is_active: true, + }); + setsnackOpen(true); + if (newRoom ? true : false) { + setmasage(" Create success"); + setOpen(!open); + window.location.reload(false); + } else { + setmasage("create fails"); + } + } else { + setsnackOpen(true); + setmasage(" Check data again"); + } + }; + return ( +
+ + +

Create Room

+ { + setname(e.target.value); + }} + variant="outlined" + /> + { + setaddress(e.target.value); + }} + variant="outlined" + /> + { + setcapacity(parseInt(e.target.value)); + }} + variant="outlined" + /> + { + setprice(parseInt(e.target.value)); + }} + variant="outlined" + /> + { + setlng(parseInt(e.target.value)); + }} + style={{ width: "50%", marginTop: "10px" }} + label="Longtitude" + variant="outlined" + /> + { + setlat(parseInt(e.target.value)); + }} + style={{ width: "50%", marginTop: "10px" }} + label="Latitude" + variant="outlined" + /> + { + setdescription(e.target.value); + }} + label="description" + variant="outlined" + /> + { + setimage(e.target.value); + }} + variant="outlined" + /> + { + setrating(parseInt(e.target.value)); + }} + style={{ width: "100%", marginTop: "10px" }} + label="rating" + variant="outlined" + /> + { + setstatus(e.target.value); + }} + style={{ width: "100%", marginTop: "10px" }} + label="Status" + variant="outlined" + > + {currencies.map((option) => ( + + {option.label} + + ))} + + +
+
+ +
+ ); +} diff --git a/src/pages/manager_room/edit.jsx b/src/pages/manager_room/edit.jsx new file mode 100644 index 0000000..a321af6 --- /dev/null +++ b/src/pages/manager_room/edit.jsx @@ -0,0 +1,259 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import Modal from "@mui/material/Modal"; +import Button from "@mui/material/Button"; +import TextField from "@mui/material/TextField"; +import MenuItem from "@mui/material/MenuItem"; +import { roomAPI } from "../../services/room.api"; +import SimpleSnackbar from "../../components/snackbars"; +import mapboxgl from "mapbox-gl"; +mapboxgl.accessToken = + "pk.eyJ1IjoiY3NpZGUiLCJhIjoiY2xhYTBrMXIwMDF4bzNwcDExMDdmNG10ZCJ9.dJ8utYc12qEivXTKawbZxg"; +const currencies = [ + { + value: "available", + label: "Available", + }, + { + value: "full", + label: "Full", + }, + { + value: "pending", + label: "Pending", + }, + { + value: "maintain", + label: "Maintain", + }, +]; +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + bgcolor: "background.paper", + border: "2px solid #000", + boxShadow: 24, + pt: 2, + px: 4, + pb: 3, +}; + +export default function EditModal(props) { + const [open, setOpen] = React.useState(false); + const [snackOpen, setsnackOpen] = React.useState(false); + const [masage, setmasage] = React.useState(""); + const [name, setname] = React.useState(""); + const [address, setaddress] = React.useState(""); + const [capacity, setcapacity] = React.useState(0); + const [price, setprice] = React.useState(0); + const [lng, setlng] = React.useState(-70.9); + const [lat, setlat] = React.useState(42.35); + const [status, setstatus] = React.useState("false"); + const [description, setdescription] = React.useState(""); + const [image, setimage] = React.useState(""); + const [rating, setrating] = React.useState(0); + const mapContainer = React.useRef(null); + const map = React.useRef(null); + const [zoom, setZoom] = React.useState(9); + React.useEffect(() => { + setOpen(props.show); + setname(props?.data?.name); + setaddress(props?.data?.address); + setcapacity(props?.data?.capacity); + setprice(props?.data?.price); + if (props.data?.location) { + setlng(parseInt(props.data?.location?.y)); + setlat(parseInt(props?.data?.location?.x)); + } + setstatus(props?.data?.status); + setdescription(props?.data?.description); + setimage(props?.data?.image); + setrating(props?.data?.rating); + map.current = new mapboxgl.Map({ + container: mapContainer.current, + style: "mapbox://styles/mapbox/streets-v11", + center: [lng, lat], + zoom: zoom, + }); + }, [props, props?.data]); + + const handleClose = () => { + setOpen(false); + }; + const EditRoom = async () => { + if ( + name !== "" && + address !== "" && + lng && + lat && + status !== "" && + image !== "" && + rating && + props?.data?.roomId + ) { + const newRoom = await roomAPI.edit(props?.data?.roomId, { + name, + address, + capacity, + price, + status, + description, + image, + rating, + location: { lng, lat }, + is_active: true, + }); + setsnackOpen(true); + if (newRoom ? true : false) { + setmasage(" Create success"); + setOpen(!open); + window.location.reload(false); + } else { + setmasage("create fails"); + } + } else { + setsnackOpen(true); + setmasage(" Check data again"); + } + }; + return ( +
+ + +

Edit Room

+ { + setname(e.target.value); + }} + variant="outlined" + /> + { + setaddress(e.target.value); + }} + variant="outlined" + /> + { + setcapacity(parseInt(e.target.value)); + }} + variant="outlined" + /> + { + setprice(parseInt(e.target.value)); + }} + variant="outlined" + /> + + + { + setdescription(e.target.value); + }} + label="description" + variant="outlined" + /> + { + setimage(e.target.value); + }} + variant="outlined" + /> + { + setrating(parseInt(e.target.value)); + }} + style={{ width: "100%", marginTop: "10px" }} + label="rating" + variant="outlined" + /> + { + setstatus(e.target.value); + }} + style={{ width: "100%", marginTop: "10px" }} + label="Status" + variant="outlined" + > + {currencies.map((option) => ( + + {option.label} + + ))} + + + +
+
+
+ +
+ ); +} diff --git a/src/pages/manager_room/index.jsx b/src/pages/manager_room/index.jsx new file mode 100644 index 0000000..01f8495 --- /dev/null +++ b/src/pages/manager_room/index.jsx @@ -0,0 +1,53 @@ +import { Button } from "@mui/material"; +import React from "react"; +import CreateModal from "./create"; +import ListRoom from "./list"; +import EditModal from "./edit"; +import { roomAPI } from "../../services/room.api"; +import { useNavigate, Navigate } from "react-router-dom"; +export default function ManagerRoom() { + const [modelCreateRoom, setModelCreateRoom] = React.useState(false); + const [modelEditRoom, setModelEditRoom] = React.useState(false); + const [rooms, setRooms] = React.useState([]); + const [room, setRoom] = React.useState([]); + React.useEffect(() => { + getRooms(); + }, []); + const getRooms = async () => { + const room = await roomAPI.getAll(25, 0); + setRooms(room.data); + }; + const getRoom = async (e) => { + const room = await roomAPI.getDetail(e.target.id); + setRoom(room?.data?.response); + setModelEditRoom(!modelEditRoom); + }; + return ( +
+ List Room + + +

Total:{rooms.count}

+ + { + setModelCreateRoom(!modelCreateRoom); + }} + /> + { + setModelEditRoom(!modelEditRoom); + }} + /> +
+ ); +} diff --git a/src/pages/manager_room/list.jsx b/src/pages/manager_room/list.jsx new file mode 100644 index 0000000..cb72d4a --- /dev/null +++ b/src/pages/manager_room/list.jsx @@ -0,0 +1,40 @@ +import React from "react"; +import BasicCard from "../../components/card/card"; +import { roomAPI } from "../../services/room.api"; +import { Button } from "@mui/material"; +import SimpleSnackbar from "../../components/snackbars"; +export default function ListRoom(props) { + const [snackOpen, setsnackOpen] = React.useState(false); + const [masage, setmasage] = React.useState(""); + const remove = async (roomId) => { + const isDelete = await roomAPI.Remove(roomId); + window.location.reload(false); + }; + return ( +
+ {props?.data?.map((room, index) => ( +
+ + + + +
+ ))} + +
+ ); +} diff --git a/src/services/http.js b/src/services/http.js index b815ad7..3d72133 100644 --- a/src/services/http.js +++ b/src/services/http.js @@ -13,6 +13,8 @@ export const http = axios.create({ timeout: 10000, headers: { "Content-Type": "application/json", + Authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluMUBnbWFpbC5jb20iLCJpYXQiOjE2NjkzNDMwMDIsImV4cCI6MTY3MTkzNTAwMn0.C4_sUQgsUb7eQtVTOqRPiavuJkPbv15c3L_HcrTJW3c", }, }); @@ -25,7 +27,7 @@ http.interceptors.request.use( return config; } if (accessToken) { - config.headers["Authorization"] = `Bearer ${accessToken}`; + // config.headers["Authorization"] = `Bearer ${accessToken}`; } return config; }, @@ -51,8 +53,8 @@ http.interceptors.response.use( refreshToken = refreshToken ? refreshToken : authAPI.refreshToken().finally(() => { - refreshToken = null; - }); + refreshToken = null; + }); return refreshToken .then((access_token) => { error.response.config.Authorization = access_token; diff --git a/src/services/room.api.js b/src/services/room.api.js new file mode 100644 index 0000000..8dee44d --- /dev/null +++ b/src/services/room.api.js @@ -0,0 +1,19 @@ +import { http } from "./http"; + +export const roomAPI = { + create: (data) => { + return http.post("rooms/create", data); + }, + edit: (room_id, data) => { + return http.put(`rooms/${room_id}`, data); + }, + getAll: (pageSize, pageNumber) => { + return http.get(`rooms?pageNumber=${pageNumber}&pageSize=${pageSize}`); + }, + getDetail: (room_id) => { + return http.get(`rooms/${room_id}`); + }, + Remove: (room_id) => { + return http.delete(`rooms/${room_id}`); + }, +};