Skip to content

Commit dd54d1b

Browse files
authored
Merge pull request #364 from csc301-2025-s/frontend-tests
Frontend tests + Added functionality to trash can icon for row deletion in dashboard table
2 parents e5a642e + 8375f60 commit dd54d1b

20 files changed

+1192
-408
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ node_modules/
6666

6767
# Testing
6868
/coverage
69+
frontend/test-results
6970

7071
# Production
7172
/build

backend/routes/email_routes.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,41 @@ def query_emails(request: Request, user_id: str = Depends(validate_session)) ->
8181
logger.error(f"Error fetching emails for user_id {user_id}: {e}")
8282
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
8383

84+
85+
@router.delete("/delete-email/{email_id}")
86+
async def delete_email(email_id: str, user_id: str = Depends(validate_session)):
87+
"""
88+
Delete an email record by its ID for the authenticated user.
89+
"""
90+
with Session(engine) as session:
91+
try:
92+
# Query the email record to ensure it exists and belongs to the user
93+
email_record = session.exec(
94+
select(UserEmails).where(
95+
(UserEmails.id == email_id) & (UserEmails.user_id == user_id)
96+
)
97+
).first()
98+
99+
if not email_record:
100+
logger.warning(f"Email with id {email_id} not found for user_id {user_id}")
101+
raise HTTPException(
102+
status_code=404, detail=f"Email with id {email_id} not found"
103+
)
104+
105+
# Delete the email record
106+
session.delete(email_record)
107+
session.commit()
108+
109+
logger.info(f"Email with id {email_id} deleted successfully for user_id {user_id}")
110+
return {"message": "Item deleted successfully"}
111+
112+
except Exception as e:
113+
logger.error(f"Error deleting email with id {email_id} for user_id {user_id}: {e}")
114+
raise HTTPException(
115+
status_code=500, detail=f"Failed to delete email: {str(e)}"
116+
)
117+
118+
84119
@router.post("/fetch-emails")
85120
@limiter.limit("5/minute")
86121
async def start_fetch_emails(

frontend/app/dashboard/page.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,35 @@ export default function Dashboard() {
176176
}
177177
}
178178

179+
const handleRemoveItem = async (id: string) => {
180+
try {
181+
// Make a DELETE request to the backend
182+
const response = await fetch(`${apiUrl}/delete-email/${id}`, {
183+
method: "DELETE",
184+
credentials: "include" // Include cookies for authentication
185+
});
186+
187+
if (!response.ok) {
188+
throw new Error("Failed to delete the item");
189+
}
190+
191+
// If the deletion is successful, update the local state
192+
setData((prevData) => prevData.filter((item) => item.id !== id));
193+
194+
addToast({
195+
title: "Item removed successfully",
196+
color: "success"
197+
});
198+
} catch (error) {
199+
console.error("Error deleting item:", error);
200+
addToast({
201+
title: "Failed to remove item",
202+
description: "Please try again or contact support.",
203+
color: "danger"
204+
});
205+
}
206+
};
207+
179208
return (
180209
<JobApplicationsDashboard
181210
currentPage={currentPage}
@@ -187,6 +216,7 @@ export default function Dashboard() {
187216
onDownloadSankey={downloadSankey}
188217
onNextPage={nextPage}
189218
onPrevPage={prevPage}
219+
onRemoveItem={handleRemoveItem}
190220
/>
191221
);
192222
}

frontend/app/preview/dashboard/page.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
import React, { useState, useEffect } from "react";
44
import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, useDisclosure, Button } from "@heroui/react";
55
import { useRouter } from "next/navigation";
6+
import { addToast } from "@heroui/toast";
67

7-
import JobApplicationsDashboard, { Application } from "@/components/JobApplicationsDashboard";
8+
import JobApplicationsDashboard, { Application } from "@/components/JobApplicationsDashboardPreview";
89
import { mockData } from "@/utils/mockData";
910

