Skip to content

Commit

Permalink
Merge branch 'main' into moveAvgRatingsLandlordInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrus14 authored Oct 20, 2023
2 parents 16a14c0 + 7ee33ff commit 56cdaf1
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 15 deletions.
23 changes: 22 additions & 1 deletion backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ app.get('/api/review/:status', async (req, res) => {
res.status(200).send(JSON.stringify(reviews));
});

/**
* Takes in the location type in the URL and returns the number of reviews made forr that location
*/
app.get('/api/review/:location/count', async (req, res) => {
const { location } = req.params;
const buildingsByLocation = (await buildingsCollection.where('area', '==', location).get()).docs;
// get IDs for buildings and filter reviews by this
const buildingIds = buildingsByLocation.map((doc) => doc.id);
const reviewDocs = (await reviewCollection.where('status', '==', 'APPROVED').get()).docs;
const reviews: Review[] = reviewDocs.map((doc) => {
const data = doc.data();
const review = { ...data, date: data.date.toDate() } as ReviewInternal;
return { ...review, id: doc.id } as ReviewWithId;
});
// add the counts together after data is fetched
const approvedReviewCount = reviews.filter((review) =>
buildingIds.includes(review.aptId ? review.aptId : '0')
).length;
res.status(200).send(JSON.stringify({ count: approvedReviewCount }));
});

