Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"cross-env": "^10.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1"
Expand All @@ -13,7 +14,7 @@
"typescript": "^4.9.5"
},
"scripts": {
"start": "PORT=3004 react-scripts start",
"start": "cross-env PORT=3004 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
Expand All @@ -36,4 +37,4 @@
"last 1 safari version"
]
}
}
}
55 changes: 39 additions & 16 deletions client/src/components/Evaluations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ClassService from '../services/ClassService';
import EnrollmentService from '../services/EnrollmentService';

import { ImportGradeComponent } from './ImportGrade';
import InfoButton from './InfoButton';

interface EvaluationsProps {
onError: (errorMessage: string) => void;
Expand All @@ -21,7 +22,7 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
// Predefined evaluation goals
const evaluationGoals = [
'Requirements',
'Configuration Management',
'Configuration Management',
'Project Management',
'Design',
'Tests',
Expand All @@ -40,6 +41,17 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
}
}, [onError]);

const [discrepancies, setDiscrepancies] = useState<any[]>([]);

const loadDiscrepancies = useCallback(async (classId: string) => {
try {
const data = await EnrollmentService.getDiscrepancys(classId);
setDiscrepancies(data);
} catch (error) {
onError(`Failed to load discrepancies: ${(error as Error).message}`);
}
}, [onError]);

// Load all classes on component mount
useEffect(() => {
loadClasses();
Expand All @@ -50,10 +62,11 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
if (selectedClassId) {
const classObj = classes.find(c => c.id === selectedClassId);
setSelectedClass(classObj || null);
loadDiscrepancies(selectedClassId); // CARREGAR DISCREPÂNCIAS QUANDO A TURMA É SELECIONADA
} else {
setSelectedClass(null);
}
}, [selectedClassId, classes]);
}, [selectedClassId, classes, loadDiscrepancies]);

