From e99cfe62a04c7d3c8a9a6681d7bf96e3b88c0d5e Mon Sep 17 00:00:00 2001 From: Jane Kamata Date: Sun, 16 Feb 2025 14:33:45 -0500 Subject: [PATCH] Squashed commit of the following: commit 725946d145192dc879155c673e4b6a601e0b0184 Author: Ryaken Nakamoto <108379394+Ryaken-Nakamoto@users.noreply.github.com> Date: Wed Feb 12 09:11:12 2025 -0500 Connecting Front-End to DynamoDB Grant Data (#72) * created satchel store for all grants fetched from the backend to display in grantlist commit fa05bd1452556460436c4e5b47228eae14f87e3d Author: Jane Kamata <38163243+janekamata@users.noreply.github.com> Date: Wed Jan 29 19:02:58 2025 -0500 ISO Date typed interface (#66) * Added navigation controls from account settings to grant list; changed dashboard to account * [JAN-1] Notifications base layer wrap-up * [JAN-11] Add navigation controls to go from the "dashboard" < -- > grant list * [JAN-1] Notifications base layer wrap-up * Created typed interface for ISO dates and tested --------- Co-authored-by: Jaren Adams commit a91bc983760c1de56320cd48b22106f620c0b0fa Merge: f888c3b b47a86d Author: prooflesben <122566738+prooflesben@users.noreply.github.com> Date: Wed Jan 29 18:33:48 2025 -0500 Merge pull request #67 from Code-4-Community/ryaken Fixed commit b47a86d9c34e0b09ec630d2f243f0c2dfbe7d315 Author: ryaken-nakamoto Date: Wed Jan 29 18:12:57 2025 -0500 got rid of dependency commit 0bbf3b6bcbed3df0523a547bc0e48fc25c3a807b Author: Jaren Adams Date: Tue Jan 28 20:37:53 2025 -0500 code style changes commit 33b29483bbf1d7e0ebf473dd231adf1af68584ea Merge: 5f6d437 f888c3b Author: Jaren Adams Date: Tue Jan 28 20:27:45 2025 -0500 pull in recent updates commit 5f6d4375d98802515ef84ecd3866bc14cc8b5715 Author: ryaken-nakamoto Date: Wed Jan 22 20:01:04 2025 -0500 go away lock commit 4c858b5d1ac0f6cdc45828a1a948378a94ecbf03 Author: ryaken-nakamoto Date: Wed Jan 22 19:58:46 2025 -0500 cleared node modules in frontend commit 53c37d0bf7a6b5c713ec27e4ddd0bc72e2f3aa31 Author: ryaken-nakamoto Date: Wed Jan 22 19:49:30 2025 -0500 fixed gitignore commit 06fbf94ea397c9e9125de3584cac4492db5b49a4 Author: ryaken-nakamoto Date: Wed Jan 22 19:47:59 2025 -0500 fix crypto error commit a1c29aa0a3d439538369c85f12bec02fd6d2c046 Author: ryaken-nakamoto Date: Wed Jan 22 19:34:55 2025 -0500 try4 commit bf1ed9a1990b60a874f4f1c0d5a10885e9c355e2 Author: ryaken-nakamoto Date: Wed Jan 22 19:29:16 2025 -0500 try3 commit 48c1832bc1e47816523e75c871f0888e80c80035 Author: ryaken-nakamoto Date: Wed Jan 22 19:18:57 2025 -0500 try2 commit 9b39987cd42219ca1e895ebda530cd79fe626c92 Author: ryaken-nakamoto Date: Wed Jan 22 18:58:54 2025 -0500 json change commit 52b5fcbc2f56ad239625b29208fffdabe94e29ff Author: ryaken-nakamoto Date: Tue Jan 21 18:38:32 2025 -0500 added save functionality status commit c291cef48aea6d203a77df42008734a89a41ff6c Author: ryaken-nakamoto Date: Wed Jan 15 18:52:36 2025 -0500 cleaning up App.css and index.css, margin removed on edges when grant is expanded --- backend/src/grant/grant.controller.ts | 12 +- backend/src/grant/grant.model.ts | 4 +- backend/src/grant/grant.service.ts | 35 +++++ backend/src/main.ts | 5 +- .../src/notifications/notification.module.ts | 9 +- frontend/package.json | 2 +- frontend/src/App.css | 38 +---- frontend/src/external/bcanSatchel/actions.ts | 9 ++ frontend/src/external/bcanSatchel/mutators.ts | 11 +- frontend/src/external/bcanSatchel/store.ts | 21 +++ .../grant-info/components/GrantAttributes.tsx | 15 +- .../src/grant-info/components/GrantItem.tsx | 67 +++++---- .../src/grant-info/components/GrantList.tsx | 72 +++++++--- .../grant-info/components/StatusContext.tsx | 14 ++ frontend/src/index.css | 1 + package-lock.json | 133 ++++++++++++++++-- 16 files changed, 344 insertions(+), 104 deletions(-) create mode 100644 frontend/src/grant-info/components/StatusContext.tsx diff --git a/backend/src/grant/grant.controller.ts b/backend/src/grant/grant.controller.ts index c1b14b5..fe80973 100644 --- a/backend/src/grant/grant.controller.ts +++ b/backend/src/grant/grant.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param, Put, Body } from '@nestjs/common'; +import { Controller, Get, Param, Put, Body, Patch } from '@nestjs/common'; import { GrantService } from './grant.service'; @Controller('grant') @@ -29,4 +29,14 @@ export class GrantController { return await this.grantService.unarchiveGrants(grantIds) } + @Put('save/status') + async saveStatus( + @Body('status') status: string + ) { + await this.grantService.updateGrant(1, 'status', status) + return { message: 'Status has been updated' }; + } + + + } \ No newline at end of file diff --git a/backend/src/grant/grant.model.ts b/backend/src/grant/grant.model.ts index 307ff13..35859f8 100644 --- a/backend/src/grant/grant.model.ts +++ b/backend/src/grant/grant.model.ts @@ -14,4 +14,6 @@ export interface Grant { attached_resources: string[]; comments: string[]; isArchived : boolean; -} \ No newline at end of file +} + +// TODO: [JAN-13} Switch deadline to Proper "Date Time" diff --git a/backend/src/grant/grant.service.ts b/backend/src/grant/grant.service.ts index 99018f6..f51fbc0 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -9,6 +9,7 @@ export class GrantService { // function to retrieve all grants in our database async getAllGrants(): Promise { + // loads in the environment variable for the table now const params = { TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || 'TABLE_FAILURE', }; @@ -79,4 +80,38 @@ export class GrantService { }; return successfulUpdates; } + + /** + * Given primary key, attribute name, and the content to update, queries database to update + * that info. Returns true if operation was successful. Assumes inputs are valid. + * @param grantId + * @param attributeName + * @param newValue + */ + async updateGrant(grantId: number, attributeName: string, newValue: string): Promise { + const params = { + TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || 'TABLE_FAILURE', + Key: { + grantId: grantId + }, + UpdateExpression: `set #s = :newValue`, + ExpressionAttributeNames: { + '#s': attributeName, + }, + ExpressionAttributeValues: { + ":newValue": newValue, + }, + ReturnValues: "UPDATED_NEW", + } + console.log(params); + + try { + const result = await this.dynamoDb.update(params).promise(); + console.log(result); + } catch(err) { + console.log(err); + throw new Error(`Failed to update Grant ${grantId} attribute + ${attributeName} with ${newValue}`); + } + } } \ No newline at end of file diff --git a/backend/src/main.ts b/backend/src/main.ts index b8ddad5..4a5ce3d 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -9,10 +9,13 @@ async function bootstrap() { region: process.env.AWS_REGION, accessKeyId: process.env.OPEN_HATCH, secretAccessKey: process.env.CLOSED_HATCH - }); + }); + const app = await NestFactory.create(AppModule); app.enableCors(); await app.listen(3001); } dotenv.config(); bootstrap(); + + diff --git a/backend/src/notifications/notification.module.ts b/backend/src/notifications/notification.module.ts index c01890e..6a4ff79 100644 --- a/backend/src/notifications/notification.module.ts +++ b/backend/src/notifications/notification.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { NotificationController } from './notification.controller'; import { NotificationService } from './notifcation.service'; - @Module({ - providers: [NotificationService], - controllers: [NotificationController], - exports: [NotificationService], + providers: [NotificationService], // providers perform business logic + controllers: [NotificationController], // controllers directly take in http requests + // and are the starting point for anything happening + exports: [NotificationService], // by putting it under exports, this service is available + // to other modules outside of this }) export class NotificationsModule {} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index b1fd242..5ed066f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,4 +47,4 @@ "vite-tsconfig-paths": "^5.1.4", "vitest": "^2.1.8" } -} +} \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css index 6cfcbf0..9e6597b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,45 +1,9 @@ #root { - max-width: 1280px; + max-width: 100%; margin: 0 auto; - padding: 2rem; text-align: center; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} .app-container { display: flex; diff --git a/frontend/src/external/bcanSatchel/actions.ts b/frontend/src/external/bcanSatchel/actions.ts index b8cd2ae..760016e 100644 --- a/frontend/src/external/bcanSatchel/actions.ts +++ b/frontend/src/external/bcanSatchel/actions.ts @@ -1,4 +1,5 @@ import { action } from 'satcheljs'; +import { Grant } from './store.js' /** * Set whether the user is authenticated, update the user object, @@ -24,3 +25,11 @@ export const updateUserProfile = action('updateUserProfile', (user: any) => ({ * Completely log out the user (clear tokens, user data, etc.). */ export const logoutUser = action('logoutUser'); + +/** + * Moves along the all grants that are fetched from back end to mutator. + */ +export const fetchAllGrants = action( + 'fetchAllGrants', + (grants: Grant[]) => ({grants}) +); diff --git a/frontend/src/external/bcanSatchel/mutators.ts b/frontend/src/external/bcanSatchel/mutators.ts index e423df7..f8ea033 100644 --- a/frontend/src/external/bcanSatchel/mutators.ts +++ b/frontend/src/external/bcanSatchel/mutators.ts @@ -1,5 +1,5 @@ import { mutator } from 'satcheljs'; -import { setAuthState, updateUserProfile, logoutUser } from './actions'; +import { setAuthState, updateUserProfile, logoutUser, fetchAllGrants } from './actions'; import { getAppStore } from './store'; /** @@ -34,3 +34,12 @@ mutator(logoutUser, () => { store.user = null; store.accessToken = null; }); + + +/** + * Reassigns all grants to new grants from the backend. + */ +mutator(fetchAllGrants, (actionMessage) => { + const store = getAppStore(); + store.allGrants = actionMessage.grants; +}); diff --git a/frontend/src/external/bcanSatchel/store.ts b/frontend/src/external/bcanSatchel/store.ts index c05bb42..027d6b8 100644 --- a/frontend/src/external/bcanSatchel/store.ts +++ b/frontend/src/external/bcanSatchel/store.ts @@ -10,6 +10,26 @@ export interface AppState { isAuthenticated: boolean; user: User | null; accessToken: string | null; + allGrants: Grant[] | [] +} + +// model for Grant objects, matches exactly the same as backend grant.model +// TODO: should synchronize from same file? +export interface Grant { + grantId: number; + organization_name: string; + description: string; + is_bcan_qualifying: boolean; + status: string; + amount: number; + deadline: string; + notifications_on_for_user: boolean; + reporting_requirements: string; + restrictions: string; + point_of_contacts: string[]; + attached_resources: string[]; + comments: string[]; + isArchived : boolean; } // Define initial state @@ -17,6 +37,7 @@ const initialState: AppState = { isAuthenticated: false, user: null, accessToken: null, + allGrants: [] }; const store = createStore('appStore', initialState); diff --git a/frontend/src/grant-info/components/GrantAttributes.tsx b/frontend/src/grant-info/components/GrantAttributes.tsx index bdcce6f..b915cd0 100644 --- a/frontend/src/grant-info/components/GrantAttributes.tsx +++ b/frontend/src/grant-info/components/GrantAttributes.tsx @@ -1,18 +1,29 @@ -import React from 'react'; +import React, { useContext} from 'react'; import './styles/GrantAttributes.css'; +import {StatusContext} from './StatusContext.tsx'; interface GrantAttributesProps { isEditing: boolean; } export const GrantAttributes: React.FC = ({isEditing}) => { + + // placeholder for now before reworking, will remove redundant useState() + const { curStatus, setCurStatus } = useContext(StatusContext); + + const handleChange = (event: React.ChangeEvent) => { + setCurStatus(event.target.value); + }; + return (
Status
{isEditing ? ( ) : ( diff --git a/frontend/src/grant-info/components/GrantItem.tsx b/frontend/src/grant-info/components/GrantItem.tsx index 8422969..e4c621f 100644 --- a/frontend/src/grant-info/components/GrantItem.tsx +++ b/frontend/src/grant-info/components/GrantItem.tsx @@ -2,30 +2,48 @@ import React, { useState } from "react"; import "./styles/GrantItem.css"; import { GrantAttributes } from "./GrantAttributes"; import GrantDetails from "./GrantDetails"; +import { StatusContext } from "./StatusContext"; +import { Grant } from "@/external/bcanSatchel/store.ts"; -export interface GrantItemProps { - grantName: string; - applicationDate: Date; - generalStatus: string; - amount: number; - restrictionStatus: string; +interface GrantItemProps { + grant: Grant; } -const GrantItem: React.FC = (props) => { - const { - grantName, - applicationDate, - generalStatus, - amount, - restrictionStatus, - } = props; +// TODO: [JAN-14] Make uneditable field editable (ex: Description, Application Reqs, Additional Notes) +const GrantItem: React.FC = ({ grant }) => { const [isExpanded, setIsExpanded] = useState(false); const [isEditing, setIsEditing] = useState(false); + const [curStatus, setCurStatus] = useState(grant.status); const toggleExpand = () => { setIsExpanded(!isExpanded); }; - const toggleEdit = () => setIsEditing((prev) => !prev); + // when toggleEdit gets saved, then updates the backend to update itself with whatever + // is shown in the front-end + + const toggleEdit = async () => { + if (isEditing) { + // if you are saving + try { + const response = await fetch( + "http://localhost:3001/grant/save/status", + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: curStatus }), + } + ); + const result = await response.json(); + console.log(result); + } catch (err) { + console.error("Error saving data:", err); + } + // holding result for now + } + setIsEditing(!isEditing); + }; return ( // class name with either be grant-item or grant-item-expanded @@ -34,21 +52,22 @@ const GrantItem: React.FC = (props) => { className={`grant-summary ${isExpanded ? "expanded" : ""}`} onClick={toggleExpand} > -
  • {grantName}
  • -
  • - {applicationDate.toISOString().split("T")[0]} -
  • -
  • {generalStatus}
  • -
  • ${amount}
  • -
  • {restrictionStatus}
  • +
  • {grant.organization_name}
  • +
  • {"no attribute for app-date"}
  • + //.toISOString().split("T")[0] +
  • {grant.status}
  • +
  • ${grant.amount}
  • +
  • {grant.restrictions}
  • {isExpanded && (

    Community Development Initiative Grant

    - - + + + +