Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
62a0df2
Add peer evaluation scheduler demo code
ritika-290 Apr 7, 2026
966af8d
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
363a460
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
a2ef866
Add server setup and UI testing
ritika-290 Apr 7, 2026
061af95
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
fd85df0
Commit frontend UI improvements and React designs
ritika-290 Apr 7, 2026
9773195
Remove frontend_design_react_ideas.md
ritika-290 Apr 7, 2026
4e5155b
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
5f9962c
Integrate Auto-Reassignment and Accountability system into main NPTEL…
ritika-290 Apr 7, 2026
1f8aa5d
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
ed883d9
Add seed data to test auto-reassignment and accountability logic
ritika-290 Apr 7, 2026
d461437
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
81c0fa3
Finalize auto-reassignment UI, notifications, and escalation logic
ritika-290 Apr 7, 2026
24a112d
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
95b614a
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
df58286
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
207c58d
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
7c53ea8
Fix controller and seed data for correct task population
ritika-290 Apr 7, 2026
ceeee34
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
2faa148
feat: enhance Student Dashboard task cards and accountability stats
ritika-290 Apr 7, 2026
3b78fbb
checkout: temporary commit for worktree checkout
ritika-290 Apr 7, 2026
f686030
Admin DashbOard
ritika-290 Apr 7, 2026
4ea7ab4
Added auto reassignment feature with dashboard updates
ritika-290 Apr 7, 2026
7a6b7f3
Initial commit
ritika-290 Apr 9, 2026
0739934
Removed node_modules and fixed gitignore
ritika-290 Apr 9, 2026
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
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Dependencies
node_modules/
**/node_modules/

# Logs
*.log

# Environment
.env
.env.*

# Build files
dist/
build/
coverage/

# OS files
.DS_Store
Thumbs.db

# Editor
.vscode/
.idea/
51 changes: 14 additions & 37 deletions Peer_Evaluation_V3_NPTEL/.gitignore
Original file line number Diff line number Diff line change
@@ -1,47 +1,24 @@
node_modules/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Dependency directories
node_modules
/node_modules
/.pnp
.pnp.js
# Environment files
.env
.env.*

# Build output
dist
dist-ssr
/build
/coverage
# Build folders
dist/
build/
coverage/

# Miscellaneous system files
# OS files
.DS_Store
Thumbs.db

# Local environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

**/node_modules
**/*.json
**/.env
**/vite.config.ts
Peer_Evaluation_V3_NPTEL/backend/package-lock.json
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Editor files
.vscode/
.idea/
16 changes: 12 additions & 4 deletions Peer_Evaluation_V3_NPTEL/backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Peer_Evaluation_V3_NPTEL/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon --exec node --loader ts-node/esm src/server.ts"
"dev": "nodemon --exec node --loader ts-node/esm src/server.ts",
"reset:dashboard-seed": "node --loader ts-node/esm src/scripts/resetDashboardSeed.ts"
},
"keywords": [],
"author": "",
Expand All @@ -26,7 +27,7 @@
"mongoose": "^8.15.1",
"multer": "^2.0.1",
"nanoid": "^5.1.5",
"node-cron": "^4.1.0",
"node-cron": "^4.2.1",
"nodemailer": "^7.0.3",
"nodemon": "^3.1.10",
"npm": "^11.4.2",
Expand All @@ -47,6 +48,7 @@
"@types/jsonwebtoken": "^9.0.9",
"@types/multer": "^1.4.13",
"@types/node": "^22.15.30",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.17",
"@types/pdf-parse": "^1.1.5",
"@types/pdfkit": "^0.14.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { Request, Response } from 'express';
import { Evaluation } from '../../models/Evaluation.ts';
import {
ESCALATION_REASSIGNMENT_THRESHOLD,
EvaluationService,
} from '../../services/EvaluationService.ts';
import { Types } from 'mongoose';
import { Submission } from '../../models/Submission.ts';
import { clearDashboardSeedData, seedDatabase } from '../../seed/seedData.ts';

const getEntityId = (value: any) => {
if (!value) return null;
if (typeof value === 'string') return value;
if (value._id) return value._id.toString();
return value.toString();
};

const isDashboardDemoUser = (user: any) => {
const email = typeof user?.email === 'string' ? user.email : '';
return email.endsWith('.dashboard@example.com');
};

