Skip to content

Commit

Permalink
Implement admin "select shift times" calendar (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
7tint authored Mar 6, 2022
1 parent a7a91b6 commit 6b1ad2a
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 5 deletions.
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"@emotion/styled": "^11",
"@fontsource/inter": "^4.5.1",
"@fontsource/raleway": "^4.5.0",
"@fullcalendar/interaction": "^5.10.1",
"@fullcalendar/react": "^5.10.1",
"@fullcalendar/timegrid": "^5.10.1",
"@rjsf/bootstrap-4": "^2.5.1",
"@rjsf/core": "^2.5.1",
"@testing-library/jest-dom": "^5.11.4",
Expand All @@ -35,6 +38,7 @@
"json-schema": "^0.4.0",
"json2csv": "^5.0.6",
"jsonwebtoken": "^8.5.1",
"moment": "^2.29.1",
"react": "^17.0.1",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.1",
Expand Down
72 changes: 72 additions & 0 deletions frontend/src/components/admin/ShiftCalendar/ShiftCalendar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
a:hover {
text-decoration: none;
color: inherit;
}

.fc table {
font-weight: 400;
letter-spacing: 0.2px;
line-height: 18px;
font-size: 16px;
}

.fc .fc-col-header-cell-cushion {
font-family: "Raleway", sans-serif;
font-size: 18px;
font-weight: 500;
letter-spacing: 0.2px;
line-height: 18px;
text-transform: uppercase;
}

.fc .fc-scrollgrid-sync-inner {
align-items: center;
display: flex;
height: 75px;
justify-content: center;
}

.fc-theme-standard .fc-scrollgrid {
border: none;
}

.fc-v-event .fc-event-main-frame {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}

.fc-event-title-container {
display: none;
}

.fc .fc-timegrid-axis-cushion,
.fc .fc-timegrid-slot-label-cushion {
font-size: 14px;
padding-right: 0.5rem;
padding-left: 0.5rem;
}

.fc-timegrid-event .fc-event-time {
white-space: normal;
text-align: center;
flex-shrink: unset;
padding-left: 0.3rem;
padding-right: 0.3rem;
}

.fc-timegrid-event-harness > .fc-timegrid-event {
display: block;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.25);
border-radius: 4px;
}

.fc-theme-standard td,
.fc-theme-standard th {
border: 0.5px solid #ddd;
}

.fc .fc-timegrid-col.fc-day-today {
background-color: inherit;
}
127 changes: 127 additions & 0 deletions frontend/src/components/admin/ShiftCalendar/ShiftCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import FullCalendar, {
DateSelectArg,
EventChangeArg,
EventClickArg,
EventInput,
} from "@fullcalendar/react";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import React, { useState } from "react";
import {
Box,
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from "@chakra-ui/react";
import colors from "../../../theme/colors";
import "./ShiftCalendar.css";
import { getTime, getWeekday } from "../../../utils/DateTimeUtils";

export type Event = {
id: string;
start: Date;
end: Date;
};

type ShiftCalendarProps = {
events: Event[];
selectedEvent: Event | null;
setSelectedEvent: (event: Event | null) => void;
addEvent: (newEvent: DateSelectArg) => void;
changeEvent: (event: Event, oldEvent: Event, currEvents: Event[]) => void;
deleteEvent: (currEvents: Event[]) => void;
};

const ShiftCalendar = ({
events,
selectedEvent,
setSelectedEvent,
addEvent,
changeEvent,
deleteEvent,
}: ShiftCalendarProps): React.ReactElement => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

const openModal = (): void => {
setIsModalOpen(true);
};

const closeModal = (): void => {
setIsModalOpen(false);
};

const deleteDialog = (event: Event) => {
openModal();
setSelectedEvent(event);
};

const onDeleteEvent = (currEvents: Event[]) => {
deleteEvent(currEvents);
closeModal();
};

return (
<Box>
<Modal onClose={closeModal} isOpen={isModalOpen} isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader mt={3}>Delete Shift?</ModalHeader>
<ModalCloseButton />
<ModalBody>
{selectedEvent
? `Are you sure you want to delete the event on ${getWeekday(
selectedEvent.start,
)} from ${getTime(selectedEvent.start)} to ${getTime(
selectedEvent.end,
)}?`
: ""}
</ModalBody>
<ModalFooter>
<Button colorScheme="gray" mr={3} onClick={closeModal}>
Cancel
</Button>
<Button
colorScheme="red"
onClick={() => onDeleteEvent(events.slice())}
>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<FullCalendar
allDaySlot={false}
dayHeaderFormat={{ weekday: "short" }}
editable
eventChange={(arg: EventChangeArg) =>
changeEvent(arg.event as Event, arg.oldEvent as Event, events.slice())
}
eventClick={(arg: EventClickArg) => deleteDialog(arg.event as Event)}
eventColor={colors.violet}
eventTimeFormat={{
hour: "numeric",
minute: "2-digit",
meridiem: "short",
}}
events={events as EventInput[]}
headerToolbar={false}
initialView="timeGridWeek"
plugins={[timeGridPlugin, interactionPlugin]}
select={(selectInfo: DateSelectArg) => addEvent(selectInfo)}
selectMirror
selectable
scrollTime="09:00:00"
slotDuration="00:15:00"
slotLabelInterval="01:00"
timeZone="UTC"
/>
</Box>
);
};

