Skip to content

Commit 9e98979

Browse files
committed
feat: enhance project review functionality with improved score handling and UI updates
1 parent e86d746 commit 9e98979

1 file changed

Lines changed: 76 additions & 27 deletions

File tree

  • app/[locale]/dashboard/jurado/proyectos

app/[locale]/dashboard/jurado/proyectos/page.tsx

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export default function JuradoProyectosPage() {
4545
const [confirmAction, setConfirmAction] = useState<"review" | null>(null)
4646
const [judgingStage, setJudgingStage] = useState<"admin" | "judge">("admin")
4747
const [myReviews, setMyReviews] = useState<Set<string>>(new Set())
48+
const [myReviewByProject, setMyReviewByProject] = useState<Record<string, any>>({})
49+
const [myReviewDocIdByProject, setMyReviewDocIdByProject] = useState<Record<string, string>>({})
4850

4951
useEffect(() => {
5052
if (!db) return
@@ -79,6 +81,18 @@ export default function JuradoProyectosPage() {
7981
unsubReviews = onSnapshot(reviewsQuery, (snapshot) => {
8082
const reviewedIds = new Set(snapshot.docs.map(doc => doc.data().projectId))
8183
setMyReviews(reviewedIds)
84+
85+
const reviewsByProject: Record<string, any> = {}
86+
const reviewDocIdsByProject: Record<string, string> = {}
87+
snapshot.docs.forEach((d) => {
88+
const data = d.data() as any
89+
if (data.projectId) {
90+
reviewsByProject[data.projectId] = data
91+
reviewDocIdsByProject[data.projectId] = d.id
92+
}
93+
})
94+
setMyReviewByProject(reviewsByProject)
95+
setMyReviewDocIdByProject(reviewDocIdsByProject)
8296
})
8397
}
8498

@@ -106,10 +120,31 @@ export default function JuradoProyectosPage() {
106120
}
107121
}, [db, user?.id])
108122

123+
useEffect(() => {
124+
if (!selectedProject) return
125+
126+
const defaultScores: Record<string, number> = {}
127+
scoringCriteria.filter(c => (c.targetRole || "judge") === "judge").forEach(c => {
128+
defaultScores[c.id] = 10
129+
})
130+
131+
const existingReview = myReviewByProject[selectedProject.id]
132+
if (existingReview) {
133+
const prefilledScores = { ...defaultScores, ...(existingReview.rawScores || {}) }
134+
setReviewScores(prefilledScores)
135+
setReviewComment(existingReview.comment || "")
136+
return
137+
}
138+
139+
setReviewScores(defaultScores)
140+
setReviewComment("")
141+
}, [selectedProject?.id, scoringCriteria, myReviewByProject])
142+
109143
const handleReview = async () => {
110144
if (!db || !selectedProject || !user) return
111145
setSubmittingReview(true)
112146
try {
147+
const nowIso = new Date().toISOString()
113148
const scoresWithWeights: Record<string, number> = {}
114149
let totalWeightedScore = 0
115150

@@ -132,9 +167,17 @@ export default function JuradoProyectosPage() {
132167
totalScore: totalWeightedScore,
133168
comment: reviewComment,
134169
disqualified: false,
135-
createdAt: new Date().toISOString()
170+
updatedAt: nowIso,
171+
}
172+
const existingReviewDocId = myReviewDocIdByProject[selectedProject.id]
173+
if (existingReviewDocId) {
174+
await updateDoc(doc(db, "projectReviews", existingReviewDocId), reviewData)
175+
} else {
176+
await addDoc(collection(db, "projectReviews"), {
177+
...reviewData,
178+
createdAt: nowIso,
179+
})
136180
}
137-
await addDoc(collection(db, "projectReviews"), reviewData)
138181

139182
// Calculate new averages from all relevant reviews (judges and admins)
140183
const reviewsSnap = await getDocs(query(collection(db, "projectReviews"), where("projectId", "==", selectedProject.id)))
@@ -177,7 +220,7 @@ export default function JuradoProyectosPage() {
177220
setSelectedProject((prev: any) => ({ ...prev, status: "reviewed", disqualified: anyDisqualified, totalScore: avgTotal, reviewCount: judgeReviews.length }))
178221
setShowDetails(false)
179222
setReviewComment("")
180-
toast({ title: locale === "es" ? "Evaluación enviada" : "Evaluation submitted" })
223+
toast({ title: existingReviewDocId ? (locale === "es" ? "Evaluación actualizada" : "Evaluation updated") : (locale === "es" ? "Evaluación enviada" : "Evaluation submitted") })
181224
} catch (error) {
182225
console.error("Error submitting review:", error)
183226
} finally {
@@ -186,12 +229,16 @@ export default function JuradoProyectosPage() {
186229
}
187230

188231
const filteredProjects = useMemo(() => {
189-
// Judges don't see anything during admin screening phase
190-
if (judgingStage === "admin") return []
232+
const hasFinalists = projects.some(p => p.isFinalist && !p.disqualified)
191233

192234
return projects.filter(p => {
193-
// Judges only evaluate finalists and never disqualified ones
194-
if (!p.isFinalist || p.disqualified) return false
235+
// Prefer finalists once they exist; otherwise fallback to submitted/reviewed projects.
236+
if (p.disqualified) return false
237+
if (hasFinalists) {
238+
if (!p.isFinalist) return false
239+
} else {
240+
if (p.status !== "submitted" && p.status !== "reviewed") return false
241+
}
195242

196243
const isReviewedByMe = myReviews.has(p.id)
197244
const matchesSearch = p.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -203,7 +250,7 @@ export default function JuradoProyectosPage() {
203250

204251
return matchesSearch && statusMatch
205252
})
206-
}, [projects, searchTerm, statusFilter, judgingStage, myReviews])
253+
}, [projects, searchTerm, statusFilter, myReviews])
207254

208255
const getCategoryName = (categoryId: string) => {
209256
if (!categoryId) return "-"
@@ -244,9 +291,9 @@ export default function JuradoProyectosPage() {
244291
<section>
245292
<div className="flex flex-col gap-2 mb-6">
246293
<h3 className="font-pixel text-2xl text-brand-yellow font-pixel">{t.judge.projectsToScore}</h3>
247-
{judgingStage === "admin" && (
294+
{judgingStage === "admin" && !projects.some(p => p.isFinalist && !p.disqualified) && (
248295
<div className="p-3 text-center rounded-md bg-brand-orange/10 border border-brand-orange/30 text-brand-orange text-xs font-pixel">
249-
WAITING FOR ADMINS TO SELECT FINALISTS...
296+
NO FINALISTS YET. SHOWING SUBMITTED PROJECTS.
250297
</div>
251298
)}
252299
</div>
@@ -288,27 +335,30 @@ export default function JuradoProyectosPage() {
288335
</div>
289336

290337
<Dialog open={showDetails} onOpenChange={setShowDetails}>
291-
<DialogContent className="glass-effect border-brand-cyan/30 max-w-2xl max-h-[90vh] overflow-y-auto">
338+
<DialogContent className="glass-effect border-brand-cyan/30 w-[95vw] max-w-2xl max-h-[90vh] overflow-y-auto overflow-x-hidden">
292339
<DialogHeader>
293-
<DialogTitle className="font-pixel text-brand-yellow flex items-center justify-between">
294-
<div className="flex items-center gap-2"><FileText size={18} /> {selectedProject?.title}</div>
340+
<DialogTitle className="font-pixel text-brand-yellow flex items-start justify-between gap-2">
341+
<div className="flex min-w-0 items-start gap-2">
342+
<FileText size={18} className="shrink-0" />
343+
<span className="break-all leading-snug">{selectedProject?.title}</span>
344+
</div>
295345
</DialogTitle>
296346
</DialogHeader>
297347
{selectedProject && (
298348
<div className="space-y-6 mt-4">
299349
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
300350
<div className="p-3 rounded bg-brand-navy/60 border border-brand-cyan/10">
301351
<p className="text-[10px] text-brand-cyan/60 uppercase mb-1">Team</p>
302-
<p className="text-brand-yellow text-sm font-pixel">{selectedProject.teamName}</p>
352+
<p className="text-brand-yellow text-sm font-pixel break-all">{selectedProject.teamName}</p>
303353
</div>
304354
<div className="p-3 rounded bg-brand-navy/60 border border-brand-cyan/10">
305355
<p className="text-[10px] text-brand-cyan/60 uppercase mb-1">Category</p>
306-
<p className="text-brand-cyan text-sm">{getCategoryName(selectedProject.categoryId)}</p>
356+
<p className="text-brand-cyan text-sm break-words">{getCategoryName(selectedProject.categoryId)}</p>
307357
</div>
308358
</div>
309359
<div className="p-4 rounded bg-black/40 border border-brand-cyan/10">
310360
<p className="text-[10px] text-brand-cyan/60 uppercase mb-2">Description</p>
311-
<p className="text-brand-cyan/80 text-sm whitespace-pre-wrap">{selectedProject.description}</p>
361+
<p className="text-brand-cyan/80 text-sm whitespace-pre-wrap break-all">{selectedProject.description}</p>
312362
</div>
313363
<div className="flex flex-wrap gap-4">
314364
{selectedProject.githubRepoUrl && <a href={selectedProject.githubRepoUrl.startsWith('http') ? selectedProject.githubRepoUrl : `https://${selectedProject.githubRepoUrl}`} target="_blank" rel="noreferrer" className="flex items-center gap-2 px-3 py-2 rounded bg-brand-cyan/10 text-brand-cyan hover:bg-brand-cyan/20 text-xs"><Github size={14} /> GitHub</a>}
@@ -333,9 +383,13 @@ export default function JuradoProyectosPage() {
333383
</div>
334384
)}
335385

336-
{!myReviews.has(selectedProject.id) && selectedProject.status !== "disqualified" ? (
386+
{selectedProject.status !== "disqualified" ? (
337387
<div className="mt-8 pt-6 border-t border-brand-cyan/20">
338-
<h3 className="font-pixel text-lg text-brand-yellow mb-4">Project Review</h3>
388+
<h3 className="font-pixel text-lg text-brand-yellow mb-4">
389+
{myReviews.has(selectedProject.id)
390+
? (locale === "es" ? "Editar Evaluación" : "Edit Evaluation")
391+
: "Project Review"}
392+
</h3>
339393
<div className="space-y-4">
340394
{scoringCriteria.filter(c => (c.targetRole || "judge") === "judge").map(criteria => (
341395
<div key={criteria.id} className="grid grid-cols-4 items-center gap-4">
@@ -374,7 +428,9 @@ export default function JuradoProyectosPage() {
374428
disabled={submittingReview}
375429
>
376430
<CheckCircle className="mr-2 w-4 h-4" />
377-
Submit Evaluation
431+
{myReviews.has(selectedProject.id)
432+
? (locale === "es" ? "Actualizar Evaluación" : "Update Evaluation")
433+
: "Submit Evaluation"}
378434
</Button>
379435
</div>
380436
</div>
@@ -383,14 +439,7 @@ export default function JuradoProyectosPage() {
383439
<div className="mt-8 pt-6 border-t border-red-500/20 text-center">
384440
<p className="text-red-500 font-pixel text-sm uppercase">This project has been disqualified.</p>
385441
</div>
386-
) : (
387-
<div className="mt-8 pt-6 border-t border-brand-cyan/20 text-center">
388-
<div className="flex flex-col items-center gap-2 text-brand-cyan/60">
389-
<CheckCircle className="w-8 h-8 text-green-400" />
390-
<p className="font-pixel text-sm uppercase">{locale === "es" ? "Ya has evaluado este proyecto" : "You have already evaluated this project"}</p>
391-
</div>
392-
</div>
393-
)}
442+
) : null}
394443
</div>
395444
)}
396445
</DialogContent>

0 commit comments

Comments
 (0)