diff --git a/backend/src/grant/grant.controller.ts b/backend/src/grant/grant.controller.ts index c1b14b5c..913f7965 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') @@ -10,6 +10,8 @@ export class GrantController { return await this.grantService.getAllGrants(); } + // the @Param looks for arguments, in the decorator above the function definition, and + // then uses it as a parameter @Get(':id') async getGrantById(@Param('id') GrantId: string) { return await this.grantService.getGrantById(parseInt(GrantId, 10)); @@ -29,4 +31,15 @@ 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 307ff13d..35859f87 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 99018f63..f51fbc03 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 b8ddad52..4a5ce3d6 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 c01890ec..6a4ff795 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 b1fd2421..5ed066f8 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 6cfcbf03..9e6597b6 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/grant-info/components/GrantAttributes.tsx b/frontend/src/grant-info/components/GrantAttributes.tsx index bdcce6ff..b915cd0b 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 6ce794ed..e440ab11 100644 --- a/frontend/src/grant-info/components/GrantItem.tsx +++ b/frontend/src/grant-info/components/GrantItem.tsx @@ -1,8 +1,10 @@ -import React, {useState} from 'react'; +import React, {useState, useEffect } from 'react'; import './styles/GrantItem.css'; import { GrantAttributes } from './GrantAttributes'; import GrantDetails from './GrantDetails'; +import {StatusContext} from './StatusContext'; +// TODO: [JAN-14] Make uneditable field editable (ex: Description, Application Reqs, Additional Notes) interface GrantItemProps { grantName: string; applicationDate: string; @@ -11,24 +13,69 @@ interface GrantItemProps { restrictionStatus: string; } const GrantItem: React.FC = (props) => { + // will change back to const later once below is achieved. const { grantName, applicationDate, generalStatus, amount, restrictionStatus } = props; - + // NOTE: For now, generalStatus will be changed to demonstrate fetching from the database + // Once Front-End display matches database schema, the query will actually be made in + // GrantList.tsx. Furthermore, there is no established way for the front-end to know + // which grant will be edited. Will default to GrantId: 1 for now as well. const [isExpanded, setIsExpanded] = useState(false); const [isEditing, setIsEditing] = useState(false); + const [curStatus, setCurStatus] = useState(generalStatus); + + // NOTE: ^^this is also a placeholder for generalStatus + + // fetching initial status + useEffect(() => { + const fetchStatus = async () => { + try { + const rawResponse = await fetch('http://localhost:3001/grant/1'); + if (rawResponse.ok) { + const data = await rawResponse.json(); + setCurStatus(data.status); + } else { + console.error('Failed to fetch grant status:', rawResponse.statusText); + } + } catch (err) { + console.error('Error fetching status:', err); + } + }; + fetchStatus(); + }, []); 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 - return ( + 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
  • {grantName}
  • {applicationDate}
  • -
  • {generalStatus}
  • +
  • {curStatus}
  • {/*This is replacing generalStatus for now*/}
  • ${amount}
  • {restrictionStatus}
@@ -37,8 +84,10 @@ const GrantItem: React.FC = (props) => {

Community Development Initiative Grant

- - + + + +