Skip to content

Commit f7c8efa

Browse files
committed
Aggiungi funzionalità per disiscrivere i partecipanti dalle attività e migliora l'interfaccia utente per la gestione dei partecipanti
1 parent 07073b8 commit f7c8efa

File tree

5 files changed

+145
-5
lines changed

5 files changed

+145
-5
lines changed

requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
fastapi
22
uvicorn
3+
4+
# Test requirements
5+
pytest
6+
requests
7+
httpx

src/app.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,19 @@ def signup_for_activity(activity_name: str, email: str):
103103
# Add student
104104
activity["participants"].append(email)
105105
return {"message": f"Signed up {email} for {activity_name}"}
106+
107+
108+
@app.delete("/activities/{activity_name}/participants")
109+
def unregister_from_activity(activity_name: str, email: str):
110+
"""Unregister a student from an activity"""
111+
# Validate activity exists
112+
if activity_name not in activities:
113+
raise HTTPException(status_code=404, detail="Activity not found")
114+
115+
activity = activities[activity_name]
116+
# Validate student is signed up
117+
if email not in activity["participants"]:
118+
raise HTTPException(status_code=404, detail=f"Student {email} is not signed up for {activity_name}")
119+
120+
activity["participants"].remove(email)
121+
return {"message": f"Unregistered {email} from {activity_name}"}

src/static/app.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ document.addEventListener("DOMContentLoaded", () => {
1212

1313
// Clear loading message
1414
activitiesList.innerHTML = "";
15+
// Reset activity select so we don't duplicate options on refresh
16+
activitySelect.innerHTML = '<option value="">-- Select an activity --</option>';
1517

1618
// Populate activities list
1719
Object.entries(activities).forEach(([name, details]) => {
@@ -26,9 +28,14 @@ document.addEventListener("DOMContentLoaded", () => {
2628
participantsHTML = `
2729
<div class="participants-section">
2830
<strong>Partecipanti:</strong>
29-
<ul class="participants-list">
30-
${details.participants.map(p => `<li>${p}</li>`).join("")}
31-
</ul>
31+
<ul class="participants-list">
32+
${details.participants
33+
.map(
34+
(p) =>
35+
`<li><span class="participant-email">${p}</span><button class="delete-participant" data-activity="${name}" data-email="${p}" aria-label="Unregister">✖</button></li>`
36+
)
37+
.join("")}
38+
</ul>
3239
</div>
3340
`;
3441
} else {
@@ -61,6 +68,46 @@ document.addEventListener("DOMContentLoaded", () => {
6168
}
6269
}
6370

71+
// Handle unregister/delete participant clicks using event delegation
72+
activitiesList.addEventListener("click", async (event) => {
73+
const target = event.target;
74+
if (target.classList && target.classList.contains("delete-participant")) {
75+
const activity = target.getAttribute("data-activity");
76+
const email = target.getAttribute("data-email");
77+
78+
if (!activity || !email) return;
79+
80+
try {
81+
const response = await fetch(
82+
`/activities/${encodeURIComponent(activity)}/participants?email=${encodeURIComponent(email)}`,
83+
{ method: "DELETE" }
84+
);
85+
86+
const result = await response.json();
87+
88+
if (response.ok) {
89+
messageDiv.textContent = result.message;
90+
messageDiv.className = "success";
91+
// Refresh the activities list to reflect removal
92+
fetchActivities();
93+
} else {
94+
messageDiv.textContent = result.detail || "An error occurred";
95+
messageDiv.className = "error";
96+
}
97+
98+
messageDiv.classList.remove("hidden");
99+
setTimeout(() => {
100+
messageDiv.classList.add("hidden");
101+
}, 5000);
102+
} catch (err) {
103+
console.error("Error unregistering:", err);
104+
messageDiv.textContent = "Failed to unregister. Please try again.";
105+
messageDiv.className = "error";
106+
messageDiv.classList.remove("hidden");
107+
}
108+
}
109+
});
110+
64111
// Handle form submission
65112
signupForm.addEventListener("submit", async (event) => {
66113
event.preventDefault();
@@ -82,6 +129,8 @@ document.addEventListener("DOMContentLoaded", () => {
82129
messageDiv.textContent = result.message;
83130
messageDiv.className = "success";
84131
signupForm.reset();
132+
// Refresh activities immediately so UI reflects new participant
133+
fetchActivities();
85134
} else {
86135
messageDiv.textContent = result.detail || "An error occurred";
87136
messageDiv.className = "error";

src/static/styles.css

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,39 @@ section h3 {
8989

9090
.participants-list {
9191
margin-top: 6px;
92-
margin-left: 18px;
93-
list-style-type: disc;
92+
margin-left: 0;
93+
padding-left: 0;
94+
list-style: none;
9495
color: #333;
9596
font-size: 15px;
9697
}
9798

99+
.participants-list li {
100+
display: flex;
101+
align-items: center;
102+
justify-content: space-between;
103+
padding: 4px 0;
104+
}
105+
106+
.participant-email {
107+
word-break: break-word;
108+
margin-right: 8px;
109+
}
110+
111+
.delete-participant {
112+
background: transparent;
113+
border: none;
114+
color: #c62828;
115+
font-size: 14px;
116+
cursor: pointer;
117+
padding: 4px 6px;
118+
border-radius: 4px;
119+
}
120+
121+
.delete-participant:hover {
122+
background-color: rgba(198, 40, 40, 0.08);
123+
}
124+
98125
.no-participants {
99126
background-color: #f9fbe7;
100127
border: 1px dashed #cddc39;

tests/test_api.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from fastapi.testclient import TestClient
2+
import sys
3+
from pathlib import Path
4+
5+
# Ensure src is on path
6+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
7+
8+
from app import app, activities
9+
10+
11+
client = TestClient(app)
12+
13+
14+
def test_get_activities():
15+
response = client.get("/activities")
16+
assert response.status_code == 200
17+
data = response.json()
18+
assert isinstance(data, dict)
19+
# Should contain at least one known activity
20+
assert "Chess Club" in data
21+
22+
23+
def test_signup_and_unregister_flow():
24+
activity = "Chess Club"
25+
26+
27+
# Ensure not present initially
28+
if email in activities[activity]["participants"]:
29+
activities[activity]["participants"].remove(email)
30+
31+
# Signup
32+
rv = client.post(f"/activities/{activity}/signup?email={email}")
33+
assert rv.status_code == 200
34+
assert email in activities[activity]["participants"]
35+
36+
# Attempt duplicate signup should fail
37+
rv2 = client.post(f"/activities/{activity}/signup?email={email}")
38+
assert rv2.status_code == 400
39+
40+
# Unregister
41+
rv3 = client.delete(f"/activities/{activity}/participants?email={email}")
42+
assert rv3.status_code == 200
43+
assert email not in activities[activity]["participants"]

0 commit comments

Comments
 (0)