diff --git a/src/components/CurrentHourIndicator/CurrentHourIndicator.module.css b/src/components/CurrentHourIndicator/CurrentHourIndicator.module.css new file mode 100644 index 0000000000..d2c250035a --- /dev/null +++ b/src/components/CurrentHourIndicator/CurrentHourIndicator.module.css @@ -0,0 +1,19 @@ +.round { + background-color: red; + border-radius: 100%; + width: 15px; + height: 15px; +} +.line { + width: 100%; + height: 1px; + background-color: red; + margin: auto; +} +.container { + position: relative; + display: flex; + flex-direction: row; + top: -8px; + left: -9px; +} diff --git a/src/components/CurrentHourIndicator/CurrentHourIndicator.test.tsx b/src/components/CurrentHourIndicator/CurrentHourIndicator.test.tsx new file mode 100644 index 0000000000..084817dc0d --- /dev/null +++ b/src/components/CurrentHourIndicator/CurrentHourIndicator.test.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import CurrentHourIndicator from './CurrentHourIndicator'; + +describe('Testing Current Hour Indicator', () => { + test('Component Should be rendered properly', async () => { + const { getByTestId } = render(); + expect(getByTestId('container')).toBeInTheDocument(); + }); +}); diff --git a/src/components/CurrentHourIndicator/CurrentHourIndicator.tsx b/src/components/CurrentHourIndicator/CurrentHourIndicator.tsx new file mode 100644 index 0000000000..9e4a85454a --- /dev/null +++ b/src/components/CurrentHourIndicator/CurrentHourIndicator.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import styles from './CurrentHourIndicator.module.css'; + +const CurrentHourIndicator = (): JSX.Element => { + return ( +
+
+
+
+ ); +}; + +export default CurrentHourIndicator; diff --git a/src/components/EventCalendar/EventCalendar.module.css b/src/components/EventCalendar/EventCalendar.module.css index 53f02847eb..1229e44a45 100644 --- a/src/components/EventCalendar/EventCalendar.module.css +++ b/src/components/EventCalendar/EventCalendar.module.css @@ -38,6 +38,47 @@ grid-template-columns: repeat(7, minmax(0, 1fr)); grid-template-rows: repeat(6, 1fr); } +.calendar_hour_text_container { + display: flex; + flex-direction: row; + align-items: flex-end; + border-right: 1px solid #8d8d8d55; + width: 40px; +} +.calendar_hour_text { + top: -10px; + left: -5px; + position: relative; + color: #707070; + font-size: 12px; +} +.calendar_timezone_text { + top: -10px; + left: -11px; + position: relative; + color: #707070; + font-size: 9px; +} +.calendar_hour_block { + display: flex; + flex-direction: row; + border-bottom: 1px solid #8d8d8d55; + position: relative; + height: 50px; + border-bottom-right-radius: 5px; +} +.event_list_parent { + position: relative; + width: 100%; +} +.event_list_parent_current { + background-color: #def6e1; + position: relative; + width: 100%; +} +.dummyWidth { + width: 1px; +} .day { background-color: #ffffff; padding-left: 0.3rem; @@ -47,6 +88,7 @@ color: #4b4b4b; font-weight: 600; height: 8rem; + position: relative; } .day__outside { background-color: #eeeded; @@ -75,6 +117,11 @@ margin-left: 20px; border: none; } +.selectType { + padding: 16px 10px 16px 10px; + border-radius: 10px; + margin: 0px 5px 0px 5px; +} .btn__more { border: 0px; font-size: 14px; @@ -84,6 +131,7 @@ transition: all 200ms; position: relative; display: block; + margin: 2px; } .btn__more:hover { color: #3ce080; @@ -92,12 +140,21 @@ .expand_event_list { display: block; } - +.list_container { + padding: 5px; + width: fit-content; + display: flex; + flex-direction: row; +} +.event_list_hour { + display: flex; + flex-direction: row; +} .expand_list_container { width: 200px; max-height: 250px; z-index: 10; - position: relative; + position: absolute; left: auto; right: auto; overflow: auto; @@ -105,6 +162,10 @@ background-color: rgb(241, 241, 241); border: 1px solid rgb(201, 201, 201); border-radius: 5px; + margin: 5px; +} +.flex_grow { + flex-grow: 1; } @media only screen and (max-width: 700px) { diff --git a/src/components/EventCalendar/EventCalendar.test.tsx b/src/components/EventCalendar/EventCalendar.test.tsx index bd242ea179..a7b966a519 100644 --- a/src/components/EventCalendar/EventCalendar.test.tsx +++ b/src/components/EventCalendar/EventCalendar.test.tsx @@ -3,6 +3,7 @@ import Calendar from './EventCalendar'; import { render, screen, fireEvent, act } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; +import { debug } from 'jest-preview'; import { DELETE_EVENT_MUTATION, @@ -135,10 +136,10 @@ describe('Calendar', () => { ); - const prevButton = screen.getByTestId('prevmonth'); + const prevButton = screen.getByTestId('prevmonthordate'); fireEvent.click(prevButton); //testing next month button - const nextButton = screen.getByTestId('nextmonth'); + const nextButton = screen.getByTestId('nextmonthordate'); fireEvent.click(nextButton); //Testing year change for (let index = 0; index < 13; index++) { @@ -148,6 +149,34 @@ describe('Calendar', () => { fireEvent.click(prevButton); } }); + it('Should show prev and next date on clicking < & > buttons in the day view', async () => { + render( + + + + + + ); + await act(async () => { + fireEvent.click(screen.getByText('Month')); + }); + await act(async () => { + fireEvent.click(screen.getByText('Day')); + }); + //testing previous date button + const prevButton = screen.getByTestId('prevmonthordate'); + fireEvent.click(prevButton); + //testing next date button + const nextButton = screen.getByTestId('nextmonthordate'); + fireEvent.click(nextButton); + //Testing year change and month change + for (let index = 0; index < 366; index++) { + fireEvent.click(prevButton); + } + for (let index = 0; index < 732; index++) { + fireEvent.click(nextButton); + } + }); it('Should render eventlistcard of current day event', () => { const currentDayEventMock = [ { @@ -203,15 +232,29 @@ describe('Calendar', () => { ); //Changing the month - const prevButton = screen.getByTestId('prevmonth'); + const prevButton = screen.getByTestId('prevmonthordate'); fireEvent.click(prevButton); // Clicking today button - const todayButton = screen.getByTestId('nextmonth'); + const todayButton = screen.getByTestId('today'); fireEvent.click(todayButton); // const todayCell = screen.getByText(new Date().getDate().toString()); // expect(todayCell).toHaveClass(styles.day__today); }); + it('Should open the day view calendar', async () => { + await act(async () => { + render(); + }); + expect(screen.getByText('Month')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText('Month')); + }); + expect(screen.getByText('Day')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText('Day')); + }); + expect(screen.getByText('12 AM')).toBeInTheDocument(); + }); it('Should expand and contract when clicked on View all and View less button', () => { const multipleEventData = [ { @@ -272,7 +315,230 @@ describe('Calendar', () => { fireEvent.click(lessButton); expect(screen.queryByText('Event 3')).not.toBeInTheDocument(); }); + it('Should Expand and contract when clicked on view all and view less in day view', async () => { + const multipleEventData = [ + { + _id: '1', + title: 'Event 1', + description: 'This is event 1', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: undefined, + endTime: undefined, + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '2', + title: 'Event 2', + description: 'This is event 2', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: undefined, + endTime: undefined, + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '3', + title: 'Event 3', + description: 'This is event 3', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '14:00', + endTime: '16:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '4', + title: 'Event 4', + description: 'This is event 4', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '14:00', + endTime: '16:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '5', + title: 'Event 5', + description: 'This is event 5', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '17:00', + endTime: '19:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + ]; + render( + + + + + + ); + expect(screen.getByText('Month')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText('Month')); + }); + expect(screen.getByText('Day')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText('Day')); + }); + const moreButtons = screen.getAllByText('View all'); + moreButtons.forEach((moreButton) => { + fireEvent.click(moreButton); + expect(screen.getByText('View less')).toBeInTheDocument(); + const lessButton = screen.getByText('View less'); + fireEvent.click(lessButton); + expect(screen.queryByText('View less')).not.toBeInTheDocument(); + }); + }); + it('Should check without any all day events', async () => { + const multipleEventData = [ + { + _id: '1', + title: 'Event 1', + description: 'This is event 1', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '17:00', + endTime: '19:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + ]; + render( + + + + + + ); + expect(screen.getByText('Month')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText('Month')); + }); + expect(screen.getByText('Day')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText('Day')); + }); + expect(screen.getByText('Event 1')).toBeInTheDocument(); + debug(); + }); + it('Should handle window resize in day view', async () => { + const multipleEventData = [ + { + _id: '1', + title: 'Event 1', + description: 'This is event 1', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: undefined, + endTime: undefined, + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '2', + title: 'Event 2', + description: 'This is event 2', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: undefined, + endTime: undefined, + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '3', + title: 'Event 3', + description: 'This is event 3', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '14:00', + endTime: '16:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '4', + title: 'Event 4', + description: 'This is event 4', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '14:00', + endTime: '16:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: '5', + title: 'Event 5', + description: 'This is event 5', + startDate: new Date().toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0], + location: 'Los Angeles', + startTime: '17:00', + endTime: '19:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + ]; + render( + + + + + + ); + await act(async () => { + fireEvent.click(screen.getByText('Month')); + }); + await act(async () => { + fireEvent.click(screen.getByText('Day')); + }); + await act(async () => { + window.innerWidth = 500; + window.dispatchEvent(new Event('resize')); + }); + }); test('Handles window resize', () => { render( diff --git a/src/components/EventCalendar/EventCalendar.tsx b/src/components/EventCalendar/EventCalendar.tsx index 154f173de8..8e79e9099e 100644 --- a/src/components/EventCalendar/EventCalendar.tsx +++ b/src/components/EventCalendar/EventCalendar.tsx @@ -4,6 +4,8 @@ import Button from 'react-bootstrap/Button'; import React, { useState, useEffect } from 'react'; import styles from './EventCalendar.module.css'; import { ChevronLeft, ChevronRight } from '@mui/icons-material'; +import { Dropdown } from 'react-bootstrap'; +import CurrentHourIndicator from 'components/CurrentHourIndicator/CurrentHourIndicator'; interface InterfaceEvent { _id: string; @@ -12,8 +14,8 @@ interface InterfaceEvent { startDate: string; endDate: string; location: string; - startTime: string; - endTime: string; + startTime: string | undefined; + endTime: string | undefined; allDay: boolean; recurring: boolean; registrants?: InterfaceIEventAttendees[]; @@ -39,6 +41,11 @@ enum Role { SUPERADMIN = 'SUPERADMIN', ADMIN = 'ADMIN', } + +export enum ViewType { + DAY = 'Day', + MONTH = 'Month', +} interface InterfaceIEventAttendees { userId: string; user?: string; @@ -71,13 +78,41 @@ const Calendar: React.FC = ({ 'November', 'December', ]; + const hours = [ + '12 AM', + '01 AM', + '02 AM', + '03 AM', + '04 AM', + '05 AM', + '06 AM', + '07 AM', + '08 AM', + '09 AM', + '10 AM', + '11 AM', + '12 PM', + '01 PM', + '02 PM', + '03 PM', + '04 PM', + '05 PM', + '06 PM', + '07 PM', + '08 PM', + '09 PM', + '10 PM', + '11 PM', + ]; const today = new Date(); + const [currentDate, setCurrentDate] = useState(today.getDate()); const [currentMonth, setCurrentMonth] = useState(today.getMonth()); const [currentYear, setCurrentYear] = useState(today.getFullYear()); const [events, setEvents] = useState(null); const [expanded, setExpanded] = useState(-1); const [windowWidth, setWindowWidth] = useState(window.screen.width); + const [viewType, setViewType] = useState(ViewType.MONTH); useEffect(() => { function handleResize(): void { @@ -129,6 +164,10 @@ const Calendar: React.FC = ({ setEvents(data); }, [eventData, orgData, userRole, userId]); + const handleChangeView = (item: any): void => { + setViewType(item); + }; + const handlePrevMonth = (): void => { if (currentMonth === 0) { setCurrentMonth(11); @@ -146,10 +185,234 @@ const Calendar: React.FC = ({ setCurrentMonth(currentMonth + 1); } }; + + const handlePrevDate = (): void => { + if (currentDate > 1) { + setCurrentDate(currentDate - 1); + } else { + if (currentMonth > 0) { + const lastDayOfPrevMonth = new Date( + currentYear, + currentMonth, + 0 + ).getDate(); + setCurrentDate(lastDayOfPrevMonth); + setCurrentMonth(currentMonth - 1); + } else { + setCurrentDate(31); + setCurrentMonth(11); + setCurrentYear(currentYear - 1); + } + } + }; + + const handleNextDate = (): void => { + const lastDayOfCurrentMonth = new Date( + currentYear, + currentMonth - 1, + 0 + ).getDate(); + if (currentDate < lastDayOfCurrentMonth) { + setCurrentDate(currentDate + 1); + } else { + if (currentMonth < 12) { + setCurrentDate(1); + setCurrentMonth(currentMonth + 1); + } else { + setCurrentDate(1); + setCurrentMonth(1); + setCurrentYear(currentYear + 1); + } + } + }; + const handleTodayButton = (): void => { setCurrentYear(today.getFullYear()); setCurrentMonth(today.getMonth()); + setCurrentDate(today.getDate()); }; + + const timezoneString = `UTC${ + new Date().getTimezoneOffset() > 0 ? '-' : '+' + }${String(Math.floor(Math.abs(new Date().getTimezoneOffset()) / 60)).padStart( + 2, + '0' + )}:${String(Math.abs(new Date().getTimezoneOffset()) % 60).padStart(2, '0')}`; + + const renderHours = (): JSX.Element => { + const toggleExpand = (index: number): void => { + if (expanded === index) { + setExpanded(-1); + } else { + setExpanded(index); + } + }; + + const allDayEventsList: any = events + ?.filter((datas) => { + const currDate = new Date(currentYear, currentMonth, currentDate); + if ( + datas.startTime == undefined && + datas.startDate == dayjs(currDate).format('YYYY-MM-DD') + ) { + return datas; + } + }) + .map((datas: InterfaceEvent) => { + return ( + + ); + }); + + return ( + <> +
+
+

{timezoneString}

+
+
+
0 + ? styles.event_list_parent_current + : styles.event_list_parent + } + > +
+
+ {expanded === -100 + ? allDayEventsList + : allDayEventsList?.slice(0, 1)} +
+ {(allDayEventsList?.length > 2 || + (windowWidth <= 700 && allDayEventsList?.length > 0)) && ( + + )} +
+
+
+ {hours.map((hour, index) => { + const timeEventsList: any = events + ?.filter((datas) => { + const currDate = new Date(currentYear, currentMonth, currentDate); + + if ( + datas.startTime?.slice(0, 2) == (index % 24).toString() && + datas.startDate == dayjs(currDate).format('YYYY-MM-DD') + ) { + return datas; + } + }) + .map((datas: InterfaceEvent) => { + return ( + + ); + }); + + return ( +
+
+

{`${hour}`}

+
+
+
0 + ? styles.event_list_parent_current + : styles.event_list_parent + } + > + {index % 24 == new Date().getHours() && + new Date().getDate() == currentDate && ( + + )} +
+
+ {expanded === index + ? timeEventsList + : timeEventsList?.slice(0, 1)} +
+ {(timeEventsList?.length > 1 || + (windowWidth <= 700 && timeEventsList?.length > 0)) && ( + + )} +
+
+
+ ); + })} + + ); + }; + const renderDays = (): JSX.Element[] => { const monthStart = new Date(currentYear, currentMonth, 1); const monthEnd = new Date(currentYear, currentMonth + 1, 0); @@ -259,39 +522,71 @@ const Calendar: React.FC = ({
+
+ {viewType == ViewType.DAY ? `${currentDate}` : ``}{' '} {months[currentMonth]} {currentYear}
-
+
+
+ + + {viewType || ViewType.MONTH} + + + + {ViewType.MONTH} + + + {ViewType.DAY} + + + +
- -
- {weekdays.map((weekday, index) => ( -
- {weekday} +
+ {viewType == ViewType.MONTH ? ( +
+
+ {weekdays.map((weekday, index) => ( +
+ {weekday} +
+ ))} +
+
{renderDays()}
- ))} + ) : ( +
{renderHours()}
+ )}
-
{renderDays()}
); }; diff --git a/src/components/EventListCard/EventListCard.tsx b/src/components/EventListCard/EventListCard.tsx index f316cc344b..1920701c82 100644 --- a/src/components/EventListCard/EventListCard.tsx +++ b/src/components/EventListCard/EventListCard.tsx @@ -22,8 +22,8 @@ interface InterfaceEventListCardProps { eventDescription: string; regDate: string; regEndDate: string; - startTime: string; - endTime: string; + startTime: string | undefined; + endTime: string | undefined; allDay: boolean; recurring: boolean; isPublic: boolean; diff --git a/src/components/UserPortal/EventCard/EventCard.test.tsx b/src/components/UserPortal/EventCard/EventCard.test.tsx index a21dc3e7be..e300469f87 100644 --- a/src/components/UserPortal/EventCard/EventCard.test.tsx +++ b/src/components/UserPortal/EventCard/EventCard.test.tsx @@ -11,6 +11,7 @@ import { Provider } from 'react-redux'; import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import userEvent from '@testing-library/user-event'; +import { debug } from 'jest-preview'; const MOCKS = [ { @@ -46,8 +47,8 @@ describe('Testing Event Card In User portal', () => { endDate: '2023-04-15', isRegisterable: true, isPublic: true, - endTime: '19:49:12Z', - startTime: '17:49:12Z', + endTime: '19:49:12', + startTime: '17:49:12', recurring: false, allDay: true, creator: { @@ -75,6 +76,7 @@ describe('Testing Event Card In User portal', () => { ); + debug(); await waitFor(() => expect(queryByText('Test Event')).toBeInTheDocument()); await waitFor(() => expect(queryByText('This is a test event')).toBeInTheDocument()