export default ShiftCalendar;
2 changes: 1 addition & 1 deletion frontend/src/components/common/PostingDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { CalendarIcon, TimeIcon } from "@chakra-ui/icons";
import PocCard from "./PocCard";
import { PostingResponseDTO } from "../../types/api/PostingTypes";
import { formatDateString } from "../../utils/DateUtils";
import { formatDateString } from "../../utils/DateTimeUtils";

type PostingDetailsProps = {
postingDetails: PostingResponseDTO;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,63 @@
import React from "react";
import { DateSelectArg } from "@fullcalendar/react";
import React, { useState } from "react";
import { Container, Divider } from "@chakra-ui/react";
import ShiftCalendar, {
Event,
} from "../../../admin/ShiftCalendar/ShiftCalendar";

const CreatePostingShiftsPage = (): React.ReactElement => {
return <div />;
const [events, setEvents] = useState<Event[]>([]);
const [eventCount, setEventCount] = useState<number>(0);
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);

const addEvent = (newEvent: DateSelectArg) => {
setEvents([
...events,
{
start: newEvent.start,
end: newEvent.end,
id: `event-${eventCount}`,
},
]);
setEventCount(eventCount + 1);
};

const changeEvent = (event: Event, oldEvent: Event, currEvents: Event[]) => {
const newEvent = currEvents.find(
(currEvent) => currEvent.id === oldEvent.id,
);
if (newEvent) {
newEvent.start = event.start;
newEvent.end = event.end;
setEvents([...currEvents]);
}
};

const deleteEvent = (currEvents: Event[]) => {
if (selectedEvent) {
for (let i = 0; i < currEvents.length; i += 1) {
if (currEvents[i].id === selectedEvent.id) {
currEvents.splice(i, 1);
break;
}
}
setEvents(currEvents);
}
};

return (
<Container maxW="container.lg">
<ShiftCalendar
events={events}
selectedEvent={selectedEvent}
setSelectedEvent={setSelectedEvent}
addEvent={addEvent}
changeEvent={changeEvent}
deleteEvent={deleteEvent}
/>
<Divider my={4} />
</Container>
);
};

export default CreatePostingShiftsPage;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { gql, useQuery } from "@apollo/client";
import { Text, Box, HStack, Select } from "@chakra-ui/react";

import { PostingResponseDTO } from "../../../../types/api/PostingTypes";
import { dateInRange } from "../../../../utils/DateUtils";
import { dateInRange } from "../../../../utils/DateTimeUtils";
import { FilterType } from "../../../../types/DateFilterTypes";
import PostingCard from "../../../volunteer/PostingCard";
import EmptyPostingCard from "../../../volunteer/EmptyPostingCard";
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/volunteer/PostingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Tag,
} from "@chakra-ui/react";