app.get('/api/apts/:ids', async (req, res) => {
try {
const { ids } = req.params;
Expand Down Expand Up @@ -300,7 +321,7 @@ app.post('/api/remove-like', authenticate, likeHandler(true));

app.put('/api/update-review-status/:reviewDocId/:newStatus', async (req, res) => {
const { reviewDocId, newStatus } = req.params;
const statusList = ['PENDING', 'APPROVED', 'DECLINED'];
const statusList = ['PENDING', 'APPROVED', 'DECLINED', 'DELETED'];
try {
if (!statusList.includes(newStatus)) {
res.status(400).send('Invalid status type');
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/components/Admin/AdminReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import axios from 'axios';
type Props = {
readonly review: ReviewWithId;
readonly setToggle: React.Dispatch<React.SetStateAction<boolean>>;
readonly declinedSection: boolean;
};

export type RatingInfo = {
Expand Down Expand Up @@ -61,7 +62,7 @@ const useStyles = makeStyles(() => ({
},
}));

const AdminReviewComponent = ({ review, setToggle }: Props): ReactElement => {
const AdminReviewComponent = ({ review, setToggle, declinedSection }: Props): ReactElement => {
const { detailedRatings, overallRating, date, reviewText, photos } = review;
const formattedDate = format(new Date(date), 'MMM dd, yyyy').toUpperCase();
const { root, dateText, ratingInfo, photoStyle, photoRowStyle } = useStyles();
Expand Down Expand Up @@ -169,6 +170,17 @@ const AdminReviewComponent = ({ review, setToggle }: Props): ReactElement => {

<CardActions>
<Grid container spacing={2} alignItems="center" justifyContent="flex-end">
{declinedSection && (
<Grid item>
<Button
onClick={() => changeStatus('DELETED')}
variant="contained"
style={{ color: colors.black }}
>
<strong>Delete</strong>
</Button>
</Grid>
)}
<Grid item>
<Button
onClick={() => changeStatus('DECLINED')}
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/components/Review/PropertyInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const useStyles = makeStyles({
card: {
borderRadius: '10px',
backgroundColor: colors.red6,
cursor: 'pointer', // Add cursor pointer to make it clickable
},
reviewNum: {
fontWeight: 700,
Expand All @@ -39,14 +40,26 @@ const PropertyCard = ({ buildingData, numReviews, company }: CardProps): ReactEl
const { id, name, address } = buildingData;
const [reviewData, setReviewData] = useState<ReviewWithId[]>([]);

/**
* Handles clicking on the Apartment card. Scrolls up on the window, upward
* to reach the top with a smooth behavior. The top is defined as 0 since this is
* where we want to reach.
*/
const handleCardClick = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
};

useEffect(() => {
get<ReviewWithId[]>(`/api/review/aptId/${id}/APPROVED`, {
callback: setReviewData,
});
}, [id]);

return (
<Card className={card}>
<Card className={card} onClick={handleCardClick}>
<CardContent>
<Grid container direction="row" alignItems="center">
<Grid item>
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/utils/NavBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ function GetButtonColor(lab: string) {
: 'primary';
}

/**
* NavBar Component
*
* This component is the navigation bar that is used on all pages throughout the CUApts website. It provides routing to the Home and FAQ pages
* and the Login/Sign Out buttons.
* @param headersData: An array of objects representing navigation links. Each object should have label (string) and href (string) properties.
* @param user: (firebase.User | null) The current user object, can be null if the user is not authenticated.
* @param setUser: function to set user.
* @returns the NavBar component.
*/

const NavBar = ({ headersData, user, setUser }: Props): ReactElement => {
const initialUserState = !user ? 'Sign In' : 'Sign Out';
const [buttonText, setButtonText] = useState(initialUserState);
Expand Down
61 changes: 53 additions & 8 deletions frontend/src/pages/AdminPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,43 @@ const useStyles = makeStyles((theme) => ({
const AdminPage = (): ReactElement => {
const [pendingData, setPendingData] = useState<ReviewWithId[]>([]);
const [declinedData, setDeclinedData] = useState<ReviewWithId[]>([]);
const [approvedData, setApprovedData] = useState<ReviewWithId[]>([]);

type ReviewCount = { count: number };
const [ctownReviewCount, setCtownReviewCount] = useState<ReviewCount>({ count: 0 });
const [westReviewCount, setWestReviewCount] = useState<ReviewCount>({ count: 0 });
const [dtownReviewCount, setDtownReviewCount] = useState<ReviewCount>({ count: 0 });
const [northReviewCount, setNorthReviewCount] = useState<ReviewCount>({ count: 0 });
const [toggle, setToggle] = useState(false);

const { container } = useStyles();

// calls the APIs and the callback function to set the reviews for each review type
useEffect(() => {
get<ReviewWithId[]>(`/api/review/PENDING`, {
callback: setPendingData,
const reviewTypes = new Map<string, React.Dispatch<React.SetStateAction<ReviewWithId[]>>>([
['PENDING', setPendingData],
['DECLINED', setDeclinedData],
['APPROVED', setApprovedData],
]);
reviewTypes.forEach((cllbck, reviewType) => {
get<ReviewWithId[]>(`/api/review/${reviewType}`, {
callback: cllbck,
});
});
}, [toggle]);

// sets counts for each location
useEffect(() => {
get<ReviewWithId[]>(`/api/review/DECLINED`, {
callback: setDeclinedData,
const reviewCounts = new Map<string, React.Dispatch<React.SetStateAction<ReviewCount>>>([
['COLLEGETOWN', setCtownReviewCount],
['DOWNTOWN', setDtownReviewCount],
['WEST', setWestReviewCount],
['NORTH', setNorthReviewCount],
]);
reviewCounts.forEach((cllbck, location) => {
get<ReviewCount>(`/api/review/${location}/count`, {
callback: cllbck,
});
});
}, [toggle]);

Expand All @@ -44,25 +69,45 @@ const AdminPage = (): ReactElement => {
<Grid container spacing={5} justifyContent="center">
<Grid item xs={12} sm={12}>
<Typography variant="h3">
<strong>Pending Reviews</strong>
<strong>Review Counts</strong>
</Typography>
<ul>
<li>Total: {approvedData.length}</li>
<li>Collegetown: {ctownReviewCount.count}</li>
<li>West: {westReviewCount.count}</li>
<li>Downtown: {dtownReviewCount.count}</li>
<li>North: {northReviewCount.count}</li>
</ul>
</Grid>
<Grid item xs={12} sm={12}>
<Typography variant="h3">
<strong>Pending Reviews ({pendingData.length})</strong>
</Typography>
<Grid container item spacing={3}>
{sortReviews(pendingData, 'date').map((review, index) => (
<Grid item xs={12} key={index}>
<AdminReviewComponent review={review} setToggle={setToggle} />
<AdminReviewComponent
review={review}
setToggle={setToggle}
declinedSection={false}
/>
</Grid>
))}
</Grid>
</Grid>

<Grid item xs={12} sm={12}>
<Typography variant="h3">
<strong>Declined Reviews</strong>
<strong>Declined Reviews ({declinedData.length})</strong>
</Typography>
<Grid container item spacing={3}>
{sortReviews(declinedData, 'date').map((review, index) => (
<Grid item xs={12} key={index}>
<AdminReviewComponent review={review} setToggle={setToggle} />
<AdminReviewComponent
review={review}
setToggle={setToggle}
declinedSection={true}
/>
</Grid>
))}
</Grid>
Expand Down
28 changes: 27 additions & 1 deletion frontend/src/pages/ApartmentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ const useStyles = makeStyles((theme) => ({
},
}));

/**
* ApartmentPage Component
*
* This component represents a page for viewing and leaving reviews for apartments.
* It displays apartment information, reviews, and provides functionality to leave new reviews,
* sort reviews, and interact with existing reviews (like/dislike). Additionally, it contains information
* about the landloard and other related properties.
*
* @component
* @param props - The props for the ApartmentPage component.
* @param user props.user - The current user, null if not logged in.
* @param setUser - Function to set the current user.
* @returns ApartmentPage The ApartmentPage component.
*/
const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
const { aptId } = useParams<Record<string, string>>();
const [landlordData, setLandlordData] = useState<Landlord>();
Expand All @@ -133,6 +147,7 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
const [isClicked, setIsClicked] = useState<boolean>(true);
const [resultsToShow, setResultsToShow] = useState<number>(reviewData.length);

// Set the number of results to show based on mobile or desktop view.
useEffect(() => {
if (isMobile) {
setResultsToShow(5);
Expand All @@ -145,6 +160,8 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
// setResultsToShow(resultsToShow + 5);
// };


// Set 'notFound' to true when a page is not found.
const handlePageNotFound = () => {
setNotFound(true);
};
Expand All @@ -161,28 +178,33 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
expandOpen,
} = useStyles();

// Set the page title based on whether apartment data is loaded.
useTitle(
() => (loaded && apt !== undefined ? `${apt.name}` : 'Apartment Reviews'),
[loaded, apt]
);

// Fetch apartment data based on the 'aptId' parameter and handle page not found error.
useEffect(() => {
get<ApartmentWithId[]>(`/api/apts/${aptId}`, {
callback: setAptData,
errorHandler: handlePageNotFound,
});
}, [aptId]);

// Set the apartment data once it's fetched.
useEffect(() => {
setApt(aptData[0]);
}, [aptData]);

// Fetch approved reviews for the current apartment.
useEffect(() => {
get<ReviewWithId[]>(`/api/review/aptId/${aptId}/APPROVED`, {
callback: setReviewData,
});
}, [aptId, showConfirmation, toggle]);

// Fetch information about buildings related to the apartment and the landlord's data.
useEffect(() => {
get<Apartment[]>(`/api/buildings/${apt?.landlordId}`, {
callback: setBuildings,
Expand All @@ -193,6 +215,7 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
});
}, [apt]);

// Handle resizing of the window depending on mobile and if it is clicked.
useEffect(() => {
const handleResize = () => {
setIsClicked(window.innerWidth <= 600);
Expand All @@ -203,10 +226,12 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
return () => window.removeEventListener('resize', handleResize);
}, []);

// Set the average rating after calculating it from the data.
useEffect(() => {
setAveRatingInfo(calculateAveRating(reviewData));
}, [reviewData]);

// If all the information is there, then setLoaded to be true.
useEffect(() => {
if (
aptData &&
Expand All @@ -221,6 +246,7 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
}
}, [aptData, apt, landlordData, buildings, reviewData, aveRatingInfo, otherProperties]);

// Use setLikedReviews to indicate the number of likes.
useEffect(() => {
return subscribeLikes(setLikedReviews);
}, []);
Expand Down Expand Up @@ -553,7 +579,7 @@ const ApartmentPage = ({ user, setUser }: Props): ReactElement => {
<Toast
isOpen={showConfirmation}
severity="success"
message="Review successfully submitted!"
message="Review submitted! Your review is awaiting approval from the admin."
time={toastTime}
/>
)}
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4116,9 +4116,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001286:
version "1.0.30001431"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz"
integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==
version "1.0.30001525"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz"
integrity sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==

capture-exit@^2.0.0:
version "2.0.0"
Expand Down

0 comments on commit 56cdaf1

Please sign in to comment.