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
10 changes: 4 additions & 6 deletions backend/src/grant/grant.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Controller, Get, Param, Put, Body, Patch } from '@nestjs/common';
import { GrantService } from './grant.service';
import {Grant} from "./grant.model";

@Controller('grant')
export class GrantController {
Expand Down Expand Up @@ -29,12 +30,9 @@ 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' };
@Put('save')
async saveGrant(@Body() grantData: Grant) {
return await this.grantService.updateGrant(grantData)
}


Expand Down
45 changes: 23 additions & 22 deletions backend/src/grant/grant.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,36 +82,37 @@ export class GrantService {
}

/**
* 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
* Will push or overwrite new grant data to database
* @param grantData
*/
async updateGrant(grantId: number, attributeName: string, newValue: string): Promise<void> {
async updateGrant(grantData: Grant): Promise<string> {
// dynamically creates the update expression/attribute names based on names of grant interface
// assumption: grant interface field names are exactly the same as db storage naming

const updateKeys = Object.keys(grantData).filter(
key => key != 'grantId'
);
const UpdateExpression = "SET " + updateKeys.map((key) => `#${key} = :${key}`).join(", ");
const ExpressionAttributeNames = updateKeys.reduce((acc, key) =>
({ ...acc, [`#${key}`]: key }), {});
const ExpressionAttributeValues = updateKeys.reduce((acc, key) =>
({ ...acc, [`:${key}`]: grantData[key as keyof typeof grantData] }), {});

const params = {
TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || 'TABLE_FAILURE',
Key: {
grantId: grantId
},
UpdateExpression: `set #s = :newValue`,
ExpressionAttributeNames: {
'#s': attributeName,
},
ExpressionAttributeValues: {
":newValue": newValue,
},
TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || "TABLE_FAILURE",
Key: { grantId: grantData.grantId },
UpdateExpression,
ExpressionAttributeNames,
ExpressionAttributeValues,
ReturnValues: "UPDATED_NEW",
}
console.log(params);
};

try {
const result = await this.dynamoDb.update(params).promise();
console.log(result);
return JSON.stringify(result); // returns the changed attributes stored in db
} catch(err) {
console.log(err);
throw new Error(`Failed to update Grant ${grantId} attribute
${attributeName} with ${newValue}`);
throw new Error(`Failed to update Grant ${grantData.grantId}`)
}
}
}
213 changes: 101 additions & 112 deletions frontend/src/grant-info/components/GrantAttributes.tsx
Original file line number Diff line number Diff line change
@@ -1,124 +1,113 @@
import React, { useContext} from 'react';
import React from 'react';
import './styles/GrantAttributes.css';
import {StatusContext} from './StatusContext.tsx';
import {Grant} from "@/external/bcanSatchel/store.ts";
interface GrantAttributesProps {
isEditing: boolean;
curGrant: Grant;
setCurGrant: React.Dispatch<React.SetStateAction<Grant>>;
}

export const GrantAttributes: React.FC<GrantAttributesProps> = ({isEditing}) => {
export const GrantAttributes: React.FC<GrantAttributesProps> = ({curGrant, setCurGrant, isEditing}) => {

// placeholder for now before reworking, will remove redundant useState()
const { curStatus, setCurStatus } = useContext(StatusContext);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setCurStatus(event.target.value);
const {name, value} = event.target;

// only modifies the changed field
setCurGrant(curGrant => ({
...curGrant,
[name]: value
}));
};

return (
<div className="grant-attributes">
<div className="attribute-row">
<div className="attribute-label">Status</div>
{isEditing ? (
<input
type="text" /*This is also a place holder for updating the status*/
className="attribute-value"
value={curStatus}
onChange={handleChange}
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Does BCAN qualify?</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Deadline</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
name="deadline"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Notification Date</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
name="notificationDate"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Report Due:</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Estimated Completion Time:</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Scope Document:</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Grantmaker POC:</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
<div className="attribute-row">
<div className="attribute-label">Timeline:</div>
{isEditing ? (
<input
type="text"
className="attribute-value"
/>
) : (
<span className="attribute-value"></span>
)}
</div>
</div>
);
return (
<div className="grant-attributes">
<div className="attribute-row">
<div className="attribute-label">Status</div>
<input
type="text"
name="status"
className="attribute-value"
value={curGrant.status}
onChange={handleChange}
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Does BCAN qualify?</div>
<input
type="text"
className="attribute-value"
name="is_bcan_qualifying"
value={curGrant.is_bcan_qualifying ? "Yes" : "No"}
onChange={handleChange}
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Deadline</div>
<input
type="text"
className="attribute-value"
name="deadline"
value={curGrant.deadline}
onChange={handleChange}
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Notification Date</div>
<input
type="text"
className="attribute-value"
name="notificationDate"
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Report Due:</div>
<input
type="text"
className="attribute-value"
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Estimated Completion Time:</div>
<input
type="text"
className="attribute-value"
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Scope Document:</div>
<input
type="text"
className="attribute-value"
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Grantmaker POC:</div>
<input
type="text"
className="attribute-value"
name="point_of_contacts"
value={curGrant.point_of_contacts}
onChange={handleChange}
readOnly={!isEditing}
/>
</div>
<div className="attribute-row">
<div className="attribute-label">Timeline:</div>
<input
type="text"
className="attribute-value"
readOnly={!isEditing}
/>
</div>
</div>
);
};
Loading