Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/src/main-page/dashboard/CsvExportButton.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -77,7 +77,7 @@ const columns: CsvColumn<Grant>[] = [
const CsvExportButton: React.FC = observer(() => {
const { yearFilter } = getAppStore();
const [isProcessing, setIsProcessing] = useState(false);
const { grants } = useProcessGrantData();
const { grants } = ProcessGrantData();
const onClickDownload = async () => {
setIsProcessing(true);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main-page/grants/GrantPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function GrantPage() {
<div className="top-half">
</div>
<div className="flex justify-end align-middle p-4 gap-4">
<GrantSearch onGrantSelect={setSelectedGrant} />
<GrantSearch />
<AddGrantButton onClick={() => setShowNewGrantModal(true)} />
</div>
<div className="grid grid-cols-5 gap-8 px-4">
Expand Down
104 changes: 54 additions & 50 deletions frontend/src/main-page/grants/filter-bar/GrantSearch.tsx
Original file line number Diff line number Diff line change
@@ -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<Grant[]>([]);
const [showDropdown, setShowDropdown] = useState(false);
const [dropdownGrants, setDropdownGrants] = useState<Grant[]>([]);
// @ts-ignore
const [grants, _setGrants] = useState<Grant[]>([]);
// const [showDropdown, setShowDropdown] = useState(false);
// const [dropdownGrants, setDropdownGrants] = useState<Grant[]>([]);

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<HTMLInputElement>) => {
setUserInput(e.target.value);
Expand All @@ -42,38 +45,39 @@ function GrantSearch({ onGrantSelect }: any) {

const performSearch = (query: string) => {
if (!query) {
setDropdownGrants([]);
setShowDropdown(false);
// setDropdownGrants([]);
// setShowDropdown(false);
updateSearchQuery("");
return;
}
const fuse = new Fuse<Grant>(grants, {
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 (
<div className="search-bar-main-container">
Expand Down Expand Up @@ -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 && (
<div className="dropdown-container">
{dropdownGrants.length > 0 ? (
dropdownGrants.map((grant, index) => (
Expand All @@ -127,7 +131,7 @@ function GrantSearch({ onGrantSelect }: any) {
<div className="dropdown-item">No results found</div>
)}
</div>
)}
)} */}
</div>
</form>
</div>
Expand Down
44 changes: 15 additions & 29 deletions frontend/src/main-page/grants/filter-bar/processGrantData.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<Grant[]>([]);
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 };
};
Loading