From c291cef48aea6d203a77df42008734a89a41ff6c Mon Sep 17 00:00:00 2001 From: ryaken-nakamoto Date: Wed, 15 Jan 2025 18:52:36 -0500 Subject: [PATCH 01/12] cleaning up App.css and index.css, margin removed on edges when grant is expanded --- frontend/src/App.css | 38 +------------------------------------- frontend/src/index.css | 1 + 2 files changed, 2 insertions(+), 37 deletions(-) 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/index.css b/frontend/src/index.css index 6119ad9a..4afd79ee 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -46,6 +46,7 @@ button { cursor: pointer; transition: border-color 0.25s; } + button:hover { border-color: #646cff; } From 52b5fcbc2f56ad239625b29208fffdabe94e29ff Mon Sep 17 00:00:00 2001 From: ryaken-nakamoto Date: Tue, 21 Jan 2025 18:38:32 -0500 Subject: [PATCH 02/12] added save functionality status --- backend/dist/app.module.js | 3 + backend/dist/auth/auth.controller.js | 1 - backend/dist/main.js | 8 + backend/package.json | 1 + backend/src/app.module.ts | 1 + backend/src/grant/grant.controller.ts | 15 +- backend/src/grant/grant.model.ts | 4 +- backend/src/grant/grant.service.ts | 35 + backend/src/main.ts | 4 +- .../src/notifications/notification.module.ts | 11 +- frontend/package-lock.json | 26 + .../grant-info/components/GrantAttributes.tsx | 15 +- .../src/grant-info/components/GrantItem.tsx | 63 +- .../src/grant-info/components/GrantList.tsx | 3 +- .../grant-info/components/StatusContext.tsx | 14 + package-lock.json | 1153 ++--------------- package.json | 1 + 17 files changed, 275 insertions(+), 1083 deletions(-) create mode 100644 frontend/src/grant-info/components/StatusContext.tsx diff --git a/backend/dist/app.module.js b/backend/dist/app.module.js index 37400a33..7a1f646c 100644 --- a/backend/dist/app.module.js +++ b/backend/dist/app.module.js @@ -12,6 +12,9 @@ const auth_module_1 = require("./auth/auth.module"); const user_module_1 = require("./user/user.module"); const grant_module_1 = require("./grant/grant.module"); const notification_module_1 = require("./notifications/notification.module"); +// this does an ascethic, modular grouping of all the sub-modules, encapsulating all +// of the different functions, such as authentication actions, notification actions, user actions, +// and actual grant operations let AppModule = class AppModule { }; AppModule = __decorate([ diff --git a/backend/dist/auth/auth.controller.js b/backend/dist/auth/auth.controller.js index c922ad75..d6c91c61 100644 --- a/backend/dist/auth/auth.controller.js +++ b/backend/dist/auth/auth.controller.js @@ -1,5 +1,4 @@ "use strict"; -// src/auth/auth.controller.ts var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); diff --git a/backend/dist/main.js b/backend/dist/main.js index 75b977ac..4042b5bc 100644 --- a/backend/dist/main.js +++ b/backend/dist/main.js @@ -32,14 +32,22 @@ const dotenv = __importStar(require("dotenv")); const aws_sdk_1 = __importDefault(require("aws-sdk")); /* ! */ async function bootstrap() { + // actually opens the link for us to use the AWS services aws_sdk_1.default.config.update({ region: process.env.AWS_REGION, accessKeyId: process.env.OPEN_HATCH, secretAccessKey: process.env.CLOSED_HATCH }); + // and then actually launches the app by bundling all of the modules that make up the backend const app = await core_1.NestFactory.create(app_module_1.AppModule); + // this enables the API to be accessed app.enableCors(); + // and this is the port that it goes to await app.listen(3001); } +// actually load in the values of the .env dotenv.config(); +// call our defined function bootstrap(); +// for my notes only +// crud endpoints, Create, Read, Update Delete diff --git a/backend/package.json b/backend/package.json index f80152eb..39e4f6d7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,6 +12,7 @@ "@nestjs/common": "^8.0.0", "@nestjs/core": "^8.0.0", "@nestjs/jwt": "^8.0.0", + "@nestjs/mapped-types": "*", "@nestjs/passport": "^8.0.0", "@nestjs/platform-express": "^8.4.7", "aws-sdk": "^2.1030.0", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 6f0ab686..d565c8b5 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -4,6 +4,7 @@ import { UserModule } from './user/user.module'; import { GrantModule } from './grant/grant.module'; import { NotificationsModule } from './notifications/notification.module'; + @Module({ imports: [AuthModule, UserModule, GrantModule, NotificationsModule], }) 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 b1436a93..7dec6efb 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -13,6 +13,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', }; @@ -83,4 +84,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 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..0b97569d 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -9,10 +9,12 @@ 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..f508b4de 100644 --- a/backend/src/notifications/notification.module.ts +++ b/backend/src/notifications/notification.module.ts @@ -4,8 +4,11 @@ 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 +export class NotificationsModule {} + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 87a4e68a..c0fd4aae 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4695,6 +4695,32 @@ "url": "https://opencollective.com/mobx" } }, + "node_modules/mobx-react": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.2.0.tgz", + "integrity": "sha512-dkGWCx+S0/1mfiuFfHRH8D9cplmwhxOV5CkXMp38u6rQGG2Pv3FWYztS0M7ncR6TyPRQKaTG/pnitInoYE9Vrw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mobx-react-lite": "^4.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/mobx-react-lite": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.0.tgz", 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

- - + + + +