diff --git a/frontend/src/Login.tsx b/frontend/src/Login.tsx index 6a85663..931085f 100644 --- a/frontend/src/Login.tsx +++ b/frontend/src/Login.tsx @@ -21,7 +21,7 @@ const Login = observer(() => { const success = await login(username, password); if (success) { - navigate("/grant-info"); + navigate("/main/all-grants"); } else { setFailure(true); } diff --git a/frontend/src/main-page/MainPage.tsx b/frontend/src/main-page/MainPage.tsx index 84bf706..5074dfc 100644 --- a/frontend/src/main-page/MainPage.tsx +++ b/frontend/src/main-page/MainPage.tsx @@ -14,7 +14,7 @@ function MainPage() {
-
+
} /> diff --git a/frontend/src/main-page/grants/GrantPage.tsx b/frontend/src/main-page/grants/GrantPage.tsx index e6e287a..2ec013c 100644 --- a/frontend/src/main-page/grants/GrantPage.tsx +++ b/frontend/src/main-page/grants/GrantPage.tsx @@ -1,12 +1,12 @@ -import "./styles/GrantPage.css"; -import GrantList from "./grant-list/GrantList.tsx"; +// import GrantList from "./grant-list/GrantList.tsx"; import AddGrantButton from "./new-grant/AddGrant.tsx"; import GrantSearch from "./filter-bar/GrantSearch.tsx"; import NewGrantModal from "./new-grant/NewGrantModal.tsx"; import { useEffect, useState } from "react"; import { Grant } from "../../../../middle-layer/types/Grant.ts"; -import FilterBar from "./filter-bar/FilterBar.tsx"; +// import FilterBar from "./filter-bar/FilterBar.tsx"; +import GrantItem from "./grant-view/GrantView.tsx"; import { useAuthContext } from "../../context/auth/authContext"; import { updateEndDateFilter, @@ -14,28 +14,89 @@ import { updateSearchQuery, updateStartDateFilter, updateYearFilter, + fetchAllGrants, } from "../../external/bcanSatchel/actions.ts"; import { toJS } from "mobx"; - -import { fetchGrants } from "./filter-bar/processGrantData.ts"; +import { observer } from "mobx-react-lite"; +import { ProcessGrantData } from "./filter-bar/processGrantData.ts"; import { UserStatus } from "../../../../middle-layer/types/UserStatus.ts"; import { Navigate } from "react-router-dom"; import BellButton from "../navbar/Bell.tsx"; +import GrantCard from "./grant-list/GrantCard.tsx"; +import { api } from "../../api.ts"; +// still needed potentially? interface GrantPageProps { showOnlyMyGrants?: boolean; //if true, filters grants by user email } -function GrantPage({ showOnlyMyGrants = false }: GrantPageProps) { +function GrantPage({}: GrantPageProps) { const [showNewGrantModal, setShowNewGrantModal] = useState(false); const [wasGrantSubmitted, setWasGrantSubmitted] = useState(false); - const [selectedGrant, setSelectedGrant] = useState(null); + + // Use ProcessGrantData reactively to get filtered grants + const { grants } = ProcessGrantData(); + const [curGrant, setCurGrant] = useState(null); + + // Set the first grant when grants are loaded (only on initial mount) + useEffect(() => { + if (grants.length > 0 && curGrant === null) { + setCurGrant(grants[0]); + } + }, [grants]); + + // If the NewGrantModal has been closed and a new grant submitted (or existing grant edited), + // refetch the grants list and update the current grant to reflect any changes + // SHOULD BE CHANGED TO ALSO ACCOMODATE DELETIONS (CURRENTLY ONLY UPDATES IF GRANT WAS CREATED/EDITED, NOT DELETED) + useEffect(() => { + if (!wasGrantSubmitted || !curGrant) return; + + const updateGrant = async () => { + try { + const response = await api(`/grant/${curGrant.grantId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (response.ok) { + const updatedGrant = await response.json(); + setCurGrant(updatedGrant); + console.log("✅ Grant refreshed:", updatedGrant); + } else { + console.error("❌ Failed to fetch updated grant"); + } + } catch (err) { + console.error("Error fetching updated grant:", err); + } + }; + + const updateGrants = async () => { + try { + const response = await api("/grant"); + if (!response.ok) { + throw new Error(`HTTP Error, Status: ${response.status}`); + } + const updatedGrants: Grant[] = await response.json(); + fetchAllGrants(updatedGrants); + console.log("✅ Grants list refreshed"); + } catch (error) { + console.error("Error fetching grants:", error); + } + }; + + updateGrants(); + updateGrant(); + setWasGrantSubmitted(false); + }, [wasGrantSubmitted]); + const [openModal, setOpenModal] = useState(false); const { user } = useAuthContext(); //gets current logged in user const userObj = toJS(user); - const currentUserEmail = userObj?.email || ""; //safe fallback + // const currentUserEmail = userObj?.email || ""; //safe fallback console.log("Current logged-in user:", userObj); // reset filters on initial render @@ -47,26 +108,43 @@ function GrantPage({ showOnlyMyGrants = false }: GrantPageProps) { updateSearchQuery(""); }, []); - useEffect(() => { - if (!showNewGrantModal && wasGrantSubmitted) { - fetchGrants(); - setWasGrantSubmitted(false); - console.log("Use effect called in GrantPage"); - } - }, [showNewGrantModal, wasGrantSubmitted]); - return user ? ( user?.position !== UserStatus.Inactive ? ( -
-
-
- +
+
+ +
+ +
+ + FILTERS GO HERE + setShowNewGrantModal(true)} /> -
- +
+ +
+
+ {grants.map((grant) => ( + setCurGrant(grant)} + /> + ))} +
+
+ {curGrant ? ( + + ) : ( +
+ No grants found. +
+ )}
-
+ + {/*
@@ -74,15 +152,14 @@ function GrantPage({ showOnlyMyGrants = false }: GrantPageProps) {
setSelectedGrant(null)} currentUserEmail={currentUserEmail} showOnlyMyGrants={showOnlyMyGrants} />
-
+
*/}
{showNewGrantModal && ( { to={item.linkTo ? item.linkTo : "#"} >
diff --git a/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx b/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx index 87b2e1f..283c7e0 100644 --- a/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx +++ b/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx @@ -50,7 +50,7 @@ function GrantSearch() { }} /> void; +} + +const GrantCard: React.FC = ({ grant, isSelected, onClick }) => { + + const formattedDate = new Date(grant.application_deadline).toLocaleDateString('en-US', { + month: 'numeric', + day: 'numeric', + year: '2-digit', + }); + + return ( +
+ {/* Colored left border indicator + Grant Name + Amount*/} +
+
+ {/* Eligibility Badge - Shows on hover over the line */} +
+ {grant.does_bcan_qualify ? 'Eligible' : 'Not Eligible'} +
+
+
+
+ {grant.organization} +
+
+ ${grant.amount} +
+
+
+ + {/* Card content */} +
+ + Due: {formattedDate} + + +
+
+ ); +}; + +export default GrantCard; diff --git a/frontend/src/main-page/grants/grant-list/StatusIndicator.tsx b/frontend/src/main-page/grants/grant-list/StatusIndicator.tsx index 884176a..258ff43 100644 --- a/frontend/src/main-page/grants/grant-list/StatusIndicator.tsx +++ b/frontend/src/main-page/grants/grant-list/StatusIndicator.tsx @@ -17,7 +17,7 @@ const StatusIndicator: React.FC = ({ curStatus }) => { return (
{labelText} diff --git a/frontend/src/main-page/grants/grant-view/GrantView.tsx b/frontend/src/main-page/grants/grant-view/GrantView.tsx index 534f01b..9ae50f7 100644 --- a/frontend/src/main-page/grants/grant-view/GrantView.tsx +++ b/frontend/src/main-page/grants/grant-view/GrantView.tsx @@ -1,6 +1,5 @@ -import React, { useEffect, useState, useLayoutEffect } from "react"; +import React, { useState, useLayoutEffect, useEffect } from "react"; import { Grant } from "../../../../../middle-layer/types/Grant"; -import { api } from "../../../api"; import { observer } from "mobx-react-lite"; import StatusIndicator from "../../grants/grant-list/StatusIndicator"; import { @@ -19,9 +18,10 @@ interface GrantItemProps { } const GrantItem: React.FC = observer(({ grant }) => { - const [curGrant, setCurGrant] = useState(grant); - const [wasGrantSubmitted, setWasGrantSubmitted] = useState(false); - + + useEffect(() => { + }, [grant]); + const useTruncatedElement = ({ ref, }: { @@ -38,7 +38,7 @@ const GrantItem: React.FC = observer(({ grant }) => { } else { setIsTruncated(false); } - }, [ref]); + }, [ref, grant.description]); const toggleIsShowingMore = () => setIsShowingMore((prev) => !prev); @@ -54,36 +54,6 @@ const GrantItem: React.FC = observer(({ grant }) => { ref, }); - // If the NewGrantModal has been closed and a new grant submitted (or existing grant edited), - // fetch the grant at this index so that all new changes are immediately reflected - useEffect(() => { - const updateGrant = async () => { - if (wasGrantSubmitted) { - try { - const response = await api(`/grant/${grant.grantId}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - - if (response.ok) { - const updatedGrant = await response.json(); - setCurGrant(updatedGrant); - console.log("✅ Grant refreshed:", updatedGrant); - } else { - console.error("❌ Failed to fetch updated grant"); - } - } catch (err) { - console.error("Error fetching updated grant:", err); - } - setWasGrantSubmitted(false); - } - }; - - updateGrant(); - }, [wasGrantSubmitted]); - function formatDate(isoString: string): string { if (!isoString) return "N/A"; const date = new Date(isoString); @@ -104,8 +74,8 @@ const GrantItem: React.FC = observer(({ grant }) => {
{/* Left side */}
-

{curGrant.organization}

- +

{grant.organization}

+
{/* Right side */}
@@ -134,7 +104,7 @@ const GrantItem: React.FC = observer(({ grant }) => { className={` ${!isShowingMore && "line-clamp-3"}`} onClick={toggleIsShowingMore} > - {curGrant?.description || "N/A"} + {grant?.description || "N/A"}

{isTruncated && ( +