diff --git a/frontend/package-lock.json b/frontend/package-lock.json old mode 100644 new mode 100755 index f5f40a5..1b29875 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3829,9 +3829,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dependencies": { "isarray": "0.0.1" } @@ -7263,9 +7263,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "requires": { "isarray": "0.0.1" } diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx index c9e9b07..324788b 100644 --- a/frontend/src/components/App.tsx +++ b/frontend/src/components/App.tsx @@ -10,7 +10,10 @@ import { UserSettingsProvider } from '../contexts/UserSettingsContext'; import { clarity } from 'react-microsoft-clarity'; import GETAROOM_ENV from '../util/getARoomEnv'; -export const GarApp = styled(Divider)(() => ({})); +export const GarApp = styled(Divider)(() => ({ + width: '100%', + height: '100%' +})); const App = () => { if (GETAROOM_ENV().VITE_CLARITY_ID != null) { diff --git a/frontend/src/components/AvailableRoomList/AvailableRoomList.test.tsx b/frontend/src/components/AvailableRoomList/AvailableRoomList.test.tsx index ca5c460..67d0dff 100644 --- a/frontend/src/components/AvailableRoomList/AvailableRoomList.test.tsx +++ b/frontend/src/components/AvailableRoomList/AvailableRoomList.test.tsx @@ -119,6 +119,7 @@ describe('AvailableRoomList', () => { }, selectedRoom: fakeRooms[0] }; + it('renders room data', async () => { render( <AvailableRoomList diff --git a/frontend/src/components/AvailableRoomList/AvailableRoomList.tsx b/frontend/src/components/AvailableRoomList/AvailableRoomList.tsx old mode 100644 new mode 100755 index 368e09f..c15926b --- a/frontend/src/components/AvailableRoomList/AvailableRoomList.tsx +++ b/frontend/src/components/AvailableRoomList/AvailableRoomList.tsx @@ -1,7 +1,7 @@ -import { Box, List, ToggleButton, Typography } from '@mui/material'; +import { useEffect, useState } from 'react'; +import { Box, List, ToggleButton } from '@mui/material'; import { styled } from '@mui/material/styles'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import { Booking, Preferences, Room } from '../../types'; +import { Preferences, Room } from '../../types'; import RoomCard from '../RoomCard/RoomCard'; import NoRoomsCard from '../RoomCard/NoRoomsCard'; import { sortByFavoritedAndName } from '../../util/arrayUtils'; @@ -20,26 +20,14 @@ const TimePickerButton = styled(ToggleButton)(() => ({ } })); -export async function isFavorited(room: Room, pref?: Preferences) { - try { - if (pref === undefined || pref.fav_rooms === undefined) { - return false; - } - if (pref.fav_rooms.includes(room.id)) { - room.favorited = true; - } else { - room.favorited = false; - } - } catch { - // add error notification +function checkIfFavorited(room: Room, pref?: Preferences) { + if (pref && pref.fav_rooms) { + room.favorited = pref.fav_rooms.includes(room.id); + } else { room.favorited = false; } } -function noAvailableRooms(rooms: Room[]) { - return rooms.length === 0; -} - type BookingListProps = { bookingDuration: number; rooms: Room[]; @@ -67,30 +55,42 @@ const AvailableRoomList = (props: BookingListProps) => { selectedRoom } = props; + const [updatedRooms, setUpdatedRooms] = useState<Room[]>([]); + + // Effect to update room favorited status + useEffect(() => { + const updateFavoritedRooms = () => { + const roomsCopy = [...rooms]; // Make a shallow copy of rooms + for (const room of roomsCopy) { + checkIfFavorited(room, preferences); + } + setUpdatedRooms(roomsCopy); // Update state after processing all rooms + }; + + updateFavoritedRooms(); + }, [rooms, preferences]); + return ( <Box id="available-room-list"> <List> - {noAvailableRooms(rooms) ? ( + {updatedRooms.length === 0 ? ( <NoRoomsCard /> ) : ( - sortByFavoritedAndName<Room>(rooms).map((room) => - isAvailableFor(bookingDuration, room, startingTime) - ? (isFavorited(room, preferences), - ( - <li key={room.id}> - <RoomCard - room={room} - onClick={handleCardClick} - bookingLoading={bookingLoading} - disableBooking={false} - isSelected={selectedRoom === room} - expandFeatures={expandedFeaturesAll} - preferences={preferences} - setPreferences={setPreferences} - /> - </li> - )) - : null + sortByFavoritedAndName<Room>(updatedRooms).map((room) => + isAvailableFor(bookingDuration, room, startingTime) ? ( + <li key={room.id}> + <RoomCard + room={room} + onClick={handleCardClick} + bookingLoading={bookingLoading} + disableBooking={false} + isSelected={selectedRoom === room} + expandFeatures={expandedFeaturesAll} + preferences={preferences} + setPreferences={setPreferences} + /> + </li> + ) : null ) )} </List> diff --git a/frontend/src/components/BookingDrawer/BookingDrawer.tsx b/frontend/src/components/BookingDrawer/BookingDrawer.tsx index 7d94a21..4ca690d 100644 --- a/frontend/src/components/BookingDrawer/BookingDrawer.tsx +++ b/frontend/src/components/BookingDrawer/BookingDrawer.tsx @@ -162,24 +162,30 @@ export const Alert = (props: { return <></>; } return ( - <RowAlert> - <ColAlertIcon> - <span - style={{ - color: '#FBFBF6', - fontSize: '20px', - fontFamily: 'Material Icons', - textAlign: 'center', - fontWeight: '400' - }} - > - not_interested - </span> - </ColAlertIcon> - <ColAlertMessage> - <Typography variant={'body2'}>{props.alertText}</Typography> - </ColAlertMessage> - </RowAlert> + <> + {props.showAlert && ( + <RowAlert> + <ColAlertIcon> + <span + style={{ + color: '#FBFBF6', + fontSize: '20px', + fontFamily: 'Material Icons', + textAlign: 'center', + fontWeight: '400' + }} + > + not_interested + </span> + </ColAlertIcon> + <ColAlertMessage> + <Typography variant={'body2'}> + {props.alertText} + </Typography> + </ColAlertMessage> + </RowAlert> + )} + </> ); }; @@ -470,7 +476,8 @@ const BookingDrawer = (props: Props) => { }, [startingTime]); useEffect(() => { - if (room && DateTime.fromISO(room.nextCalendarEvent) < DateTime.now()) { + const date = DateTime.fromFormat(startingTime, 'hh:mm'); + if (room && date < DateTime.now()) { setAlertText( `Room is currently unavailable for ${unavailable} minutes. You may book the room in advance. Your starting @@ -479,8 +486,12 @@ const BookingDrawer = (props: Props) => { setStartingTime(getNextAvailableTime(room)); setShowAlert(true); unavailable = getUnavailableTimeInMinutes(room); + } else { + if (showAlert) { + setShowAlert(false); + } } - }, [room]); + }, [startingTime]); return ( <BottomDrawer diff --git a/frontend/src/components/BookingDrawer/DurationPicker.tsx b/frontend/src/components/BookingDrawer/DurationPicker.tsx index a293567..78f574d 100644 --- a/frontend/src/components/BookingDrawer/DurationPicker.tsx +++ b/frontend/src/components/BookingDrawer/DurationPicker.tsx @@ -3,14 +3,10 @@ import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import { styled, Typography } from '@mui/material'; -const DurationButton = styled(ToggleButton)(() => ({ - padding: '4px 4px' -})); +const DurationButton = styled(ToggleButton)(() => ({})); const DurationButtonGroup = styled(ToggleButtonGroup)(() => ({ - minWidth: '100%', - padding: '8px 24px', - marginBottom: '0px !important' + minWidth: '100%' })); type DurationPickerProps = { @@ -74,7 +70,7 @@ const DurationPicker = (props: DurationPickerProps) => { value={quickDuration} aria-label={toHourMinuteFormat(quickDuration)} > - {quickDuration} + {quickDuration} min </DurationButton> ); } @@ -90,7 +86,6 @@ const DurationPicker = (props: DurationPickerProps) => { exclusive onChange={handleChange} aria-label="duration picker" - sx={{ marginBottom: '24px' }} fullWidth > <DurationButton @@ -98,28 +93,28 @@ const DurationPicker = (props: DurationPickerProps) => { value={'15'} aria-label="15 minutes" > - 15 + 15 min </DurationButton> <DurationButton data-testid="DurationPicker30" value={'30'} aria-label="30 minutes" > - 30 + 30 min </DurationButton> <DurationButton data-testid="DurationPicker60" value={'60'} aria-label="1 hour" > - 60 + 60 min </DurationButton> <DurationButton data-testid="DurationPicker120" value={'120'} aria-label="2 hours" > - 120 + 120 min </DurationButton> {CustomDurationValueButton()} <DurationButton diff --git a/frontend/src/components/BookingView/BookingView.tsx b/frontend/src/components/BookingView/BookingView.tsx old mode 100644 new mode 100755 index c7a0ba8..8608095 --- a/frontend/src/components/BookingView/BookingView.tsx +++ b/frontend/src/components/BookingView/BookingView.tsx @@ -42,7 +42,6 @@ import { AnalyticsEventEnum } from '../../analytics/AnalyticsEvent'; import { triggerGoogleAnalyticsEvent } from '../../analytics/googleAnalytics/googleAnalyticsService'; import { BookingEvent } from '../../analytics/googleAnalytics/googleAnalyticsEvents'; import PageHeaderWithUserIcon from '../util/pageHeaderWithUserIcon'; -import AlertBox from '../util/alertBox'; import BottomDrawer, { DrawerContent } from '../BottomDrawer/BottomDrawer'; const UPDATE_FREQUENCY = 30000; @@ -79,6 +78,10 @@ const RowCentered = styled(Box)(({ theme }) => ({ width: '100%' })); +const BookingViewContent = styled('div')(({ theme }) => ({ + paddingTop: '56px' +})); + type BookingViewProps = { preferences?: Preferences; setPreferences: (pref: Preferences) => void; @@ -701,120 +704,122 @@ function BookingView(props: BookingViewProps) { </Box> </BottomDrawer> </div> - <Box - sx={{ - paddingLeft: '16px', - marginBottom: DEFAULT_STYLES.defaultSpacer - }} - > - <UserDrawer - open={showUserSettingsMenu} - toggle={toggleSettingsDrawer} - name={name} - expandedFeaturesAll={expandedFeaturesAll} - setExpandedFeaturesAll={setExpandedFeaturesAll} - /> - <DefaultVerticalSpacer /> - <CenterAlignedStack - direction={'row'} - onClick={moveToChooseOfficePage} + <BookingViewContent> + <Box + sx={{ + paddingLeft: '16px', + marginBottom: DEFAULT_STYLES.defaultSpacer + }} > - <Typography - textAlign="left" - variant="subtitle1" - color={'#ce3b20'} - style={{ cursor: 'pointer' }} - display="flex" + <UserDrawer + open={showUserSettingsMenu} + toggle={toggleSettingsDrawer} + name={name} + expandedFeaturesAll={expandedFeaturesAll} + setExpandedFeaturesAll={setExpandedFeaturesAll} /> - <ArrowBackIcon - sx={{ fontSize: '20px' }} - ></ArrowBackIcon> - <Box> - <Typography variant={'subtitle1'}> - {preferences?.building - ? preferences.building.name - : 'Back'} - </Typography> - </Box> - </CenterAlignedStack> - <RowCentered> + <DefaultVerticalSpacer /> + <CenterAlignedStack + direction={'row'} + onClick={moveToChooseOfficePage} + > + <Typography + textAlign="left" + variant="subtitle1" + color={'#ce3b20'} + style={{ cursor: 'pointer' }} + display="flex" + /> + <ArrowBackIcon + sx={{ fontSize: '20px' }} + ></ArrowBackIcon> + <Box> + <Typography variant={'subtitle1'}> + {preferences?.building + ? preferences.building.name + : 'Back'} + </Typography> + </Box> + </CenterAlignedStack> <PageHeaderWithUserIcon onClick={openSettingsDrawer} isOpen={showUserSettingsMenu} title={'ROOMS'} /> - </RowCentered> - </Box> - <StartingTimePicker - startingTime={startingTime} - setStartingTime={setStartingTime} - title="starting time" - setExpandTimePickerDrawer={setExpandTimePickerDrawer} - /> - - <CurrentBooking - bookings={bookings} - updateRooms={updateRooms} - updateBookings={updateBookings} - setBookings={setBookings} - preferences={preferences} - setPreferences={setPreferences} - /> - - {!areRoomsFetched(rooms) ? ( - <CenteredProgress /> - ) : ( - <AvailableRoomList - bookingDuration={getBookingDuration()} + </Box> + <StartingTimePicker startingTime={startingTime} - rooms={displayRooms} - expandedFeaturesAll={expandedFeaturesAll} - preferences={preferences} - setPreferences={setPreferences} + setStartingTime={setStartingTime} + title="starting time" setExpandTimePickerDrawer={setExpandTimePickerDrawer} - bookingLoading={bookingLoading} - handleCardClick={handleCardClick} - selectedRoom={selectedRoom} /> - )} - {areRoomsFetched(rooms) ? ( - <BusyRoomList - rooms={rooms} + <CurrentBooking bookings={bookings} + updateRooms={updateRooms} + updateBookings={updateBookings} + setBookings={setBookings} preferences={preferences} setPreferences={setPreferences} - bookingLoading={bookingLoading} - handleCardClick={handleCardClick} - selectedRoom={selectedRoom} - /> - ) : null} - - <div id="filtering-container" onClick={openFiltering}> - <FilteringDrawer - open={expandFilteringDrawer} - toggle={toggleFilteringDrawn} - roomSize={roomSize} - setRoomSize={setRoomSize} - resources={resources} - setResources={setResources} - customFilter={customFilter} - setCustomFilter={setCustomFilter} - onlyFavourites={onlyFavourites} - setOnlyFavourites={setOnlyFavourites} - filterCount={filterCount} - allFeatures={allFeatures} - duration={getBookingDuration()} - setDuration={setDuration} - onChange={handleDurationChange} - additionalDuration={additionalDuration} - setBookingDuration={setBookingDuration} - setAdditionalDuration={setAdditionalDuration} - setExpandDurationTimePickerDrawer={ - setExpandDurationTimePickerDrawer - } /> - </div> + + {!areRoomsFetched(rooms) ? ( + <CenteredProgress /> + ) : ( + <AvailableRoomList + bookingDuration={getBookingDuration()} + startingTime={startingTime} + rooms={displayRooms} + expandedFeaturesAll={expandedFeaturesAll} + preferences={preferences} + setPreferences={setPreferences} + setExpandTimePickerDrawer={ + setExpandTimePickerDrawer + } + bookingLoading={bookingLoading} + handleCardClick={handleCardClick} + selectedRoom={selectedRoom} + /> + )} + + {areRoomsFetched(rooms) ? ( + <BusyRoomList + rooms={rooms} + bookings={bookings} + preferences={preferences} + setPreferences={setPreferences} + bookingLoading={bookingLoading} + handleCardClick={handleCardClick} + selectedRoom={selectedRoom} + /> + ) : null} + + <div id="filtering-container" onClick={openFiltering}> + <FilteringDrawer + open={expandFilteringDrawer} + toggle={toggleFilteringDrawn} + roomSize={roomSize} + setRoomSize={setRoomSize} + resources={resources} + setResources={setResources} + customFilter={customFilter} + setCustomFilter={setCustomFilter} + onlyFavourites={onlyFavourites} + setOnlyFavourites={setOnlyFavourites} + filterCount={filterCount} + allFeatures={allFeatures} + duration={getBookingDuration()} + setDuration={setDuration} + onChange={handleDurationChange} + additionalDuration={additionalDuration} + setBookingDuration={setBookingDuration} + setAdditionalDuration={setAdditionalDuration} + setExpandDurationTimePickerDrawer={ + setExpandDurationTimePickerDrawer + } + /> + </div> + </BookingViewContent> </Box> </Box> ); diff --git a/frontend/src/components/BookingView/FilteringDrawer.tsx b/frontend/src/components/BookingView/FilteringDrawer.tsx index dbadfc6..2ce99cf 100644 --- a/frontend/src/components/BookingView/FilteringDrawer.tsx +++ b/frontend/src/components/BookingView/FilteringDrawer.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Typography } from '@mui/material'; import TextField from '@mui/material/TextField'; @@ -27,7 +27,7 @@ export const SmallText = styled(Typography)(({ theme }) => ({ lineHeight: '12px', fontWeight: 'bold', fontStyle: 'normal', - margin: '24px 8px 8px 0' + margin: '8px 0px' })); interface Props { @@ -52,6 +52,52 @@ interface Props { setBookingDuration: (minutes: number) => void; } +function CustomFilterTextField(props: { + value: string; + setCustomFilter: (customFilter: string) => void; +}) { + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + props.setCustomFilter(searchTerm); + // Send Axios request here + }, 2000); + + return () => clearTimeout(delayDebounceFn); + }, [searchTerm]); + + useEffect(() => { + setSearchTerm(props.value); + }, [props.value]); + + return ( + <TextField + onChange={(event) => setSearchTerm(event.target.value)} + value={searchTerm} + placeholder="Room name, resource..." + size="small" + slotProps={{ + input: { + startAdornment: ( + <InputAdornment position="start"> + <SearchIcon /> + </InputAdornment> + ), + sx: { + fontFamily: 'Studio Feixen Sans', + fontSize: '16px', + fontStyle: 'normal', + fontWeight: 2, + lineHeight: 'normal', + borderRadius: '20px' + } + } + }} + /> + ); +} + // Note: Actual filtering of the rooms is done one level up in booking view const FilteringDrawer = (props: Props) => { const { showUserSettingsMenu } = useUserSettings(); @@ -120,9 +166,6 @@ const FilteringDrawer = (props: Props) => { setResources(newResources); }; - const handleCustomFilter = (event: React.ChangeEvent<HTMLInputElement>) => { - setCustomFilter(event.target.value); - }; const handleDurationChange = (newDuration: number) => { if (newDuration !== -1) { setBookingDuration(newDuration); @@ -165,34 +208,24 @@ const FilteringDrawer = (props: Props) => { <Row> <SmallText>Custom Filter</SmallText> </Row> - <TextField - onChange={handleCustomFilter} + <CustomFilterTextField + setCustomFilter={setCustomFilter} value={customFilter} - placeholder="Room name, resource..." - size="small" - slotProps={{ - input: { - startAdornment: ( - <InputAdornment position="start"> - <SearchIcon /> - </InputAdornment> - ) - } - }} /> - <Row> + <Row sx={{ marginTop: '24px' }}> <SmallText>Custom Duration (Minutes)</SmallText> </Row> - <DurationPicker - onChange={handleDurationChange} - bookingDuration={duration} - setExpandDurationTimePickerDrawer={ - props.setExpandDurationTimePickerDrawer - } - additionalDuration={additionalDuration} - /> - + <Row> + <DurationPicker + onChange={handleDurationChange} + bookingDuration={duration} + setExpandDurationTimePickerDrawer={ + props.setExpandDurationTimePickerDrawer + } + additionalDuration={additionalDuration} + /> + </Row> <Row> <SmallText>Room Size (People)</SmallText> </Row> @@ -222,7 +255,7 @@ const FilteringDrawer = (props: Props) => { ))} </StyledToggleButtonGroup> <Row> - <SmallText>Favourites</SmallText> + <SmallText>Pinned rooms</SmallText> </Row> <ToggleButton value="favourites" diff --git a/frontend/src/components/BookingView/StartingTimePicker.tsx b/frontend/src/components/BookingView/StartingTimePicker.tsx index 5525146..04553ba 100644 --- a/frontend/src/components/BookingView/StartingTimePicker.tsx +++ b/frontend/src/components/BookingView/StartingTimePicker.tsx @@ -3,18 +3,11 @@ import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import { Box, styled, Typography } from '@mui/material'; import { DateTime } from 'luxon'; -import { - getHourMinute, - timeFormat, - timeToHalfAndFullHours, - formatTimeToHalfAndFullHours -} from '../util/Time'; +import { formatTimeToHalfAndFullHours } from '../util/Time'; import AlertBox from '../util/alertBox'; const StartingTimeButton = styled(ToggleButton)(() => ({})); -const StartingTimePickerContent = styled(Box)(({ theme }) => ({ - margin: '8px 24px' -})); +const StartingTimePickerContent = styled(Box)(({ theme }) => ({})); type StartingTimePickerProps = { startingTime: string; @@ -70,63 +63,69 @@ const StartingTimePicker = (props: StartingTimePickerProps) => { }; return ( - <StartingTimePickerContent> - <Typography variant="subtitle1" textAlign="left"> + <> + <Typography + variant="subtitle1" + textAlign="left" + sx={{ marginLeft: '16px' }} + > {title} </Typography> - <ToggleButtonGroup - data-testid="StartingTimePicker" - color="primary" - value={startingTime} - exclusive - onChange={handleChange} - aria-label="duration picker" - sx={{ margin: '8px 0' }} - fullWidth - > - <StartingTimeButton - data-testid="StartingTimePicker1" - value={startingTimeNow} - aria-label={startingTimeNow} - > - {startingTimeNow} - </StartingTimeButton> - <StartingTimeButton - data-testid="StartingTimePicker2" - value={startingTime2} - aria-label={startingTime2} - > - {startingTime2} - </StartingTimeButton> - <StartingTimeButton - data-testid="StartingTimePicker3" - value={startingTime3} - aria-label={startingTime3} - > - {startingTime3} - </StartingTimeButton> - <StartingTimeButton - data-testid="StartingTimePicker4" - value={startingTime4} - aria-label={startingTime4} - > - {startingTime4} - </StartingTimeButton> - {CustomStartingTimeButton()} - <StartingTimeButton - data-testid="StartingTimePickerCustom" - value={startingTimeCustom} - aria-label={startingTimeCustom} + <StartingTimePickerContent> + <ToggleButtonGroup + data-testid="StartingTimePicker" + color="primary" + value={startingTime} + exclusive + onChange={handleChange} + aria-label="duration picker" + sx={{ marginTop: '8px', paddingBottom: '8px' }} + fullWidth > - {startingTimeCustom} - </StartingTimeButton> - </ToggleButtonGroup> - {props.startingTime !== 'Now' && ( - <AlertBox - alertText={`Note! You are booking the room for a future time`} - /> - )} - </StartingTimePickerContent> + <StartingTimeButton + data-testid="StartingTimePicker1" + value={startingTimeNow} + aria-label={startingTimeNow} + > + {startingTimeNow} + </StartingTimeButton> + <StartingTimeButton + data-testid="StartingTimePicker2" + value={startingTime2} + aria-label={startingTime2} + > + {startingTime2} + </StartingTimeButton> + <StartingTimeButton + data-testid="StartingTimePicker3" + value={startingTime3} + aria-label={startingTime3} + > + {startingTime3} + </StartingTimeButton> + <StartingTimeButton + data-testid="StartingTimePicker4" + value={startingTime4} + aria-label={startingTime4} + > + {startingTime4} + </StartingTimeButton> + {CustomStartingTimeButton()} + <StartingTimeButton + data-testid="StartingTimePickerCustom" + value={startingTimeCustom} + aria-label={startingTimeCustom} + > + {startingTimeCustom} + </StartingTimeButton> + </ToggleButtonGroup> + {props.startingTime !== 'Now' && ( + <AlertBox + alertText={`Note! You are booking the room for a future time`} + /> + )} + </StartingTimePickerContent> + </> ); }; diff --git a/frontend/src/components/BuildingList/BuildingList.tsx b/frontend/src/components/BuildingList/BuildingList.tsx old mode 100644 new mode 100755 index 0b22a53..9a0c57b --- a/frontend/src/components/BuildingList/BuildingList.tsx +++ b/frontend/src/components/BuildingList/BuildingList.tsx @@ -45,6 +45,10 @@ const EndBox = styled(Box)(({ theme }) => ({ alignItems: 'center' })); +const BuildingListContent = styled('div')(({ theme }) => ({ + paddingTop: '60px' +})); + const handleProfileMenuOpen = (e: React.MouseEvent<HTMLElement>) => { // TODO villep NOT IMPLEMENTED }; @@ -137,18 +141,18 @@ const BuildingList = (props: BuildingSelectProps) => { }; return ( - <div style={{ padding: '16px' }}> + <BuildingListContent> <Stack id="preferences-view" height="100%" justifyContent="space-around" alignItems="left" > - <div - style={{ + <Box + sx={{ height: '100%', width: '100%', - padding: '0px 16px' + padding: '24px 16px' }} > <FormGroup sx={{ alignItems: 'left' }}> @@ -170,7 +174,7 @@ const BuildingList = (props: BuildingSelectProps) => { SORT BASED ON </Typography> </FormGroup> - </div> + </Box> <ToggleButtonGroup className="ToggleButtonGroupStyle" @@ -238,7 +242,7 @@ const BuildingList = (props: BuildingSelectProps) => { expandedFeaturesAll={expandedFeaturesAll} setExpandedFeaturesAll={setExpandedFeaturesAll} /> - </div> + </BuildingListContent> ); }; diff --git a/frontend/src/components/DurationTimePickerDrawer/DurationTimePickerDrawer.tsx b/frontend/src/components/DurationTimePickerDrawer/DurationTimePickerDrawer.tsx index 87fee53..7072f21 100644 --- a/frontend/src/components/DurationTimePickerDrawer/DurationTimePickerDrawer.tsx +++ b/frontend/src/components/DurationTimePickerDrawer/DurationTimePickerDrawer.tsx @@ -30,6 +30,12 @@ const DurationDrawerContent = styled(DrawerContent)(({ theme }) => ({ justifyContent: 'center' })); +const DurationDrawerButtons = styled('div')(({ theme }) => ({ + width: '100%', + margin: '8px 24px', + padding: '0px 24px' +})); + interface DurationTimePickerDrawerProps { open: boolean; toggle: (open: boolean) => void; @@ -100,13 +106,15 @@ const DurationTimePickerDrawer = (props: DurationTimePickerDrawerProps) => { /> </BoxForm> <Row> - <DrawerButtonPrimary - aria-label="confirm" - data-testid={'set-duration-button'} - onClick={() => handleSetDuration()} - > - Confirm - </DrawerButtonPrimary> + <DurationDrawerButtons> + <DrawerButtonPrimary + aria-label="confirm" + data-testid={'set-duration-button'} + onClick={() => handleSetDuration()} + > + Confirm + </DrawerButtonPrimary> + </DurationDrawerButtons> </Row> </DurationDrawerContent> </DurationBox> diff --git a/frontend/src/components/MainView/MainView.tsx b/frontend/src/components/MainView/MainView.tsx index 8b49730..8397dad 100644 --- a/frontend/src/components/MainView/MainView.tsx +++ b/frontend/src/components/MainView/MainView.tsx @@ -23,6 +23,7 @@ import { COLORS } from '../../theme_2024'; const MainContent = styled(Box)(({ theme }) => ({ id: 'main-view', // Changed to proper object key-value pair display: 'flex', + width: '100%', flexDirection: 'column', minHeight: '100vh', alignItems: 'center', diff --git a/frontend/src/components/RoomCard/RoomCard.tsx b/frontend/src/components/RoomCard/RoomCard.tsx index 22647ca..dc62815 100644 --- a/frontend/src/components/RoomCard/RoomCard.tsx +++ b/frontend/src/components/RoomCard/RoomCard.tsx @@ -4,7 +4,6 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { Booking, Preferences, Room } from '../../types'; import { updatePreferences } from '../../services/preferencesService'; -import { getNumberWithOrdinalSuffix } from '../../util/commonUtils'; import TimeLeft, { getTimeDiff, @@ -13,7 +12,7 @@ import TimeLeft, { } from '../util/TimeLeft'; import Group from '@mui/icons-material/People'; -import { Card, CardActionArea, CircularProgress } from '@mui/material'; +import { Card, CardActionArea, CircularProgress, Stack } from '@mui/material'; import { minutesToSimpleString } from '../BookingDrawer/BookingDrawer'; import { DateTime, DateTimeMaybeValid } from 'luxon'; import { roomFreeIn } from '../BusyRoomList/BusyRoomList'; @@ -21,13 +20,13 @@ import { styled } from '@mui/material/styles'; import { CenterAlignedStack, CheckCircle, - DEFAULT_STYLES, DoNotDisturb, ScheduleCircle } from '../../theme_2024'; import { dateTimeToTimeString } from '../util/Time'; import { ReservationStatus } from '../../enums'; import BookmarkButton from '../util/bookmarkButton'; +import Floor from '../../icons/Floor'; function getName(room: Room) { return room.name; @@ -123,7 +122,8 @@ export const Row = styled(Box)(({ theme }) => ({ const EndBox = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'flex-end', - alignItems: 'center' + alignItems: 'center', + gap: '16px' })); const StartBox = styled(Box)(({ theme }) => ({ @@ -187,23 +187,6 @@ const RoomCardTitleWithDescription = (props: { {getName(props.room)} </Typography> </Box> - {/* Wrap the h4 in a Box */} - <Box sx={{ width: '100%', marginTop: '8px' }}> - {' '} - {/* You can adjust margin as needed */} - <Typography - variant={'h4'} - align={'left'} - sx={{ - paddingLeft: DEFAULT_STYLES.smallerSpacer, - wordWrap: 'break-word', // Wrap text if necessary - whiteSpace: 'normal' - }} - > - {getNumberWithOrdinalSuffix(parseInt(getFloor(props.room)))}{' '} - floor - </Typography> - </Box> </CenterAlignedStack> ); }; @@ -225,18 +208,28 @@ class RoomCardFavoriteButton extends React.Component<{ } } -class RoomCardCapacityBox extends React.Component<{ +class RoomCardIndicatorsBox extends React.Component<{ busy: boolean | undefined; room: Room; }> { render() { return ( - <EndBox> - <Group color="inherit" /> - <Typography variant={'h3'} marginLeft={'8px'}> - {getCapacity(this.props.room)} - </Typography> - </EndBox> + <> + <EndBox> + <Stack direction={'row'}> + <Floor /> + <Typography variant={'h3'} marginLeft={'8px'}> + {getFloor(this.props.room)} + </Typography> + </Stack> + <Stack direction={'row'}> + <Group color="inherit" /> + <Typography variant={'h3'} marginLeft={'8px'}> + {getCapacity(this.props.room)} + </Typography> + </Stack> + </EndBox> + </> ); } } @@ -529,7 +522,7 @@ const RoomCard = (props: RoomCardProps) => { isBusy={isBusy} room={room} /> - <RoomCardCapacityBox busy={isBusy} room={room} /> + <RoomCardIndicatorsBox busy={isBusy} room={room} /> </Row> <Row justifyContent={'left'} alignItems={'left'}> <ReservationStatusText diff --git a/frontend/src/components/StartingTimePickerDrawer/StartingTimePickerDrawer.tsx b/frontend/src/components/StartingTimePickerDrawer/StartingTimePickerDrawer.tsx index d09da6f..00d3c80 100644 --- a/frontend/src/components/StartingTimePickerDrawer/StartingTimePickerDrawer.tsx +++ b/frontend/src/components/StartingTimePickerDrawer/StartingTimePickerDrawer.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { MultiSectionDigitalClock } from '@mui/x-date-pickers/MultiSectionDigitalClock'; -import { TextField, Box, styled } from '@mui/material'; +import { Box, styled } from '@mui/material'; import { DateTime } from 'luxon'; import { @@ -19,6 +19,12 @@ export const BoxForm = styled(GetARoomForm)(({ theme }) => ({ flexWrap: 'wrap' })); +const TimePickerButtons = styled('div')(({ theme }) => ({ + width: '100%', + margin: '8px 24px', + padding: '0px 24px' +})); + interface StartingTimePickerDrawerProps { open: boolean; toggle: (open: boolean) => void; @@ -112,23 +118,25 @@ const StartingTimePickerDrawer = (props: StartingTimePickerDrawerProps) => { data-testid="CustomStartingTimeClock" /> </BoxForm> - <Row> - <DrawerButtonSecondary - aria-label="set to now" - onClick={() => handleSetTime(true)} - > - Set to Now - </DrawerButtonSecondary> - </Row> - <Row> - <DrawerButtonPrimary - aria-label="confirm" - onClick={() => handleSetTime(false)} - data-testid="ConfirmStartingTimeButton" - > - Confirm - </DrawerButtonPrimary> - </Row> + <TimePickerButtons> + <Row> + <DrawerButtonSecondary + aria-label="set to now" + onClick={() => handleSetTime(true)} + > + Set to Now + </DrawerButtonSecondary> + </Row> + <Row> + <DrawerButtonPrimary + aria-label="confirm" + onClick={() => handleSetTime(false)} + data-testid="ConfirmStartingTimeButton" + > + Confirm + </DrawerButtonPrimary> + </Row> + </TimePickerButtons> </DrawerContent> </Box> </BottomDrawer> diff --git a/frontend/src/components/TimePickerDrawer/TimePickerDrawer.tsx b/frontend/src/components/TimePickerDrawer/TimePickerDrawer.tsx index 2b7f3b3..bae8a3d 100644 --- a/frontend/src/components/TimePickerDrawer/TimePickerDrawer.tsx +++ b/frontend/src/components/TimePickerDrawer/TimePickerDrawer.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { TextField, Box } from '@mui/material'; +import { TextField, Box, styled } from '@mui/material'; import { DateTime } from 'luxon'; import { diff --git a/frontend/src/components/util/TimeLeft.tsx b/frontend/src/components/util/TimeLeft.tsx index ae700f6..2b4d1ce 100644 --- a/frontend/src/components/util/TimeLeft.tsx +++ b/frontend/src/components/util/TimeLeft.tsx @@ -89,14 +89,14 @@ const TimeLeft = (props: TimeLeftProps) => { return ( <CenteredText text1={timeLeftText} - text1Variant={'body1'} + text1Variant={'subtitle2'} text1AriaLabel={'Time left text'} text1TestId={'TimeLeftLabelTest'} text2={getTimeLeft(endTime)} - text2Variant={'body1'} + text2Variant={'body2'} text2TestId={'TimeLeftTest'} text2AriaLabel={'Time left text'} - text2Sx={{ fontWeight: 'bold' }} + text2Sx={{ fontWeight: 4 }} /> ); }; diff --git a/frontend/src/components/util/alertBox.tsx b/frontend/src/components/util/alertBox.tsx index 182b69c..bf31d33 100644 --- a/frontend/src/components/util/alertBox.tsx +++ b/frontend/src/components/util/alertBox.tsx @@ -5,8 +5,8 @@ const AlertBox = (props: { alertText: string; sx?: SxProps }) => { return ( <Box sx={{ - width: 327, - height: 73, + minWidth: '327px', + minHeight: '73px', display: 'flex', alignItems: 'center', borderRadius: 2, @@ -18,6 +18,7 @@ const AlertBox = (props: { alertText: string; sx?: SxProps }) => { <Box sx={{ width: 40, + minHeight: '73px', height: '100%', display: 'flex', justifyContent: 'center', @@ -38,13 +39,25 @@ const AlertBox = (props: { alertText: string; sx?: SxProps }) => { {/* Text Container */} <Box - flexGrow={1} - paddingX={1} - paddingY={1} - display="flex" - alignItems="center" + sx={{ + flexGrow: 1, + px: 1, // Shorthand for paddingX + py: 1, // Shorthand for paddingY + display: 'flex', + alignItems: 'center', + width: '100%', + lineHeight: 'normal' + }} > - <Typography variant="h6">{props.alertText}</Typography> + <Typography + variant="h6" + sx={{ + whiteSpace: 'normal', + wordBreak: 'break-word' + }} + > + {props.alertText} + </Typography> </Box> </Box> ); diff --git a/frontend/src/components/util/bookmarkButton.tsx b/frontend/src/components/util/bookmarkButton.tsx index 6e51f71..d20251e 100644 --- a/frontend/src/components/util/bookmarkButton.tsx +++ b/frontend/src/components/util/bookmarkButton.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; -import { IconButton } from '@mui/material'; +import { Container, Divider, IconButton, styled } from '@mui/material'; import { Bookmark, BookmarkBorder } from '@mui/icons-material'; import { PropsWithChildren } from 'react'; import { children } from 'happy-dom/lib/PropertySymbol'; +const ButtonContent = styled('span')(({ theme }) => ({})); function BookmarkButton( props: PropsWithChildren<{ @@ -14,16 +15,16 @@ function BookmarkButton( }> ) { return ( - <IconButton aria-label="favorite room" onClick={props.onClick}> + <ButtonContent aria-label="favorite room" onClick={props.onClick}> {props.isSelected ? ( - <Bookmark sx={{ color: '#F04E30' }} /> + <Bookmark sx={{ color: '#F04E30', padding: '1px' }} /> ) : ( <BookmarkBorder color={props.changeColor ? 'disabled' : 'inherit'} /> )} {props.children} - </IconButton> + </ButtonContent> ); } diff --git a/frontend/src/icons/Floor.tsx b/frontend/src/icons/Floor.tsx new file mode 100644 index 0000000..13ccad6 --- /dev/null +++ b/frontend/src/icons/Floor.tsx @@ -0,0 +1,20 @@ +const Floor = () => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 20 20" + fill="none" + > + <path + d="M5 15V16H14C14.5523 16 15 15.5523 15 15V6H14V9H11V12H8V15H5Z" + fill="#1D1D1D" + stroke="#1D1D1D" + strokeWidth="2" + /> + </svg> + ); +}; + +export default Floor; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 69fda0b..df0c7c3 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,5 +1,4 @@ import React, { StrictMode } from 'react'; -import ReactDOM from 'react-dom'; import './index.css'; import App from './components/App'; import reportWebVitals from './create-react-app/reportWebVitals'; @@ -11,6 +10,7 @@ import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; import { createRoot } from 'react-dom/client'; +import { COLORS } from './theme_2024'; checkEnvVariables(); const element: HTMLElement | null = document.getElementById('root'); @@ -22,6 +22,11 @@ if (!element) { const root = createRoot(element); root.render( <StrictMode> + <style> + {`body { + background-color: ${COLORS.BACKGROUND_PRIMARY}; + }`} + </style> <App /> </StrictMode> ); diff --git a/frontend/src/theme_2024.ts b/frontend/src/theme_2024.ts index 56744f4..46b5e47 100644 --- a/frontend/src/theme_2024.ts +++ b/frontend/src/theme_2024.ts @@ -208,6 +208,14 @@ export const DEFAULT_THEME_2024: ThemeOptions = { lineHeight: '12px', textTransform: 'uppercase' }, + subtitle2: { + color: COLORS.TEXT_PRIMARY, + fontFamily: 'StudioFeixenSans-Regular', + fontStyle: 'normal', + fontWeight: 2, + fontSize: '16px', + lineHeight: 'normal' + }, h4: { color: COLORS.TEXT_DIMGREY, fontFamily: 'StudioFeixenSans-Regular',