diff --git a/client/public/index.html b/client/public/index.html index e589c04b3d4f2a3d2052a749f3080463afc62935..677fd0833ca8205928a6f1811c4d117e53f3ee82 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -10,7 +10,7 @@ content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> - <title>React App</title> + <title>Fahrtenbuch</title> </head> <body> <div id="root"></div> diff --git a/client/public/locales/de/translation.json b/client/public/locales/de/translation.json index 25b920d9e05711319add1894617c25859b153452..1d6c6a31b1b59e80d48214e5cbd9636e82871fba 100644 --- a/client/public/locales/de/translation.json +++ b/client/public/locales/de/translation.json @@ -62,6 +62,16 @@ "labelCurrentlyRented": "Momentan augeliehene Boote", "buttonShowAll": "Alle anzeigen" }, + "boatManager": { + "blockedBoats": "Gesperrte Boote", + "availableBoats": "Verfügbare Boote" + }, + "managedBoatTile": { + "buttonInfo": "Info", + "buttonUnblock": "Entsperren", + "buttonBlock": "Sperren" + + }, "routes": { "/": "Wassersport", "/book": "Boot buchen", @@ -69,6 +79,7 @@ "/signout": "Bootrückgabe", "/login": "Login", "/staff/overview": "Bootsübersicht", - "/staff/boattypes" : "Bootstypen" + "/staff/boattypes": "Bootstypen", + "/staff/manage": "Boote Verwalten" } } \ No newline at end of file diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 0fa76a30f2995f12dff4946de450d06395a85c53..31b1ff78ab75e0154a34dbeebaaca26ba37a61b6 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -62,6 +62,15 @@ "labelCurrentlyRented": "Currently Rented Boats", "buttonShowAll": "Show all" }, + "boatManager": { + "blockedBoats": "Blocked Boats", + "availableBoats": "Available Boats" + }, + "managedBoatTile": { + "buttonInfo": "Info", + "buttonUnblock": "Unblock", + "buttonBlock": "Block" + }, "routes": { "/": "Water Sports", "/book": "Book a Boat", @@ -69,6 +78,7 @@ "/signout": "Sign Out", "/login": "Login", "/staff/overview": "Boat Overview", - "/staff/boattypes" : "Boat Types" + "/staff/boattypes" : "Boat Types", + "/staff/manage": "Manage Boats" } } \ No newline at end of file diff --git a/client/src/Router.tsx b/client/src/Router.tsx index 5b22368139422c445e1da58d3eb16ed3fd742f74..9e4bb848759bee3eb356b479ef4c4172edf11b46 100644 --- a/client/src/Router.tsx +++ b/client/src/Router.tsx @@ -2,6 +2,7 @@ import React from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import StaffLayout from "./components/StaffLayout"; import UserLayout from "./components/UserLayout"; +import BoatManager from "./pages/BoatManager"; import BoatOverview from "./pages/BoatOverview"; import BoatTypeOverview from "./pages/BoatTypeOverview"; import BookingForm from "./pages/BookingForm"; @@ -37,6 +38,7 @@ function Router() { <Route path="staff" element={<StaffLayout />}> <Route path="overview" element={<BoatOverview />} /> <Route path="boattypes" element={<BoatTypeOverview />} /> + <Route path="manage" element={<BoatManager />} /> </Route> </Routes> </BrowserRouter> diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx deleted file mode 100644 index 8a83863e0c1bd9a5acc122fa419b2d98b47e7972..0000000000000000000000000000000000000000 --- a/client/src/components/Header.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import { Navbar, Container, Nav } from "react-bootstrap"; -import { Link } from "react-router-dom"; - -function Header() { - return ( - <Navbar bg="green" variant="dark"> - <Container> - <Navbar.Brand as={Link} to="/"> - Navbar - </Navbar.Brand> - <Nav className="me-auto"> - <Nav.Link as={Link} to="/vehicleTypes"> - Types - </Nav.Link> - <Nav.Link as={Link} to="/login"> - Login - </Nav.Link> - </Nav> - </Container> - </Navbar> - ); -} -export default Header; diff --git a/client/src/components/ManagedBoatTile.tsx b/client/src/components/ManagedBoatTile.tsx index 3b6878f94ad4a9a2a94d7fc598a3d70b1649dc7d..f0e2ce41086ee21b11717db037eeee1255f02b26 100644 --- a/client/src/components/ManagedBoatTile.tsx +++ b/client/src/components/ManagedBoatTile.tsx @@ -5,17 +5,20 @@ import { useTranslation } from "react-i18next"; // import { Link } from "react-router-dom"; function ManagedBoatTile(props: any) { - const {t} = useTranslation(); - return( + const { t } = useTranslation(); + return ( <Col> - <div className={`card-body shadow position-relative mb-1 rounded-2 ${props.blocked ? 'bg-dark' : 'bg-green'}` } style={{height : "80px"}}> - <div className="text-uppercase fs-4 tile-label-boatName position-absolute ms-2 mt-1 top-0 start-0 text-white fw-bold">{props.boatName}</div> + <div className={`card-body shadow position-relative mb-1 rounded-2 ${props.blocked ? 'bg-info' : 'bg-success'}`} style={{ height: "90px" }}> + <div title={props.boatName} className="text-uppercase fs-4 tile-label-boatName position-absolute ms-2 mt-1 top-0 start-0 text-center text-white fw-bold" + style={{ width: "95%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{props.boatName}</div> <div className="tile-label-time position-absolute me-2 mt-2 top-0 end-0 text-white">{props.lastUsed}</div> - <Button variant="light" className="mb-2 ms-2 bottom-0 start-0 shadow-sm bg-white border w-50 text-uppercase rounded-pill">{t("managedBoatTile.buttonInfo")}</Button> - <Button variant="light" className={`mb-2 me-2 bottom-0 end-0 shadow-sm ${props.blocked ? 'bg-green' : 'bg-danger'} border w-50 text-uppercase rounded-pill`}>{props.blocked ? t("managedBoatTile.buttonUnblock") : t("managedBoatTile.buttonUBlock")}</Button> + <div className="d-flex justify-content-between" style={{ marginTop: "24px", width: "104%", marginLeft: "-2%" }}> + <Button variant="secondary" style={{ flex: 0.48 }} className="mb-2 ms-2 bottom-0 start-0 shadow-sm text-uppercase rounded-pill">{t("managedBoatTile.buttonInfo")}</Button> + <Button variant={`${props.blocked ? 'success' : 'danger'}`} style={{ flex: 0.48 }} className={`mb-2 me-2 bottom-0 end-0 shadow-sm text-uppercase rounded-pill`}>{props.blocked ? t("managedBoatTile.buttonUnblock") : t("managedBoatTile.buttonBlock")}</Button> + </div> </div> </Col> - + ); } diff --git a/client/src/components/RentedBoatTile.tsx b/client/src/components/RentedBoatTile.tsx index 1ea3c769f2be64156237086ec98b70ba9648d765..df40ea0b615565cd2fb420a6986e53a5b4a9c9fc 100644 --- a/client/src/components/RentedBoatTile.tsx +++ b/client/src/components/RentedBoatTile.tsx @@ -4,10 +4,11 @@ import { Col } from "react-bootstrap"; // import { Link } from "react-router-dom"; function RentedBoatTile(props: any) { - return( + return ( <Col> - <div className={`card-body shadow position-relative mb-1 rounded-2 ${props.overdue ? 'bg-danger' : 'bg-secondary'}` } style={{height : "80px"}}> - <div className="text-uppercase fs-4 tile-label-boatName position-absolute ms-2 mt-1 top-0 start-0 text-white fw-bold">{props.boatName}</div> + <div className={`card-body shadow position-relative mb-1 rounded-2 ${props.overdue ? 'bg-danger' : 'bg-secondary'} rentedBoatTile`} style={{ height: "90px" }}> + <div className="text-uppercase fs-4 tile-label-boatName position-absolute ms-2 mt-1 top-0 start-0 text-white fw-bold" + style={{ maxWidth: "98%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{props.boatName}</div> <div className="tile-label-time position-absolute me-2 mt-2 top-0 end-0 text-white">{props.time}</div> <div className="tile-label-persons position-absolute ms-2 mb-2 bottom-0 start-0 text-white">{props.persons}</div> {/* Conditonally render the overdue text if it is set*/} @@ -16,7 +17,7 @@ function RentedBoatTile(props: any) { } </div> </Col> - + ); } diff --git a/client/src/components/StaffLayout.tsx b/client/src/components/StaffLayout.tsx index 68f18394eaa58055a10e43db9809552a2c9969c8..518f1dd77facfa67246388424cb41a16d53001d6 100644 --- a/client/src/components/StaffLayout.tsx +++ b/client/src/components/StaffLayout.tsx @@ -1,27 +1,33 @@ import { Container, Nav, Navbar } from "react-bootstrap"; import { useTranslation } from "react-i18next"; -import {Outlet, useLocation} from "react-router-dom"; +import { Outlet, useLocation } from "react-router-dom"; function StaffLayout() { - const {t} = useTranslation(); + const { t } = useTranslation(); const location = useLocation(); return ( <div> - <Navbar sticky="top" className="bg-secondary" variant="dark" expand="lg"> - <Container> - <Navbar.Brand className="fw-bold">{t(`routes.${location.pathname}`)}</Navbar.Brand> - <Navbar.Toggle /> - <Navbar.Collapse> - <Nav className="mr-auto" activeKey={location.pathname}> - <Nav.Link href="/staff/overview">{t("staffNav.buttonBoatOverview")}</Nav.Link> - <Nav.Link>{t("staffNav.buttonManageBoats")}</Nav.Link> - <Nav.Link>{t("staffNav.buttonStatistics")}</Nav.Link> - <Nav.Link>{t("staffNav.buttonLogout")}</Nav.Link> - </Nav> - </Navbar.Collapse> - </Container> - </Navbar> - <Outlet /> + <Navbar sticky="top" className="bg-secondary" variant="dark" expand="lg"> + <Container> + <Navbar.Brand className="fw-bold">{t(`routes.${location.pathname}`)}</Navbar.Brand> + <Navbar.Toggle /> + <Navbar.Collapse> + <Nav className="mr-auto d-flex justify-content-between w-100"> + <div className="d-flex"> + <Nav activeKey={location.pathname}> + <Nav.Link href="/staff/overview">{t("staffNav.buttonBoatOverview")}</Nav.Link> + <Nav.Link href="/staff/manage">{t("staffNav.buttonManageBoats")}</Nav.Link> + <Nav.Link>{t("staffNav.buttonStatistics")}</Nav.Link> + </Nav> + </div> + <div> + <Nav.Link>{t("staffNav.buttonLogout")}</Nav.Link> + </div> + </Nav> + </Navbar.Collapse> + </Container> + </Navbar> + <Outlet /> </div> ); } diff --git a/client/src/components/StaffModal.tsx b/client/src/components/StaffModal.tsx index ed8f8e54fe26ec6e1b74285bf71a4235787eb196..3de2e8603346fc23e0f90a67d174f69300db5f88 100644 --- a/client/src/components/StaffModal.tsx +++ b/client/src/components/StaffModal.tsx @@ -9,6 +9,7 @@ function StaffModal(props: { onSuccess?: any; onHide?: any; successText?: string; + hideSuccess?: boolean; hideText?: string; show: boolean; }) { @@ -34,14 +35,15 @@ function StaffModal(props: { variant={props.hideColor || "secondary"} onClick={props.onHide} > - {props.hideText || t("staffModal.Close")} + {props.hideText || t("staffModal.Cancel")} </Button> - <Button + {!props.hideSuccess ? <Button variant={props.successColor || "primary"} onClick={props.onSuccess} > {props.successText || t("staffModal.Done")} - </Button> + </Button> : {}} + </Modal.Footer> </Modal> ); diff --git a/client/src/pages/BoatManager.tsx b/client/src/pages/BoatManager.tsx new file mode 100644 index 0000000000000000000000000000000000000000..589a22268a7f3988cd15d95abe2fa10177f4464f --- /dev/null +++ b/client/src/pages/BoatManager.tsx @@ -0,0 +1,59 @@ +import React, { useEffect, useState } from "react"; +import { Button, Container, Row } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; +import Divider from "../components/Divider"; +import ManagedBoatTile from "../components/ManagedBoatTile"; +import { faEdit, faTrashAlt } from "@fortawesome/free-regular-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import StaffModal from "../components/StaffModal" + +function BoatManager() { + const { t } = useTranslation(); + const [addElement, setAddElement] = useState<any>(false); + + return ( + <div className="text-center position-relative"> + <h2 className="d-flex justify-content-center my-3">{t("boatManager.blockedBoats")}</h2> + {/*TODO: Make dynamic*/} + <div className="py-3 bg-light border"> + <Container> + <div className="overdue-tiles text-center"> + <Row xs={1} md={2} xxl={3}> + <ManagedBoatTile boatName="Nicholas Unless-Jesus-Christ-Had-Died-For-Thee-Thou-Hadst-Been-Damned Barbon" blocked={true}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 7" blocked={true}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 13" blocked={true}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 16" blocked={true}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 22" blocked={true}></ManagedBoatTile> + </Row> + </div> + <Button variant="light" className="mt-3 shadow-sm bg-white border w-50 text-uppercase rounded-pill">{t("boatOverview.buttonShowAll")}</Button> + </Container> + </div> + + <h2 className="my-3">{t("boatManager.availableBoats")}</h2> + <div className="py-3 bg-light border"> + <Container> + <div className="overdue-tiles text-center"> + <Row xs={1} md={2} xxl={3}> + <ManagedBoatTile boatName="Canoe 2" time="13:15-15:14" persons="3 Person" blocked={false}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 5" time="13:15-15:14" persons="3 Person" blocked={false}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 6" time="13:15-15:14" persons="3 Person" blocked={false}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 9" time="13:15-15:14" persons="3 Person" blocked={false}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 18" time="13:15-15:14" persons="3 Person" blocked={false}></ManagedBoatTile> + <ManagedBoatTile boatName="Canoe 20" time="13:15-15:14" persons="3 Person" blocked={false}></ManagedBoatTile> + </Row> + </div> + <Button variant="light" className="mt-3 shadow-sm bg-white border w-50 text-uppercase rounded-pill">{t("boatOverview.buttonShowAll")}</Button> + </Container> + </div> + <div className="d-flex px-2 py-1 justify-content-end bg-white border-top" style={{ position: "sticky", right: "5px", bottom: "0", zIndex: 3 }}> + <Button onClick={() => { setAddElement(true) }} variant="secondary"><FontAwesomeIcon icon={faPlus} className="text-white" /> Add Boat</Button> + </div> + <StaffModal header={"Add BoatType"} show={addElement} successText="Add" onHide={() => { setAddElement(false) }} onSuccess={() => { setAddElement(undefined) }}> + </StaffModal> + </div> + ); +} + +export default BoatManager; \ No newline at end of file diff --git a/client/src/pages/BoatOverview.tsx b/client/src/pages/BoatOverview.tsx index 78f00a5599372450cafc734a2a435c3ad1a63add..876eafd42b3fade260485f669391ba6105dfebfc 100644 --- a/client/src/pages/BoatOverview.tsx +++ b/client/src/pages/BoatOverview.tsx @@ -1,11 +1,15 @@ +import React, { useEffect, useState } from "react"; import { Button, Container, Row } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import Divider from "../components/Divider"; import RentedBoatTile from "../components/RentedBoatTile"; +import StaffModal from "../components/StaffModal"; function BoatOverview() { - const {t} = useTranslation(); + const { t } = useTranslation(); + const [infoElement, setInfoElement] = useState<any>(undefined); + return ( <div className="text-center"> {/*TODO: Make dynamic*/} @@ -13,8 +17,8 @@ function BoatOverview() { <div className="py-3 bg-light border"> <Container> <div className="overdue-tiles text-center"> - <Row xs={1} md={2} xxl={3}> - <RentedBoatTile boatName="Canoe 1" time="13:15-15:14" persons="1 Person" overdue="Overdue!"></RentedBoatTile> + <Row xs={1} md={2} xxl={3}> + <RentedBoatTile boatName="Canoe 1" time="13:15-15:14" persons="1 Person" overdue="Overdue!" onClick={()=>{setInfoElement({boatName:"ABC"})}}></RentedBoatTile> <RentedBoatTile boatName="Canoe 7" time="13:15-15:14" persons="3 Person" overdue="Overdue!"></RentedBoatTile> <RentedBoatTile boatName="Canoe 13" time="13:15-15:14" persons="3 Person" overdue="Overdue!"></RentedBoatTile> <RentedBoatTile boatName="Canoe 16" time="13:15-15:14" persons="3 Person" overdue="Overdue!"></RentedBoatTile> @@ -27,20 +31,22 @@ function BoatOverview() { <h2 className="my-3">{t("boatOverview.labelCurrentlyRented")}</h2> <div className="py-3 bg-light border"> - <Container> - <div className="overdue-tiles text-center"> - <Row xs={1} md={2} xxl={3}> - <RentedBoatTile boatName="Canoe 2" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> - <RentedBoatTile boatName="Canoe 5" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> - <RentedBoatTile boatName="Canoe 6" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> - <RentedBoatTile boatName="Canoe 9" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> - <RentedBoatTile boatName="Canoe 18" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> - <RentedBoatTile boatName="Canoe 20" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> - </Row> - </div> - <Button variant="light" className="mt-3 shadow-sm bg-white border w-50 text-uppercase rounded-pill">{t("boatOverview.buttonShowAll")}</Button> - </Container> + <Container> + <div className="overdue-tiles text-center"> + <Row xs={1} md={2} xxl={3}> + <RentedBoatTile boatName="Canoe 2" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> + <RentedBoatTile boatName="Canoe 5" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> + <RentedBoatTile boatName="Canoe 6" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> + <RentedBoatTile boatName="Canoe 9" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> + <RentedBoatTile boatName="Canoe 18" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> + <RentedBoatTile boatName="Canoe 20" time="13:15-15:14" persons="3 Person" overdue={false}></RentedBoatTile> + </Row> + </div> + <Button variant="light" className="mt-3 shadow-sm bg-white border w-50 text-uppercase rounded-pill">{t("boatOverview.buttonShowAll")}</Button> + </Container> </div> + <StaffModal header={"Boat Info"} show={infoElement} hideSuccess={true} onHide={() => { setInfoElement(undefined) }} onSuccess={() => { setInfoElement(undefined) }}> + </StaffModal> </div> ); } diff --git a/client/src/pages/BoatTypeOverview.tsx b/client/src/pages/BoatTypeOverview.tsx index 77bc6da5ae3f078e60757e95465f344dd707cb9f..3cff90c4bcae68b49266d75a24f47cc35dcab45e 100644 --- a/client/src/pages/BoatTypeOverview.tsx +++ b/client/src/pages/BoatTypeOverview.tsx @@ -4,9 +4,9 @@ import { useTranslation } from "react-i18next"; import { faEdit, faTrashAlt } from "@fortawesome/free-regular-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import StaffModal from "../components/StaffModal" +import StaffModal from "../components/StaffModal"; -function BoatOverview() { +function BoatTypeOverview() { const { t } = useTranslation(); const [editElement, setEditElement] = useState<any>(undefined); const [deleteElement, setDeleteElement] = useState<any>(undefined); @@ -14,7 +14,7 @@ function BoatOverview() { return ( <div className="m-1 h-100"> <div className="my-2 d-flex justify-content-end mx-2"> - <Button onClick={()=>{setAddElement(true)}} variant="secondary"><FontAwesomeIcon icon={faPlus} className="text-white" /> Add</Button> + <Button onClick={() => { setAddElement(true) }} variant="secondary"><FontAwesomeIcon icon={faPlus} className="text-white" /> Add</Button> </div> <Table responsive striped bordered hover> <thead> @@ -53,10 +53,10 @@ function BoatOverview() { } </tbody> </Table> - <StaffModal header={"Add BoatType"} show={addElement} successText="Add" onHide={() => { setAddElement(false) }}> + <StaffModal header={"Add BoatType"} show={addElement} successText="Add" onHide={() => { setAddElement(false) }} onSuccess={() => { setAddElement(undefined) }}> </StaffModal> - <StaffModal header={"Edit BoatType"} show={editElement} onHide={() => { setEditElement(undefined) }}> + <StaffModal header={"Edit BoatType"} show={editElement} onHide={() => { setEditElement(undefined) }} onSuccess={() => { setEditElement(undefined) }}> </StaffModal> <StaffModal header={"Delete BoatType?"} hideColor="secondary" successText="Delete" successColor="danger" show={deleteElement} onHide={() => { setDeleteElement(undefined) }} onSuccess={() => { setDeleteElement(undefined) }}> @@ -66,4 +66,4 @@ function BoatOverview() { ); } -export default BoatOverview; \ No newline at end of file +export default BoatTypeOverview; \ No newline at end of file diff --git a/client/src/styles/app.scss b/client/src/styles/app.scss index 95bc07362dfabad79a6125513e76a71a58a3480f..bc5e3751b479f6c1477c2e44ff209ecf3e4a54e2 100644 --- a/client/src/styles/app.scss +++ b/client/src/styles/app.scss @@ -1,5 +1,7 @@ $primary: #99CC00; $secondary: #003366; +$info: #6c757d; +$success: #1EB236; $font-family: Roboto; $font-weight-base: 300; @@ -22,4 +24,26 @@ $body-color: $secondary; cursor: pointer; transform: scale(1.2); } +} +.rentedBoatTile.bg-danger { + &:hover{ + background: lighten(rgb(220, 53, 69), 6%)!important; + cursor: pointer; + } + + &:active { + background: rgb(220, 53, 69)!important; + box-shadow: inset 0 0 8px rgba(0,0,0,0.5)!important; + } +} +.rentedBoatTile.bg-secondary { + &:hover{ + background: lighten(rgb(0,51,102), 6%)!important; + cursor: pointer; + } + + &:active { + background: rgb(0,51,102)!important; + box-shadow: inset 0 0 8px rgba(255,255,255,0.5)!important; + } } \ No newline at end of file