export const getEvaluationsOverview = async (req: Request, res: Response): Promise<void> => {
try {
const now = new Date();
const total = await Evaluation.countDocuments();
const pending = await Evaluation.countDocuments({ status: 'pending' });
const overdue = await Evaluation.countDocuments({
status: { $in: ['pending', 'overdue'] },
deadline: { $lt: now }
});
const escalated = await Evaluation.countDocuments({ status: 'escalated' });
const reassignedCount = await Evaluation.countDocuments({ reassignmentCount: { $gt: 0 } });

const evaluations = await Evaluation.find()
.populate('evaluator', 'name email')
.populate('evaluatee', 'name email')
.populate('exam', 'title')
.sort({ deadline: 1 });

const submissionKeys = evaluations
.map((evaluation) => ({
exam: getEntityId(evaluation.exam),
student: getEntityId(evaluation.evaluatee),
}))
.filter((item): item is { exam: string; student: string } => Boolean(item.exam && item.student));

const submissions = submissionKeys.length
? await Submission.find({
$or: submissionKeys.map(({ exam, student }) => ({
exam,
student,
})),
}).select('_id exam student')
: [];

const submissionLookup = new Map(
submissions.map((submission) => [
`${submission.exam.toString()}::${submission.student.toString()}`,
String(submission._id),
])
);

const evaluationRows = evaluations.map((evaluation) => {
const examId = getEntityId(evaluation.exam);
const evaluateeId = getEntityId(evaluation.evaluatee);
const submissionId =
examId && evaluateeId ? submissionLookup.get(`${examId}::${evaluateeId}`) ?? null : null;
const isOverdue =
['pending', 'overdue'].includes(evaluation.status) &&
new Date(evaluation.deadline) < now;
const isFrequentlyReassigned = evaluation.reassignmentCount >= ESCALATION_REASSIGNMENT_THRESHOLD;

return {
_id: evaluation._id,
submissionId,
assignedStudent: evaluation.evaluator,
evaluatee: evaluation.evaluatee,
exam: evaluation.exam,
deadline: evaluation.deadline,
status: evaluation.status,
reassignmentCount: evaluation.reassignmentCount,
isOverdue,
isFrequentlyReassigned,
isDashboardDemo:
isDashboardDemoUser(evaluation.evaluator) || isDashboardDemoUser(evaluation.evaluatee),
};
});

res.json({
statistics: {
total,
pending,
overdue,
escalated,
reassignedCount
},
evaluations: evaluationRows
});
} catch (error) {
console.error('Error in getEvaluationsOverview:', error);
res.status(500).json({ message: 'Internal server error' });
}
};

export const manualReassign = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;

// Check if evaluation exists
const evaluation = await Evaluation.findById(id);
if (!evaluation) {
res.status(404).json({ message: 'Evaluation not found' });
return;
}

// Use existing auto-reassignment logic
const reassignedEvaluation = await EvaluationService.reassignTask(
evaluation._id as unknown as Types.ObjectId
);

if (!reassignedEvaluation) {
res.status(400).json({ message: 'Evaluation could not be reassigned' });
return;
}

res.json({ message: 'Evaluation reassigned successfully', evaluation: reassignedEvaluation });
} catch (error) {
console.error('Error in manualReassign:', error);
res.status(500).json({ message: 'Failed to reassign evaluation' });
}
};

export const updateEvaluationDeadline = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const { deadline } = req.body;

const parsedDeadline = deadline ? new Date(deadline) : null;
if (!parsedDeadline || Number.isNaN(parsedDeadline.getTime())) {
res.status(400).json({ message: 'A valid deadline is required.' });
return;
}

const evaluation = await Evaluation.findById(id);
if (!evaluation) {
res.status(404).json({ message: 'Evaluation not found' });
return;
}

evaluation.deadline = parsedDeadline;

if (['pending', 'overdue'].includes(evaluation.status)) {
evaluation.status = parsedDeadline <= new Date() ? 'overdue' : 'pending';
}

await evaluation.save();

res.json({ message: 'Evaluation deadline updated successfully', evaluation });
} catch (error) {
console.error('Error in updateEvaluationDeadline:', error);
res.status(500).json({ message: 'Failed to update evaluation deadline' });
}
};

export const overrideReassignTask = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;

const evaluation = await Evaluation.findById(id);
if (!evaluation) {
res.status(404).json({ message: 'Evaluation not found' });
return;
}

const reassignedEvaluation = await EvaluationService.reassignTask(
evaluation._id as unknown as Types.ObjectId,
{ override: true }
);

if (!reassignedEvaluation) {
res.status(400).json({ message: 'Override reassignment failed. No eligible student found.' });
return;
}

res.json({
message: 'Evaluation override reassigned successfully',
evaluation: reassignedEvaluation,
});
} catch (error) {
console.error('Error in overrideReassignTask:', error);
res.status(500).json({ message: 'Failed to override reassign evaluation' });
}
};

export const escalateTask = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;

const evaluation = await Evaluation.findByIdAndUpdate(
id,
{ status: 'escalated' },
{ new: true }
);

if (!evaluation) {
res.status(404).json({ message: 'Evaluation not found' });
return;
}

res.json({ message: 'Evaluation escalated successfully', evaluation });
} catch (error) {
console.error('Error in escalateTask:', error);
res.status(500).json({ message: 'Failed to escalate evaluation' });
}
};

export const resetDashboardDemoData = async (_req: Request, res: Response): Promise<void> => {
try {
await clearDashboardSeedData();
await seedDatabase();

res.json({ message: 'Dashboard demo data reset successfully.' });
} catch (error) {
console.error('Error in resetDashboardDemoData:', error);
res.status(500).json({ message: 'Failed to reset dashboard demo data' });
}
};
Loading