From 2125b34f312659055f8a8a89bab5cffdce5d2f3b Mon Sep 17 00:00:00 2001 From: zhoum84 Date: Sun, 14 Jan 2024 19:47:16 -0600 Subject: [PATCH 1/2] fetch all, filter, sort --- client/src/components/cards/session-card.tsx | 176 ++++++++++++++++++ .../components/toolbars/session-toolbar.tsx | 56 ++++-- client/src/pages/sessions/add-session.tsx | 130 +++++++++++++ client/src/pages/sessions/sessions.tsx | 169 ++++++++++++++++- 4 files changed, 513 insertions(+), 18 deletions(-) create mode 100644 client/src/components/cards/session-card.tsx create mode 100644 client/src/pages/sessions/add-session.tsx diff --git a/client/src/components/cards/session-card.tsx b/client/src/components/cards/session-card.tsx new file mode 100644 index 0000000..556a987 --- /dev/null +++ b/client/src/components/cards/session-card.tsx @@ -0,0 +1,176 @@ +import { BellIcon, ChevronDownIcon } from "@radix-ui/react-icons"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import { Badge } from "@/components/ui/badge"; +import type { Session } from "@/pages/sessions/sessions"; +import { Button } from "@/components/ui/button"; +import { Icons } from "../ui/icons"; +import { useAuth } from "@/contexts/AuthContext"; +import { useNavigate } from "react-router-dom"; +import { type Staff } from "@/contexts/AuthContext"; + +const SessionCard = ({ session }: { session: Session }) => { + const navigate = useNavigate(); + const { mongoUser, refresh, setRefresh } = useAuth(); + + // const getLoanStatus = () => { + // if (!beneficiary.loan) return "No Loan"; + + // return beneficiary.loan?.loanStatus; + // }; + + const formatDate = (date: Date) => { + const options = { year: "numeric", month: "long", day: "numeric" }; + return new Date(date).toLocaleDateString([], options); + }; + + const compareDate = (date: Date) => { + const now = new Date(); + const formattedDate = new Date(date); + + //This is a check for same day, because now.getTime() does not equal formattedDate.getTime() + if ( + now.getFullYear() === formattedDate.getFullYear() && + now.getMonth() === formattedDate.getMonth() && + now.getDay() === formattedDate.getDay() + ) + return "Today"; + if (Date.now() < formattedDate.getTime()) return "Upcoming"; + return "Complete"; + }; + + const handleGoToSession = (e: React.MouseEvent) => { + e.stopPropagation(); + navigate(`./${session._id}`); + }; + + const handleGoToSessionEdit = (e: React.MouseEvent) => { + e.stopPropagation(); + navigate(`./${session._id}?f=1`); + }; + + // const handleBookmarkBeneficiary = async (e: React.MouseEvent) => { + // e.stopPropagation(); + // console.log("bookmarking beneficiary"); + // let newBookmarks; + // if (mongoUser?.bookmarkedBeneficiaries?.includes(beneficiary._id ?? "")) { + // // We need to remove bookmark + // newBookmarks = mongoUser?.bookmarkedBeneficiaries?.filter( + // (bookmark) => bookmark !== beneficiary._id, + // ); + // } else { + // // We need to add bookmark + // newBookmarks = mongoUser?.bookmarkedBeneficiaries?.concat( + // beneficiary._id ?? "", + // ); + // } + // try { + // await fetch(`http://localhost:3001/user/edit?_id=${mongoUser?._id}`, { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ + // bookmarkedBeneficiaries: newBookmarks, + // }), + // }).then((res) => res.json() as unknown as Staff); + // setRefresh(!refresh); + // } catch { + // console.log("error bookmarking beneficiary"); + // } + // }; + + return ( + navigate(`./${session._id}`)} + > + +
+ + {formatDate(session?.sessionDate)} + + + ({session?.region}) + +
+
+ + + + + + Quick Actions + + {/* handleBookmarkBeneficiary(e)} + > + Bookmark User + */} + handleGoToSessionEdit(e)} + > + Edit Session + + View Session + + handleGoToSession(e)}> + View Profile + + + +
+
+ +
+ + + {compareDate(session?.sessionDate)} + +
+ Expected: {session?.expectedAttendance.length} +
+ {/* {beneficiary?.phoneNumber?.length ?? 0 > 0 ? ( +
#: {beneficiary?.phoneNumber}
+ ) : null} */} +
+
+
+ ); +}; + +export default SessionCard; diff --git a/client/src/components/toolbars/session-toolbar.tsx b/client/src/components/toolbars/session-toolbar.tsx index 3705887..7169a6d 100644 --- a/client/src/components/toolbars/session-toolbar.tsx +++ b/client/src/components/toolbars/session-toolbar.tsx @@ -2,36 +2,43 @@ import { Button } from "@/components/ui/button"; import { Combobox } from "@/components/combobox"; import { Icons } from "../ui/icons"; import { Input } from "@/components/ui/input"; +import { DoubleArrowUpIcon, DoubleArrowDownIcon } from "@radix-ui/react-icons"; + import React from "react"; +import { type SetURLSearchParams } from "react-router-dom"; const sessionStatus = [ { - value: "pending", + value: "1", label: "Completed", }, { - value: "good", + value: "2", label: "Coming Up", }, { - value: "bad", + value: "3", label: "Happening Soon", }, { - value: "s", + value: "4", label: "Has Missing Attendees", }, ]; const sortBy = [ { - value: "jd", + value: "1", label: "Meeting Date", }, { - value: "init-la", + value: "2", label: "Expected Attendence", }, + { + value: "3", + label: "Region", + }, ]; const BeneficiaryToolbar = ({ @@ -41,13 +48,17 @@ const BeneficiaryToolbar = ({ setStatus, sort, setSort, + sortDirection, + setSortDirection, }: { - query: string; - setQuery: React.Dispatch>; + query: string | null; + setQuery: SetURLSearchParams; status: string; setStatus: React.Dispatch>; sort: string; setSort: React.Dispatch>; + sortDirection: string; + setSortDirection: React.Dispatch>; }) => { return (
@@ -55,8 +66,8 @@ const BeneficiaryToolbar = ({ setQuery(e.target.value)} + value={query ?? ""} + onChange={(e) => setQuery({ f: e.target.value })} /> - {query !== "" || status !== "" || sort !== "" ? ( + {sort !== "" ? ( + sortDirection === "Ascending" ? ( + + ) : ( + + ) + ) : ( + <> + )} + {!!query || !!status || !!sort ? ( + + + + + ); +} diff --git a/client/src/pages/sessions/sessions.tsx b/client/src/pages/sessions/sessions.tsx index ac471d1..6ba7a65 100644 --- a/client/src/pages/sessions/sessions.tsx +++ b/client/src/pages/sessions/sessions.tsx @@ -1,14 +1,148 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; +import { useSearchParams } from "react-router-dom"; import { DashboardHeader } from "@/components/header"; import { DashboardShell } from "@/components/shell"; import { ItemCreateButton } from "@/components/create-item-button"; +import SessionCard from "@/components/cards/session-card"; import SessionToolbar from "@/components/toolbars/session-toolbar"; +import { AddSession } from "./add-session"; -const Beneficiaries = () => { - const [query, setQuery] = useState(""); +export interface Session { + _id?: string; + sessionDate?: Date; + region?: string; + staff?: string[]; + archived?: boolean; + __V?: number; + actualAttendance?: string[]; + expectedAttendance?: string[]; +} + +const Sessions = () => { + const [query, setQuery] = useSearchParams(); const [status, setStatus] = useState(""); const [sort, setSort] = useState(""); + const [sortDirection, setSortDirection] = useState("Ascending"); + const [notifyNew, setNotifyNew] = useState(false); + const [sessions, setSessions] = useState([]); + + // I've been trying to have it sort by date by default, but I can't figure it out. + // There's also a bug where reseting when *only* the Sort By is changed will not work + // This bug (is it even a bug?) is also present in the beneficiaries page too + useEffect(() => { + if (!sort) return; + + // direction is connected to the double arrow button in the toolbar. + // it controls direction of the sort + const direction = sortDirection === "Ascending" ? 1 : -1; + switch (sort) { + case "1": + console.log("sort by Date"); + const dateSort = [...sessions]; + dateSort.sort((a, b) => { + if (a.sessionDate && b.sessionDate) { + return ( + direction * + (new Date(a.sessionDate).getTime() - + new Date(b.sessionDate).getTime()) + ); + } + return 0; + }); + setSessions(dateSort); + break; + case "2": + console.log("sort by attendance"); + const expectedSort = [...sessions]; + expectedSort.sort((a, b) => { + if (a.expectedAttendance && b.expectedAttendance) { + return ( + direction * + (a.expectedAttendance.length - b.expectedAttendance.length) + ); + } + return 0; + }); + setSessions(expectedSort); + break; + case "3": + const regionSort = [...sessions]; + regionSort.sort((a, b) => { + if (a.region && b.region) { + return direction * a.region.localeCompare(b.region); + } + return 0; + }); + setSessions(regionSort); + break; + case "4": + // TODO + } + }, [sort, sortDirection]); + + useEffect(() => { + const getSessions = async () => { + try { + const data: Session[] = await fetch( + "http://localhost:3001/session/sessions", + { + headers: { + "Content-Type": "application/json", + }, + }, + ).then((res: Response) => res.json() as unknown as Session[]); + //console.log(data); + // TODO: decide what the initial sort should be + setSessions(data); + } catch (error) { + console.error(error); + } + }; + void getSessions(); + }, [notifyNew]); + + // Status is checked via dates. + // There isn't a status parameter in the Session object. + const handleFilters = (s: Session) => { + if (!status) return true; + + switch (status) { + case "1": + if (s.sessionDate) return compareDate(s.sessionDate) === "Complete"; + case "2": + if (s.sessionDate) return compareDate(s.sessionDate) === "Upcoming"; + case "3": + if (s.sessionDate) return compareDate(s.sessionDate) === "Today"; + case "4": + if (s.expectedAttendance && s.actualAttendance) + return s.actualAttendance.length !== s.expectedAttendance.length; + + // TODO: add rest of cases + } + }; + + // I have this function defined twice: here and session-card.tsx + // Please fix if necessary + const compareDate = (date: Date) => { + const now = new Date(); + // formattedDate is necessary + const formattedDate = new Date(date); + //This is a check for same day, because now.getTime() does not equal formattedDate.getTime() + if ( + now.getFullYear() === formattedDate.getFullYear() && + now.getMonth() === formattedDate.getMonth() && + now.getDay() === formattedDate.getDay() + ) + return "Today"; + if (Date.now() < formattedDate.getTime()) return "Upcoming"; + return "Complete"; + }; + + // const formatDate = (date: Date) => { + // const options = { year: "numeric", month: "long", day: "numeric" }; + // return new Date(date).toLocaleDateString([], options); + // }; return ( @@ -16,19 +150,40 @@ const Beneficiaries = () => { heading="Sessions" text="View and manage your session data." > - + -
sessions
+ {/* I don't get the get("f") stuff so please change if it is unnecessary */} +
+ {sessions + .filter((ses) => { + if (!query.get("f")) return true; + return ( + // Can't figure how to search for a date. That should be added. + ses.region + ?.toLowerCase() + .includes(query.get("f")?.toLowerCase() ?? "") ?? + ses._id + ?.toLowerCase() + .includes(query.get("f")?.toLowerCase() ?? "") + ); + }) + .filter((ses) => handleFilters(ses)) + .map((ses, i) => { + return ; + })} +
); }; -export default Beneficiaries; +export default Sessions; From ca2dafeff67886fd98ccfa86874f405396d388dc Mon Sep 17 00:00:00 2001 From: zhoum84 Date: Sun, 21 Jan 2024 14:20:15 -0600 Subject: [PATCH 2/2] add session editing --- client/src/pages/sessions/add-session.tsx | 24 +++++++++++------------ client/src/pages/sessions/sessions.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/src/pages/sessions/add-session.tsx b/client/src/pages/sessions/add-session.tsx index 63d1f2c..3347349 100644 --- a/client/src/pages/sessions/add-session.tsx +++ b/client/src/pages/sessions/add-session.tsx @@ -22,16 +22,16 @@ export function AddSession({ notify: boolean; setNotify: React.Dispatch>; }) { - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); + const [location, setLocation] = useState(""); + const [date, setDate] = useState(""); const [phoneNumber, setPhoneNumber] = useState(); const [currentSavings, setCurrentSavings] = useState(""); const [currentSpending, setCurrentSpending] = useState(""); const handleAddSession = async () => { const data = { - firstName, - lastName, + location, + date, phoneNumber, currentSavings, currentSpending, @@ -64,23 +64,23 @@ export function AddSession({
setFirstName(e.target.value)} + id="location" + value={location} + onChange={(e) => setLocation(e.target.value)} className="col-span-3" />
setLastName(e.target.value)} + id="date" + value={date} + onChange={(e) => setDate(e.target.value)} className="col-span-3" />
diff --git a/client/src/pages/sessions/sessions.tsx b/client/src/pages/sessions/sessions.tsx index 6ba7a65..99ef3e7 100644 --- a/client/src/pages/sessions/sessions.tsx +++ b/client/src/pages/sessions/sessions.tsx @@ -162,7 +162,7 @@ const Sessions = () => { sortDirection={sortDirection} setSortDirection={setSortDirection} /> - {/* I don't get the get("f") stuff so please change if it is unnecessary */} + {/* I don't get the get("f") stuff so please change if it is unnecessary. Also right now it only searches by location. */}
{sessions .filter((ses) => {