Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions frontend/__tests__/BuildingDrawer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ jest.mock("@mui/material", () => ({
...jest.requireActual("@mui/material"),
useMediaQuery: jest.fn().mockReturnValue(false),
}));
jest.mock("next/navigation", () => ({
...jest.requireActual("next/navigation"),
useRouter: jest.fn().mockReturnValue({
push: jest.fn(),
replace: jest.fn(),
}),
}));

describe("BuildingDrawer", () => {
it("Building Drawer shows close button", () => {
Expand Down
8 changes: 7 additions & 1 deletion frontend/app/map/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"use client";

import { Suspense } from "react";

import { Map } from "../../components/Map";

export default function Page() {
return <Map />;
return (
<Suspense fallback={null}>
<Map />
</Suspense>
);
}
19 changes: 14 additions & 5 deletions frontend/app/room/[room]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ import BookingCalendar from "../../../components/BookingCalendar";
import FeedbackButton from "../../../components/FeedbackButton";
import LoadingCircle from "../../../components/LoadingCircle";
import RoomBackButton from "../../../components/RoomBackButton";
import ViewOnMapButton from "../../../components/ViewOnMapButton";
import useBookings from "../../../hooks/useBookings";
import useBuilding from "../../../hooks/useBuilding";
import useRoom from "../../../hooks/useRoom";
import room_photos from "../../../public/room-photos.json";
import { getBuildingIdFromRoomId } from "../../../utils/utils";

const adjustDateIfMidnight = (inputDate: Date): Date => {
// Check if the time is midnight (00:00:00)
Expand Down Expand Up @@ -115,6 +117,7 @@ const RoomPageHeader: React.FC<{ room: Room; buildingName: string }> = ({
: "This room is managed externally by its associated school. Please contact the school to request a booking";

const ratings = useRoomRatings(room.id);
const buildingId = getBuildingIdFromRoomId(room.id);
const ratingValue = (() => {
// round rating to nearest .5 if a rating exists
if (!ratings || !ratings.data) return 0;
Expand Down Expand Up @@ -180,11 +183,17 @@ const RoomPageHeader: React.FC<{ room: Room; buildingName: string }> = ({
<Typography variant="h4" sx={{ fontWeight: 550 }}>
{room.name}
</Typography>
<BookingButton
school={room.school}
usage={room.usage}
onClick={toggleDialog}
/>
<Stack
direction={{ xs: "column", sm: "row" }}
sx={{ justifyContent: "space-between" }}
>
<ViewOnMapButton buildingId={buildingId} />
<BookingButton
school={room.school}
usage={room.usage}
onClick={toggleDialog}
/>
</Stack>
</Stack>

<Stack direction="row" spacing={2}>
Expand Down
30 changes: 22 additions & 8 deletions frontend/components/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Building } from "@common/types";
import useBuilding from "@frontend/hooks/useBuilding";
import Box from "@mui/material/Box";
import {
GoogleMap,
Expand All @@ -7,13 +8,16 @@ import {
useJsApiLoader,
} from "@react-google-maps/api";
import { DarkModeContext } from "app/clientLayout";
import React, { useContext, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
import React, { useContext, useEffect, useMemo, useState } from "react";
import { useDebounceValue } from "usehooks-ts";
import BuildingDrawer from "views/BuildingDrawer";

import { GOOGLE_API_KEY } from "../config";
import useBuildings from "../hooks/useBuildings";
import useUserLocation from "../hooks/useUserLocation";
import { setCurrentBuilding } from "../redux/currentBuildingSlice";
import { useDispatch } from "../redux/hooks";
import calculateDistance from "../utils/calculateDistance";
import getMapType from "../utils/getMapType";
import MapMarker from "./MapMarker";
Expand Down Expand Up @@ -55,7 +59,7 @@ export const Map = () => {
// Fetch data
const { buildings } = useBuildings();
const { isDarkMode } = useContext(DarkModeContext);

const dispatch = useDispatch();
// Use debounce to allow moving from marker to popup without popup hiding
const [currentHover, setCurrentHover] = useState<Building | null>(null);
const [debouncedCurrentHover] = useDebounceValue(currentHover, 50);
Expand All @@ -70,15 +74,25 @@ export const Map = () => {
});

const distances = useMemo(() => {
if (!(buildings && userLat && userLng && isInBounds(userLat, userLng))) {
return [];
if (buildings && userLat && userLng && isInBounds(userLat, userLng)) {
return buildings.map((building) =>
calculateDistance(userLat, userLng, building.lat, building.long)
);
}

return buildings.map((building) =>
calculateDistance(userLat, userLng, building.lat, building.long)
);
return [];
}, [buildings, userLat, userLng]);

//set current building to search param query - THIS IS WHERE OTHER QUERIES CAN GO
const searchParams = useSearchParams();
const buildingId = searchParams.get("building");
const { building } = useBuilding(buildingId || "");

useEffect(() => {
if (building) {
dispatch(setCurrentBuilding(building || null));
}
}, [building, dispatch]);

const mapOptions = useMemo(
() => ({
clickableIcons: false,
Expand Down
20 changes: 16 additions & 4 deletions frontend/components/MapMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Typography } from "@mui/material";
import Box, { BoxProps } from "@mui/material/Box";
import { styled, useTheme } from "@mui/material/styles";
import Image, { ImageProps } from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import React from "react";

import useBuilding from "../hooks/useBuilding";
Expand Down Expand Up @@ -69,20 +70,27 @@ const MapMarker: React.FC<{
const { building } = useBuilding(buildingId);
const { status: liveStatus } = useBuildingStatus(buildingId);
const theme = useTheme();

const router = useRouter();
// This one uses stale data so markers don't disappear
const status: BuildingStatus | undefined = liveStatus;
const freerooms = getNumFreerooms(status);
const totalRooms = getTotalRooms(status);
const searchParams = useSearchParams();
const params = new URLSearchParams(searchParams);

const dispatch = useDispatch();
const currentBuilding = useSelector(selectCurrentBuilding);
const isCurrentBuilding = currentBuilding?.id === building?.id;

const showPopup = currentHover?.id === building?.id;

const [appearLeft, setAppearLeft] = React.useState(false);
const [appearAbove, setAppearAbove] = React.useState(false);
const [appearLeft, setAppearLeft] = React.useState(false);

const handleSelectBuilding = () => {
dispatch(setCurrentBuilding(building || null));
params.set("building", buildingId); // Add or update the 'query' param
router.push(`/map?${params.toString()}`);
};

const colour =
freerooms >= 5 ? "#66bb6a" : freerooms !== 0 ? "#ffa726" : "#f44336";
Expand Down Expand Up @@ -121,6 +129,8 @@ const MapMarker: React.FC<{
sx={{
fontSize: 11,
fontWeight: 500,
translate: isCurrentBuilding ? "0px -10px" : "0px 0px",
transition: "all 0.2s ease-in-out",
textShadow:
theme.palette.mode === "light"
? "-.5px -.5px 1px #f2f2f2, .5px -.5px 1px #f2f2f2, -.5px .5px 1px #f2f2f2, .5px .5px 1px #f2f2f2"
Expand All @@ -137,6 +147,8 @@ const MapMarker: React.FC<{
borderRadius: "50%",
border: isCurrentBuilding ? `5px solid ${colour}` : "4px solid white",
backgroundColor: isCurrentBuilding ? "white" : colour,
scale: isCurrentBuilding ? 2 : 1,
transition: "all 0.2s ease-in-out",
boxShadow: isCurrentBuilding
? `0px 0px 6px 4px ${alpha(colour, 0.5)}`
: "",
Expand All @@ -145,7 +157,7 @@ const MapMarker: React.FC<{
cursor: "pointer",
},
}}
onClick={() => dispatch(setCurrentBuilding(building))}
onClick={() => handleSelectBuilding()}
/>
<Fade in={showPopup} timeout={200}>
<div style={{ position: "relative", bottom: -3 }}>
Expand Down
51 changes: 51 additions & 0 deletions frontend/components/ViewOnMapButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import useBuilding from "@frontend/hooks/useBuilding";
import { useTheme } from "@mui/material";
import Typography from "@mui/material/Typography";
import { useRouter } from "next/navigation";
import React from "react";

import { setCurrentBuilding } from "../redux/currentBuildingSlice";
import { useDispatch } from "../redux/hooks";
import Button from "./Button";

const ViewOnMapButton: React.FC<{
buildingId: string;
variant?: "default" | "full-width";
}> = ({ buildingId, variant = "default" }) => {
const theme = useTheme();

const dispatch = useDispatch();
const router = useRouter();
const { building } = useBuilding(buildingId);

const handleMapRedirect = (buildingId: string) => {
dispatch(setCurrentBuilding(building || null));
router.push(`/map?building=${buildingId}`);
};

return (
<Button
aria-label="View on Map"
name="View on Map"
sx={{
height: 45,
ml: variant === "full-width" ? "0px" : { xs: 0, sm: 1 },
my: { xs: 1, sm: 0 },
width: variant === "full-width" ? "100%" : { xs: "100%", sm: "160px" },
position: "relative",
//right: variant === "full-width" ? "6px" : "0px", //this is because building drawer has weird margin idk
backgroundColor: theme.palette.background.default,
color: theme.palette.primary.main,
}}
variant="outlined"
color="primary"
onClick={() => handleMapRedirect(buildingId)}
>
<Typography variant="body2" sx={{ fontWeight: "bold" }}>
View on Map
</Typography>
</Button>
);
};

export default ViewOnMapButton;
Loading