diff --git a/frontend/src/main-page/dashboard/CsvExportButton.tsx b/frontend/src/main-page/dashboard/CsvExportButton.tsx index cb8d0b2..650883d 100644 --- a/frontend/src/main-page/dashboard/CsvExportButton.tsx +++ b/frontend/src/main-page/dashboard/CsvExportButton.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { downloadCsv, CsvColumn } from "../../utils/csvUtils"; import { Grant } from "../../../../middle-layer/types/Grant"; -import { useProcessGrantData } from "../../main-page/grants/filter-bar/processGrantData"; +import { ProcessGrantData } from "../../main-page/grants/filter-bar/processGrantData"; import { observer } from "mobx-react-lite"; import "../grants/styles/GrantButton.css"; import { getAppStore } from "../../external/bcanSatchel/store"; @@ -77,7 +77,7 @@ const columns: CsvColumn[] = [ const CsvExportButton: React.FC = observer(() => { const { yearFilter } = getAppStore(); const [isProcessing, setIsProcessing] = useState(false); - const { grants } = useProcessGrantData(); + const { grants } = ProcessGrantData(); const onClickDownload = async () => { setIsProcessing(true); diff --git a/frontend/src/main-page/grants/GrantPage.tsx b/frontend/src/main-page/grants/GrantPage.tsx index 64f24a5..c443193 100644 --- a/frontend/src/main-page/grants/GrantPage.tsx +++ b/frontend/src/main-page/grants/GrantPage.tsx @@ -26,7 +26,7 @@ function GrantPage() {
- + setShowNewGrantModal(true)} />
diff --git a/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx b/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx index 5bd6300..0110a53 100644 --- a/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx +++ b/frontend/src/main-page/grants/filter-bar/GrantSearch.tsx @@ -1,39 +1,42 @@ import { IoIosSearch } from "react-icons/io"; -import { useEffect, useState } from "react"; +import { + // useEffect, + useState } from "react"; import Fuse from "fuse.js"; import { updateSearchQuery } from "../../../external/bcanSatchel/actions"; import { Grant } from "../../../../../middle-layer/types/Grant"; -import { api } from "../../../api"; +// import { api } from "../../../api"; import { Input } from "@chakra-ui/react"; -function GrantSearch({ onGrantSelect }: any) { +function GrantSearch() { const [userInput, setUserInput] = useState(""); - const [grants, setGrants] = useState([]); - const [showDropdown, setShowDropdown] = useState(false); - const [dropdownGrants, setDropdownGrants] = useState([]); + // @ts-ignore + const [grants, _setGrants] = useState([]); + // const [showDropdown, setShowDropdown] = useState(false); + // const [dropdownGrants, setDropdownGrants] = useState([]); - useEffect(() => { - fetchGrants(); - document.addEventListener("click", handleClickOutside); - return () => { - document.removeEventListener("click", handleClickOutside); - }; - }, []); + // useEffect(() => { + // fetchGrants(); + // document.addEventListener("click", handleClickOutside); + // return () => { + // document.removeEventListener("click", handleClickOutside); + // }; + // }, []); - const fetchGrants = async () => { - try { - const response = await api(`/grant`, { method: "GET" }); - const data: Grant[] = await response.json(); - const formattedData: Grant[] = data.map((grant: any) => ({ - ...grant, - organization_name: grant.organization || "Unknown Organization", - })); - setGrants(formattedData); - } catch (error) { - console.error("Error fetching grants:", error); - } - }; + // const fetchGrants = async () => { + // try { + // const response = await api(`/grant`, { method: "GET" }); + // const data: Grant[] = await response.json(); + // const formattedData: Grant[] = data.map((grant: any) => ({ + // ...grant, + // organization_name: grant.organization || "Unknown Organization", + // })); + // setGrants(formattedData); + // } catch (error) { + // console.error("Error fetching grants:", error); + // } + // }; const handleInputChange = (e: React.ChangeEvent) => { setUserInput(e.target.value); @@ -42,8 +45,8 @@ function GrantSearch({ onGrantSelect }: any) { const performSearch = (query: string) => { if (!query) { - setDropdownGrants([]); - setShowDropdown(false); + // setDropdownGrants([]); + // setShowDropdown(false); updateSearchQuery(""); return; } @@ -51,29 +54,30 @@ function GrantSearch({ onGrantSelect }: any) { keys: ["organization_name"], threshold: 0.3, }); - const results = fuse.search(query).map((res) => res.item); + // const results = + fuse.search(query).map((res) => res.item); updateSearchQuery(query); - setDropdownGrants(results.slice(0, 5)); - setShowDropdown(true); + // setDropdownGrants(results.slice(0, 5)); + // setShowDropdown(true); }; - const handleSelectGrant = (selectedGrant: Grant) => { - setUserInput(selectedGrant.organization); - updateSearchQuery(selectedGrant.organization); - setShowDropdown(false); - onGrantSelect?.(selectedGrant); - }; + // const handleSelectGrant = (selectedGrant: Grant) => { + // setUserInput(selectedGrant.organization); + // updateSearchQuery(selectedGrant.organization); + // // setShowDropdown(false); + // onGrantSelect?.(selectedGrant); + // }; - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as HTMLElement; - if ( - !target.closest(".search-container") && - !target.closest(".dropdown-container") - ) { - setShowDropdown(false); - } - }; + // const handleClickOutside = (event: MouseEvent) => { + // const target = event.target as HTMLElement; + // // if ( + // // !target.closest(".search-container") && + // // !target.closest(".dropdown-container") + // // ) { + // // setShowDropdown(false); + // // } + // }; return (
@@ -101,17 +105,17 @@ function GrantSearch({ onGrantSelect }: any) { className="search-input" onChange={handleInputChange} value={userInput} - onFocus={() => setShowDropdown(dropdownGrants.length > 0)} + // onFocus={() => setShowDropdown(dropdownGrants.length > 0)} style={{ paddingLeft: "2rem" }} // make room for the icon onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); - setShowDropdown(false); + // setShowDropdown(false); } }} /> - {showDropdown && ( + {/* {showDropdown && (
{dropdownGrants.length > 0 ? ( dropdownGrants.map((grant, index) => ( @@ -127,7 +131,7 @@ function GrantSearch({ onGrantSelect }: any) {
No results found
)}
- )} + )} */}
diff --git a/frontend/src/main-page/grants/filter-bar/processGrantData.ts b/frontend/src/main-page/grants/filter-bar/processGrantData.ts index 61cd2ff..7bac0cd 100644 --- a/frontend/src/main-page/grants/filter-bar/processGrantData.ts +++ b/frontend/src/main-page/grants/filter-bar/processGrantData.ts @@ -1,11 +1,11 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { getAppStore } from "../../../external/bcanSatchel/store"; import { fetchAllGrants } from "../../../external/bcanSatchel/actions"; import { Grant } from "../../../../../middle-layer/types/Grant"; import { dateRangeFilter, filterGrants, - searchFilter, + yearFilterer, statusFilter, } from "./grantFilters"; import { sortGrants } from "./grantSorter.ts"; @@ -28,38 +28,24 @@ const fetchGrants = async () => { // contains callbacks for sorting and filtering grants // stores state for list of grants/filter export const ProcessGrantData = () => { - const { - allGrants, - filterStatus, - startDateFilter, - endDateFilter, - searchQuery, - } = getAppStore(); - const [grants, setGrants] = useState([]); + const { allGrants, filterStatus, startDateFilter, endDateFilter, yearFilter } = getAppStore(); - // init grant list + // fetch grants on mount if empty useEffect(() => { - fetchGrants(); - }, []); + if (allGrants.length === 0) fetchGrants(); + }, [allGrants.length]); - // when filter changes, update grant list state - useEffect(() => { - const filters = [ - statusFilter(filterStatus), - dateRangeFilter(startDateFilter, endDateFilter), - searchFilter(searchQuery), - ]; - const filtered = filterGrants(allGrants, filters); - setGrants(filtered); - // current brute force update everything when an attribute changes - }, [allGrants, filterStatus, startDateFilter, endDateFilter, searchQuery]); + // compute filtered grants dynamically — no useState needed + const filteredGrants = filterGrants(allGrants, [ + statusFilter(filterStatus), + dateRangeFilter(startDateFilter, endDateFilter), + yearFilterer(yearFilter), + ]); - // sorts grants based on attribute given, updates grant list state + // sorting callback const onSort = (header: keyof Grant, asc: boolean) => { - const sorted = sortGrants(grants, header, asc); - setGrants(sorted); + return sortGrants(filteredGrants, header, asc); }; - // calculates total # of pages for pagination - return { grants, onSort }; + return { grants: filteredGrants, onSort }; }; diff --git a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx index 3371484..e9b168f 100644 --- a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx +++ b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx @@ -7,7 +7,7 @@ import { MdOutlinePerson2 } from "react-icons/md"; import { Grant } from "../../../../../middle-layer/types/Grant"; import { TDateISO } from "../../../../../backend/src/utils/date"; import { Status } from "../../../../../middle-layer/types/Status"; -//import { api } from "@/api"; +import { api } from "../../../api"; /** Attachment type from your middle layer */ enum AttachmentType { @@ -47,34 +47,46 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { restricted_or_unrestricted: string; // "restricted" or "unrestricted" */ // Form fields, renamed to match your screenshot - const [organization, setOrganization] = useState(""); + // @ts-ignore + const [organization, _setOrganization] = useState(""); const [bcanPocComponents, setBcanPocComponents] = useState([]); const [bcanPocRefs, setBcanPocRefs] = useState[]>([]); const [grantProviderPocComponents, setGrantProviderPocComponents] = useState([]); const [grantProviderPocRefs, setGrantProviderPocRefs] = useState[]>([]); - const [applicationDate, setApplicationDate] = useState(""); - const [grantStartDate, setGrantStartDate] = useState(""); + // @ts-ignore + const [applicationDate, _setApplicationDate] = useState(""); + // @ts-ignore + const [grantStartDate, _setGrantStartDate] = useState(""); const [reportDates, setReportDates] = useState([]); - const [timelineInYears, setTimelineInYears] = useState(0); - const [estimatedCompletionTimeInHours, setEstimatedCompletionTimeInHours] = useState(0); + // @ts-ignore + const [timelineInYears, _setTimelineInYears] = useState(0); + // @ts-ignore + const [estimatedCompletionTimeInHours, _setEstimatedCompletionTimeInHours] = useState(0); - const [doesBcanQualify, setDoesBcanQualify] = useState(false); - const [status, setStatus] = useState(Status.Potential); + // @ts-ignore + const [doesBcanQualify, _setDoesBcanQualify] = useState(false); + // @ts-ignore + const [status, _setStatus] = useState(Status.Potential); - const [amount, setAmount] = useState(0); - const [description, setDescription] = useState(""); + // @ts-ignore + const [amount, _setAmount] = useState(0); + // @ts-ignore + const [description, _setDescription] = useState(""); // Attachments array + // @ts-ignore const [attachments, setAttachments] = useState([]); // For error handling - const [errorMessage, setErrorMessage] = useState(""); + // @ts-ignore + const [_errorMessage, setErrorMessage] = useState(""); /** Add a new BCAN POC entry */ - const addBcanPoc = () => { + // @ts-ignore + const _addBcanPoc = () => { const newRef = createRef(); const newPOC = ; setBcanPocComponents([...bcanPocComponents, newPOC]); @@ -82,7 +94,8 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { }; /** Add a new Grant Provider POC entry */ - const addGrantProviderPoc = () => { + // @ts-ignore + const _addGrantProviderPoc = () => { const newRef = createRef(); const newPOC = ; setGrantProviderPocComponents([...grantProviderPocComponents, newPOC]); @@ -90,12 +103,14 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { }; /* Add a new blank report date to the list */ - const addReportDate = () => { + // @ts-ignore + const _addReportDate = () => { setReportDates([...reportDates, ""]); - };0 + }; // Add an empty attachment row - const addAttachment = () => { + // @ts-ignore + const _addAttachment = () => { setAttachments([ ...attachments, { @@ -107,30 +122,34 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { }; // Remove a specific attachment row - const removeAttachment = (index: number) => { + // @ts-ignore + const _removeAttachment = (index: number) => { const updated = [...attachments]; updated.splice(index, 1); setAttachments(updated); }; // Update a field in one attachment - const handleAttachmentChange = ( + // @ts-ignore + const _handleAttachmentChange = ( index: number, field: keyof Attachment, value: string | AttachmentType ) => { const updated = [...attachments]; - // @ts-ignore + // @ts-expect-error - Keeping for future use updated[index][field] = value; setAttachments(updated); }; - const removeReportDate = (index: number) => { + // @ts-ignore + const _removeReportDate = (index: number) => { const updated = [...reportDates]; updated.splice(index, 1); setReportDates(updated); }; - const handleReportDateChange = (index: number, value: string) => { + // @ts-ignore + const _handleReportDateChange = (index: number, value: string) => { const updated = [...reportDates]; updated[index] = value; setReportDates(updated); @@ -200,33 +219,33 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { isRestricted: false, // Default to unrestricted for now }; console.log(newGrant); - // try { - // const response = await api("/grant/new-grant", { - // method: "POST", - // headers: { "Content-Type": "application/json" }, - // body: JSON.stringify(newGrant), - // }); - - // if (!response.ok) { - // const errorData = await response.json(); - // setErrorMessage(errorData.errMessage || "Failed to add grant."); - // return; - // } - - // // Re-fetch the full list of grants - // //const grantsResponse = await api("/grant"); - // // if (!grantsResponse.ok) { - // // throw new Error("Failed to re-fetch grants."); - // // } - // // const updatedGrants = await grantsResponse.json(); - // // // Update the store - // // fetchAllGrants(updatedGrants); - - // onClose(); - // } catch (error) { - // setErrorMessage("Server error. Please try again."); - // console.error(error); - // } + try { + const response = await api("/grant/new-grant", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(newGrant), + }); + + if (!response.ok) { + const errorData = await response.json(); + setErrorMessage(errorData.errMessage || "Failed to add grant."); + return; + } + + // Re-fetch the full list of grants + const grantsResponse = await api("/grant"); + if (!grantsResponse.ok) { + throw new Error("Failed to re-fetch grants."); + } + const updatedGrants = await grantsResponse.json(); + // Update the store + fetchAllGrants(updatedGrants); + + onClose(); + } catch (error) { + setErrorMessage("Server error. Please try again."); + console.error(error); + } }; return ( diff --git a/frontend/src/main-page/grants/styles/NewGrantModal.css b/frontend/src/main-page/grants/styles/NewGrantModal.css index f55e0df..9af0436 100644 --- a/frontend/src/main-page/grants/styles/NewGrantModal.css +++ b/frontend/src/main-page/grants/styles/NewGrantModal.css @@ -26,10 +26,13 @@ .modal-content { background: white; /* Peach background */ width: 100%; - max-width: 1200px; /* More width to accommodate multiple columns */ + max-width: 80%; + max-height: 80%; /* More width to accommodate multiple columns */ padding: 2rem; border: 3px solid black; border-radius: 5px; + flex: 1 1 auto; + overflow-y: auto; /* Scroll if content exceeds max height */ } /*