Skip to content

Commit

Permalink
lumen: 👥 Add 'book for someone' feature
Browse files Browse the repository at this point in the history
GitOrigin-RevId: 314c6f97223d4d3bfc0c29a23c34c22a44e59f01
  • Loading branch information
petitbenjamin authored and p-bizouard committed Mar 4, 2024
1 parent 637e8b6 commit 1e209bf
Show file tree
Hide file tree
Showing 18 changed files with 307 additions and 114 deletions.
22 changes: 5 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It is used in production at French engineering school CentraleSupélec and enabl

[Watch the 1-minute demo >>](https://vimeo.com/250163250)

![The booking pop-up](docs/assets/booking-popup.png)
![The booking pop-up](https://github.com/oxlay/Resa/blob/master/images-for-readme/booking-popup.png)

## General structure

Expand All @@ -33,7 +33,7 @@ Advanced features:
- see all the events planned for a room on a particular day
- use direct link to book a particular room: `resa.example.com/recherche/{roomId}` (especially useful if you put QR codes outside of rooms that can be booked)

![The room list](docs/assets/room-list.png)
![The room list](https://github.com/oxlay/Resa/blob/master/images-for-readme/room-list.png)

## Built with

Expand All @@ -44,25 +44,13 @@ Advanced features:

As well as many other projects you can find in `front/package.json` and `back/package.json`.

## Contributors
## Authors

### Initial version

**Lead developer:** [Anatole Beuzon](https://github.com/anatolebeuzon)
**Lead developer:** Anatole Beuzon

**Project manager:** Michel Guennoc

**Contributors and reviewers:** [Ronan Pelliard](https://github.com/rpelliard) and [Sami Tabet](https://github.com/sfluor)

### Since first release

- [Benjamin Koltes](https://github.com/Ayc0)
- [Guillaume Denis](https://github.com/silently)
- [Teo Lefebvre](https://github.com/TeoLefebvre)
- [Marius Verdier](https://github.com/marius-verdier)
- [Thomas Houdin](https://github.com/gamma3591)
- [Louis Vauterin](https://github.com/Louis-Vauterin)
- [Antoine Fonlupt](https://github.com/Antoine-Fonlupt)
**Contributors and reviewers:** Ronan Pelliard and Sami Tabet

## License

Expand Down
7 changes: 3 additions & 4 deletions back/src/webservice/book.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,9 @@ async function getStapledPersonEventList(
.filter((event) => event.room !== null) // Filter out events about rooms that don't exist anymore in GEODE
.filter((event) => event.endDate.isAfter(moment().subtract(3, "months"))); // Filter out past bookings older than 3 months old

parsedEvents.sort(
(eventA, eventB) => eventA.startDate.valueOf() - eventB.startDate.valueOf(),
);

parsedEvents.sort(
(eventA, eventB) => eventA.startDate.valueOf() - eventB.startDate.valueOf(),
);

// Group past and future events separately
const groupedParsedEvents /* : { [key: "future" | "past" ]: Event_Room[] } */ = groupBy(
Expand Down
Binary file removed docs/assets/booking-popup.png
Binary file not shown.
Binary file removed docs/assets/room-list.png
Binary file not shown.
2 changes: 0 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

[_Resa_](https://resa.centralesupelec.fr/) est conçue pour faciliter l'accès aux espaces de travail et de réunion sur le campus universitaire. Que vous soyez étudiant, enseignant, personnel administratif ou membre de la communauté universitaire, notre service vous permet de réserver facilement des salles adaptées à vos besoins.

![The booking pop-up](assets/booking-popup.png)

## Caractéristiques principales

- **Réservation simplifiée** : Notre interface vous permet de trouver et de réserver une salle en quelques clics. Vous pouvez consulter les disponibilités, les capacités d'accueil et les équipements disponibles associés à chaque salle.
Expand Down
64 changes: 48 additions & 16 deletions front/src/actions/bookings/modify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,46 @@ import {
RECEIVE_MODIF_ALREADY_BOOKED_ERROR,
} from './types';

export const initializeModifModal = makeActionCreator(INITIALIZE_MODIF_MODAL, 'event', 'modifType');
export const setNameOfModifiedEvent = makeActionCreator(SET_NAME_OF_MODIFIED_EVENT, 'newName');
export const setStartHourOfModifiedEvent = makeActionCreator(SET_START_HOUR_OF_MODIFIED_EVENT, 'newStartHour');
export const setStartMinutesOfModifiedEvent = makeActionCreator(SET_START_MINUTES_OF_MODIFIED_EVENT, 'newStartMinutes');
export const setEndHourOfModifiedEvent = makeActionCreator(SET_END_HOUR_OF_MODIFIED_EVENT, 'newEndHour');
export const setEndMinutesOfModifiedEvent = makeActionCreator(SET_END_MINUTES_OF_MODIFIED_EVENT, 'newEndMinutes');
export const setRoomIdOfModifiedEvent = makeActionCreator(SET_ROOM_ID_OF_MODIFIED_EVENT, 'newRoomId');
export const initializeModifModal = makeActionCreator(
INITIALIZE_MODIF_MODAL,
'event',
'modifType',
);
export const setNameOfModifiedEvent = makeActionCreator(
SET_NAME_OF_MODIFIED_EVENT,
'newName',
);
export const setStartHourOfModifiedEvent = makeActionCreator(
SET_START_HOUR_OF_MODIFIED_EVENT,
'newStartHour',
);
export const setStartMinutesOfModifiedEvent = makeActionCreator(
SET_START_MINUTES_OF_MODIFIED_EVENT,
'newStartMinutes',
);
export const setEndHourOfModifiedEvent = makeActionCreator(
SET_END_HOUR_OF_MODIFIED_EVENT,
'newEndHour',
);
export const setEndMinutesOfModifiedEvent = makeActionCreator(
SET_END_MINUTES_OF_MODIFIED_EVENT,
'newEndMinutes',
);
export const setRoomIdOfModifiedEvent = makeActionCreator(
SET_ROOM_ID_OF_MODIFIED_EVENT,
'newRoomId',
);
export const attemptModifConfirm = makeActionCreator(ATTEMPT_MODIF_CONFIRM);
export const requestModif = makeActionCreator(REQUEST_MODIF);
export const receiveModifConfirmation = makeActionCreator(RECEIVE_MODIF_CONFIRMATION);
export const receiveModifUnknownError = makeActionCreator(RECEIVE_MODIF_UNKNOWN_ERROR);
export const receiveModifAlreadyBookedError = makeActionCreator(RECEIVE_MODIF_ALREADY_BOOKED_ERROR);
export const receiveModifConfirmation = makeActionCreator(
RECEIVE_MODIF_CONFIRMATION,
);
export const receiveModifUnknownError = makeActionCreator(
RECEIVE_MODIF_UNKNOWN_ERROR,
);
export const receiveModifAlreadyBookedError = makeActionCreator(
RECEIVE_MODIF_ALREADY_BOOKED_ERROR,
);

export function sendModifRequest(event, newAttr) {
function getUpdatedISOstring(isoDate, hour, minutes) {
Expand All @@ -43,7 +71,9 @@ export function sendModifRequest(event, newAttr) {

// Do nothing if eventName is empty
if (!newAttr.eventName) return;

if (newAttr.forUserName) {
newAttr.eventName = `<${newAttr.forUserName}> ${newAttr.eventName}`;
}
dispatch(requestModif());

// Format dates
Expand All @@ -60,10 +90,10 @@ export function sendModifRequest(event, newAttr) {

// Test if something has changed
if (
event.name === newAttr.eventName
&& event.startDate === newStartDate
&& event.endDate === newEndDate
&& event.room.id === newAttr.roomId
event.name === newAttr.eventName &&
event.startDate === newStartDate &&
event.endDate === newEndDate &&
event.room.id === newAttr.roomId
) {
dispatch(receiveModifConfirmation());
return;
Expand All @@ -80,7 +110,9 @@ export function sendModifRequest(event, newAttr) {
},
body: JSON.stringify({
eventId: event.id,
newEventName: newAttr.eventName,
newEventName: newAttr.forUserName
? `<${newAttr.forUserName}> ${newAttr.eventName}`
: newAttr.eventName,
newStartDate,
newEndDate,
newRoomId: newAttr.roomId,
Expand Down
13 changes: 6 additions & 7 deletions front/src/actions/rooms/book/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { forceFetchBookings } from 'actions/bookings/list';
import {
SELECT_ROOM_TO_BOOK,
SET_EVENT_NAME,
SET_FOR_USER_NAME,
SET_VIDEO_PROVIDER,
ATTEMPT_BOOK_CONFIRM,
REQUEST_BOOK,
Expand All @@ -22,6 +23,7 @@ export const selectRoomToBook = makeActionCreator(
'payload',
);
export const setEventName = makeActionCreator(SET_EVENT_NAME, 'payload');
export const setForUserName = makeActionCreator(SET_FOR_USER_NAME, 'payload');
export const setVideoProvider = makeActionCreator(
SET_VIDEO_PROVIDER,
'payload',
Expand Down Expand Up @@ -50,12 +52,9 @@ export function sendBookRequest() {
dispatch(attemptBookConfirm());

const state = getState();
const { room, eventName, videoProvider } = state.search.book;
const {
selectedDate,
selectedStartTime,
selectedEndTime,
} = state.search.dateTime;
const { room, eventName, forUserName, videoProvider } = state.search.book;
const { selectedDate, selectedStartTime, selectedEndTime } =
state.search.dateTime;
const formattedDate = getFormattedDate(
selectedDate,
selectedStartTime,
Expand All @@ -75,7 +74,7 @@ export function sendBookRequest() {
'Content-Type': 'application/json',
},
body: JSON.stringify({
eventName,
eventName: forUserName ? `<${forUserName}> ${eventName}` : eventName,
videoProvider,
startDate: formattedDate.start,
endDate: formattedDate.end,
Expand Down
1 change: 1 addition & 0 deletions front/src/actions/rooms/book/types.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const SELECT_ROOM_TO_BOOK = 'SELECT_ROOM_TO_BOOK';
export const SET_EVENT_NAME = 'SET_EVENT_NAME';
export const SET_FOR_USER_NAME = 'SET_FOR_USER_NAME';
export const SET_VIDEO_PROVIDER = 'SET_VIDEO_PROVIDER';
export const ATTEMPT_BOOK_CONFIRM = 'ATTEMPT_BOOK_CONFIRM';
export const REQUEST_BOOK = 'REQUEST_BOOK';
Expand Down
46 changes: 25 additions & 21 deletions front/src/components/partials/EventList.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ const EventList = ({
Aucun évènement n&apos;est prévu
{!useToday && ` le ${moment(selectedDate).format('D MMMM')} `}
{useToday && " aujourd'hui "}
en
&nbsp;
en &nbsp;
<span className="font-weight-bold">{roomName}</span>
</h6>
);
Expand All @@ -37,13 +36,19 @@ const EventList = ({
{events.length >= 2 && `${events.length} évènements sont prévus`}
{!useToday && ` le ${moment(selectedDate).format('D MMMM')} `}
{useToday && " aujourd'hui "}
en
&nbsp;
en &nbsp;
<span className="font-weight-bold">{roomName}</span>
</h6>
<div className="list-group mt-3">
{events.map((event) => {
const isHighlighted = event.id === highlightedEvent;
const matchForUserNameAndEventName = event.name.match(/<(.*?)>(.*)/);
let forUserName = null;
let eventName = event.name;
if (matchForUserNameAndEventName) {
forUserName = matchForUserNameAndEventName[1].trim();
eventName = matchForUserNameAndEventName[2].trim();
}
return (
<div
className={`list-group-item flex-column align-items-start ${
Expand All @@ -52,29 +57,28 @@ const EventList = ({
key={event.id}
>
<div className="d-flex w-100 justify-content-between">
<h5 className="mb-1">{event.name}</h5>
<h5 className="mb-1">{eventName}</h5>
<span className={!isHighlighted ? 'text-muted' : ''}>
de&nbsp;
{moment(event.startDate)
.utc()
.format('H[h]mm')}
{moment(event.startDate).utc().format('H[h]mm')}
&nbsp;à&nbsp;
{moment(event.endDate)
.utc()
.format('H[h]mm')}
{moment(event.endDate).utc().format('H[h]mm')}
</span>
</div>
<span className={!isHighlighted ? 'text-muted' : ''}>
Réservé par
&nbsp;
<a
className={!isHighlighted ? 'text-secondary' : 'text-white'}
href={`mailto:${event.author.email}`}
>
{recapitalize(event.author.firstName)}
&nbsp;
{recapitalize(event.author.lastName)}
</a>
Réservé par &nbsp;
{forUserName ? (
<span>{recapitalize(forUserName)}</span>
) : (
<a
className={!isHighlighted ? 'text-secondary' : 'text-white'}
href={`mailto:${event.author.email}`}
>
{recapitalize(event.author.firstName)}
&nbsp;
{recapitalize(event.author.lastName)}
</a>
)}
</span>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ const Booking = ({
>
Modifier le titre
</button>
<button
type="button"
className="dropdown-item"
data-toggle="modal"
data-target="#modifyEventModal"
onClick={() => {
handleModifyEvent(event, 'forUserName');
}}
>
Modifier pour le compte de
</button>
{event.room.allowBooking && (
<button
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// lib
import React from 'react';
import PropTypes from 'prop-types';
// src
import BookingSummary from './BookingSummary';

const ForUserNameChangeBody = ({
event,
handleForUserNameInputChange,
forUserName,
detectEnter,
attemptedConfirm,
}) => [
<div
className={attemptedConfirm ? 'form-group was-validated' : 'form-group'}
key="forUserNameForm"
>
<label htmlFor="forUserName">Pour le compte de :</label>
<input
type="text"
className={forUserName ? 'form-control valid' : 'form-control invalid'}
id="forUserName"
maxLength="150"
value={forUserName}
onChange={handleForUserNameInputChange}
onKeyPress={detectEnter}
/>
</div>,
<div className="text-center" key="eventDetails">
<BookingSummary event={event} />
</div>,
];

ForUserNameChangeBody.propTypes = {
event: PropTypes.object.isRequired,
handleForUserNameInputChange: PropTypes.func.isRequired,
forUserName: PropTypes.string.isRequired,
detectEnter: PropTypes.func.isRequired,
attemptedConfirm: PropTypes.bool.isRequired,
};

export default ForUserNameChangeBody;
Loading

0 comments on commit 1e209bf

Please sign in to comment.