diff --git a/.eslintrc.json b/.eslintrc.json
index fd40dae..5bd272e 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -8,6 +8,7 @@
"plugin:react/recommended",
"plugin:prettier/recommended"
],
+ "parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
diff --git a/package.json b/package.json
index 5cac9b3..7aa722c 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,6 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.4",
"react-html-email": "^3.0.0",
- "react-icons": "^5.0.1",
"react-router-dom": "^6.20.0",
"react-script": "^2.0.5",
"react-icons": "^5.0.1",
diff --git a/src/App.jsx b/src/App.jsx
index 5ec7bfe..fdb9d98 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -15,7 +15,6 @@ import PublishedSchedule from './pages/PublishedSchedule/PublishedSchedule';
import Playground from './pages/Playground/Playground';
import Planner from './pages/Planner/Planner';
import Navbar from './components/Navbar/Navbar';
-import NotificationSandbox from './pages/NotificationSandbox/NotificationSandbox';
import Accounts from './pages/Accounts/Accounts';
import { AuthContextProvider, useAuthContext } from './common/AuthContext';
@@ -56,13 +55,6 @@ const App = () => {
} />
} />
} />
-
- }
- />
{
redirectPath="/login"
roles={[ADMIN_ROLE]}
/>
- } />
+ } />
diff --git a/src/components/AddDayForm/AddDayForm.jsx b/src/components/AddDayForm/AddDayForm.jsx
new file mode 100644
index 0000000..1313b4a
--- /dev/null
+++ b/src/components/AddDayForm/AddDayForm.jsx
@@ -0,0 +1,153 @@
+/* eslint-disable react/jsx-props-no-spreading */
+import {
+ Box,
+ FormLabel,
+ Input,
+ FormControl,
+ FormErrorMessage,
+ Button,
+ Textarea,
+ useToast,
+ Heading,
+ Flex
+} from '@chakra-ui/react';
+import { PropTypes } from 'prop-types';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { useForm } from 'react-hook-form';
+import * as yup from 'yup';
+import { NPOBackend } from '../../utils/auth_utils';
+
+const schema = yup.object({
+ date: yup.date().nullable().transform((curr, orig) => orig === '' ? null : curr).required('Date required'),
+ location: yup.string().required('Location required').max(50, 'Location exceeds 50 character limit'),
+ details: yup
+ .string()
+ .max(50, 'Details exceeds 50 character limit'),
+});
+
+const AddDayForm = ({ onClose, onOpen, setDayId, dayData, setShouldDataRevalidate }) => {
+ const toast = useToast();
+ const {
+ handleSubmit,
+ register,
+ formState: { errors },
+ } = useForm({
+ resolver: yupResolver(schema),
+ });
+
+ const submitData = async data => {
+ const { date, details, location } = data;
+ toast.closeAll();
+ try {
+ const payload = {
+ eventDate: date,
+ location: location,
+ notes: details,
+ };
+
+ let response;
+ if (dayData) {
+ response = await NPOBackend.put(`/day/${dayData.id}`, payload);
+ setShouldDataRevalidate(true);
+ } else {
+ response = await NPOBackend.post('/day/', payload);
+ }
+
+ if (response.status === 200 || (response.status == 201 && response.data.status === 'Success')) {
+ const id = dayData ? dayData.id : response.data['id'];
+ setDayId(id);
+ onOpen(id);
+ } else if (response.status == 201 && response.data.status === 'Failed') {
+ toast({
+ title: 'This date already exists in the schedule.',
+ description: `${date.toLocaleDateString()}`,
+ status: 'error',
+ variant: 'subtle',
+ position: 'top-right',
+ containerStyle: {
+ mt: '6rem',
+ },
+ duration: 3000,
+ isClosable: true,
+ });
+ }
+
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ return (
+
+ {!dayData ? 'Add New Day' : 'Edit Day Details'}
+
+
+ );
+};
+
+AddDayForm.propTypes = {
+ onClose: PropTypes.func,
+ onOpen: PropTypes.func,
+ setDayId: PropTypes.func,
+ dayData: PropTypes.object,
+ setShouldDataRevalidate: PropTypes.func
+};
+AddDayForm.defaultProps = {
+ onClose: () => {},
+ onOpen: () => {},
+ dayData: null
+};
+export default AddDayForm;
diff --git a/src/components/AddEventToPublishedScheduleForm/AddEventToPublishedScheduleForm.jsx b/src/components/AddEventToPublishedScheduleForm/AddEventToPublishedScheduleForm.jsx
index 51a6579..ea6398d 100644
--- a/src/components/AddEventToPublishedScheduleForm/AddEventToPublishedScheduleForm.jsx
+++ b/src/components/AddEventToPublishedScheduleForm/AddEventToPublishedScheduleForm.jsx
@@ -1,133 +1,446 @@
/* eslint-disable react/jsx-props-no-spreading */
import {
- Box,
- FormLabel,
- Input,
- FormControl,
- FormErrorMessage,
- Button,
- Textarea,
- Checkbox,
- Editable,
- EditablePreview,
- } from '@chakra-ui/react';
- import { yupResolver } from '@hookform/resolvers/yup';
- import { useForm } from 'react-hook-form';
- import * as yup from 'yup';
-
- const schema = yup.object({
- confirmed: yup.boolean().default(true).required("Confirmation required"),
- startTime: yup.date().required('Start time required'),
- endTime: yup.date().required('End time required').min(yup.ref('startTime'), 'End time must be after start time'),
- cohort: yup.number().required('Cohort required').min(2000),
- notes: yup.string().nullable()
+ Box,
+ FormLabel,
+ Input,
+ FormControl,
+ FormErrorMessage,
+ Button,
+ Textarea,
+ Checkbox,
+ useToast,
+ Heading,
+ Flex,
+ Text,
+ Stack
+} from '@chakra-ui/react';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { useForm } from 'react-hook-form';
+import { useContext, useEffect, useState } from 'react';
+import { NPOBackend } from '../../utils/auth_utils';
+import * as yup from 'yup';
+import {
+ seasonOptions,
+ yearOptions,
+ subjectOptions,
+ eventOptions,
+} from '../Catalog/SearchFilter/filterOptions';
+import useSearchFilters from '../Catalog/SearchFilter/useSearchFilters';
+import Dropdown from '../Dropdown/Dropdown';
+import PropTypes from 'prop-types';
+import { PlannerContext } from '../Planner/PlannerContext';
+import PlannedEvent, { convertTimeToMinutes } from '../Planner/PlannedEvent';
+
+const schema = yup.object({
+ startTime: yup.string().required('Start time is required'),
+ endTime: yup.string()
+ .required('End time is required')
+ .test('is-after', 'End time must be after start time', function(endTime) {
+ const startTime = this.parent.startTime;
+ return startTime && endTime && startTime < endTime;
+ }),
+ host: yup.string().max(50, 'Host exceeds 50 character limit').default('').nullable(),
+ title: yup.string().required('Title Required').max(50, 'Title exceeds 50 character limit'),
+ description: yup
+ .string()
+ .max(256, 'Description exceeds 256 character limit')
+ .default('')
+ .nullable(),
+ tentative: yup.boolean()
+});
+
+const AddEventToPublishedScheduleForm = ({ closeForm }) => {
+ const { plannedEventsContext, dayId, editContext, currEventContext } = useContext(PlannerContext);
+ const [plannedEvents, setPlannedEvents] = plannedEventsContext;
+ const [eventData, setCurrEvent] = currEventContext;
+ const [isEdit, setIsEdit] = editContext;
+ const { filters, filterValues } = useSearchFilters();
+ const [seasonFilter, yearFilter, subjectFilter, eventFilter] = filters;
+ const [checkboxVal, setCheckboxVal] = useState(undefined);
+ const [formData, setFormData] = useState({...eventData});
+
+ useEffect(() => {
+ if (Object.keys(eventData).length === 0) {
+ setCheckboxVal(false);
+ setFormData({});
+ reset();
+ setValue('description', '');
+ return;
+ }
+
+ setValue('title', eventData.title);
+ setValue('host', eventData.host);
+ setValue('description', eventData.description);
+ setFormData(eventData);
+ if (!isEdit) {
+ setCheckboxVal(false);
+ } else {
+ setCheckboxVal(eventData && eventData.confirmed !== null && !eventData.confirmed);
+ }
+ if (isEdit) {
+ setValue('startTime', eventData.startTime);
+ setValue('endTime', eventData.endTime);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [eventData]);
+
+ useEffect(() => {
+ if (formData.startTime && formData.endTime && formData.startTime < formData.endTime) {
+ // if (isEdit) {
+ // // setPlannedEvents([...plannedEvents.filter(e => e.id != -1 && e.id != eventData.id)]);
+ // }
+ const newPlannedEvent = new PlannedEvent(
+ -1,
+ formData.title,
+ convertTimeToMinutes(formData.startTime),
+ convertTimeToMinutes(formData.endTime),
+ formData.host,
+ checkboxVal ? true : false
+ )
+ setPlannedEvents([...plannedEvents.filter(e => e.id != -1 && e.id != eventData.id), newPlannedEvent]);
+ } else {
+ setPlannedEvents(plannedEvents.filter(e => e.id != -1));
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [formData])
+
+ const toast = useToast();
+ const {
+ setValue,
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors },
+ } = useForm({
+ resolver: yupResolver(schema),
});
-
-
- const AddEventToPublishedScheduleForm = () => {
- const {
- register,
- handleSubmit,
- setValue,
- formState: { errors },
- } = useForm({
- resolver: yupResolver(schema),
- });
-
-
- const handleConfirmedChange = (e) => {
- setValue('confirmed', e.target.checked);
- };
-
-
- return (
-
-
+
+ );
+};
+
+AddEventToPublishedScheduleForm.propTypes = {
+ closeForm: PropTypes.func
+};
+
+export default AddEventToPublishedScheduleForm;
diff --git a/src/components/Catalog/CatalogTable.jsx b/src/components/Catalog/CatalogTable.jsx
index d2aed72..8481ce1 100644
--- a/src/components/Catalog/CatalogTable.jsx
+++ b/src/components/Catalog/CatalogTable.jsx
@@ -1,12 +1,24 @@
import { Container, Badge, IconButton, Table, Thead, Tr, Th, Td, Tbody, Text } from '@chakra-ui/react';
import { /*EditIcon,*/ DeleteIcon } from '@chakra-ui/icons'; // add 'EditIcon' to reinstate edit button.
+import { IoIosAddCircleOutline } from "react-icons/io";
import s from './Catalog.module.css';
import PropTypes from 'prop-types';
-const CatalogTable = ({ tableData, /*handleEditForm,*/ handleDeleteClick }) => {
+const CatalogTable = ({ tableData, handleActionClick, onDayPlanner, setCurrEvent }) => {
+ const setDataAndOpenPSForm = (eventData) => {
+ setCurrEvent(eventData);
+ handleActionClick();
+ }
+
return (
-
-
+
+
Event |
Host |
@@ -84,16 +96,47 @@ const CatalogTable = ({ tableData, /*handleEditForm,*/ handleDeleteClick }) => {
))}
-
- {/* }
- onClick={() =>
- handleEditForm({
+ { !onDayPlanner ?
+ |
+ {/* }
+ onClick={() =>
+ handleEditForm({
+ id,
+ title,
+ host,
+ year,
+ eventType,
+ subject,
+ description,
+ })
+ }
+ /> */}
+ }
+ onClick={() => handleActionClick(id)}
+ />
+ |
+ :
+
+ }
+ onClick={() => setDataAndOpenPSForm({
id,
title,
host,
@@ -102,19 +145,10 @@ const CatalogTable = ({ tableData, /*handleEditForm,*/ handleDeleteClick }) => {
subject,
description,
season
- })
- }
- /> */}
- }
- onClick={() => handleDeleteClick(id)}
- />
- |
+ })}
+ />
+
+ }
))}
@@ -124,8 +158,13 @@ const CatalogTable = ({ tableData, /*handleEditForm,*/ handleDeleteClick }) => {
CatalogTable.propTypes = {
tableData: PropTypes.arrayOf(PropTypes.any),
- // handleEditForm: PropTypes.func.isRequired,
- handleDeleteClick: PropTypes.func.isRequired,
+ handleActionClick: PropTypes.func.isRequired,
+ onDayPlanner: PropTypes.bool,
+ setCurrEvent: PropTypes.func,
+};
+
+CatalogTable.defaultProps = {
+ onDayPlanner: false
};
export default CatalogTable;
diff --git a/src/components/Catalog/CreateEventForm/CreateEventForm.jsx b/src/components/Catalog/CreateEventForm/CreateEventForm.jsx
index ff751cc..5ca2463 100644
--- a/src/components/Catalog/CreateEventForm/CreateEventForm.jsx
+++ b/src/components/Catalog/CreateEventForm/CreateEventForm.jsx
@@ -124,7 +124,7 @@ const CreateEventForm = ({ eventData, setDataShouldRevalidate, closeModal }) =>
{/* TITLE */}
-
+
Title *
{errors.title && errors.title.message}
@@ -133,7 +133,7 @@ const CreateEventForm = ({ eventData, setDataShouldRevalidate, closeModal }) =>
{/* DESCRIPTION */}
-
+
Description
@@ -207,7 +207,7 @@ const CreateEventForm = ({ eventData, setDataShouldRevalidate, closeModal }) =>
{/* HOST */}
-
+
Host
{errors.host && errors.host.message}
@@ -246,10 +246,12 @@ CreateEventForm.propTypes = {
setDataShouldRevalidate: PropTypes.func,
closeModal: PropTypes.func,
};
+// dayId: PropTypes.number,
+
CreateEventForm.defaultProps = {
eventData: undefined,
- setModified: undefined,
+ setDataShouldRevalidate: undefined,
closeModal: () => {},
};
diff --git a/src/components/Catalog/SearchFilter/filterOptions.js b/src/components/Catalog/SearchFilter/filterOptions.js
index a317550..7ef251c 100644
--- a/src/components/Catalog/SearchFilter/filterOptions.js
+++ b/src/components/Catalog/SearchFilter/filterOptions.js
@@ -2,7 +2,6 @@ const seasonOptions = [
{ value: 'fall', name: 'Fall' },
{ value: 'spring', name: 'Spring' },
{ value: 'summer', name: 'Summer' },
- { value: 'winter', name: 'Winter' },
];
const yearOptions = [
{ value: 'junior', name: 'Junior' },
diff --git a/src/components/Dropdown/Dropdown.jsx b/src/components/Dropdown/Dropdown.jsx
index e661ec9..af5860a 100644
--- a/src/components/Dropdown/Dropdown.jsx
+++ b/src/components/Dropdown/Dropdown.jsx
@@ -12,7 +12,7 @@ import { ChevronDownIcon } from '@chakra-ui/icons';
import { FilterCheckbox } from '../Catalog/SearchFilter/SearchFilter';
import { useState, useEffect } from 'react';
-const Dropdown = ({ options, filter, selected, defaults, badgeColor }) => {
+const Dropdown = ({ options, filter, selected, defaults, badgeColor, width }) => {
const { getCheckboxProps } = filter;
const [selectedOptions, setSelectedOptions] = useState([]);
@@ -21,9 +21,19 @@ const Dropdown = ({ options, filter, selected, defaults, badgeColor }) => {
}, [selected]);
useEffect(() => {
+ // console.log('inital default');
if (defaults && defaults[0] !== '')
filter.setValue(defaults);
- }, [])
+ }, []);
+
+ useEffect(() => {
+ // console.log('update default', defaults);
+ if (defaults && defaults[0] !== '')
+ filter.setValue(defaults);
+ if (!defaults) {
+ filter.setValue([]);
+ }
+ }, [defaults]);
return (
@@ -78,7 +71,7 @@ DailyEvent.propTypes = {
startTime: PropTypes.string.isRequired,
endTime: PropTypes.string.isRequired,
eventTitle: PropTypes.string.isRequired,
- location: PropTypes.string.isRequired,
+ description: PropTypes.string,
confirmed: PropTypes.bool.isRequired,
};
diff --git a/src/components/Events/EventInfo.jsx b/src/components/Events/EventInfo.jsx
index 66fe75a..3f1a3c6 100644
--- a/src/components/Events/EventInfo.jsx
+++ b/src/components/Events/EventInfo.jsx
@@ -1,50 +1,95 @@
import PropTypes from 'prop-types';
-import { Box, Text, Grid, IconButton } from '@chakra-ui/react';
+import { Box, Text, IconButton, HStack, Button, useDisclosure, Modal, ModalHeader, ModalCloseButton, ModalOverlay, ModalContent, ModalBody, ModalFooter, useToast } from '@chakra-ui/react';
import { DeleteIcon } from '@chakra-ui/icons';
import { LuPen } from 'react-icons/lu';
+import PlannerModal from '../Planner/PlannerModal';
+import { NPOBackend } from '../../utils/auth_utils';
+
+const EventInfo = ({ dayId, eventDate, day, startTime, endTime, location, notes, setShouldDataRevalidate }) => {
+ const { isOpen: isOpenPlanner, onOpen: onOpenPlanner, onClose: onClosePlanner } = useDisclosure();
+ const { isOpen: isOpenDelete, onOpen: onOpenDelete, onClose: onCloseDelete } = useDisclosure();
+ const toast = useToast();
+
+ const handlePlannerClose = () => {
+ setShouldDataRevalidate(true);
+ onClosePlanner();
+ }
+
+ const handleDeleteDay = async () => {
+ const res = await NPOBackend.delete(`/day/${dayId}`);
+ if (res.status === 200) {
+ toast({
+ title: 'Date deleted',
+ status: 'success',
+ variant: 'subtle',
+ position: 'top-right',
+ containerStyle: {
+ mt: '6rem',
+ mr: '100px'
+ },
+ duration: 3000,
+ isClosable: true,
+ });
+ setShouldDataRevalidate(true);
+ }
+ }
-const EventInfo = ({ eventDate, day, startTime, endTime, location, notes }) => {
return (
-
-
-
-
- {eventDate}
-
-
- {day} {startTime} - {endTime}
-
-
-
-
- {location}
-
-
-
- Details:
- {notes != null ? 'notes' : 'No notes.'}
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {eventDate}
+
+
+ {day} {startTime} - {endTime}
+
+
+ {location}
+
+ Details:
+ {notes ? notes : 'No notes.'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete Day?
+
+
+ Are you sure? You can't undo this action afterwards.
+
+
+
+
+
+
+
+
+
);
};
EventInfo.propTypes = {
+ dayId: PropTypes.number.isRequired,
eventDate: PropTypes.string.isRequired,
day: PropTypes.string.isRequired,
startTime: PropTypes.string.isRequired,
endTime: PropTypes.string.isRequired,
location: PropTypes.string.isRequired,
notes: PropTypes.string,
+ setShouldDataRevalidate: PropTypes.func,
};
export default EventInfo;
diff --git a/src/components/Events/Events.jsx b/src/components/Events/Events.jsx
index a0838a3..fd3a462 100644
--- a/src/components/Events/Events.jsx
+++ b/src/components/Events/Events.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Grid } from '@chakra-ui/react';
-const Events = ({ eventData, location }) => {
+const Events = ({ eventData }) => {
const formatDate = (date) => {
let time = date.split(":");
@@ -17,6 +17,15 @@ const Events = ({ eventData, location }) => {
let maxId = eventData.reduce((maxVal, event) => Math.max(maxVal, event.id), -Infinity)+1;
+ eventData.sort((a, b) => {
+ if (a.startTime < b.startTime) {
+ return -1;
+ } else if (a.startTime > b.startTime) {
+ return 1;
+ }
+ return 0;
+ });
+
const eventDataWithBreaks = [];
if (eventData.length == 1) {
eventDataWithBreaks.push(eventData[0]);
@@ -33,7 +42,6 @@ const Events = ({ eventData, location }) => {
startTime: currentEvent.endTime,
endTime: nextEvent.startTime,
title: 'Break / Team Time',
- location: 'N/A',
confirmed: true,
});
maxId++;
@@ -42,16 +50,17 @@ const Events = ({ eventData, location }) => {
if (eventData.length > 1) {
eventDataWithBreaks.push(eventData[eventData.length - 1]);
}
- console.log(eventDataWithBreaks);
+ // console.log(eventDataWithBreaks);
return (
{eventDataWithBreaks.map(item => (
))}
@@ -61,7 +70,6 @@ const Events = ({ eventData, location }) => {
Events.propTypes = {
eventData: PropTypes.array.isRequired,
- location: PropTypes.string.isRequired,
};
export default Events;
diff --git a/src/components/Events/PublishedScheduleTable.jsx b/src/components/Events/PublishedScheduleTable.jsx
index 4447f0c..6a72151 100644
--- a/src/components/Events/PublishedScheduleTable.jsx
+++ b/src/components/Events/PublishedScheduleTable.jsx
@@ -2,26 +2,42 @@ import { NPOBackend } from '../../utils/auth_utils.js';
import Events from './Events.jsx';
import EventInfo from './EventInfo.jsx';
import PropTypes from 'prop-types';
-
import { useEffect, useState } from 'react';
+import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Box, IconButton, useDisclosure } from '@chakra-ui/react';
+import AddDayModal from '../../pages/PublishedSchedule/AddDayModal.jsx'
+import { AddIcon } from '@chakra-ui/icons';
+import { useAuthContext } from '../../common/AuthContext.jsx';
+import AUTH_ROLES from '../../utils/auth_config.js';
+const { ADMIN_ROLE } = AUTH_ROLES.AUTH_ROLES;
-import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Box } from '@chakra-ui/react';
const PublishedScheduleTable = ({ season }) => {
+ const {currentUser} = useAuthContext();
+
const [eventsInDay, setEventsInDay] = useState([]);
const seasonType = season.split(' ')[0];
const seasonYear = season.split(' ')[1];
+ const [dataShouldRevalidate, setShouldDataRevalidate] = useState(false);
+ const { isOpen: isOpenDay, onOpen: onOpenDay, onClose: onCloseDay } = useDisclosure();
+
+ const renderTable = async () => {
+ const { data } = await NPOBackend.get(
+ `/published-schedule/season?season=${seasonType}&year=${seasonYear}`,
+ );
+ setEventsInDay(data);
+ };
useEffect(() => {
- const renderTable = async () => {
- const { data } = await NPOBackend.get(
- `/published-schedule/season?season=${seasonType}&year=${seasonYear}`,
- );
- setEventsInDay(data);
- };
renderTable();
}, [seasonType, seasonYear]);
+ useEffect(() => {
+ if (dataShouldRevalidate) {
+ renderTable();
+ setShouldDataRevalidate(false);
+ }
+ }, [dataShouldRevalidate])
+
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const formatDate = date => {
@@ -41,8 +57,29 @@ const PublishedScheduleTable = ({ season }) => {
return (
-
-
+ {currentUser.type === ADMIN_ROLE &&
+ }
+ >
+ Create
+
+ }
+
+
+
+
+
Info |
@@ -52,18 +89,20 @@ const PublishedScheduleTable = ({ season }) => {
{eventsInDay.map(item => (
-
+ |
|
-
+
|
))}
diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.jsx
index 038b201..8ab3a2c 100644
--- a/src/components/Navbar/Navbar.jsx
+++ b/src/components/Navbar/Navbar.jsx
@@ -1,12 +1,14 @@
import { NavLink } from 'react-router-dom';
-import { Flex, HStack, Link, Text, Image } from '@chakra-ui/react';
+import { Flex, Link, Text, Image, useDisclosure, IconButton, Spacer } from '@chakra-ui/react';
import { BellIcon } from '@chakra-ui/icons';
import PropTypes from 'prop-types';
import Logout from '../Authentication/Logout';
+import NotificationsDrawer from '../../pages/NotificationDrawer/NotificationDrawer';
const Navbar = ({ hasLoaded, isAdmin }) => {
- // console.log(hasLoaded, isAdmin);
+ const { isOpen, onOpen, onClose } = useDisclosure();
+
const makeNavTabs = (page, path) => {
const selectedTab = location.pathname == path;
return (
@@ -40,52 +42,37 @@ const Navbar = ({ hasLoaded, isAdmin }) => {
return (
-
-
-
- {makeNavTabs('Schedule', '/publishedSchedule')}
- {makeNavTabs('Catalog', '/catalog')}
- {makeNavTabs('Accounts', '/accounts')}
-
-
-
-
-
-
+
+ {makeNavTabs('Schedule', '/publishedSchedule')}
+ {makeNavTabs('Catalog', '/catalog')}
+ {makeNavTabs('Accounts', '/accounts')}
+
+
+ } mr={5}/>
+
+
+
)
}
- return (
+ return (
-
-
-
- {makeNavTabs('Schedule', '/publishedSchedule')}
-
-
-
-
-
+
+ {makeNavTabs('Schedule', '/publishedSchedule')}
+
+
);
};
diff --git a/src/components/Notifications/Notifications.jsx b/src/components/Notifications/Notifications.jsx
index 59668b8..b9f76cf 100644
--- a/src/components/Notifications/Notifications.jsx
+++ b/src/components/Notifications/Notifications.jsx
@@ -22,9 +22,7 @@ const Notifications = () => {
const approveAccount = async id => {
try {
- console.log('Approving', id);
await NPOBackend.put(`/users/approve/${id}`);
- console.log('Approved', id);
} catch (e) {
console.log(e);
}
@@ -32,9 +30,7 @@ const Notifications = () => {
const declineAccount = async id => {
try {
- console.log('Declining', id);
await NPOBackend.delete(`/users/${id}`);
- console.log('Declined', id);
} catch (e) {
console.log(e);
}
diff --git a/src/components/PaginationFooter/PaginationFooter.jsx b/src/components/PaginationFooter/PaginationFooter.jsx
new file mode 100644
index 0000000..d8ede78
--- /dev/null
+++ b/src/components/PaginationFooter/PaginationFooter.jsx
@@ -0,0 +1,120 @@
+import { Select, Flex, Box, HStack, Text } from '@chakra-ui/react';
+import {
+ Pagination,
+ usePagination,
+ PaginationNext,
+ PaginationPrevious,
+ PaginationContainer,
+ } from '@ajna/pagination';
+import { useState, useEffect } from 'react';
+import { PropTypes } from 'prop-types';
+import { NPOBackend } from '../../utils/auth_utils';
+
+const PaginationFooter = ({ setData, table, searchTerm, selectedFilters, isModified }) => {
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+ const [rowRangeString, setRowRangeString] = useState('');
+ const [dataCount, setDataCount] = useState(0);
+
+ const { currentPage, setCurrentPage, pagesCount } = usePagination({
+ pagesCount: Math.ceil(dataCount / rowsPerPage),
+ initialState: { currentPage: 1 },
+ });
+
+ // when the number of rows or the next page is clicked, get the desired data from the backend
+ useEffect(() => {
+ const refreshData = async () => {
+ const params = {
+ title: searchTerm,
+ ...selectedFilters,
+ limit: rowsPerPage,
+ page: currentPage
+ };
+ const { data } = await NPOBackend.get('/catalog', {
+ params: params
+ });
+
+ setData(data); // data on the page
+ setDataCount(data.length); // total number of data across all pages
+
+ const start = (currentPage - 1) * rowsPerPage + 1;
+ setRowRangeString(`${start} - ${start + data.length - 1}`); // range of data on the page
+ };
+ refreshData();
+ }, [currentPage, rowsPerPage, setData, table, searchTerm, selectedFilters, isModified]);
+
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [setCurrentPage, rowsPerPage, table]);
+
+
+ return (
+
+
+
+ Show rows per page
+
+
+
+
+
+
+ {rowRangeString}
+
+ of {dataCount}
+
+
+
+ ‹
+ ›
+
+
+
+
+
+ );
+ }
+ PaginationFooter.propTypes = {
+ setData: PropTypes.func,
+ table: PropTypes.string.isRequired,
+ data: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string,
+ }),
+ ),
+ searchTerm: PropTypes.string.isRequired,
+ selectedFilters: PropTypes.shape({
+ subject: PropTypes.string,
+ eventType: PropTypes.string,
+ season: PropTypes.string,
+ year: PropTypes.string,
+ }),
+ isModified: PropTypes.bool.isRequired,
+ };
+
+ PaginationFooter.defaultProps = {
+ data: [],
+ table: '',
+ setData: () => {},
+ };
+
+export default PaginationFooter;
+
+
+
diff --git a/src/components/Planner/PlannedEvent.js b/src/components/Planner/PlannedEvent.js
index c597ae4..e69d2be 100644
--- a/src/components/Planner/PlannedEvent.js
+++ b/src/components/Planner/PlannedEvent.js
@@ -7,23 +7,30 @@ export default class PlannedEvent {
name; // string
startTime; // number (minutes since midnight - e.g. 60 * 10 = 10AM)
endTime; // number (minutes since midnight - e.g. 60 * 10 = 10AM)
+ hostName;
isTentative; // boolean
- constructor(id, name, startTime, endTime, isTentative = false) {
+ constructor(id, name, startTime, endTime, hostName, isTentative = false) {
this.id = id;
this.name = name;
this.startTime = startTime;
this.endTime = endTime;
+ this.hostName = hostName;
this.isTentative = isTentative;
}
- calculateGridStyles() {
+ calculateGridStyles(addEvents) {
const earliestHourInMinutes = PlannedEvent.EARLIEST_HOUR * MINUTES_PER_HOUR;
const gridRowCell = Math.floor((this.startTime - earliestHourInMinutes) / MINUTES_PER_HOUR) + 1;
// TODO: increase column start to 3 if overlapping
- const gridColumnStart = 2;
+ let gridColumnStart = 2;
+ addEvents.forEach(({startTime, endTime}) => {
+ if (this.startTime < endTime && this.endTime > startTime) {
+ gridColumnStart = 3;
+ }
+ })
// Offset from top border: (e.g. 50% from top if start is at 4:30PM)
const offsetTop = (100 * (this.startTime % MINUTES_PER_HOUR)) / MINUTES_PER_HOUR;
@@ -39,3 +46,11 @@ export default class PlannedEvent {
};
}
}
+
+const convertTimeToMinutes = (timeString) => {
+ const [hours, minutes] = timeString.split(":").slice(0,2).map(Number);
+ const totalMinutes = hours*MINUTES_PER_HOUR + minutes;
+ return totalMinutes;
+}
+
+export { convertTimeToMinutes }
diff --git a/src/components/Planner/PlannerContext.jsx b/src/components/Planner/PlannerContext.jsx
index 640239a..024bb8a 100644
--- a/src/components/Planner/PlannerContext.jsx
+++ b/src/components/Planner/PlannerContext.jsx
@@ -7,13 +7,18 @@ const PlannerContext = createContext(undefined);
*/
// eslint-disable-next-line react/prop-types
-function PlannerContextProvider({ children }) {
+function PlannerContextProvider({ children, dayId }) {
const [plannedEvents, setPlannedEvents] = useState([]); // PlannedEvent[]
+ const [isEdit, setIsEdit] = useState(false);
+ const [currEvent, setCurrEvent] = useState({});
return (
{children}
diff --git a/src/components/Planner/PlannerEvents/AddEventOverlay.jsx b/src/components/Planner/PlannerEvents/AddEventOverlay.jsx
deleted file mode 100644
index d573fa1..0000000
--- a/src/components/Planner/PlannerEvents/AddEventOverlay.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Stack, Text, Button } from '@chakra-ui/react';
-import s from '../PlannerLayout.module.css';
-
-// eslint-disable-next-line react/prop-types
-const AddEventOverlay = ({ setOverlayIsVisible }) => {
- return (
-
-
- MM/DD/YYYY | Location
-
-
-
- Event Information
-
-
-
-
-
-
-
- );
-};
-
-export default AddEventOverlay;
diff --git a/src/components/Planner/PlannerEvents/PlannerEvents.jsx b/src/components/Planner/PlannerEvents/PlannerEvents.jsx
index f49d2ea..48fc1a4 100644
--- a/src/components/Planner/PlannerEvents/PlannerEvents.jsx
+++ b/src/components/Planner/PlannerEvents/PlannerEvents.jsx
@@ -1,157 +1,145 @@
+import { useState, useEffect, useContext } from 'react';
import s from '../PlannerLayout.module.css';
-import {
- TableContainer,
- Table,
- Thead,
- Tr,
- Th,
- Tbody,
- Text,
- Button,
- Input,
- InputGroup,
- InputLeftElement,
- Stack,
- Select,
-} from '@chakra-ui/react';
-import { Search2Icon, AddIcon } from '@chakra-ui/icons';
-import { useState } from 'react';
-import AddEventOverlay from './AddEventOverlay';
+import { Text, Button, Heading, Box, IconButton, HStack, Flex, useDisclosure } from '@chakra-ui/react';
+import { AddIcon, EditIcon } from '@chakra-ui/icons';
+import Catalog from '../../../pages/Catalog/Catalog';
+import PropTypes from 'prop-types';
+import { PlannerContext } from '../PlannerContext';
+import { NPOBackend } from '../../../utils/auth_utils';
+import AddEventToPublishedScheduleForm from '../../AddEventToPublishedScheduleForm/AddEventToPublishedScheduleForm';
+import AddDayModal from '../../../pages/PublishedSchedule/AddDayModal';
-const PlannerEvents = () => {
- const [overlayIsVisible, setOverlayIsVisible] = useState(false);
+const PlannerEvents = ({ onClose }) => {
+ const [isAddingEvent, setIsAddingEvent] = useState(false);
+ // const [existingEventData, setExistingEventData] = useState({});
+ const [dateHeader, setDateHeader] = useState('');
+ const [dayData, setDayData] = useState({});
+ const { isOpen: isOpenDay, onOpen: onOpenDay, onClose: onCloseDay } = useDisclosure();
+
+ const { plannedEventsContext, dayId, editContext, currEventContext } = useContext(PlannerContext);
+ const [isEdit, setIsEdit] = editContext;
+ const setCurrEvent = currEventContext[1]; // fix?
+ const [dataShouldRevalidate, setShouldDataRevalidate] = useState(false);
+ const plannedEvents = plannedEventsContext[0];
+
+ const getDayData = async () => {
+ try {
+ // console.log('getDayData');
+ const response = await NPOBackend.get(`/day/${dayId}`);
+ const responseData = response.data[0];
+ const [datePart] = responseData.eventDate.split('T');
+ const dateObj = new Date(responseData.eventDate);
+ // console.log(dateObj);
+ setDateHeader(dateObj.toLocaleDateString({ year: 'numeric', month: 'short', day: '2-digit' }));
+ setDayData({id: responseData.id, eventDate: datePart, location: responseData.location, details: responseData.notes});
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ useEffect(() => {
+ // console.log('fetch data first time?')
+ getDayData();
+ }, [dayId]);
+
+ useEffect(() => {
+ if (dataShouldRevalidate) {
+ // console.log('reset data');
+ getDayData();
+ setShouldDataRevalidate(false);
+ }
+ }, [dataShouldRevalidate])
+
+ const handleCreateNewEvent = () => {
+ setCurrEvent({});
+ togglePSForm();
+ }
+
+ const togglePSForm = () => {
+ if (isEdit) {
+ setIsEdit(false);
+ return;
+ }
+ setIsAddingEvent(!isAddingEvent);
+ };
+
+ const closeModal = async () => {
+ // delete day if empty
+ if (plannedEvents.length === 0) {
+ try {
+ console.log('deleting day!!')
+ await NPOBackend.delete(`/day/${dayId}`);
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ onClose();
+ }
return (
-
- {overlayIsVisible &&
}
+
+ {/* {overlayIsVisible &&
} */}
-
- Add Event to Day
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- EVENT |
- HOST |
- TAGS |
-
-
-
- {/* TODO: insert mapping of catalog/pub sched events here, table entry component yet to be made */}
-
-
-
-
-
-
-
+ Finish Day
+
+
+
+
);
};
+PlannerEvents.propTypes = {
+ onClose: PropTypes.func,
+};
+
export default PlannerEvents;
diff --git a/src/components/Planner/PlannerLayout.jsx b/src/components/Planner/PlannerLayout.jsx
index 2685b97..65c7e3d 100644
--- a/src/components/Planner/PlannerLayout.jsx
+++ b/src/components/Planner/PlannerLayout.jsx
@@ -2,16 +2,23 @@ import s from './PlannerLayout.module.css';
import PlannerTimeline from './PlannerTimeline/PlannerTimeline';
import PlannerEvents from './PlannerEvents/PlannerEvents';
import { PlannerContextProvider } from './PlannerContext';
+import PropTypes from 'prop-types';
+
+const PlannerLayout = ({ dayId, onClose }) => {
-const PlannerLayout = () => {
return (
-
+
-
+
);
};
+PlannerLayout.propTypes = {
+ dayId: PropTypes.number,
+ onClose: PropTypes.func,
+};
+
export default PlannerLayout;
diff --git a/src/components/Planner/PlannerLayout.module.css b/src/components/Planner/PlannerLayout.module.css
index 7fcd66f..0799c47 100644
--- a/src/components/Planner/PlannerLayout.module.css
+++ b/src/components/Planner/PlannerLayout.module.css
@@ -38,7 +38,8 @@
/* PLANNER TIMELINE */
#planner-timeline-container {
- background-color: var(--chakra-colors-blackAlpha-200);
+ margin-left: 1rem;
+ background-color: white;
width: clamp(250px, 30%, 600px);
}
@@ -65,17 +66,22 @@
.timeline-event-container {
background-color: var(--chakra-colors-gray-300);
height: 100%;
- border-radius: 6px;
+ border-radius: 7px;
padding: 0.25rem 0.5rem;
+ position: absolute;
+ width: 100%;
+ overflow: hidden;
}
/* PLANNER EVENTS */
#planner-events-container {
padding: 2rem;
- padding-right: max(5%, 2rem);
+ /* padding-right: max(5%, 2rem); */
flex-grow: 1;
- background-color: #f2f2f2;
+ background-color: #edf2f7;
position: relative;
+ max-height: 100vh;
+ overflow-y: auto;
}
#planner-browse {
diff --git a/src/components/Planner/PlannerModal.jsx b/src/components/Planner/PlannerModal.jsx
new file mode 100644
index 0000000..b7c8f53
--- /dev/null
+++ b/src/components/Planner/PlannerModal.jsx
@@ -0,0 +1,29 @@
+import {
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalBody,
+} from '@chakra-ui/react'
+import PropTypes from 'prop-types';
+import PlannerLayout from './PlannerLayout.jsx';
+
+const PlannerModal = ({isOpen, onClose, dayId}) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+PlannerModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ dayId: PropTypes.number.isRequired,
+};
+
+export default PlannerModal;
diff --git a/src/components/Planner/PlannerTimeline/PlannerTimeline.jsx b/src/components/Planner/PlannerTimeline/PlannerTimeline.jsx
index 92d9f67..6a4f659 100644
--- a/src/components/Planner/PlannerTimeline/PlannerTimeline.jsx
+++ b/src/components/Planner/PlannerTimeline/PlannerTimeline.jsx
@@ -1,46 +1,78 @@
import s from '../PlannerLayout.module.css';
-import { useMemo, useContext, useEffect } from 'react';
+import { useMemo, useContext, useEffect, useState } from 'react';
import { generateTimestamps, minutesInFormattedTime } from '../chrono';
-import { Badge, Text } from '@chakra-ui/react';
+import { Badge, Text, Box, IconButton, HStack, useDisclosure } from '@chakra-ui/react';
+import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
import { PlannerContext } from '../PlannerContext';
-import PlannedEvent from '../PlannedEvent';
-import { MINUTES_PER_HOUR } from '../chrono';
+import PlannedEvent, { convertTimeToMinutes } from '../PlannedEvent';
+import { NPOBackend } from '../../../utils/auth_utils';
+// import PropTypes from 'prop-types';
+import RemoveTimelineEventModal from '../RemoveTimelineEventModal';
const PlannerTimeline = () => {
- const { plannedEventsContext } = useContext(PlannerContext);
+ const { plannedEventsContext, dayId, currEventContext, editContext } = useContext(PlannerContext);
+ const { isOpen: isRemoveOpen, onOpen: onRemoveOpen, onClose: onRemoveClose } = useDisclosure();
const [plannedEvents, setPlannedEvents] = plannedEventsContext;
+ const [eventData, setCurrEvent] = currEventContext;
+ const [isEdit, setIsEdit] = editContext;
+ const [eventHover, setEventHover] = useState(-2);
+ const [deleteItemId, setDeleteItemId] = useState(-1);
+ //const [addedEvents, setAddedEvents] = useState([]);
+
+ const addedEvents = [];
+
+ const fetchEditData = async (id) => {
+ const publishedScheduleReponse = await NPOBackend.get(`/published-schedule/${id}`);
+ const responseData = publishedScheduleReponse.data[0];
+ if (responseData) {
+ setCurrEvent(responseData);
+ }
+ }
+
+ const startEditAndSetCurrEventId = (id) => {
+ // console.log('selected', id);
+ if (isEdit) {
+ // add back the original state of the currently edited event
+ const reAddedEvent = new PlannedEvent(
+ eventData.id,
+ eventData.title,
+ convertTimeToMinutes(eventData.startTime),
+ convertTimeToMinutes(eventData.endTime),
+ eventData.host,
+ !eventData.confirmed
+ )
+ setPlannedEvents([...plannedEvents.filter(e => e.id != -1), reAddedEvent]);
+ }
+ setIsEdit(true);
+ fetchEditData(id);
+ }
+
+ const fetchDayInfo = async (id) => {
+ const psEvents = await NPOBackend.get((`/published-schedule/dayId?dayId=${id}`));
+ return psEvents.data.data;
+ }
+
useEffect(() => {
- // TODO: receive planned events via props and translate them here
- setPlannedEvents([
- new PlannedEvent(
- 'sample-1',
- 'Sample Event 1',
- MINUTES_PER_HOUR * 9.5,
- MINUTES_PER_HOUR * 10.5,
- false,
- ),
- new PlannedEvent(
- 'sample-2',
- 'Sample Event 2',
- MINUTES_PER_HOUR * 11,
- MINUTES_PER_HOUR * 13,
- false,
- ),
- new PlannedEvent(
- 'sample-3',
- 'Sample Event 3',
- MINUTES_PER_HOUR * 13.5,
- MINUTES_PER_HOUR * 16,
- false,
- ),
- ]);
-
- // Sample Events:
- // sample-1: 9:30 - 10:30
- // sample-2: 11:00 - 1:00
- // sample-3: 13:30 - 16:00
- }, [setPlannedEvents]);
+ // console.log('updating timeline!');
+ updateTimeline();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const updateTimeline = async () => {
+ const psEvents = await fetchDayInfo(dayId);
+ // console.log(psEvents);
+ if (psEvents && psEvents[0].id) {
+ setPlannedEvents(psEvents.map((data) => new PlannedEvent(
+ data.id,
+ data.title,
+ convertTimeToMinutes(data.startTime),
+ convertTimeToMinutes(data.endTime),
+ data.host,
+ !data.confirmed,
+ )));
+ }
+ }
// Memoize function call for time labels to increase efficiency between component re-renders
const timelineBlocks = useMemo(() => {
@@ -65,30 +97,88 @@ const PlannerTimeline = () => {
);
}, []);
+ // console.log(plannedEvents);
+
return (
{timelineBlocks}
{plannedEvents.map(plannedEvent => {
- const { id, name, startTime, endTime } = plannedEvent;
- const gridStyles = plannedEvent.calculateGridStyles();
+ // console.log(plannedEvent);
+ const { id, name, startTime, endTime, hostName, isTentative } = plannedEvent;
+ const gridStyles = plannedEvent.calculateGridStyles(addedEvents);
+ addedEvents.push({startTime, endTime});
const formattedStartTime = minutesInFormattedTime(startTime);
const formattedEndTime = minutesInFormattedTime(endTime);
+ let border_color = '#2B93D1';
+ let background_color = '#BEE3F8';
+ let border_style = 'none none none solid';
+ const border_width = '2px 2px 2px 10px'
+
+ if (isTentative) {
+ border_color = '#F69052';
+ background_color = '#FEF1DC';
+ }
+
+ if (id == -1 && !isTentative) {
+ border_style = 'dashed dashed dashed solid';
+ background_color = '#eaf8ff';
+ } else if (id == -1 && isTentative) {
+ border_style = 'dashed dashed dashed solid';
+ background_color = '#fffaf0';
+ }
+
return (
-
-
- {name}
-
-
- {formattedStartTime} - {formattedEndTime}
-
-
+
setEventHover(id)}
+ onMouseLeave={() => setEventHover(-2)}
+ >
+
+
+
+ {name}
+
+
+ {formattedStartTime} - {formattedEndTime}
+
+
+ {hostName}
+
+
+ {id == eventHover &&
+
+ }
+ onClick={() => startEditAndSetCurrEventId(id)}
+ size="sm"
+ />
+ }
+ onClick={() => {
+ setDeleteItemId(id);
+ onRemoveOpen();
+ }}
+ size="sm"
+ />
+ {/* */}
+
+ }
+
+
);
})}
+
);
diff --git a/src/components/Planner/RemoveTimelineEventModal.jsx b/src/components/Planner/RemoveTimelineEventModal.jsx
new file mode 100644
index 0000000..16860b0
--- /dev/null
+++ b/src/components/Planner/RemoveTimelineEventModal.jsx
@@ -0,0 +1,72 @@
+import PropTypes from 'prop-types';
+import {
+ Button,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalOverlay,
+ ModalHeader,
+ ModalCloseButton,
+ ModalFooter,
+ useToast
+} from '@chakra-ui/react';
+import { NPOBackend } from '../../utils/auth_utils';
+import { useContext } from 'react';
+import { PlannerContext } from './PlannerContext';
+
+const RemoveTimelineEventModal = ({ isOpen, onClose, deleteItemId }) => {
+
+ const { plannedEventsContext } = useContext(PlannerContext);
+ const [plannedEvents, setPlannedEvents] = plannedEventsContext;
+
+ const toast = useToast();
+
+ const handleConfirmDelete = async idToDelete => {
+ try {
+ console.log('id to delete:', idToDelete);
+ toast.closeAll();
+ await NPOBackend.delete(`/published-schedule/${idToDelete}`);
+ setPlannedEvents(plannedEvents.filter(e => (e.id != -1 && e.id != idToDelete)));
+ toast({
+ title: 'Event Removed',
+ status: 'success',
+ variant: 'subtle',
+ position: 'top-right',
+ containerStyle: {
+ mt: '6rem',
+ },
+ duration: 3000,
+ isClosable: true,
+ });
+ // setDataShouldRevalidate(true);
+ onClose();
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ // console.log(deleteItemId);
+
+ return (
+
+
+
+ Remove Event?
+
+ Are you sure? You can't undo this action afterwards.
+
+
+
+
+
+
+ );
+};
+
+RemoveTimelineEventModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ deleteItemId: PropTypes.number.isRequired,
+};
+
+export default RemoveTimelineEventModal;
diff --git a/src/components/SearchAndFilter/SearchAndFilter.jsx b/src/components/SearchAndFilter/SearchAndFilter.jsx
new file mode 100644
index 0000000..040aaca
--- /dev/null
+++ b/src/components/SearchAndFilter/SearchAndFilter.jsx
@@ -0,0 +1,51 @@
+import { Input, Select, Flex } from '@chakra-ui/react';
+import PropTypes from 'prop-types';
+const subjectsOptions = ['life skills', 'science', 'technology', 'engineering', 'math', 'college readiness'];
+const eventOptions = ['guest speaker', 'study-trip', 'workshop', 'other'];
+const yearOptions = ['junior', 'senior', 'both'];
+const seasonOptions = ['fall', 'spring', 'summer'];
+
+const SearchAndFilter = ({ onSearch, onChange }) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+SearchAndFilter.propTypes = {
+ onSearch: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ };
+
+ export default SearchAndFilter;
diff --git a/src/pages/Catalog/Catalog.jsx b/src/pages/Catalog/Catalog.jsx
index 243a5a6..3a4517b 100644
--- a/src/pages/Catalog/Catalog.jsx
+++ b/src/pages/Catalog/Catalog.jsx
@@ -27,8 +27,10 @@ import {
subjectOptions,
eventOptions,
} from '../../components/Catalog/SearchFilter/filterOptions';
+import PropTypes from 'prop-types';
+import s from '../../components/Planner/PlannerLayout.module.css';
-export default function Catalog() {
+export default function Catalog({ onDayPlanner, addExistingEventFunc, setCurrEvent }) {
const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure();
// const {
// isOpen: isEditFormOpen,
@@ -45,7 +47,6 @@ export default function Catalog() {
const [dataShouldRevalidate, setDataShouldRevalidate] = useState(false);
const [totalRowCount, setTotalRowCount] = useState(0);
const [deleteItemId, setDeleteItemId] = useState(-1);
- // const [editData, setEditData] = useState({});
const [searchTerm, setSearchTerm] = useState('');
const { filters, clearFilters, filterValues } = useSearchFilters();
@@ -86,13 +87,17 @@ export default function Catalog() {
),
};
- const { data } = await NPOBackend.get('/catalog', {
- params: params,
- });
- const { count, events: tableData } = data;
+ try {
+ const { data } = await NPOBackend.get('/catalog', {
+ params: params,
+ });
+ const { count, events: tableData } = data;
- setTableData(tableData);
- setTotalRowCount(Number(count[0].count));
+ setTableData(tableData);
+ setTotalRowCount(Number(count[0].count));
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
}, [searchTerm, pageSize, currentPage, filterValues]);
// Fetch data on component mount and pagination update
@@ -115,26 +120,27 @@ export default function Catalog() {
return (
- }
- >
- Create
-
-
-
+ { !onDayPlanner &&
+ }
+ >
+ Create
+
+ }
+
- Event Catalog
+ {!onDayPlanner && 'Event Catalog'}
@@ -157,7 +163,7 @@ export default function Catalog() {
onChange={handleSearch}
/>
-
+
@@ -183,20 +189,29 @@ export default function Catalog() {
overflow="hidden"
borderWidth="1px"
borderRadius="12px"
+ maxH={onDayPlanner && '50vh'}
+ overflowY="auto"
+ className={s['gray-scrollbar-vertical']}
>
-
-
+ {tableData && (
+ <>
+
+
+ >
+ )}
);
}
+
+Catalog.propTypes = {
+ onDayPlanner: PropTypes.bool,
+ addExistingEventFunc: PropTypes.func,
+ setCurrEvent: PropTypes.func,
+};
+
+Catalog.defaultProps = {
+ onDayPlanner: false
+};
diff --git a/src/pages/NotificationDrawer/NotificationDrawer.jsx b/src/pages/NotificationDrawer/NotificationDrawer.jsx
new file mode 100644
index 0000000..23da9a3
--- /dev/null
+++ b/src/pages/NotificationDrawer/NotificationDrawer.jsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ Container,
+ Drawer,
+ DrawerBody,
+ DrawerHeader,
+ DrawerOverlay,
+ DrawerContent,
+ DrawerCloseButton,
+} from '@chakra-ui/react';
+import Notifications from '../../components/Notifications/Notifications';
+
+const NotificationsDrawer = ({isOpen, onClose}) => {
+ const buttonRef = React.useRef();
+
+ return (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+ );
+};
+
+NotificationsDrawer.propTypes = {
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func,
+};
+
+export default NotificationsDrawer;
diff --git a/src/pages/NotificationSandbox/NotificationSandbox.jsx b/src/pages/NotificationSandbox/NotificationSandbox.jsx
deleted file mode 100644
index a9dd6c9..0000000
--- a/src/pages/NotificationSandbox/NotificationSandbox.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import {
- Button,
- Container,
- Drawer,
- DrawerBody,
- DrawerHeader,
- DrawerOverlay,
- DrawerContent,
- DrawerCloseButton,
- useDisclosure,
-} from '@chakra-ui/react';
-import Notifications from '../../components/Notifications/Notifications';
-import { BellIcon } from '@chakra-ui/icons';
-
-const NotificationSandbox = () => {
- const { isOpen, onOpen, onClose } = useDisclosure();
- const buttonRef = React.useRef();
-
- return (
-
-
Notification Sandbox
- {/* TODO: Move Notification button and drawer to navbar once layout component is pushed */}
- <>
-
-
-
-
-
-
- Notifications
-
-
-
-
-
-
- >
-
- );
-};
-
-export default NotificationSandbox;
diff --git a/src/pages/Planner/Planner.jsx b/src/pages/Planner/Planner.jsx
index 6930af5..7e5ccd5 100644
--- a/src/pages/Planner/Planner.jsx
+++ b/src/pages/Planner/Planner.jsx
@@ -1,11 +1,16 @@
import PlannerLayout from '../../components/Planner/PlannerLayout';
+import PropTypes from 'prop-types';
-const Planner = () => {
+const Planner = ({ dayId }) => {
return (
<>
-
+
>
);
};
+Planner.propTypes = {
+ dayId: PropTypes.number,
+};
+
export default Planner;
diff --git a/src/pages/PublishedSchedule/AddDayModal.jsx b/src/pages/PublishedSchedule/AddDayModal.jsx
new file mode 100644
index 0000000..101619d
--- /dev/null
+++ b/src/pages/PublishedSchedule/AddDayModal.jsx
@@ -0,0 +1,56 @@
+import {
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalBody,
+ useDisclosure,
+} from '@chakra-ui/react'
+// import { AddIcon, EditIcon } from '@chakra-ui/icons';
+import AddDayForm from '../../components/AddDayForm/AddDayForm.jsx';
+import { useState } from 'react';
+import PlannerModal from '../../components/Planner/PlannerModal.jsx';
+import PropTypes from 'prop-types';
+
+const AddDayModal = ({ dayData, isOpenDay, onCloseDay, setShouldDataRevalidate}) => {
+ const { isOpen: isOpenPlanner, onOpen: onOpenPlanner, onClose: onClosePlanner } = useDisclosure();
+ const [dayId, setDayId] = useState(-1);
+
+ const handleOpenPlanner = () => {
+ onCloseDay(); // Close the first modal when opening the second modal
+ onOpenPlanner(); // Open the second modal
+ };
+
+ const handlePlannerClose = () => {
+ setShouldDataRevalidate(true);
+ onClosePlanner();
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {!dayData && }
+ >
+ );
+};
+
+AddDayModal.propTypes = {
+ isEdit: PropTypes.bool,
+ dayData: PropTypes.object,
+ isOpenDay: PropTypes.bool,
+ onCloseDay: PropTypes.func,
+ setShouldDataRevalidate: PropTypes.func,
+};export default AddDayModal;
diff --git a/src/pages/PublishedSchedule/PublishedSchedule.jsx b/src/pages/PublishedSchedule/PublishedSchedule.jsx
index a731542..839c550 100644
--- a/src/pages/PublishedSchedule/PublishedSchedule.jsx
+++ b/src/pages/PublishedSchedule/PublishedSchedule.jsx
@@ -19,30 +19,28 @@ const PublishedSchedule = () => {
let year = today.getFullYear();
let season;
- if (month >= 3 && month <= 5) {
+ if (month >= 1 && month <= 5) {
season = 'Spring';
- } else if (month >= 9 && month <= 11) {
+ } else if (month >= 9 && month <= 12) {
season = 'Fall';
} else if (month >= 6 && month <= 8) {
season = 'Summer';
- } else {
- season = 'Winter';
}
return season + ' ' + year.toString();
};
+ const curSeason = getTodaySeason();
+
useEffect(() => {
const renderTable = async () => {
const { data } = await NPOBackend.get('/published-schedule/all-seasons');
-
- setSelectedSeason(currentUser.type === USER_ROLE ? data[0] : ''); // We assume the current season is the first one in the list
-
- const index = data.indexOf(curSeason);
- if (index !== -1) {
- data.splice(index, 1);
+ if (data.indexOf(curSeason) == -1) {
+ data.unshift(curSeason);
}
- const seasonOrder = ['Summer', 'Fall', 'Winter', 'Spring'];
+ setSelectedSeason(currentUser.type === USER_ROLE ? curSeason : data[0]); // We assume the current season is the first one in the list
+
+ const seasonOrder = ['Fall', 'Summer', 'Spring'];
data.sort((a, b) => {
// Compare years first
if (a.split(' ')[1] !== b.split(' ')[1]) {
@@ -55,11 +53,9 @@ const PublishedSchedule = () => {
setAllSeasons(data);
};
- renderTable();
+ renderTable();
}, [currentUser]);
- const curSeason = getTodaySeason();
-
//update chakra table container accordingly
return (
@@ -75,15 +71,17 @@ const PublishedSchedule = () => {
{/* tables for each season */}
diff --git a/src/utils/auth_utils.js b/src/utils/auth_utils.js
index 68adf11..a0a3964 100644
--- a/src/utils/auth_utils.js
+++ b/src/utils/auth_utils.js
@@ -32,7 +32,6 @@ const firebaseConfig = {
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
-console.log({auth});
// TEMP: Make sure to remove
const NPOBackend = axios.create({
@@ -194,7 +193,7 @@ const logInWithEmailAndPassword = async (email, password, redirectPath, navigate
//if approved status is false, tell user to wait for approval
if (!user.data[0].approved) {
throw new Error('Your account is currently under review. Please wait for approval.');
- }
+ }
cookies.set(cookieKeys.ROLE, user.data[0].type, cookieConfig);
cookies.set(cookieKeys.APPROVED, user.data[0].approved, cookieConfig);