Skip to content

Commit

Permalink
Implemented Admin Page Pending-Buildings and Contact Questions Status
Browse files Browse the repository at this point in the history
- Implemented change status buttons to data cards
- Implemented status update endpoints
  • Loading branch information
CasperL1218 committed Feb 25, 2025
1 parent ed2c756 commit a9737af
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 52 deletions.
67 changes: 65 additions & 2 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
ApartmentWithLabel,
ApartmentWithId,
CantFindApartmentForm,
CantFindApartmentFormWithId,
QuestionForm,
QuestionFormWithId,
LocationTravelTimes,
} from '@common/types/db-types';
// Import Firebase configuration and types
Expand Down Expand Up @@ -476,7 +478,7 @@ app.get('/api/pending-buildings/:status', async (req, res) => {
const apartments: CantFindApartmentForm[] = apartmentDocs.map((doc) => {
const data = doc.data();
const apartment = { ...data, date: data.date.toDate() } as CantFindApartmentForm;
return { ...apartment, id: doc.id } as CantFindApartmentForm;
return { ...apartment, id: doc.id } as CantFindApartmentFormWithId;
});
res.status(200).send(JSON.stringify(apartments));
});
Expand All @@ -487,7 +489,7 @@ app.get('/api/contact-questions/:status', async (req, res) => {
const questions: QuestionForm[] = questionDocs.map((doc) => {
const data = doc.data();
const question = { ...data, date: data.date.toDate() } as QuestionForm;
return { ...question, id: doc.id } as QuestionForm;
return { ...question, id: doc.id } as QuestionFormWithId;
});
res.status(200).send(JSON.stringify(questions));
});
Expand Down Expand Up @@ -1015,6 +1017,37 @@ app.post('/api/add-pending-building', authenticate, async (req, res) => {
}
});

// API endpoint to update the status of a pending building report.
app.put(
'/api/update-pending-building-status/:buildingId/:newStatus',
authenticate,
async (req, res) => {
try {
const { buildingId, newStatus } = req.params;

const validStatuses = ['PENDING', 'COMPLETED', 'DELETED'];
if (!validStatuses.includes(newStatus)) {
res.status(400).send('Error: Invalid status type');
return;
}

const doc = pendingBuildingsCollection.doc(buildingId);

const docSnapshot = await doc.get();
if (!docSnapshot.exists) {
res.status(404).send('Error: Building not found');
return;
}

await doc.update({ status: newStatus });
res.status(200).send('Success');
} catch (err) {
console.error(err);
res.status(500).send('Error updating building status');
}
}
);