const handleClassSelection = (classId: string) => {
setSelectedClassId(classId);
Expand All @@ -75,6 +88,7 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
await EnrollmentService.updateEvaluation(selectedClass.id, studentCPF, goal, grade);
// Reload classes to get updated enrollment data
await loadClasses();
await loadDiscrepancies(selectedClass.id); // RECARREGAR DISCREPÂNCIAS QUANDO UMA NOTA É ALTERADA
} catch (error) {
onError(`Failed to update evaluation: ${(error as Error).message}`);
}
Expand All @@ -94,7 +108,7 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
return (
<div className="evaluation-section">
<h3>Evaluations</h3>

{/* Class Selection */}
<div className="class-selection-container">
<label htmlFor="classSelect">Select Class:</label>
Expand All @@ -114,10 +128,10 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
</div>

{!selectedClass && (
<div style={{
padding: '20px',
border: '2px dashed #ccc',
borderRadius: '8px',
<div style={{
padding: '20px',
border: '2px dashed #ccc',
borderRadius: '8px',
textAlign: 'center',
color: '#666',
marginTop: '20px'
Expand All @@ -128,10 +142,10 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
)}

{selectedClass && selectedClass.enrollments.length === 0 && (
<div style={{
padding: '20px',
border: '2px dashed #ccc',
borderRadius: '8px',
<div style={{
padding: '20px',
border: '2px dashed #ccc',
borderRadius: '8px',
textAlign: 'center',
color: '#666',
marginTop: '20px'
Expand All @@ -149,7 +163,7 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
<ImportGradeComponent classID={selectedClassId} />
</div>
<h4>{selectedClass.topic} ({selectedClass.year}/{selectedClass.semester})</h4>

<div className="evaluation-matrix">
<table className="evaluation-table">
<thead>
Expand All @@ -163,19 +177,28 @@ const Evaluations: React.FC<EvaluationsProps> = ({ onError }) => {
<tbody>
{selectedClass.enrollments.map(enrollment => {
const student = enrollment.student;

// Create a map of evaluations for quick lookup
const studentEvaluations = enrollment.evaluations.reduce((acc, evaluation) => {
acc[evaluation.goal] = evaluation.grade;
return acc;
}, {} as {[goal: string]: string});
}, {} as { [goal: string]: string });

return (
<tr key={student.cpf} className="student-row">
<td className="student-name-cell">{student.name}</td>
<td className="student-name-cell">{student.name}
{ }
{(() => {
const disc = discrepancies.find(d => d.studentCPF === student.cpf);
if (disc) {
<InfoButton text={`Discrepância: ${disc.DiscrepancyCount.toFixed(2)}`} />
}
return null;
})()}
</td>
{evaluationGoals.map(goal => {
const currentGrade = studentEvaluations[goal] || '';

return (
<td key={goal} className="evaluation-cell">
<select
Expand Down
60 changes: 60 additions & 0 deletions client/src/components/InfoButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useState } from "react";

type InfoButtonProps = {
text: string;
};

const InfoButton: React.FC<InfoButtonProps> = ({ text }) => {
const [show, setShow] = useState(false);

return (
<div style={{ position: "relative", display: "inline-block", marginLeft: "4px" }}>
<div
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
style={{
width: "16px",
height: "16px",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="#555"
style={{ display: "block" }}
>
<circle cx="12" cy="12" r="10" stroke="#555" strokeWidth="2" fill="none" />
<line x1="12" y1="10" x2="12" y2="16" stroke="#555" strokeWidth="2" />
<circle cx="12" cy="7" r="1.5" fill="#555" />
</svg>
</div>

{show && (
<div
style={{
position: "absolute",
top: "-35px",
left: "0",
background: "#333",
color: "white",
padding: "6px 8px",
fontSize: "11px",
borderRadius: "4px",
whiteSpace: "nowrap",
boxShadow: "0px 2px 6px rgba(0,0,0,0.25)",
zIndex: 999,
}}
>
{text}
</div>
)}
</div>
);
};

export default InfoButton;
14 changes: 14 additions & 0 deletions client/src/services/EnrollmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ class EnrollmentService {
throw error;
}
}

static async getDiscrepancys(classId: string): Promise<any[]> {
try {
const response = await fetch(`${API_BASE_URL}/api/classes/${classId}/discrepancys`);
if (!response.ok) {
throw new Error('Failed to fetch discrepancies');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching discrepancies:', error);
throw error;
}
}
}

export default EnrollmentService;
26 changes: 16 additions & 10 deletions server/src/models/Classes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Class } from './Class';
import { Enrollment } from './Enrollment';
import { Student } from './Student';

export class Classes {
Expand Down Expand Up @@ -31,6 +32,17 @@ export class Classes {
return true;
}

// Merge evaluations and self-evaluations from one enrollment to another
private mergeEnrollmentsData(from: Enrollment, to: Enrollment): void {
from.getEvaluations().forEach(evaluation =>
to.addOrUpdateEvaluation(evaluation.getGoal(), evaluation.getGrade())
);

from.getSelfEvaluations().forEach(selfEvaluation =>
to.addOrUpdateSelfEvaluation(selfEvaluation.getGoal(), selfEvaluation.getGrade())
);
}

// Update class
updateClass(updatedClass: Class): Class {
const existingClass = this.findClassById(updatedClass.getClassId());
Expand All @@ -54,22 +66,16 @@ export class Classes {
const existingEnrollment = existingClass.findEnrollmentByStudentCPF(studentCPF);

if (existingEnrollment) {
// Update existing enrollment's evaluations
const updatedEvaluations = updatedEnrollment.getEvaluations();
updatedEvaluations.forEach(evaluation => {
existingEnrollment.addOrUpdateEvaluation(evaluation.getGoal(), evaluation.getGrade());
});
// Update existing enrollment's evaluations and self-evaluations
this.mergeEnrollmentsData(updatedEnrollment, existingEnrollment);
} else {
// Add new enrollment that doesn't exist yet
try {
existingClass.addEnrollment(updatedEnrollment.getStudent());
const newEnrollment = existingClass.findEnrollmentByStudentCPF(studentCPF);
if (newEnrollment) {
// Copy over evaluations from updated enrollment
const updatedEvaluations = updatedEnrollment.getEvaluations();
updatedEvaluations.forEach(evaluation => {
newEnrollment.addOrUpdateEvaluation(evaluation.getGoal(), evaluation.getGrade());
});
// Copy over evaluations and self-evaluations from updated enrollment
this.mergeEnrollmentsData(updatedEnrollment, newEnrollment);
}
} catch (error) {
// Enrollment already exists, this shouldn't happen but handle gracefully
Expand Down
Loading