import { formatDateStringWithYear } from "../../utils/DateUtils";
import { formatDateStringWithYear } from "../../utils/DateTimeUtils";
import { SkillResponseDTO } from "../../types/api/SkillTypes";

type PostingCardProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import moment from "moment";
import { FilterType } from "../types/DateFilterTypes";

// eslint-disable-next-line import/prefer-default-export
Expand Down Expand Up @@ -32,3 +33,11 @@ export const dateInRange = (start: string, filterType: FilterType): boolean => {
(filterType === "week" ? MS_PER_WEEK : MS_PER_WEEK * 4)
);
};

export const getWeekday = (dateStringInput: Date): string => {
return moment(dateStringInput).format("dddd");
};

export const getTime = (dateStringInput: Date): string => {
return moment(dateStringInput).format("hh:mm A");
};
45 changes: 45 additions & 0 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1972,6 +1972,46 @@
resolved "https://registry.yarnpkg.com/@fontsource/raleway/-/raleway-4.5.0.tgz#d5c464abe655d2e2cb134c503db455b7af6c03a6"
integrity sha512-Rzj90wbZQnNzazqzoiu5HzMEMdqMJLUVFOo699sinTXrZRm1aB5iX2HTiK2VlPnH4M6u8yYnJ7CebOyamfWlqw==

"@fullcalendar/common@~5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@fullcalendar/common/-/common-5.10.1.tgz#a019951743852277a4095e536fd7716f6f85b9aa"
integrity sha512-EumKIJcQTvQdTs75/9dmeREFgjcRVWzqHJS1Xvlz5mNsmB+w9EINCHETRjChtAQg1WD/lTQyVj4sHsKO7vCMSw==
dependencies:
tslib "^2.1.0"

"@fullcalendar/daygrid@~5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-5.10.1.tgz#bdee4f58364fdab631b2abf8b56094ab5776f203"
integrity sha512-sfUMP+rew0krsBffgNcWWKhBCiyytGfRKZJoc64E8ohX7VWjPcPZuB1xgO5U4wPLmNkT0rZiHoGeQGTXw1+ZKg==
dependencies:
"@fullcalendar/common" "~5.10.1"
tslib "^2.1.0"

"@fullcalendar/interaction@^5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@fullcalendar/interaction/-/interaction-5.10.1.tgz#dfa74b5c50bbd5608eb50aeab6e579c8d20cb367"
integrity sha512-H1g1QeXg7yXtUcKmVtfg7uzm5R5ElFTvYniiXU+8kJda69IDg7Lee+Y7UDv5dvLb5/HxO86RhPVxRtcOQ8XdXw==
dependencies:
"@fullcalendar/common" "~5.10.1"
tslib "^2.1.0"

"@fullcalendar/react@^5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-5.10.1.tgz#bb1652357f98b7dc01894a41e7204f1391999791"
integrity sha512-+kV5+o9qGjkoO5j0r3oawhig+hP3inE68bHKBl6dtGQ8mi3GkEGsTL12MY2n5N0hXon6+2psIQLnAjMyRYZbmg==
dependencies:
"@fullcalendar/common" "~5.10.1"
tslib "^2.1.0"

"@fullcalendar/timegrid@^5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@fullcalendar/timegrid/-/timegrid-5.10.1.tgz#fa7feb909bf599eac1466b9e70c0d56ce0d1aefc"
integrity sha512-0O0m+JzFBlg8gxYr/rIjZViRlbndCtjZlDjjIylQHFBeWC32e3cpHEavKGbTIBLN8SDilUYAJnE21abSqC2G/w==
dependencies:
"@fullcalendar/common" "~5.10.1"
"@fullcalendar/daygrid" "~5.10.1"
tslib "^2.1.0"

"@graphql-typed-document-node/core@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950"
Expand Down Expand Up @@ -8850,6 +8890,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==

move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
Expand Down

0 comments on commit 6b1ad2a

Please sign in to comment.