Skip to content

Commit

Permalink
issue-171: added non-preferred rooms in available rooms dropdown (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
ali-ahnaf authored Jan 27, 2025
1 parent 4f9cad0 commit 387fcdb
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 192 deletions.
4 changes: 2 additions & 2 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiResponse, BookRoomDto, DeleteResponse, EventResponse, GetAvailableRoomsQueryDto, IConferenceRoom, StatusTypes } from '@quickmeet/shared';
import { ApiResponse, BookRoomDto, DeleteResponse, EventResponse, GetAvailableRoomsQueryDto, StatusTypes, IAvailableRooms } from '@quickmeet/shared';
import axios, { AxiosInstance } from 'axios';
import { toast } from 'react-hot-toast';
import { secrets } from '@config/secrets';
Expand Down Expand Up @@ -135,7 +135,7 @@ export default class Api {
const params: GetAvailableRoomsQueryDto = { startTime, duration, timeZone, seats, floor, eventId };
const res = await this.client.get('/api/rooms/available', { params, signal });

return res.data as ApiResponse<IConferenceRoom[]>;
return res.data as ApiResponse<IAvailableRooms>;
} catch (error: any) {
return this.handleError(error);
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/AttendeeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { isEmailValid } from '@/helpers/utility';
import PeopleAltRoundedIcon from '@mui/icons-material/PeopleAltRounded';
import { Autocomplete, Box, Chip, debounce, TextField, Typography } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import type { IPeopleInformation } from '@quickmeet/shared';
import { IPeopleInformation } from '@quickmeet/shared';
import { useState } from 'react';
import toast from 'react-hot-toast';

Expand Down
318 changes: 173 additions & 145 deletions client/src/components/RoomsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Box, MenuItem, Select, SelectChangeEvent, Skeleton, Typography } from '@mui/material';
import { Box, ListSubheader, MenuItem, Select, SelectChangeEvent, Skeleton, styled, Typography } from '@mui/material';
import { ReactElement } from 'react';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { IConferenceRoom } from '@quickmeet/shared';
import { IAvailableRoomsDropdownOption } from '@/helpers/types';
import { usePreferences } from '@/context/PreferencesContext';

interface DropdownProps {
id: string;
sx?: any;
options?: RoomsDropdownOption[];
options: IAvailableRoomsDropdownOption;
value?: string;
disabled?: boolean;
onChange: (id: string, value: string) => void;
Expand All @@ -16,6 +18,12 @@ interface DropdownProps {
currentRoom?: IConferenceRoom;
}

interface StyledMenuItemProps {
i: number;
option: RoomsDropdownOption;
currentRoom?: IConferenceRoom;
}

export interface RoomsDropdownOption {
text: string;
value: string; // the main value used for api calls
Expand All @@ -24,8 +32,148 @@ export interface RoomsDropdownOption {
isBusy?: boolean;
}

const CustomMenuItem = ({ i, option, currentRoom }: StyledMenuItemProps) => {
return (
<MenuItem value={option.value} key={i}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
{/* Left section */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
<Typography
variant="subtitle1"
sx={{
textDecoration: option.isBusy ? 'line-through' : 'inherit',
}}
>
{option.text}
</Typography>
{currentRoom && option.value === currentRoom.email && <CheckCircleIcon color="success" sx={{ ml: 1 }} />}
</Box>

{/* Right section */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
}}
>
<Typography
variant="body2"
sx={[
(theme) => ({
color: theme.palette.grey[200],
}),
]}
>
{option.seats} {option.seats > 1 ? 'persons' : 'person'}
</Typography>
<Typography variant="body2">{option.floor}</Typography>
</Box>
</Box>
</MenuItem>
);
};

const StyledHintTypography = styled(Typography)(({ theme }) => ({
color: theme.palette.grey[500],
fontStyle: 'italic',
fontWeight: 400,
}));

const RenderNoRooms = ({ icon }: { icon?: ReactElement }) => {
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
{icon && icon}

<StyledHintTypography ml={2} variant="subtitle2">
No rooms available
</StyledHintTypography>
</Box>
);
};

const RenderPlaceholder = ({ icon, loading, placeholder }: { icon?: ReactElement; loading?: boolean; placeholder?: string }) => {
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
{icon && icon}
{loading ? (
<Skeleton
animation="wave"
sx={{
width: '100%',
mx: 2,
borderRadius: 0.5,
}}
/>
) : (
<StyledHintTypography ml={2} variant="subtitle2">
{placeholder}
</StyledHintTypography>
)}
</Box>
);
};