1011
export default function PreviewDashboard() {
1112
const { isOpen, onOpen, onClose } = useDisclosure();
1213
const [data, setData] = useState<Application[]>([]);
1314
const [loading, setLoading] = useState(true);
1415
const [downloading, setDownloading] = useState(false);
15-
const router = useRouter();
1616

1717
const [currentPage, setCurrentPage] = useState(1);
1818
const [totalPages, setTotalPages] = useState(1);
19+
20+
const router = useRouter();
21+
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
22+
1923
useEffect(() => {
2024
setLoading(true);
2125
const dataTimeout = setTimeout(() => {
@@ -113,6 +117,35 @@ export default function PreviewDashboard() {
113117
</Modal>
114118
);
115119

120+
const handleRemoveItem = async (id: string) => {
121+
try {
122+
// Make a DELETE request to the backend
123+
const response = await fetch(`${apiUrl}/delete-email/${id}`, {
124+
method: "DELETE",
125+
credentials: "include" // Include cookies for authentication
126+
});
127+
128+
if (!response.ok) {
129+
throw new Error("Failed to delete the item");
130+
}
131+
132+
// If the deletion is successful, update the local state
133+
setData((prevData) => prevData.filter((item) => item.id !== id));
134+
135+
addToast({
136+
title: "Item removed successfully",
137+
color: "success"
138+
});
139+
} catch (error) {
140+
console.error("Error deleting item:", error);
141+
addToast({
142+
title: "Failed to remove item",
143+
description: "Please try again or contact support.",
144+
color: "danger"
145+
});
146+
}
147+
};
148+
116149
return (
117150
<JobApplicationsDashboard
118151
currentPage={currentPage}
@@ -126,6 +159,7 @@ export default function PreviewDashboard() {
126159
onDownloadSankey={downloadSankey}
127160
onNextPage={nextPage}
128161
onPrevPage={prevPage}
162+
onRemoveItem={handleRemoveItem}
129163
/>
130164
);
131165
}

frontend/app/preview/processing/page.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,23 @@ export default function PreviewProcessing() {
3535
}, [router]);
3636

3737
return (
38-
<div className="flex flex-col items-center justify-center h-screen">
39-
<h1 className="text-3xl font-semibold mb-4">We are processing your job!</h1>
40-
<Spinner />
41-
<div className="mb-3" />
42-
<Progress
43-
aria-label="Downloading..."
44-
className="max-w-md"
45-
showValueLabel={true}
46-
size="md"
47-
value={progress}
48-
/>
49-
<div className="mb-3" />
50-
<p className="text-lg mt-4">
51-
Your job is being processed. You will be redirected to the download page once it&#39;s ready.
52-
</p>
38+
<div className="flex flex-col items-center justify-center h-full">
39+
<div className="flex flex-col items-center justify-center">
40+
<h1 className="text-3xl font-semibold mb-4">We are processing your job!</h1>
41+
<Spinner />
42+
<div className="mb-3" />
43+
<Progress
44+
aria-label="Downloading..."
45+
className="max-w-md"
46+
showValueLabel={true}
47+
size="md"
48+
value={progress}
49+
/>
50+
<div className="mb-3" />
51+
<p className="text-lg mt-4">
52+
Your job is being processed. You will be redirected to the download page once it&#39;s ready.
53+
</p>
54+
</div>
5355
</div>
5456
);
5557
}

frontend/app/processing/page.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,23 @@ const ProcessingPage = () => {
5454
}, [router]);
5555

5656
return (
57-
<div className="flex flex-col items-center justify-center h-screen">
58-
<h1 className="text-3xl font-semibold mb-4">We are processing your job!</h1>
59-
<Spinner />
60-
<div className="mb-3" />
61-
<Progress
62-
aria-label="Downloading..."
63-
className="max-w-md"
64-
showValueLabel={true}
65-
size="md"
66-
value={progress}
67-
/>
68-
<div className="mb-3" />
69-
<p className="text-lg mt-4">
70-
Your job is being processed. You will be redirected to the download page once it&#39;s ready.
71-
</p>
57+
<div className="flex flex-col items-center justify-center h-full">
58+
<div className="flex flex-col items-center justify-center">
59+
<h1 className="text-3xl font-semibold mb-4">We are hard at work!</h1>
60+
<Spinner />
61+
<div className="mb-3" />
62+
<Progress
63+
aria-label="Downloading..."
64+
className="max-w-md"
65+
showValueLabel={true}
66+
size="md"
67+
value={progress}
68+
/>
69+
<div className="mb-3" />
70+
<p className="text-lg mt-4">
71+
Eating rejections for dinner. You will be redirected to your dashboard soon.
72+
</p>
73+
</div>
7274
</div>
7375
);
7476
};

frontend/components/JobApplicationsDashboard.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ interface JobApplicationsDashboardProps {
4141
downloading: boolean;
4242
onDownloadCsv: () => void;
4343
onDownloadSankey: () => void;
44+
onRemoveItem: (id: string) => void;
4445
initialSortKey?: string;
4546
extraHeader?: React.ReactNode;
4647
onNextPage: () => void;
@@ -61,6 +62,7 @@ export default function JobApplicationsDashboard({
6162
downloading,
6263
onDownloadCsv,
6364
onDownloadSankey,
65+
onRemoveItem, // Accept the callback
6466
initialSortKey = "Date (Newest)",
6567
extraHeader
6668
}: JobApplicationsDashboardProps) {
@@ -73,6 +75,7 @@ export default function JobApplicationsDashboard({
7375
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
7476
const router = useRouter();
7577
const [showDelete, setShowDelete] = useState(false);
78+
const [itemToRemove, setItemToRemove] = useState<string | null>(null);
7679

7780
const [currentPage, setCurrentPage] = useState(1);
7881
const pageSize = 10;
@@ -216,7 +219,6 @@ export default function JobApplicationsDashboard({
216219
</Modal>
217220

218221
{extraHeader}
219-
220222
<Modal isOpen={showDelete} onOpenChange={(isOpen) => setShowDelete(isOpen)}>
221223
<ModalContent>
222224
{(onClose) => (
@@ -233,7 +235,16 @@ export default function JobApplicationsDashboard({
233235
<Button color="default" variant="ghost" onPress={onClose}>
234236
Cancel
235237
</Button>
236-
<Button color="danger" onPress={onClose}>
238+
<Button
239+
color="danger"
240+
onPress={() => {
241+
if (itemToRemove) {
242+
onRemoveItem(itemToRemove); // Notify the parent to remove the item
243+
setItemToRemove(null); // Clear the selected item
244+
setShowDelete(false); // Close the modal
245+
}
246+
}}
247+
>
237248
Yes, remove it
238249
</Button>
239250
</ModalFooter>
@@ -257,6 +268,7 @@ export default function JobApplicationsDashboard({
257268
<Button
258269
className="pl-3"
259270
color="primary"
271+
data-testid="Sort By"
260272
isDisabled={!data || data.length === 0}
261273
startContent={<SortIcon />}
262274
variant="bordered"
@@ -345,7 +357,10 @@ export default function JobApplicationsDashboard({
345357
isIconOnly
346358
size="sm"
347359
variant="light"
348-
onPress={() => setShowDelete(!showDelete)}
360+
onPress={() => {
361+
setItemToRemove(item.id || null);
362+
setShowDelete(true);
363+
}}
349364
>
350365
<TrashIcon className="text-gray-800 dark:text-gray-300" />
351366
</Button>

0 commit comments

Comments
 (0)