From b61c1e6711ed8c0b2682ebf1e6e4650ea71de2c8 Mon Sep 17 00:00:00 2001 From: Gillian Scott Date: Thu, 10 Apr 2025 18:04:52 -0400 Subject: [PATCH 1/6] added a bare admin dash & the applications table --- apps/backend/src/site/site.service.ts | 306 +++++++------ apps/backend/src/user/user.controller.ts | 123 +++--- apps/backend/src/user/user.service.ts | 71 ++- .../adminDashboard/AdminDashboard.tsx | 153 +++++++ apps/frontend/src/main.tsx | 10 +- .../src/pages/adminpage/AdminPage.tsx | 67 +++ .../src/pages/applicationsPage/Table.tsx | 234 ++++++++++ .../applicationsPage/applicationsPage.tsx | 31 ++ package.json | 4 +- yarn.lock | 408 ++++++++++++++++-- 10 files changed, 1156 insertions(+), 251 deletions(-) create mode 100644 apps/frontend/src/components/adminDashboard/AdminDashboard.tsx create mode 100644 apps/frontend/src/pages/adminpage/AdminPage.tsx create mode 100644 apps/frontend/src/pages/applicationsPage/Table.tsx create mode 100644 apps/frontend/src/pages/applicationsPage/applicationsPage.tsx diff --git a/apps/backend/src/site/site.service.ts b/apps/backend/src/site/site.service.ts index 6b3253ff..95ee6a9f 100644 --- a/apps/backend/src/site/site.service.ts +++ b/apps/backend/src/site/site.service.ts @@ -1,158 +1,182 @@ -import { Injectable } from "@nestjs/common"; -import { SiteInputModel, SiteModel, SiteStatus, SymbolType } from "./site.model"; -import { DynamoDbService } from "../dynamodb"; -import { NewSiteInput } from "../dtos/newSiteDTO"; +import { Injectable } from '@nestjs/common'; +import { + SiteInputModel, + SiteModel, + SiteStatus, + SymbolType, +} from './site.model'; +import { DynamoDbService } from '../dynamodb'; +import { NewSiteInput } from '../dtos/newSiteDTO'; @Injectable() export class SiteService { - - private readonly tableName = 'greenInfraBostonSites'; - - constructor(private readonly dynamoDbService: DynamoDbService) {} - - /** - * Gets a site information based on that site's id. - * @param siteId - * @returns - */ - public async getSite(siteId: number): Promise { - try{ - const key = { 'siteId': { S: siteId.toString() } }; - const data = await this.dynamoDbService.getItem(this.tableName, key); - return(this.mapDynamoDBItemToSite(siteId, data)); - } - catch(e) { - throw new Error("Unable to get site data: "+ e) - } + private readonly tableName = 'greenInfraBostonSites'; + + constructor(private readonly dynamoDbService: DynamoDbService) {} + + /** + * Gets a site information based on that site's id. + * @param siteId + * @returns + */ + public async getSite(siteId: number): Promise { + try { + const key = { siteId: { S: siteId.toString() } }; + const data = await this.dynamoDbService.getItem(this.tableName, key); + + if (!data) { + console.warn(`No site found with id: ${siteId}`); + return null; + } + + return this.mapDynamoDBItemToSite(siteId, data); + } catch (e) { + throw new Error('Unable to get site data: ' + e); } - - /** - * Scans the entire sites table and returns all rows. - * @returns the full list of sites - */ - public async getAllSites(): Promise { + } + + /** + * Scans the entire sites table and returns all rows. + * @returns the full list of sites + */ + public async getAllSites(): Promise { + try { + const data = await this.dynamoDbService.scanTable(this.tableName); + const sites: SiteModel[] = []; + for (let i = 0; i < data.length; i++) { try { - const data = await this.dynamoDbService.scanTable(this.tableName); - const sites: SiteModel[] = []; - for (let i = 0; i < data.length; i++) { - try { - sites.push(this.mapDynamoDBItemToSite(parseInt(data[i]["siteId"].S), data[i])); - } catch (error) { - console.error('Error mapping site:', error, data[i]); - } - - } - return sites; - } - catch(e) { - throw new Error("Unable to get all site data: "+ e) + sites.push( + this.mapDynamoDBItemToSite(parseInt(data[i]['siteId'].S), data[i]), + ); + } catch (error) { + console.error('Error mapping site:', error, data[i]); } + } + return sites; + } catch (e) { + throw new Error('Unable to get all site data: ' + e); } - - public async postSite(siteData: NewSiteInput) { - const siteModel = this.PostInputToSiteModel(siteData); - const newId = await this.dynamoDbService.getHighestSiteId(this.tableName) + 1; - siteModel.siteId.S = newId.toString(); - console.log("Using new ID:" + siteModel.siteId.S) - try { - const result = await this.dynamoDbService.postItem(this.tableName, siteModel); - return {...result, newSiteId: newId.toString()}; - } catch (e) { - throw new Error("Unable to post new site: " + e); - } + } + + public async postSite(siteData: NewSiteInput) { + const siteModel = this.PostInputToSiteModel(siteData); + const newId = + (await this.dynamoDbService.getHighestSiteId(this.tableName)) + 1; + siteModel.siteId.S = newId.toString(); + console.log('Using new ID:' + siteModel.siteId.S); + try { + const result = await this.dynamoDbService.postItem( + this.tableName, + siteModel, + ); + return { ...result, newSiteId: newId.toString() }; + } catch (e) { + throw new Error('Unable to post new site: ' + e); } - - - - public async getSitesByStatus(status: string): Promise { + } + + public async getSitesByStatus(status: string): Promise { + try { + const data = await this.dynamoDbService.scanTable( + this.tableName, + 'siteStatus = :status', + { ':status': { S: status } }, + ); + const sites: SiteModel[] = []; + for (let i = 0; i < data.length; i++) { try { - const data = await this.dynamoDbService.scanTable(this.tableName, "siteStatus = :status", { ":status": { S: status } } ); - const sites: SiteModel[] = []; - for (let i = 0; i < data.length; i++) { - try { - sites.push(this.mapDynamoDBItemToSite(parseInt(data[i]["siteId"].S), data[i])); - } catch (error) { - console.error('Error mapping site:', error, data[i]); - } - - } - console.log("Found " + sites.length + " \"" + status + "' sites"); - return sites; - } - catch(e) { - throw new Error("Unable to get site by status: "+ e) + sites.push( + this.mapDynamoDBItemToSite(parseInt(data[i]['siteId'].S), data[i]), + ); + } catch (error) { + console.error('Error mapping site:', error, data[i]); } - + } + console.log('Found ' + sites.length + ' "' + status + "' sites"); + return sites; + } catch (e) { + throw new Error('Unable to get site by status: ' + e); } - - public async getSitesBySymbolType(symbolType: string): Promise { + } + + public async getSitesBySymbolType(symbolType: string): Promise { + try { + const data = await this.dynamoDbService.scanTable( + this.tableName, + 'symbolType = :symbolType', + { ':symbolType': { S: symbolType } }, + ); + const sites: SiteModel[] = []; + for (let i = 0; i < data.length; i++) { try { - const data = await this.dynamoDbService.scanTable(this.tableName, "symbolType = :symbolType", { ":symbolType": { S: symbolType } } ); - const sites: SiteModel[] = []; - for (let i = 0; i < data.length; i++) { - try { - sites.push(this.mapDynamoDBItemToSite(parseInt(data[i]["siteId"].S), data[i])); - } catch (error) { - console.error('Error mapping site:', error, data[i]); - } - - } - console.log("Found " + sites.length + " \"" + symbolType + "' sites"); - return sites; + sites.push( + this.mapDynamoDBItemToSite(parseInt(data[i]['siteId'].S), data[i]), + ); + } catch (error) { + console.error('Error mapping site:', error, data[i]); } - catch(e) { - throw new Error("Unable to get site by symbol: "+ e) - } - + } + console.log('Found ' + sites.length + ' "' + symbolType + "' sites"); + return sites; + } catch (e) { + throw new Error('Unable to get site by symbol: ' + e); } - - public async deleteSite(siteId: number): Promise { - try { - const key = { 'siteId': { S: siteId.toString() } }; - await this.dynamoDbService.deleteItem(this.tableName, key); - console.log(`Deleted site with id ${siteId}`); - } catch (e) { - throw new Error("Unable to delete site data: " + e); - } + } + + public async deleteSite(siteId: number): Promise { + try { + const key = { siteId: { S: siteId.toString() } }; + await this.dynamoDbService.deleteItem(this.tableName, key); + console.log(`Deleted site with id ${siteId}`); + } catch (e) { + throw new Error('Unable to delete site data: ' + e); } - - public async adoptSite(siteId: number): Promise { - try { - const key = { 'siteId': { S: siteId.toString() } }; - const result = await this.dynamoDbService.updateField(this.tableName, key, "siteStatus", "Adopted") - } catch (e) { - throw new Error("Unable to set site status to Adopted:" + e); - } + } + + public async adoptSite(siteId: number): Promise { + try { + const key = { siteId: { S: siteId.toString() } }; + const result = await this.dynamoDbService.updateField( + this.tableName, + key, + 'siteStatus', + 'Adopted', + ); + } catch (e) { + throw new Error('Unable to set site status to Adopted:' + e); } - - private mapDynamoDBItemToSite = (objectId: number, item: { [key: string]: any }): SiteModel => { - return { - siteID: objectId, - siteName: item["siteName"].S, - siteStatus: item["siteStatus"].S, - assetType: item["assetType"].S, - symbolType: item["symbolType"].S, - siteLatitude: item["siteLatitude"].S, - siteLongitude: item["siteLongitude"].S, - dateAdopted: new Date(), //placeholder until table is updated - maintenanceReports: [], //placeholder until table is updated - neighborhood: item["neighborhood"].S, - address: item["address"].S - }; + } + + private mapDynamoDBItemToSite = ( + objectId: number, + item: { [key: string]: any }, + ): SiteModel => { + return { + siteID: objectId, + siteName: item['siteName'].S, + siteStatus: item['siteStatus'].S, + assetType: item['assetType'].S, + symbolType: item['symbolType'].S, + siteLatitude: item['siteLatitude'].S, + siteLongitude: item['siteLongitude'].S, + dateAdopted: new Date(), //placeholder until table is updated + maintenanceReports: [], //placeholder until table is updated + neighborhood: item['neighborhood'].S, + address: item['address'].S, }; - - private PostInputToSiteModel = (input: NewSiteInput): SiteInputModel => { - return { - siteId: {S: ""}, - siteName: {S: input.siteName}, - siteStatus: {S: SiteStatus.AVAILABLE}, - assetType: {S: input.assetType}, - symbolType: {S: input.symbolType as SymbolType}, - siteLatitude: {S: input.siteLatitude}, - siteLongitude: {S: input.siteLongitude}, - neighborhood: {S: input.neighborhood}, - address: {S: input.address}, - }; - } - -} \ No newline at end of file + }; + + private PostInputToSiteModel = (input: NewSiteInput): SiteInputModel => { + return { + siteId: { S: '' }, + siteName: { S: input.siteName }, + siteStatus: { S: SiteStatus.AVAILABLE }, + assetType: { S: input.assetType }, + symbolType: { S: input.symbolType as SymbolType }, + siteLatitude: { S: input.siteLatitude }, + siteLongitude: { S: input.siteLongitude }, + neighborhood: { S: input.neighborhood }, + address: { S: input.address }, + }; + }; +} diff --git a/apps/backend/src/user/user.controller.ts b/apps/backend/src/user/user.controller.ts index a45a8e3b..d6c3c702 100644 --- a/apps/backend/src/user/user.controller.ts +++ b/apps/backend/src/user/user.controller.ts @@ -1,75 +1,76 @@ -import { - Controller, - Get, - Post, - Body, - Param, - Put, -} from "@nestjs/common"; +import { Controller, Get, Post, Body, Param, Put } from '@nestjs/common'; import { ApiParam } from '@nestjs/swagger'; -import { UserService } from "./user.service"; -import { NewUserInput } from "../dtos/newUserDTO"; -import { UserModel, UserStatus, EditUserModel, Role } from "./user.model"; - +import { UserService } from './user.service'; +import { NewUserInput } from '../dtos/newUserDTO'; +import { UserModel, UserStatus, EditUserModel, Role } from './user.model'; /** * The controller for user endpoints. */ -@Controller("users") +@Controller('users') export class UserController { - constructor(private userService: UserService) {} - - @Get(":id") - public async getUser( - @Param("id") userId?: number - ): Promise { - return this.userService.getUser(userId); - } - - - @Post("/addVolunteer") - public async addVolunteer( @Body() userData: NewUserInput) { - return this.userService.postUser(userData, Role.VOLUNTEER); - } - - @Post('addAdmin') - public async addAdmin(@Body() userData: NewUserInput) { - return this.userService.postUser(userData, Role.ADMIN); - } - - @Get(":id/sites") - public async getUserSites(@Param("id") userId?: number): Promise { - return this.userService.getUserTables(userId); - } + constructor(private userService: UserService) {} + @Get(':id') + public async getUser(@Param('id') userId?: number): Promise { + return this.userService.getUser(userId); + } - @Put("/editUser/:id") - public async editUser( - @Param("id") userId?: number, - @Body() editUserModel?: EditUserModel - ): Promise { - return this.userService.editUser(userId, editUserModel); - } + @Post('/addVolunteer') + public async addVolunteer(@Body() userData: NewUserInput) { + return this.userService.postUser(userData, Role.VOLUNTEER); + } - /** - * Gets users by their status. - * @param status The status to filter users by (e.g., Approved, Pending, Denied). - * @returns A list of users with the specified status. - */ - @Get("status/:status") - @ApiParam({ - name: 'status', - description: 'The status to filter users by (e.g., Approved, Pending, Denied)', - enum: UserStatus, - }) - public async getUserByStatus( - @Param("status") status: UserStatus - ): Promise { - console.log(status); - return this.userService.getUserByStatus(status); - } + @Post('addAdmin') + public async addAdmin(@Body() userData: NewUserInput) { + return this.userService.postUser(userData, Role.ADMIN); + } + @Get(':id/sites') + public async getUserSites(@Param('id') userId?: number): Promise { + return this.userService.getUserTables(userId); + } + @Put('/editUser/:id') + public async editUser( + @Param('id') userId?: number, + @Body() editUserModel?: EditUserModel, + ): Promise { + return this.userService.editUser(userId, editUserModel); + } + /** + * Gets users by their status. + * @param status The status to filter users by (e.g., Approved, Pending, Denied). + * @returns A list of users with the specified status. + */ + @Get('status/:status') + @ApiParam({ + name: 'status', + description: + 'The status to filter users by (e.g., Approved, Pending, Denied)', + enum: UserStatus, + }) + public async getUserByStatus( + @Param('status') status: UserStatus, + ): Promise { + console.log(status); + return this.userService.getUserByStatus(status); + } + /** + * Gets users by their role. + * @param role The role to filter users by (e.g., Volunteer, Admin). + * @returns A list of users with the specified role. + */ + @Get('role/:role') + @ApiParam({ + name: 'role', + description: 'The role to filter users by (e.g., Volunteer, Admin)', + enum: Role, + }) + public async getUserByRole(@Param('role') role: Role): Promise { + console.log(role); + return this.userService.getUserByRole(role); + } } diff --git a/apps/backend/src/user/user.service.ts b/apps/backend/src/user/user.service.ts index 263b874b..b52f6b7e 100644 --- a/apps/backend/src/user/user.service.ts +++ b/apps/backend/src/user/user.service.ts @@ -3,11 +3,14 @@ import { UserModel, EditUserModel } from './user.model'; import { DynamoDbService } from '../dynamodb'; import { UserInputModel, UserStatus, Role } from './user.model'; import { NewUserInput } from '../dtos/newUserDTO'; - +import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda'; @Injectable() export class UserService { private readonly tableName = 'gibostonUsers'; + private readonly lambdaClient: LambdaClient = new LambdaClient({ + region: process.env.AWS_REGION, + }); constructor(private readonly dynamoDbService: DynamoDbService) {} /** @@ -53,7 +56,6 @@ export class UserService { } } - public async getUserTables(userId: number): Promise> { try { const key = { userId: { N: userId.toString() } }; @@ -125,7 +127,21 @@ export class UserService { if (model.status) { commands.push(`#s = :status`); expressionAttributeValues[':status'] = { S: model.status }; - expressionAttributeNames['#s'] = 'status'; // Define the alias + expressionAttributeNames['#s'] = 'status'; + if (model.status === UserStatus.DENIED) { + // Send email if status is denied + const lamdaParams = { + FunctionName: 'giSendEmail', + Payload: JSON.stringify({ + firstName: model.firstName + ? model.firstName + : originalUser.firstName, + userEmail: originalUser.email, + }), + }; + const command = new InvokeCommand(lamdaParams); + await this.lambdaClient.send(command); + } } // Make sure commands aren't empty @@ -178,6 +194,26 @@ export class UserService { } } + public async getUserByRole(role: Role): Promise { + try { + const filterExpression = '#user_role = :roleOf'; + const expressionAttributeValues = { ':roleOf': { S: role } }; + const expressionAttributeNames = { '#user_role': 'role' }; + + const data = await this.dynamoDbService.scanTable( + this.tableName, + filterExpression, + expressionAttributeValues, + expressionAttributeNames, + ); + + return data.map((item) => this.mapDynamoDBItemToUserModelV2(item)); // added data + } catch (error) { + console.error('Error fetching users by role:', error); + throw new Error(`Error fetching users by role: ${error.message}`); + } + } + /** * Maps a user's data from DynamoDB to a UserModel object. * @param objectId the user's id @@ -189,21 +225,26 @@ export class UserService { objectId: number, data?: { [key: string]: any }, ): UserModel { - const siteIds = Array.isArray(data['siteIds']?.L) - ? data['siteIds'].L.map((item) => Number(item.N)) - : []; + const getString = (key: string) => data?.[key]?.S ?? '-'; + const getNumber = (key: string) => Number(data?.[key]?.N ?? 0); + + const rawSiteIds = data?.['siteIds']; + const siteIds = + rawSiteIds && 'L' in rawSiteIds + ? rawSiteIds.L.map((item) => Number(item.N)) + : []; return { userId: objectId, - firstName: data['firstName'].S, - lastName: data['lastName'].S, - email: data['email'].S, - phoneNumber: data['phoneNumber'].N, - siteIds: siteIds, - zipCode: data['zipCode'].S, - birthDate: new Date(data['birthDate'].S), - role: data['role'].S, - status: data['status'].S, + firstName: getString('firstName'), + lastName: getString('lastName'), + email: getString('email'), + phoneNumber: getNumber('phoneNumber'), + siteIds, + zipCode: getString('zipCode'), + birthDate: new Date(getString('birthDate')), + role: getString('role') as Role, + status: getString('status') as UserStatus, }; } diff --git a/apps/frontend/src/components/adminDashboard/AdminDashboard.tsx b/apps/frontend/src/components/adminDashboard/AdminDashboard.tsx new file mode 100644 index 00000000..0397f9b0 --- /dev/null +++ b/apps/frontend/src/components/adminDashboard/AdminDashboard.tsx @@ -0,0 +1,153 @@ +import { Box } from '@mui/material'; +import FacebookIcon from '@material-ui/icons/Facebook'; +import YouTubeIcon from '@material-ui/icons/YouTube'; +//import generateInstagramIcon from './InstagramIcon'; +import { Link } from 'react-router-dom'; + +const textStyles = { + fontFamily: 'Montserrat, sans-serif', + fontSize: '40px', +}; + +const boxStyles = { + bgcolor: '#D7D7D7', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontFamily: 'Montserrat, sans-serif', + fontSize: '30px', +}; + +interface AdminDashboardProps { + setMaintenanceChecklistOpen: React.Dispatch>; +} + +function AdminDashboard({ setMaintenanceChecklistOpen }: AdminDashboardProps) { + return ( +
+ +
+ + Welcome back,{' '} + + Admin +
+ + + + + +
+
+ +
+ + + + Applications for Review + + + + + + Adoption Map + + + + Maintenance Guide + + + +
+
+ setMaintenanceChecklistOpen(true)} + style={{ cursor: 'pointer' }} + > + Maintenance Visit Checklist + +
+
+ Vid 1 + Vid 2 + Other +
+
+
+ ); +} +export default AdminDashboard; diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index 3ad4109e..d26e1200 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -8,7 +8,8 @@ import MapPage from './pages/mapPage/MapPage'; import SuccessPage from './components/volunteer/signup/SuccessPage'; import VolunteerPage from './pages/volunteerPage/VolunteerPage'; import MyAdoptedGIPage from './pages/myAdoptedGIPage/MyAdoptedGIPage'; - +import AdminPage from './pages/adminpage/AdminPage'; +import ApplicationsPage from './pages/applicationsPage/applicationsPage'; const queryClient = new QueryClient(); @@ -20,7 +21,12 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( } /> } /> } /> - } /> + } /> + } + /> + } /> } /> } /> diff --git a/apps/frontend/src/pages/adminpage/AdminPage.tsx b/apps/frontend/src/pages/adminpage/AdminPage.tsx new file mode 100644 index 00000000..93234d32 --- /dev/null +++ b/apps/frontend/src/pages/adminpage/AdminPage.tsx @@ -0,0 +1,67 @@ +import Navbar from '../Navbar'; +import VolunteerDashboard from '../../components/volunteerDashboard/VolunteerDashboard'; +import MaintenanceChecklistPopup from '../../components/volunteerDashboard/MaintenanceChecklistPopup'; +import Map from '../../components/map/Map'; +import MapLegend from '../../components/map/MapLegend'; +import { useState } from 'react'; +import { SITE_STATUS_ROADMAP } from '../../constants'; +import AdminDashboard from '../../components/adminDashboard/AdminDashboard'; + +const icons: string[] = SITE_STATUS_ROADMAP.map((option) => option.image); + +export default function AdminPage() { + const [selectedFeatures, setSelectedFeatures] = useState([]); + const [selectedStatuses, setSelectedStatuses] = useState([]); + const [maintenanceChecklistOpen, setMaintenanceChecklistOpen] = + useState(false); + + return ( +
+ +
+ + +
+ +
+ +
+
+ +
+
+
+ ); +} diff --git a/apps/frontend/src/pages/applicationsPage/Table.tsx b/apps/frontend/src/pages/applicationsPage/Table.tsx new file mode 100644 index 00000000..0e950d5f --- /dev/null +++ b/apps/frontend/src/pages/applicationsPage/Table.tsx @@ -0,0 +1,234 @@ +import { Height, VerticalAlignCenter } from '@mui/icons-material'; +import axios from 'axios'; +import { color, px } from 'framer-motion'; +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; + +export default function ApplicantsTable({ + applicants = [], +}: { + applicants?: any[]; +}) { + const [userData, setUserData] = useState<{ [key: string]: any }>({}); + const [siteData, setSiteData] = useState<{ [key: string]: any }>({}); + const [selectedStatuses, setSelectedStatuses] = useState( + [], + ); + const [filteredApplicants, setFilteredApplicants] = useState(applicants); + + enum Role { + VOLUNTEER = 'Volunteer', + ADMIN = 'Admin', + } + + enum ApplicationStatus { + APPROVED = 'Approved', + PENDING = 'Pending', + DENIED = 'Denied', + } + + const toggleStatus = (status: ApplicationStatus) => { + setSelectedStatuses((prev) => + prev.includes(status) + ? prev.filter((s) => s !== status) + : [...prev, status], + ); + }; + + useEffect(() => { + let filtered = applicants.filter( + (applicant) => userData[applicant.userId]?.role === Role.VOLUNTEER, + ); + + if (selectedStatuses.length > 0) { + filtered = filtered.filter((applicant) => + selectedStatuses.includes(applicant.status), + ); + } + + filtered.sort((a, b) => { + const dateA = new Date(a.dateApplied || 0).getTime(); + const dateB = new Date(b.dateApplied || 0).getTime(); + return dateB - dateA; + }); + + setFilteredApplicants(filtered); + }, [selectedStatuses, applicants, userData]); + + // Function to fetch user info for each applicant + const fetchUserInfo = async (userId: string) => { + if (!userId) return null; + try { + const response = await axios.get( + `${import.meta.env.VITE_API_BASE_URL}/users/${userId}`, + ); + return response.data; + } catch (error) { + console.error(`Error fetching user data for user ${userId}:`, error); + return null; + } + }; + + const fetchSiteInfo = async (siteId: string) => { + if (!siteId) return null; + try { + const response = await axios.get( + `${import.meta.env.VITE_API_BASE_URL}/sites/${siteId}`, + ); + return response.data; + } catch (error) { + console.error(`Error fetching site data for site ${siteId}:`, error); + return null; + } + }; + + useEffect(() => { + const loadUserAndSiteData = async () => { + const userMap: { [key: string]: any } = {}; + const siteMap: { [key: string]: any } = {}; + const uniqueSiteIds = new Set(); + + await Promise.all( + applicants.map(async (applicant) => { + if (applicant.userId) { + const userInfo = await fetchUserInfo(applicant.userId); + if (userInfo) { + userMap[applicant.userId] = userInfo; + } + } + + if (applicant.siteId) { + uniqueSiteIds.add(applicant.siteId); + } + }), + ); + + await Promise.all( + Array.from(uniqueSiteIds).map(async (siteId) => { + const siteInfo = await fetchSiteInfo(siteId); + if (siteInfo) { + siteMap[siteId] = siteInfo; + } + }), + ); + + setUserData(userMap); + setSiteData(siteMap); + }; + + if (applicants.length > 0) { + loadUserAndSiteData(); + } + }, [applicants]); + + const headings = { + fontFamily: 'Montserrat', + color: '#000000', + fontSize: '20px', + fontWeight: '600', + lineHeight: 'normal', + margin: '0', + }; + + const tableHeadings = { + fontFamily: 'Montserrat', + color: '#58585B', + fontSize: '16px', + fontWeight: '400', + lineHeight: 'normal', + verticalAlign: 'middle', + margin: '0', + }; + + const tableData = { + fontFamily: 'Lora', + color: '#000000', + fontSize: '16px', + fontWeight: '400', + lineHeight: 'normal', + verticalAlign: 'middle', + padding: '12px 12px', + }; + + const margLeft = { + marginLeft: '15px', + }; + + return ( +
+
+ Your Green Site Applicants +
+ + + +
+
+
+ + + + + + + + + + + + {filteredApplicants.map((applicant: any) => { + const user = userData[applicant.userId] || {}; + const site = siteData[applicant.siteId] || {}; + + return ( + + + + + + + + ); + })} + +
First NameLast NameSiteApproval StatusDate Applied
{user?.firstName || '-'}{user?.lastName || '-'} + {site?.siteName || '-'} {site?.assetType || '-'} + {applicant.status || '-'} + {applicant.dateApplied + ? new Date(applicant.dateApplied).toLocaleDateString() + : '-'} +
+
+ ); +} diff --git a/apps/frontend/src/pages/applicationsPage/applicationsPage.tsx b/apps/frontend/src/pages/applicationsPage/applicationsPage.tsx new file mode 100644 index 00000000..9be1e0dc --- /dev/null +++ b/apps/frontend/src/pages/applicationsPage/applicationsPage.tsx @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; +import Navbar from '../Navbar'; +import ApplicantsTable from './Table'; +import axios from 'axios'; + +export default function ApplicationsPage() { + const [applicants, setApplicants] = useState([]); + + const findApplicants = async () => { + const response = await axios.get( + `${import.meta.env.VITE_API_BASE_URL}/applications/applicationsInfo`, + ); + return response.data; + }; + + const fetchApplicants = async () => { + const applicants = await findApplicants(); + setApplicants(applicants); + }; + useEffect(() => { + fetchApplicants(); + }, []); + + return ( +
+ +
+ +
+ ); +} diff --git a/package.json b/package.json index e061a3ae..d2c95532 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ }, "private": true, "dependencies": { - "@aws-sdk/client-cognito-identity-provider": "^3.741.0", + "@aws-sdk/client-cognito-identity-provider": "^3.760.0", "@aws-sdk/client-dynamodb": "^3.319.0", "@aws-sdk/client-lambda": "^3.741.0", - "@aws-sdk/client-ses": "^3.738.0", + "@aws-sdk/client-ses": "^3.758.0", "@aws-sdk/util-dynamodb": "^3.319.0", "@blueprintjs/core": "^4.18.0", "@blueprintjs/table": "^4.10.1", diff --git a/yarn.lock b/yarn.lock index 185d8fab..aca0e989 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,45 +139,45 @@ "@smithy/util-utf8" "^2.0.0" tslib "^2.6.2" -"@aws-sdk/client-cognito-identity-provider@^3.741.0": - version "3.750.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.750.0.tgz#3157b1ca3d65c1272c9d2ad6eb0da0a825819025" - integrity sha512-HmBi33j/rGEHCL92Y6LM8IvrFi3WzxKiy7LbljuFSofhuJPj9+tz8PG/qI/Dc7u7pw630HEaAJ5cJBlXNKKYaQ== +"@aws-sdk/client-cognito-identity-provider@^3.760.0": + version "3.760.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.760.0.tgz#b0f3bba09ffd1ef57baaf2107e443e6b42c4bc83" + integrity sha512-nDhdT5N2l+kpq4Y1P7UEP/FB6XP5TnwTLYNNlAocKHmFyryI8YqGpCgUE/01z+/wcebQgveuP80NoidV8th2IA== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.750.0" - "@aws-sdk/credential-provider-node" "3.750.0" + "@aws-sdk/core" "3.758.0" + "@aws-sdk/credential-provider-node" "3.758.0" "@aws-sdk/middleware-host-header" "3.734.0" "@aws-sdk/middleware-logger" "3.734.0" "@aws-sdk/middleware-recursion-detection" "3.734.0" - "@aws-sdk/middleware-user-agent" "3.750.0" + "@aws-sdk/middleware-user-agent" "3.758.0" "@aws-sdk/region-config-resolver" "3.734.0" "@aws-sdk/types" "3.734.0" "@aws-sdk/util-endpoints" "3.743.0" "@aws-sdk/util-user-agent-browser" "3.734.0" - "@aws-sdk/util-user-agent-node" "3.750.0" + "@aws-sdk/util-user-agent-node" "3.758.0" "@smithy/config-resolver" "^4.0.1" - "@smithy/core" "^3.1.4" + "@smithy/core" "^3.1.5" "@smithy/fetch-http-handler" "^5.0.1" "@smithy/hash-node" "^4.0.1" "@smithy/invalid-dependency" "^4.0.1" "@smithy/middleware-content-length" "^4.0.1" - "@smithy/middleware-endpoint" "^4.0.5" - "@smithy/middleware-retry" "^4.0.6" + "@smithy/middleware-endpoint" "^4.0.6" + "@smithy/middleware-retry" "^4.0.7" "@smithy/middleware-serde" "^4.0.2" "@smithy/middleware-stack" "^4.0.1" "@smithy/node-config-provider" "^4.0.1" - "@smithy/node-http-handler" "^4.0.2" + "@smithy/node-http-handler" "^4.0.3" "@smithy/protocol-http" "^5.0.1" - "@smithy/smithy-client" "^4.1.5" + "@smithy/smithy-client" "^4.1.6" "@smithy/types" "^4.1.0" "@smithy/url-parser" "^4.0.1" "@smithy/util-base64" "^4.0.0" "@smithy/util-body-length-browser" "^4.0.0" "@smithy/util-body-length-node" "^4.0.0" - "@smithy/util-defaults-mode-browser" "^4.0.6" - "@smithy/util-defaults-mode-node" "^4.0.6" + "@smithy/util-defaults-mode-browser" "^4.0.7" + "@smithy/util-defaults-mode-node" "^4.0.7" "@smithy/util-endpoints" "^3.0.1" "@smithy/util-middleware" "^4.0.1" "@smithy/util-retry" "^4.0.1" @@ -283,45 +283,45 @@ "@smithy/util-waiter" "^4.0.2" tslib "^2.6.2" -"@aws-sdk/client-ses@^3.738.0": - version "3.750.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-ses/-/client-ses-3.750.0.tgz#89ac060f2f01a5901d7ec5c32bcb2b005cca1040" - integrity sha512-0apX2PEzT/09XiO42jNHjkszz/k2RLcIiaLbl1ngcKY1lWzMzIiGIqXw7Emei8iye2o6EsWuBG1p3k30iSyjhg== +"@aws-sdk/client-ses@^3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-ses/-/client-ses-3.758.0.tgz#0979961fb4f5a79326ecc6a2568a39b00e7eff36" + integrity sha512-cWBjZqY7SsFdTTSw3726DEPy3d7FfQ8qrw21RCukM/p3Ty42NWauHkqgxOmRygeiSY3ygHmWexc32B+4RXXqTw== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.750.0" - "@aws-sdk/credential-provider-node" "3.750.0" + "@aws-sdk/core" "3.758.0" + "@aws-sdk/credential-provider-node" "3.758.0" "@aws-sdk/middleware-host-header" "3.734.0" "@aws-sdk/middleware-logger" "3.734.0" "@aws-sdk/middleware-recursion-detection" "3.734.0" - "@aws-sdk/middleware-user-agent" "3.750.0" + "@aws-sdk/middleware-user-agent" "3.758.0" "@aws-sdk/region-config-resolver" "3.734.0" "@aws-sdk/types" "3.734.0" "@aws-sdk/util-endpoints" "3.743.0" "@aws-sdk/util-user-agent-browser" "3.734.0" - "@aws-sdk/util-user-agent-node" "3.750.0" + "@aws-sdk/util-user-agent-node" "3.758.0" "@smithy/config-resolver" "^4.0.1" - "@smithy/core" "^3.1.4" + "@smithy/core" "^3.1.5" "@smithy/fetch-http-handler" "^5.0.1" "@smithy/hash-node" "^4.0.1" "@smithy/invalid-dependency" "^4.0.1" "@smithy/middleware-content-length" "^4.0.1" - "@smithy/middleware-endpoint" "^4.0.5" - "@smithy/middleware-retry" "^4.0.6" + "@smithy/middleware-endpoint" "^4.0.6" + "@smithy/middleware-retry" "^4.0.7" "@smithy/middleware-serde" "^4.0.2" "@smithy/middleware-stack" "^4.0.1" "@smithy/node-config-provider" "^4.0.1" - "@smithy/node-http-handler" "^4.0.2" + "@smithy/node-http-handler" "^4.0.3" "@smithy/protocol-http" "^5.0.1" - "@smithy/smithy-client" "^4.1.5" + "@smithy/smithy-client" "^4.1.6" "@smithy/types" "^4.1.0" "@smithy/url-parser" "^4.0.1" "@smithy/util-base64" "^4.0.0" "@smithy/util-body-length-browser" "^4.0.0" "@smithy/util-body-length-node" "^4.0.0" - "@smithy/util-defaults-mode-browser" "^4.0.6" - "@smithy/util-defaults-mode-node" "^4.0.6" + "@smithy/util-defaults-mode-browser" "^4.0.7" + "@smithy/util-defaults-mode-node" "^4.0.7" "@smithy/util-endpoints" "^3.0.1" "@smithy/util-middleware" "^4.0.1" "@smithy/util-retry" "^4.0.1" @@ -373,6 +373,50 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" +"@aws-sdk/client-sso@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.758.0.tgz#59a249abdfa52125fbe98b1d59c11e4f08ca6527" + integrity sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.758.0" + "@aws-sdk/middleware-host-header" "3.734.0" + "@aws-sdk/middleware-logger" "3.734.0" + "@aws-sdk/middleware-recursion-detection" "3.734.0" + "@aws-sdk/middleware-user-agent" "3.758.0" + "@aws-sdk/region-config-resolver" "3.734.0" + "@aws-sdk/types" "3.734.0" + "@aws-sdk/util-endpoints" "3.743.0" + "@aws-sdk/util-user-agent-browser" "3.734.0" + "@aws-sdk/util-user-agent-node" "3.758.0" + "@smithy/config-resolver" "^4.0.1" + "@smithy/core" "^3.1.5" + "@smithy/fetch-http-handler" "^5.0.1" + "@smithy/hash-node" "^4.0.1" + "@smithy/invalid-dependency" "^4.0.1" + "@smithy/middleware-content-length" "^4.0.1" + "@smithy/middleware-endpoint" "^4.0.6" + "@smithy/middleware-retry" "^4.0.7" + "@smithy/middleware-serde" "^4.0.2" + "@smithy/middleware-stack" "^4.0.1" + "@smithy/node-config-provider" "^4.0.1" + "@smithy/node-http-handler" "^4.0.3" + "@smithy/protocol-http" "^5.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + "@smithy/url-parser" "^4.0.1" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.7" + "@smithy/util-defaults-mode-node" "^4.0.7" + "@smithy/util-endpoints" "^3.0.1" + "@smithy/util-middleware" "^4.0.1" + "@smithy/util-retry" "^4.0.1" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + "@aws-sdk/core@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.750.0.tgz#087ce3dd86e2e94e9a2828506a82223ae9f364ff" @@ -390,6 +434,23 @@ fast-xml-parser "4.4.1" tslib "^2.6.2" +"@aws-sdk/core@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.758.0.tgz#d13a4bb95de0460d5269cd5a40503c85b344b0b4" + integrity sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg== + dependencies: + "@aws-sdk/types" "3.734.0" + "@smithy/core" "^3.1.5" + "@smithy/node-config-provider" "^4.0.1" + "@smithy/property-provider" "^4.0.1" + "@smithy/protocol-http" "^5.0.1" + "@smithy/signature-v4" "^5.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + "@smithy/util-middleware" "^4.0.1" + fast-xml-parser "4.4.1" + tslib "^2.6.2" + "@aws-sdk/credential-provider-env@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.750.0.tgz#adfa47d24bb9ea0d87993c6998b1ddc38fd3444f" @@ -401,6 +462,17 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-env@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.758.0.tgz#6193d1607eedd0929640ff64013f7787f29ff6a1" + integrity sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w== + dependencies: + "@aws-sdk/core" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/property-provider" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-http@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.750.0.tgz#2879dde158dfccb21165aab95c90b7286bcdd5cf" @@ -417,6 +489,22 @@ "@smithy/util-stream" "^4.1.1" tslib "^2.6.2" +"@aws-sdk/credential-provider-http@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.758.0.tgz#f7b28d642f2ac933e81a7add08ce582b398c1635" + integrity sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q== + dependencies: + "@aws-sdk/core" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/fetch-http-handler" "^5.0.1" + "@smithy/node-http-handler" "^4.0.3" + "@smithy/property-provider" "^4.0.1" + "@smithy/protocol-http" "^5.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + "@smithy/util-stream" "^4.1.2" + tslib "^2.6.2" + "@aws-sdk/credential-provider-ini@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.750.0.tgz#5079c5732ac886d72f357c0da532749d0c7487fd" @@ -436,6 +524,25 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-ini@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.758.0.tgz#66457e71d8f5013e18111b25629c2367ed8ef116" + integrity sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw== + dependencies: + "@aws-sdk/core" "3.758.0" + "@aws-sdk/credential-provider-env" "3.758.0" + "@aws-sdk/credential-provider-http" "3.758.0" + "@aws-sdk/credential-provider-process" "3.758.0" + "@aws-sdk/credential-provider-sso" "3.758.0" + "@aws-sdk/credential-provider-web-identity" "3.758.0" + "@aws-sdk/nested-clients" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/credential-provider-imds" "^4.0.1" + "@smithy/property-provider" "^4.0.1" + "@smithy/shared-ini-file-loader" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-node@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.750.0.tgz#0eb117a287dac34040fb8cdf65d7d239b703b2ff" @@ -454,6 +561,24 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-node@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.758.0.tgz#b0a5d18e5d7f1b091fd891e2e8088578c0246cef" + integrity sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ== + dependencies: + "@aws-sdk/credential-provider-env" "3.758.0" + "@aws-sdk/credential-provider-http" "3.758.0" + "@aws-sdk/credential-provider-ini" "3.758.0" + "@aws-sdk/credential-provider-process" "3.758.0" + "@aws-sdk/credential-provider-sso" "3.758.0" + "@aws-sdk/credential-provider-web-identity" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/credential-provider-imds" "^4.0.1" + "@smithy/property-provider" "^4.0.1" + "@smithy/shared-ini-file-loader" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-process@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.750.0.tgz#04ecf72fb30dbe6b360ea9371446f13183701b5e" @@ -466,6 +591,18 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-process@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.758.0.tgz#563bfae58049afd9968ca60f61672753834ff506" + integrity sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA== + dependencies: + "@aws-sdk/core" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/property-provider" "^4.0.1" + "@smithy/shared-ini-file-loader" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-sso@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.750.0.tgz#a96afc83cfd63a957c5b9ed7913d60830c5b1f57" @@ -480,6 +617,20 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-sso@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.758.0.tgz#5098c196a2dd38ba467aca052fc5193476b8a404" + integrity sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ== + dependencies: + "@aws-sdk/client-sso" "3.758.0" + "@aws-sdk/core" "3.758.0" + "@aws-sdk/token-providers" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/property-provider" "^4.0.1" + "@smithy/shared-ini-file-loader" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-web-identity@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.750.0.tgz#2ab785cced1326f253c324d6ec10f74a02506c00" @@ -492,6 +643,18 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-web-identity@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.758.0.tgz#ea88729ee0e5de0bf5f31929d60dfd148934b6a5" + integrity sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw== + dependencies: + "@aws-sdk/core" "3.758.0" + "@aws-sdk/nested-clients" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/property-provider" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/endpoint-cache@3.723.0": version "3.723.0" resolved "https://registry.yarnpkg.com/@aws-sdk/endpoint-cache/-/endpoint-cache-3.723.0.tgz#6c5984698d3cffca4d55f5c1b14350776ee008ac" @@ -554,6 +717,19 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/middleware-user-agent@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.758.0.tgz#f3c9d2025aa55fd400acb1d699c1fbd6b4f68f34" + integrity sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg== + dependencies: + "@aws-sdk/core" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@aws-sdk/util-endpoints" "3.743.0" + "@smithy/core" "^3.1.5" + "@smithy/protocol-http" "^5.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/nested-clients@3.750.0": version "3.750.0" resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.750.0.tgz#facfef441ad78db2f544be0eb3f1f7adb16846c1" @@ -598,6 +774,50 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" +"@aws-sdk/nested-clients@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz#571c853602d38f5e8faa10178347e711e4f0e444" + integrity sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.758.0" + "@aws-sdk/middleware-host-header" "3.734.0" + "@aws-sdk/middleware-logger" "3.734.0" + "@aws-sdk/middleware-recursion-detection" "3.734.0" + "@aws-sdk/middleware-user-agent" "3.758.0" + "@aws-sdk/region-config-resolver" "3.734.0" + "@aws-sdk/types" "3.734.0" + "@aws-sdk/util-endpoints" "3.743.0" + "@aws-sdk/util-user-agent-browser" "3.734.0" + "@aws-sdk/util-user-agent-node" "3.758.0" + "@smithy/config-resolver" "^4.0.1" + "@smithy/core" "^3.1.5" + "@smithy/fetch-http-handler" "^5.0.1" + "@smithy/hash-node" "^4.0.1" + "@smithy/invalid-dependency" "^4.0.1" + "@smithy/middleware-content-length" "^4.0.1" + "@smithy/middleware-endpoint" "^4.0.6" + "@smithy/middleware-retry" "^4.0.7" + "@smithy/middleware-serde" "^4.0.2" + "@smithy/middleware-stack" "^4.0.1" + "@smithy/node-config-provider" "^4.0.1" + "@smithy/node-http-handler" "^4.0.3" + "@smithy/protocol-http" "^5.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + "@smithy/url-parser" "^4.0.1" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.7" + "@smithy/util-defaults-mode-node" "^4.0.7" + "@smithy/util-endpoints" "^3.0.1" + "@smithy/util-middleware" "^4.0.1" + "@smithy/util-retry" "^4.0.1" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + "@aws-sdk/region-config-resolver@3.734.0": version "3.734.0" resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz#45ffbc56a3e94cc5c9e0cd596b0fda60f100f70b" @@ -622,6 +842,18 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/token-providers@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz#fcab3885ba2b222ff8bb7817448d3c786dc2ddf9" + integrity sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w== + dependencies: + "@aws-sdk/nested-clients" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/property-provider" "^4.0.1" + "@smithy/shared-ini-file-loader" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@aws-sdk/types@3.734.0", "@aws-sdk/types@^3.222.0": version "3.734.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.734.0.tgz#af5e620b0e761918282aa1c8e53cac6091d169a2" @@ -675,6 +907,17 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@aws-sdk/util-user-agent-node@3.758.0": + version "3.758.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.758.0.tgz#604ccb02a5d11c9cedaea0bea279641ea9d4194d" + integrity sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw== + dependencies: + "@aws-sdk/middleware-user-agent" "3.758.0" + "@aws-sdk/types" "3.734.0" + "@smithy/node-config-provider" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.8.3": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" @@ -4114,6 +4357,20 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" +"@smithy/core@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.1.5.tgz#cc260229e45964d8354a3737bf3dedb56e373616" + integrity sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA== + dependencies: + "@smithy/middleware-serde" "^4.0.2" + "@smithy/protocol-http" "^5.0.1" + "@smithy/types" "^4.1.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-middleware" "^4.0.1" + "@smithy/util-stream" "^4.1.2" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + "@smithy/credential-provider-imds@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz#807110739982acd1588a4847b61e6edf196d004e" @@ -4236,6 +4493,20 @@ "@smithy/util-middleware" "^4.0.1" tslib "^2.6.2" +"@smithy/middleware-endpoint@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.6.tgz#7ead08fcfda92ee470786a7f458e9b59048407eb" + integrity sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg== + dependencies: + "@smithy/core" "^3.1.5" + "@smithy/middleware-serde" "^4.0.2" + "@smithy/node-config-provider" "^4.0.1" + "@smithy/shared-ini-file-loader" "^4.0.1" + "@smithy/types" "^4.1.0" + "@smithy/url-parser" "^4.0.1" + "@smithy/util-middleware" "^4.0.1" + tslib "^2.6.2" + "@smithy/middleware-retry@^4.0.6": version "4.0.6" resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.0.6.tgz#07f8259dc05835e317aaf37af7e79bae349eabb4" @@ -4251,6 +4522,21 @@ tslib "^2.6.2" uuid "^9.0.1" +"@smithy/middleware-retry@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.0.7.tgz#8bb2014842a6144f230967db502f5fe6adcd6529" + integrity sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ== + dependencies: + "@smithy/node-config-provider" "^4.0.1" + "@smithy/protocol-http" "^5.0.1" + "@smithy/service-error-classification" "^4.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + "@smithy/util-middleware" "^4.0.1" + "@smithy/util-retry" "^4.0.1" + tslib "^2.6.2" + uuid "^9.0.1" + "@smithy/middleware-serde@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz#f792d72f6ad8fa6b172e3f19c6fe1932a856a56d" @@ -4288,6 +4574,17 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@smithy/node-http-handler@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.0.3.tgz#363e1d453168b4e37e8dd456d0a368a4e413bc98" + integrity sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA== + dependencies: + "@smithy/abort-controller" "^4.0.1" + "@smithy/protocol-http" "^5.0.1" + "@smithy/querystring-builder" "^4.0.1" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@smithy/property-provider@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.0.1.tgz#8d35d5997af2a17cf15c5e921201ef6c5e3fc870" @@ -4363,6 +4660,19 @@ "@smithy/util-stream" "^4.1.1" tslib "^2.6.2" +"@smithy/smithy-client@^4.1.6": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.1.6.tgz#2183c922d086d33252012232be891f29a008d932" + integrity sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw== + dependencies: + "@smithy/core" "^3.1.5" + "@smithy/middleware-endpoint" "^4.0.6" + "@smithy/middleware-stack" "^4.0.1" + "@smithy/protocol-http" "^5.0.1" + "@smithy/types" "^4.1.0" + "@smithy/util-stream" "^4.1.2" + tslib "^2.6.2" + "@smithy/types@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.1.0.tgz#19de0b6087bccdd4182a334eb5d3d2629699370f" @@ -4436,6 +4746,17 @@ bowser "^2.11.0" tslib "^2.6.2" +"@smithy/util-defaults-mode-browser@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.7.tgz#54595ab3da6765bfb388e8e8b594276e0f485710" + integrity sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q== + dependencies: + "@smithy/property-provider" "^4.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + bowser "^2.11.0" + tslib "^2.6.2" + "@smithy/util-defaults-mode-node@^4.0.6": version "4.0.6" resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.6.tgz#213e5b32549beb48aeccbcf99cf56c97db47e70b" @@ -4449,6 +4770,19 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" +"@smithy/util-defaults-mode-node@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.7.tgz#0dea136de9096a36d84416f6af5843d866621491" + integrity sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ== + dependencies: + "@smithy/config-resolver" "^4.0.1" + "@smithy/credential-provider-imds" "^4.0.1" + "@smithy/node-config-provider" "^4.0.1" + "@smithy/property-provider" "^4.0.1" + "@smithy/smithy-client" "^4.1.6" + "@smithy/types" "^4.1.0" + tslib "^2.6.2" + "@smithy/util-endpoints@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz#44ccbf1721447966f69496c9003b87daa8f61975" @@ -4496,6 +4830,20 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" +"@smithy/util-stream@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.1.2.tgz#b867f25bc8b016de0582810a2f4092a71c5e3244" + integrity sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw== + dependencies: + "@smithy/fetch-http-handler" "^5.0.1" + "@smithy/node-http-handler" "^4.0.3" + "@smithy/types" "^4.1.0" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-buffer-from" "^4.0.0" + "@smithy/util-hex-encoding" "^4.0.0" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + "@smithy/util-uri-escape@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz#a96c160c76f3552458a44d8081fade519d214737" From 96552b697b5352276fb9726bff624c601deffd51 Mon Sep 17 00:00:00 2001 From: mahekagg <105991553+mahekagg@users.noreply.github.com> Date: Tue, 15 Apr 2025 18:49:46 -0400 Subject: [PATCH 2/6] init --- .../src/pages/applicationsPage/Table.tsx | 111 +++++++- .../pages/applicationsPage/applicantCard.tsx | 241 ++++++++++++++++++ 2 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 apps/frontend/src/pages/applicationsPage/applicantCard.tsx diff --git a/apps/frontend/src/pages/applicationsPage/Table.tsx b/apps/frontend/src/pages/applicationsPage/Table.tsx index 0e950d5f..03ea8f2c 100644 --- a/apps/frontend/src/pages/applicationsPage/Table.tsx +++ b/apps/frontend/src/pages/applicationsPage/Table.tsx @@ -3,6 +3,7 @@ import axios from 'axios'; import { color, px } from 'framer-motion'; import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; +import ApplicantCard from './applicantCard'; export default function ApplicantsTable({ applicants = [], @@ -15,6 +16,9 @@ export default function ApplicantsTable({ [], ); const [filteredApplicants, setFilteredApplicants] = useState(applicants); + const [selectedApplicant, setSelectedApplicant] = useState(null); + const [isCardOpen, setIsCardOpen] = useState(false); + const [currentApplicantIndex, setCurrentApplicantIndex] = useState(1); enum Role { VOLUNTEER = 'Volunteer', @@ -148,12 +152,93 @@ export default function ApplicantsTable({ lineHeight: 'normal', verticalAlign: 'middle', padding: '12px 12px', + cursor: 'pointer', }; const margLeft = { marginLeft: '15px', }; + const handleRowClick = (applicant: any, index: number) => { + setSelectedApplicant(applicant); + setCurrentApplicantIndex(index + 1); + setIsCardOpen(true); + }; + + const handleCloseCard = () => { + setIsCardOpen(false); + setSelectedApplicant(null); + }; + + const handleApprove = async (applicantId: string) => { + try { + await axios.put( + `${import.meta.env.VITE_API_BASE_URL}/applications/${applicantId}`, + { status: ApplicationStatus.APPROVED } + ); + + // Update local state + const updatedApplicants = filteredApplicants.map(app => + app.appId === applicantId + ? { ...app, status: ApplicationStatus.APPROVED } + : app + ); + + setFilteredApplicants(updatedApplicants); + navigateToNextApplicant(); + } catch (error) { + console.error(`Error approving application ${applicantId}:`, error); + } + }; + + const handleDeny = async (applicantId: string) => { + try { + await axios.put( + `${import.meta.env.VITE_API_BASE_URL}/applications/${applicantId}`, + { status: ApplicationStatus.DENIED } + ); + + // Update local state + const updatedApplicants = filteredApplicants.map(app => + app.appId === applicantId + ? { ...app, status: ApplicationStatus.DENIED } + : app + ); + + setFilteredApplicants(updatedApplicants); + navigateToNextApplicant(); + } catch (error) { + console.error(`Error denying application ${applicantId}:`, error); + } + }; + + const navigateToNextApplicant = () => { + const currentIndex = filteredApplicants.findIndex( + app => app.appId === selectedApplicant.appId + ); + + if (currentIndex < filteredApplicants.length - 1) { + const nextApplicant = filteredApplicants[currentIndex + 1]; + setSelectedApplicant(nextApplicant); + setCurrentApplicantIndex(currentIndex + 2); + } else { + // No more applicants to review + handleCloseCard(); + } + }; + + const navigateToPrevApplicant = () => { + const currentIndex = filteredApplicants.findIndex( + app => app.appId === selectedApplicant.appId + ); + + if (currentIndex > 0) { + const prevApplicant = filteredApplicants[currentIndex - 1]; + setSelectedApplicant(prevApplicant); + setCurrentApplicantIndex(currentIndex); + } + }; + return (
- {filteredApplicants.map((applicant: any) => { + {filteredApplicants.map((applicant: any, index: number) => { const user = userData[applicant.userId] || {}; const site = siteData[applicant.siteId] || {}; return ( - + handleRowClick(applicant, index)} + style={{ cursor: 'pointer' }} + > {user?.firstName || '-'} {user?.lastName || '-'} @@ -229,6 +318,22 @@ export default function ApplicantsTable({ })} + + {selectedApplicant && ( + + )}
); -} +} \ No newline at end of file diff --git a/apps/frontend/src/pages/applicationsPage/applicantCard.tsx b/apps/frontend/src/pages/applicationsPage/applicantCard.tsx new file mode 100644 index 00000000..c7ae41b6 --- /dev/null +++ b/apps/frontend/src/pages/applicationsPage/applicantCard.tsx @@ -0,0 +1,241 @@ +import React from 'react'; +import { Button } from '@mui/material'; + +interface ApplicantCardProps { + isOpen: boolean; + onClose: () => void; + applicant: any; + user: any; + site: any; + onApprove: (applicantId: string) => void; + onDeny: (applicantId: string) => void; + onBack: () => void; + onSkip: () => void; + currentIndex: number; + totalApplications: number; +} + +const ApplicantCard: React.FC = ({ + isOpen, + onClose, + applicant, + user, + site, + onApprove, + onDeny, + onBack, + onSkip, + currentIndex, + totalApplications, +}) => { + if (!isOpen || !applicant || !user) return null; + + const overlayStyle: React.CSSProperties = { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + zIndex: 1000, + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + }; + + const cardStyle: React.CSSProperties = { + backgroundColor: 'white', + width: '100%', + maxWidth: '450px', + height: '85vh', + margin: '7.5vh 0', + overflowY: 'auto', + borderTopLeftRadius: '8px', + borderBottomLeftRadius: '8px', + boxShadow: '-4px 0px 12px rgba(0, 0, 0, 0.15)', + fontFamily: 'Montserrat, sans-serif', + marginRight: '0', + }; + + const headerStyle: React.CSSProperties = { + padding: '20px', + borderBottom: '1px solid #eee', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }; + + const closeButtonStyle: React.CSSProperties = { + cursor: 'pointer', + fontSize: '32px', + fontWeight: 'bold', + color: '#333', + width: '40px', + height: '40px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '50%', + backgroundColor: '#f5f5f5', + transition: 'background-color 0.2s', + }; + + const contentStyle: React.CSSProperties = { + padding: '20px', + }; + + const sectionStyle: React.CSSProperties = { + marginBottom: '24px', + }; + + const labelStyle: React.CSSProperties = { + color: '#58585B', + fontSize: '16px', + marginBottom: '4px', + }; + + const valueStyle: React.CSSProperties = { + fontSize: '18px', + fontFamily: 'Lora, serif', + marginBottom: '4px', + }; + + const mapContainerStyle: React.CSSProperties = { + width: '100%', + height: '200px', + backgroundColor: '#eee', + marginBottom: '20px', + position: 'relative', + }; + + const siteInfoStyle: React.CSSProperties = { + position: 'absolute', + bottom: '0', + left: '0', + backgroundColor: 'rgba(33, 84, 63, 0.9)', + color: 'white', + padding: '12px', + width: '150px', + height: '150px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + }; + + const siteNameStyle: React.CSSProperties = { + textAlign: 'center', + fontSize: '14px', + marginBottom: '8px', + }; + + const actionButtonStyle: React.CSSProperties = { + width: '48%', + padding: '12px 0', + borderRadius: '4px', + fontSize: '16px', + fontWeight: 'bold', + textTransform: 'none', + fontFamily: 'Montserrat, sans-serif', + }; + + const actionContainerStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + marginBottom: '20px', + }; + + const navigationStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + padding: '0 0 20px', + }; + + const navButtonStyle: React.CSSProperties = { + color: '#21543F', + fontSize: '16px', + cursor: 'pointer', + fontFamily: 'Montserrat, sans-serif', + fontWeight: 'bold', + }; + + return ( +
+
+
+

Application Overview

+
×
+
+ +
+

{currentIndex} of {totalApplications} Applications

+
+ +
+ {/* Map would go here - showing as placeholder */} +
+
GREEN SITE
+
+ {site?.assetType?.toUpperCase() || 'RAIN GARDEN'} +
+
+
+ +
+
+
Site
+
{site?.siteName || 'Rain Garden 1'}
+
+ +
+
Volunteer Name
+
{`${user.firstName || ''} ${user.lastName || ''}`}
+
+ +
+
Email Address
+
{user.email || 'Not provided'}
+
+ +
+
Phone Number
+
{user.phoneNumber || 'Not provided'}
+
+ +
+
Birth Year
+
{user.birthYear || 'Not provided'}
+
+ +
+
Zip Code
+
{user.zipCode || 'Not provided'}
+
+ +
+ + +
+ +
+
Back
+
Skip
+
+
+
+
+ ); +}; + +export default ApplicantCard; \ No newline at end of file From 0fc725c6f3f43419615675c37d4fdb0ea23ef735 Mon Sep 17 00:00:00 2001 From: hams7504 Date: Tue, 15 Apr 2025 19:16:23 -0400 Subject: [PATCH 3/6] axios calls work now --- .../src/pages/applicationsPage/Table.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/pages/applicationsPage/Table.tsx b/apps/frontend/src/pages/applicationsPage/Table.tsx index 03ea8f2c..cd4c8283 100644 --- a/apps/frontend/src/pages/applicationsPage/Table.tsx +++ b/apps/frontend/src/pages/applicationsPage/Table.tsx @@ -173,9 +173,14 @@ export default function ApplicantsTable({ const handleApprove = async (applicantId: string) => { try { await axios.put( - `${import.meta.env.VITE_API_BASE_URL}/applications/${applicantId}`, - { status: ApplicationStatus.APPROVED } + `${import.meta.env.VITE_API_BASE_URL}/applications/editApplication/${applicantId}`, + null, + { + params: { applicationStatus: ApplicationStatus.APPROVED } + } ); + + console.log() // Update local state const updatedApplicants = filteredApplicants.map(app => @@ -194,8 +199,11 @@ export default function ApplicantsTable({ const handleDeny = async (applicantId: string) => { try { await axios.put( - `${import.meta.env.VITE_API_BASE_URL}/applications/${applicantId}`, - { status: ApplicationStatus.DENIED } + `${import.meta.env.VITE_API_BASE_URL}/applications/editApplication/${applicantId}`, + null, + { + params: { applicationStatus: ApplicationStatus.DENIED } + } ); // Update local state From 7f6ceacc7e8cfb53d83c1543a6f00705b2b0724c Mon Sep 17 00:00:00 2001 From: hams7504 Date: Tue, 15 Apr 2025 19:41:43 -0400 Subject: [PATCH 4/6] set site status to adopted --- apps/frontend/src/pages/applicationsPage/Table.tsx | 8 ++++++-- .../frontend/src/pages/applicationsPage/applicantCard.tsx | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/pages/applicationsPage/Table.tsx b/apps/frontend/src/pages/applicationsPage/Table.tsx index cd4c8283..5ed3f875 100644 --- a/apps/frontend/src/pages/applicationsPage/Table.tsx +++ b/apps/frontend/src/pages/applicationsPage/Table.tsx @@ -170,7 +170,7 @@ export default function ApplicantsTable({ setSelectedApplicant(null); }; - const handleApprove = async (applicantId: string) => { + const handleApprove = async (applicantId: string, siteId: string) => { try { await axios.put( `${import.meta.env.VITE_API_BASE_URL}/applications/editApplication/${applicantId}`, @@ -180,7 +180,11 @@ export default function ApplicantsTable({ } ); - console.log() + console.log(siteId) + + await axios.put( + `${import.meta.env.VITE_API_BASE_URL}/sites/adopt/${siteId}` + ); // Update local state const updatedApplicants = filteredApplicants.map(app => diff --git a/apps/frontend/src/pages/applicationsPage/applicantCard.tsx b/apps/frontend/src/pages/applicationsPage/applicantCard.tsx index c7ae41b6..0e542bd2 100644 --- a/apps/frontend/src/pages/applicationsPage/applicantCard.tsx +++ b/apps/frontend/src/pages/applicationsPage/applicantCard.tsx @@ -7,7 +7,7 @@ interface ApplicantCardProps { applicant: any; user: any; site: any; - onApprove: (applicantId: string) => void; + onApprove: (applicantId: string, siteId: string) => void; onDeny: (applicantId: string) => void; onBack: () => void; onSkip: () => void; @@ -222,7 +222,7 @@ const ApplicantCard: React.FC = ({ From f6a3c068028f7bac578df5f48d61c6e522e1c466 Mon Sep 17 00:00:00 2001 From: mahekagg <105991553+mahekagg@users.noreply.github.com> Date: Tue, 15 Apr 2025 23:27:14 -0400 Subject: [PATCH 5/6] updated formatting --- apps/frontend/src/pages/Navbar.tsx | 2 +- .../pages/applicationsPage/applicantCard.tsx | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/frontend/src/pages/Navbar.tsx b/apps/frontend/src/pages/Navbar.tsx index d99116d0..6fead9ab 100644 --- a/apps/frontend/src/pages/Navbar.tsx +++ b/apps/frontend/src/pages/Navbar.tsx @@ -39,7 +39,7 @@ function Navbar() { > = ({ }) => { if (!isOpen || !applicant || !user) return null; + const isPending = applicant.status === 'Pending'; + + + const handleApprove = () => { + onApprove(applicant.appId, applicant.siteId); + onClose(); // Close current card + }; + + const handleDeny = () => { + onDeny(applicant.appId); + onClose(); // Close current card + }; + const overlayStyle: React.CSSProperties = { position: 'fixed', top: 0, @@ -47,8 +60,8 @@ const ApplicantCard: React.FC = ({ backgroundColor: 'white', width: '100%', maxWidth: '450px', - height: '85vh', - margin: '7.5vh 0', + height: 'calc(100vh - 150px)', + margin: '100px 0 50px 0', overflowY: 'auto', borderTopLeftRadius: '8px', borderBottomLeftRadius: '8px', @@ -85,7 +98,7 @@ const ApplicantCard: React.FC = ({ }; const sectionStyle: React.CSSProperties = { - marginBottom: '24px', + marginBottom: '15px', }; const labelStyle: React.CSSProperties = { @@ -102,7 +115,7 @@ const ApplicantCard: React.FC = ({ const mapContainerStyle: React.CSSProperties = { width: '100%', - height: '200px', + height: '180px', backgroundColor: '#eee', marginBottom: '20px', position: 'relative', @@ -110,8 +123,9 @@ const ApplicantCard: React.FC = ({ const siteInfoStyle: React.CSSProperties = { position: 'absolute', - bottom: '0', - left: '0', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', backgroundColor: 'rgba(33, 84, 63, 0.9)', color: 'white', padding: '12px', From 9568b5741a43daac42fe1d70b4c036e4d6d357cc Mon Sep 17 00:00:00 2001 From: mahekagg <105991553+mahekagg@users.noreply.github.com> Date: Wed, 16 Apr 2025 00:45:20 -0400 Subject: [PATCH 6/6] change success page text --- apps/frontend/src/components/volunteer/signup/SuccessPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/frontend/src/components/volunteer/signup/SuccessPage.tsx b/apps/frontend/src/components/volunteer/signup/SuccessPage.tsx index 184650ea..d761a792 100644 --- a/apps/frontend/src/components/volunteer/signup/SuccessPage.tsx +++ b/apps/frontend/src/components/volunteer/signup/SuccessPage.tsx @@ -22,8 +22,7 @@ const SuccessPage = () => { {/* Use img tag with src attribute */}

Thank you!

- Please verify your account with
the link sent to your - email. + Thank you for submitting an application to adopt a site! We will contact you via email soon regarding the status of your site. In the meantime, please do not hesitate to reach out to the Office of Green Infrastructure if you have any questions.