const RenderSelectText = ({ icon, loading, selectedOption }: { icon?: ReactElement; loading?: boolean; selectedOption?: RoomsDropdownOption }) => {
return (
<Box
sx={{
alignItems: 'center',
display: 'flex',
}}
>
{icon && icon}
{loading ? (
<Skeleton
animation="wave"
sx={{
width: '100%',
mx: 2,
borderRadius: 0.5,
}}
/>
) : (
<Typography
variant="subtitle1"
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
ml: 2,
}}
>
{selectedOption ? selectedOption.text : ''}
</Typography>
)}
</Box>
);
};

export default function RoomsDropdown({ sx, id, disabled, currentRoom, value, options, onChange, icon, placeholder, loading }: DropdownProps) {
const height = '58px';
const { preferences } = usePreferences();

const handleChange = (event: SelectChangeEvent) => {
onChange(id, event.target.value);
Expand All @@ -40,103 +188,16 @@ export default function RoomsDropdown({ sx, id, disabled, currentRoom, value, op
variant="standard"
disabled={disabled || false}
renderValue={(selected) => {
if (!loading && options?.length === 0) {
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
{icon && icon}

<Typography
variant="subtitle2"
sx={[
(theme) => ({
color: theme.palette.grey[500],
fontStyle: 'italic',
ml: 2,
fontWeight: 400,
}),
]}
>
No rooms available
</Typography>
</Box>
);
if (!loading && options.others.length === 0 && options.preferred.length === 0) {
return <RenderNoRooms icon={icon} />;
}
const selectedOption = options?.find((option) => option.value === selected);

const selectedOption = [...options.preferred, ...options.others].find((option) => option.value === selected);
if (placeholder && selected.length === 0) {
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
{icon && icon}
{loading ? (
<Skeleton
animation="wave"
sx={{
width: '100%',
mx: 2,
borderRadius: 0.5,
}}
/>
) : (
<Typography
variant="subtitle2"
sx={[
(theme) => ({
color: theme.palette.grey[500],
fontStyle: 'italic',
ml: 2,
fontWeight: 400,
}),
]}
>
{placeholder}
</Typography>
)}
</Box>
);
return <RenderPlaceholder icon={icon} loading={loading} placeholder={placeholder} />;
}

return (
<Box
sx={{
alignItems: 'center',
display: 'flex',
}}
>
{icon && icon}
{loading ? (
<Skeleton
animation="wave"
sx={{
width: '100%',
mx: 2,
borderRadius: 0.5,
}}
/>
) : (
<Typography
variant="subtitle1"
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
ml: 2,
}}
>
{selectedOption ? selectedOption.text : ''}
</Typography>
)}
</Box>
);
return <RenderSelectText icon={icon} loading={loading} selectedOption={selectedOption} />;
}}
disableUnderline={true}
sx={[
Expand All @@ -154,60 +215,27 @@ export default function RoomsDropdown({ sx, id, disabled, currentRoom, value, op
]}
>
{placeholder && (
<MenuItem disabled value="" sx={{ pt: 0 }}>
<MenuItem disabled value="" sx={{ pt: 1 }}>
<em>{placeholder}</em>
</MenuItem>
)}
{options?.map((option, i) => (
<MenuItem value={option.value} key={i}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
{/* Left section */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
<Typography
variant="subtitle1"
sx={{
textDecoration: option.isBusy ? 'line-through' : 'inherit',
}}
>
{option.text}
</Typography>
{currentRoom && option.value === currentRoom.email && <CheckCircleIcon color="success" sx={{ ml: 1 }} />}
</Box>

{/* Right section */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
}}
>
<Typography
variant="body2"
sx={[
(theme) => ({
color: theme.palette.grey[200],
}),
]}
>
{option.seats} {option.seats > 1 ? 'persons' : 'person'}
</Typography>
<Typography variant="body2">{option.floor}</Typography>
</Box>
</Box>
</MenuItem>

{options.preferred.length === 0 && preferences.floor && (
<StyledHintTypography sx={{ my: 0.5 }}>No rooms available according to your preferences</StyledHintTypography>
)}

{options.preferred.map((option, i) => (
<CustomMenuItem key={i} currentRoom={currentRoom} option={option} i={i} />
))}

{options.others.length > 0 && (
<ListSubheader>
<StyledHintTypography sx={{ my: 0.5 }}>Others</StyledHintTypography>
</ListSubheader>
)}

{options.others.map((option, i) => (
<CustomMenuItem key={i} currentRoom={currentRoom} option={option} i={i} />
))}
</Select>
);
Expand Down
Loading

0 comments on commit 387fcdb

Please sign in to comment.