Skip to content

Commit

Permalink
Merge pull request #19 from scottylabs-labrador/frontend-calendar-modal
Browse files Browse the repository at this point in the history
Frontend calendar modal
  • Loading branch information
qianxuege authored Dec 2, 2024
2 parents 041ae63 + 929946a commit e3740fd
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 84 deletions.
5 changes: 3 additions & 2 deletions frontend/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ interface ButtonComponentProps {
textSize: string;
bgColor?: boolean;
buttonClicked?: boolean;
onClick?: () => void;
}

const Button: React.FC<ButtonComponentProps> = ({ content, clickStay, textSize, bgColor, buttonClicked }) => {
const Button: React.FC<ButtonComponentProps> = ({ content, clickStay, textSize, bgColor, buttonClicked, onClick }) => {

return (
<>
<button className={`rounded-full border border-teal px-4 py-2 flex items-center gap-1 hover:bg-teal hover:text-white ${textSize} ${
<button onClick={onClick} className={`rounded-full border border-teal px-4 py-2 flex items-center gap-1 hover:bg-teal hover:text-white ${textSize} ${
clickStay ? "bg-teal text-white" : ""} ${bgColor ? "bg-green text-white" : ""}`}>
{content}
</button>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/SavedSearches.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ const SavedSearchBtn: React.FC<ButtonComponentProps> = ({ content, clickStay, te

}

export {SavedSearchBtn};
export default SavedSearchBtn;
30 changes: 17 additions & 13 deletions frontend/src/components/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
// search input
import { SearchCard, SavedSearchBtn, SearchBar } from "./index";
import { SearchCard, SearchBar } from "./index";

import Fuse from 'fuse.js'
import { IoSearch, IoChevronBack, IoChevronForward } from "react-icons/io5";
import { RxCross1 } from "react-icons/rx";
Expand All @@ -17,8 +18,9 @@ import { Button } from "./Button";
import SearchContent from "./SearchContent";
import CategoryDropdown from "./CategoryDropdown";
import DatePickerSearch from "./DatePickerSearch";
import { AddFCEventProps, AddFCEventType } from "../types";
import { AddFCEventProps, AddFCEventType, RemoveFCEventType } from "../types";
import FullCalendar from "@fullcalendar/react";
import SavedSearchBtn from "./SavedSearches";


// https://plainenglish.io/blog/how-to-implement-a-search-bar-in-react-js
Expand All @@ -32,16 +34,21 @@ interface SearchComponentProps {
eventId: number;
setEventId: React.Dispatch<React.SetStateAction<number>>;
calendarRef: React.RefObject<FullCalendar>;
events:any[];
setEvents: React.Dispatch<React.SetStateAction<any[]>>;
handleRemoveFCEvent: RemoveFCEventType;
removeAllEvents: () => void;
}

const Search: React.FC<SearchComponentProps> = ({ page, showSearchBar,
handleSearchBarClick, handleAddFCEvent, eventId, setEventId, calendarRef, setEvents }) => {
handleSearchBarClick, handleAddFCEvent, eventId, setEventId, calendarRef, events, setEvents,
handleRemoveFCEvent, removeAllEvents }) => {
// Chang name to search bar
const [searchInput, setSearchInput] = useState("");

const [categoryName, setCategoryName] = useState<string[]>([]);
const [savedItems, setSavedItems] = useState<string[]>([]);
const [addedSearchCards, setAddedSearchCards] = useState([]);

// date picker
const [startDate, setStartDate] = useState<Dayjs | null>(dayjs());
Expand All @@ -51,9 +58,10 @@ const Search: React.FC<SearchComponentProps> = ({ page, showSearchBar,
useEffect(() => {
// Load the array from local storage when the component mounts
const storedItems = getArrayFromLocalStorage<string>('savedSearches');
// const storedEvents = getArrayFromLocalStorage<string>('savedEvents');
setSavedItems(storedItems);
// clearSavedItems();
// console.log(savedItems);
console.log(storedItems);
}, [searchInput]);


Expand Down Expand Up @@ -95,13 +103,7 @@ const Search: React.FC<SearchComponentProps> = ({ page, showSearchBar,
// console.log(savedItems);
};


// Function to handle when search bar is enterred
const handleSaveSearch = () => {
if (searchInput) {
setSearchInput('');
}
};


// search
const handleChangeSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -133,6 +135,8 @@ const Search: React.FC<SearchComponentProps> = ({ page, showSearchBar,
// console.log(`end: ${date}`);
};




return (
<div className="bg-[#F5F5F5] relative -top-1 w-full min-h-screen pl-8 pt-7 text-sans resize-x">
Expand Down Expand Up @@ -203,14 +207,14 @@ const Search: React.FC<SearchComponentProps> = ({ page, showSearchBar,
<div className="w-full mt-2 flex justify-between">
{/* <Dropdown/> */}
<Button content="Add All" clickStay={false} textSize="text-base"/>
<Button content="Reset CMUCal Events" clickStay={false} textSize="text-base"/>
<Button onClick={removeAllEvents} content="Reset CMUCal Events" clickStay={false} textSize="text-base"/>
{/* <Button content="Upload to GCal" bgColor={true} clickStay={false} textSize="text-base"/> */}
</div>

<div className="overflow-scroll" style={{height: '70vh'}}>
{ searchInput && <SearchContent searchInput={searchInput} page={page} categoryName={categoryName} startDate={startDate}
endDate={endDate} addToSavedItems={addSavedItem} handleAddFCEvent={handleAddFCEvent} eventId={eventId}
setEventId={setEventId} calendarRef={calendarRef} setEvents={setEvents}/>}
setEventId={setEventId} calendarRef={calendarRef} events={events} setEvents={setEvents} handleRemoveFCEvent={handleRemoveFCEvent}/>}
</div>

</div>
Expand Down
66 changes: 26 additions & 40 deletions frontend/src/components/SearchCard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { useEffect, useState, useContext } from "react";
import { IoLocationSharp, IoAdd } from "react-icons/io5";
import { FaRegClock, FaCheck } from "react-icons/fa";
import { AddFCEventType } from "../types";
import { AddFCEventType, RemoveFCEventType } from "../types";
import { getFormattedDateStr } from "../utils/DateManipulations";
import { handleEventRemoveFC } from "./calendar/event-utils";
import FullCalendar from "@fullcalendar/react";
import { getArrayFromLocalStorage } from "../utils/localStorageUtil";


type ButtonClickHandler = () => void;

interface SearchCardProps {
cardId: number;
eventName: string;
orgName: string;
startDate: string;
Expand All @@ -19,16 +20,15 @@ interface SearchCardProps {
location: string;
addToSavedItems: ButtonClickHandler;
handleAddFCEvent: AddFCEventType;
eventId: number;
setEventId: React.Dispatch<React.SetStateAction<number>>;
handleRemoveFCEvent: RemoveFCEventType;
calendarRef: React.RefObject<FullCalendar>;
// events: any[];
events: any[];
setEvents: React.Dispatch<React.SetStateAction<any[]>>;
// eventCategory: string;
// eventSubcategory: string;
isSelected: boolean;
}

const SearchCard: React.FC<SearchCardProps> = ({
cardId,
eventName,
orgName,
startDate,
Expand All @@ -38,56 +38,42 @@ const SearchCard: React.FC<SearchCardProps> = ({
location,
addToSavedItems,
handleAddFCEvent,
eventId,
setEventId,
handleRemoveFCEvent,
calendarRef,
// events,
setEvents
// eventCategory,
// eventSubcategory,
events,
setEvents,
isSelected
}) => {
const [buttonClicked, setButtonClicked] = useState(false);
const [cardClicked, setCardClicked] = useState(false);
const [currEventId, setCurrEventId] = useState<number>(-1);

// const [savedItems, setSavedItems] = useState<string[]>([]);

const handleBtnClick = () => {

const handleAddToCalendar = () => {
if (buttonClicked) {
setButtonClicked((prevClicked) => !prevClicked);
setEvents((prevItems) => prevItems.filter((item) => item.id !== `${currEventId}`));
handleEventRemoveFC(calendarRef, `${currEventId}`);
console.log('delete',currEventId);

let index:number = events.findIndex(obj => (obj.id == `${cardId}`));
if (index != -1) {
// included in the events list, so need to remove
handleRemoveFCEvent({calendarRef: calendarRef, eventId: `${cardId}`});
} else {
setButtonClicked((prevClicked) => !prevClicked);
// add event
addToSavedItems();
const startDateTime = getFormattedDateStr(startDate, startTime);
// console.log(currEventId);

if (endDate) {
const endDateTime = getFormattedDateStr(endDate, endTime);
handleAddFCEvent({id:`${eventId}`, title:eventName, start:startDateTime, end: endDateTime, allDay: !endTime });
handleAddFCEvent({id:`${cardId}`, searchCardId: cardId, title:eventName, start:startDateTime, end: endDateTime, allDay: !endTime });
} else {
const endDateTime = getFormattedDateStr(startDate, endTime);
// console.log(endDateTime);
handleAddFCEvent({id:`${eventId}`, title:eventName, start:startDateTime, end: endDateTime, allDay: !endTime });
handleAddFCEvent({id:`${cardId}`, searchCardId: cardId, title:eventName, start:startDateTime, end: endDateTime, allDay: !endTime });
}

setCurrEventId(_=>eventId);
setEventId(prev => prev+1);
// console.log(eventId);


}

};



const handleCardClicked = () => {
setCardClicked(!cardClicked);
}
// {eventName}

return (
<div className="bg-white w-full my-3.5 px-6 py-4 rounded-lg flex items-center align-stretch relative">
<div className="flex-1" onClick={handleCardClicked}>
Expand All @@ -113,17 +99,17 @@ const SearchCard: React.FC<SearchCardProps> = ({
<div className="flex-shrink-0 absolute right-3">
<button
className={`rounded-full border border-teal px-4 py-2 flex items-center gap-1 ${
buttonClicked ? "bg-teal text-white" : ""
isSelected ? "bg-teal text-white" : ""
}`}
style={{ minWidth: "7rem" }}
onClick={handleAddToCalendar}
onClick={handleBtnClick}
>
{buttonClicked ? (
{isSelected ? (
<FaCheck className="h-4 w-4 text-white mr-2" />
) : (
<IoAdd className="h-6 w-6 text-white mr-2 stroke-teal" />
)}
{buttonClicked ? "Added" : "Add"}
{isSelected ? "Added" : "Add"}
</button>
</div>
</div>
Expand Down
30 changes: 23 additions & 7 deletions frontend/src/components/SearchContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import PTData from "../mock_data/peer_tutoring.json";
import SIData from "../mock_data/si.json";
import ClubsData from "../mock_data/tartanconnect.json";
import CareerData from "../mock_data/handshake.json";
import { categoryListAcademics, categoryShorthandAcademics, categoryListClubs, categoryListCareer, Clubs_type, Career_type, AddFCEventProps, AddFCEventType } from "../types";
import { categoryListAcademics, categoryShorthandAcademics, categoryListClubs, categoryListCareer, Clubs_type, Career_type, AddFCEventProps, AddFCEventType, RemoveFCEventType } from "../types";
import FullCalendar from "@fullcalendar/react";


Expand All @@ -25,12 +25,23 @@ interface SearchContentProps {
eventId: number;
setEventId: React.Dispatch<React.SetStateAction<number>>;
calendarRef: React.RefObject<FullCalendar>;
events:any[];
setEvents: React.Dispatch<React.SetStateAction<any[]>>;
handleRemoveFCEvent: RemoveFCEventType;

}

const SearchContent: React.FC<SearchContentProps> = ({ searchInput, page, categoryName, startDate, endDate, addToSavedItems, handleAddFCEvent, eventId, setEventId, calendarRef, setEvents }) => {
const props = { searchInput, page, categoryName, startDate, endDate, addToSavedItems, handleAddFCEvent, eventId, setEventId, calendarRef, setEvents };
const SearchContent: React.FC<SearchContentProps> = ({ searchInput, page, categoryName, startDate, endDate, addToSavedItems, handleAddFCEvent, eventId, setEventId, calendarRef, events, setEvents, handleRemoveFCEvent }) => {
const props = { searchInput, page, categoryName, startDate, endDate, addToSavedItems, handleAddFCEvent, eventId, setEventId, calendarRef, events, setEvents, handleRemoveFCEvent };
// const [selectedSearchCards, setSelectedSearchCards] = useState<number[]>([]);

// useEffect(()=> {
// let cardNums = [];
// for (let i = 0; i<events.length; i++) {
// cardNums.push(events[i].searchCardId);
// }
// setSelectedSearchCards(cardNums);
// },[events])

const getCategoryData = (currPage:string) => {
// this is where we apply the category filters
Expand Down Expand Up @@ -128,6 +139,7 @@ const SearchContent: React.FC<SearchContentProps> = ({ searchInput, page, catego
return (
<SearchCard
key={index}
cardId={index}
eventName={`${result.item.resource_type} for ${result.item.course_id} ${result.item.course_name}`}
orgName={`Staff`}
startDate={getWeekday(result.item.events[i].weekday) || `null`}
Expand All @@ -137,10 +149,12 @@ const SearchContent: React.FC<SearchContentProps> = ({ searchInput, page, catego
location={result.item.events[i].location}
addToSavedItems={addToSavedItems}
handleAddFCEvent={handleAddFCEvent}
eventId={eventId}
setEventId={setEventId}
calendarRef={calendarRef}
events={events}
setEvents={setEvents}
handleRemoveFCEvent={handleRemoveFCEvent}
isSelected = {events.findIndex(obj => (obj.id == `${index}`)) != -1}

/>
)
}
Expand All @@ -155,6 +169,7 @@ const SearchContent: React.FC<SearchContentProps> = ({ searchInput, page, catego
return (
<SearchCard
key={index}
cardId={index}
eventName={`${result.item.event_name}`}
orgName={`${result.item.event_host}`}
startDate={getWeekday(result.item.events[i].weekday) || `null`}
Expand All @@ -164,10 +179,11 @@ const SearchContent: React.FC<SearchContentProps> = ({ searchInput, page, catego
location={result.item.events[i].location}
addToSavedItems={addToSavedItems}
handleAddFCEvent={handleAddFCEvent}
eventId={eventId}
setEventId={setEventId}
handleRemoveFCEvent={handleRemoveFCEvent}
calendarRef={calendarRef}
events={events}
setEvents={setEvents}
isSelected = {events.findIndex(obj => (obj.id == eventId)) != -1}
/>
)
}
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from '@fullcalendar/interaction';
import { INITIAL_EVENTS, createEventId, handleEventRemoveFC } from './event-utils';
import { INITIAL_EVENTS, createEventId } from './event-utils';
import { DateSelectArg, EventApi, EventClickArg, EventContentArg } from "fullcalendar";

// for dropdown
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import { RemoveFCEventType } from "../../types";

// https://codesandbox.io/s/react-fullcalendar-custom-buttons-and-header-toolbar-rmvtl?file=/src/App.js

Expand All @@ -28,9 +29,10 @@ interface CalendarProps {
showSearchBar: boolean;
events: any[];
calendarRef: RefObject<FullCalendar>;
handleRemoveFCEvent: RemoveFCEventType;
}

export default function Calendar({showSearchBar, events, calendarRef}:CalendarProps) {
export default function Calendar({showSearchBar, events, calendarRef, handleRemoveFCEvent}:CalendarProps) {

const [weekendsVisible, setWeekendsVisible] = useState<boolean>(true);
const [currentEvents, setCurrentEvents] = useState<EventApi[]>();
Expand Down Expand Up @@ -72,7 +74,8 @@ export default function Calendar({showSearchBar, events, calendarRef}:CalendarPr

const handleEventClick = (clickInfo: EventClickArg) => {
if (confirm(`Are you sure you want to delete the event '${clickInfo.event.title}'`)) {
handleEventRemoveFC(calendarRef, clickInfo.event.id);
handleRemoveFCEvent({calendarRef: calendarRef, eventId: clickInfo.event.id});

clickInfo.event.remove();
}
}
Expand Down
10 changes: 1 addition & 9 deletions frontend/src/components/calendar/event-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,4 @@ export function createEventId() {
return String(eventGuid++)
}

export const handleEventRemoveFC = (calendarRef: React.RefObject<FullCalendar>, eventId: string) => {
if (calendarRef.current) {
const calendarApi = calendarRef.current.getApi();
const event = calendarApi.getEventById(eventId);
if (event) {
event.remove();
}
}
}

Loading

0 comments on commit e3740fd

Please sign in to comment.