// API endpoint to submit a "Ask Us A Question" form.
app.post('/api/add-contact-question', authenticate, async (req, res) => {
try {
Expand All @@ -1031,6 +1064,36 @@ app.post('/api/add-contact-question', authenticate, async (req, res) => {
}
});

// API endpoint to update the status of a contact question.
app.put(
'/api/update-contact-question-status/:questionId/:newStatus',
authenticate,
async (req, res) => {
try {
const { questionId, newStatus } = req.params;

const validStatuses = ['PENDING', 'COMPLETED', 'DELETED'];
if (!validStatuses.includes(newStatus)) {
res.status(400).send('Error: Invalid status type');
return;
}

const doc = contactQuestionsCollection.doc(questionId);
const docSnapshot = await doc.get();
if (!docSnapshot.exists) {
res.status(404).send('Error: Question not found');
return;
}

await doc.update({ status: newStatus });
res.status(200).send('Success');
} catch (err) {
console.error(err);
res.status(500).send('Error updating question status');
}
}
);

const { REACT_APP_MAPS_API_KEY } = process.env;
const LANDMARKS = {
eng_quad: '42.4445,-76.4836', // Duffield Hall
Expand Down
4 changes: 4 additions & 0 deletions common/types/db-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ export type CantFindApartmentForm = {
readonly userId?: string | null;
};

export type CantFindApartmentFormWithId = CantFindApartmentForm & Id;

export type QuestionForm = {
readonly date: Date;
readonly name: string;
readonly email: string;
readonly msg: string;
readonly userId?: string | null;
};

export type QuestionFormWithId = QuestionForm & Id;
122 changes: 92 additions & 30 deletions frontend/src/components/Admin/AdminCantFindApt.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import React, { ReactElement } from 'react';
import { Card, CardContent, CardMedia, Grid, Typography } from '@material-ui/core';
import {
Box,
Button,
Card,
CardActions,
CardContent,
CardMedia,
Grid,
Typography,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
import { createAuthHeaders, getUser } from '../../utils/firebase';
import { colors } from '../../colors';
import axios from 'axios';

const useStyles = makeStyles(() => ({
root: {
borderRadius: '10px',
},
cardContent: {
border: '1px solid #e0e0e0',
borderRadius: '10px',
},
image: {
maxWidth: '30%',
height: 'auto',
Expand Down Expand Up @@ -44,6 +51,9 @@ const useStyles = makeStyles(() => ({
* Component Props for AdminCantFindApt.
*/
type Props = {
/** The ID of the pending building report. */
readonly pending_building_id: string;

/** The date of the report. */
readonly date: Date;

Expand All @@ -58,6 +68,9 @@ type Props = {

/** Function to trigger the photo carousel. */
readonly triggerPhotoCarousel: (photos: readonly string[], startIndex: number) => void;

/** Function to toggle the display. */
readonly setToggle: React.Dispatch<React.SetStateAction<boolean>>;
};

/**
Expand All @@ -77,46 +90,95 @@ type Props = {
* @returns {ReactElement} A Material-UI Card component displaying the apartment submission details
*/
const AdminCantFindApt = ({
pending_building_id,
date,
apartmentName,
apartmentAddress,
photos = [],
triggerPhotoCarousel,
setToggle,
}: Props): ReactElement => {
const { root, cardContent, image, photoStyle, photoRowStyle } = useStyles();
const { root, image, photoStyle, photoRowStyle } = useStyles();

Check warning on line 101 in frontend/src/components/Admin/AdminCantFindApt.tsx

View workflow job for this annotation

GitHub Actions / lint

'image' is assigned a value but never used
const formattedDate = new Date(date).toLocaleString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
});

/**
* Change the status of the pending building report and trigger a re-render.
*
* @param pending_building_id - The ID of the pending building report.
* @param newStatus - The new status for the pending building report.
* @returns A promise representing the completion of the operation.
*/
const changeStatus = async (pending_building_id: string, newStatus: string) => {
const endpoint = `/api/update-pending-building-status/${pending_building_id}/${newStatus}`;
let user = await getUser(true);
if (user) {
const token = await user.getIdToken(true);
await axios.put(endpoint, {}, createAuthHeaders(token));
setToggle((cur) => !cur);
}
};

return (
<Card className={root}>
<CardContent className={cardContent}>
<Typography variant="h6">Apartment Name: {apartmentName}</Typography>
<Typography variant="body1">Address: {apartmentAddress}</Typography>
<Typography variant="body1">Filed Date: {formattedDate}</Typography>
{photos.length > 0 && (
<Grid container>
<Grid item className={photoRowStyle}>
{photos.map((photo, i) => {
return (
<CardMedia
component="img"
alt="Apt image"
image={photo}
title="Apt image"
className={photoStyle}
onClick={() => triggerPhotoCarousel(photos, i)}
loading="lazy"
/>
);
})}
<Card className={root} variant="outlined">
<Box minHeight="150px">
<CardContent>
<Typography variant="h6">Apartment Name: {apartmentName}</Typography>
<Typography variant="body1">Address: {apartmentAddress}</Typography>
<Typography variant="body1">Filed Date: {formattedDate}</Typography>
{photos.length > 0 && (
<Grid container>
<Grid item className={photoRowStyle}>
{photos.map((photo, i) => {
return (
<CardMedia
component="img"
alt="Apt image"
image={photo}
title="Apt image"
className={photoStyle}
onClick={() => triggerPhotoCarousel(photos, i)}
loading="lazy"
/>
);
})}
</Grid>
</Grid>
)}
</CardContent>
</Box>

<CardActions>
<Grid container spacing={2} alignItems="center" justifyContent="flex-end">
{
<Grid item>
<Button
onClick={() => changeStatus(pending_building_id, 'DELETED')}
variant="contained"
style={{ color: colors.black }}
>
<strong>Delete</strong>
</Button>
</Grid>
}
{
<Grid item>
<Button
onClick={() => changeStatus(pending_building_id, 'COMPLETED')}
variant="outlined"
style={{ backgroundColor: colors.green1 }}
>
<strong>Done</strong>
</Button>
</Grid>
</Grid>
)}
</CardContent>
}
</Grid>
</CardActions>
</Card>
);
};
Expand Down
Loading

0 comments on commit a9737af

Please sign in to comment.