Initial RS Learning Lab Project
16
.htaccess
@ -1,18 +1,2 @@
|
||||
DirectoryIndex index.php index.html
|
||||
Options -Indexes
|
||||
Options -MultiViews
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# 0) Serve existing files/directories as-is
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# 1) Internal map: /page or /page/ -> /page.php (if such PHP file exists)
|
||||
RewriteCond %{REQUEST_FILENAME}.php -f
|
||||
RewriteRule ^(.+?)/?$ $1.php [L]
|
||||
|
||||
# 2) Optional: strip trailing slash for non-directories (keeps .php links working)
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.+)/$ $1 [R=301,L]
|
||||
|
||||
18
.htaccess_old
Normal file
@ -0,0 +1,18 @@
|
||||
DirectoryIndex index.php index.html
|
||||
Options -Indexes
|
||||
Options -MultiViews
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# 0) Serve existing files/directories as-is
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# 1) Internal map: /page or /page/ -> /page.php (if such PHP file exists)
|
||||
RewriteCond %{REQUEST_FILENAME}.php -f
|
||||
RewriteRule ^(.+?)/?$ $1.php [L]
|
||||
|
||||
# 2) Optional: strip trailing slash for non-directories (keeps .php links working)
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.+)/$ $1 [R=301,L]
|
||||
18
activate_institution.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost", "root", "", "rs_lab");
|
||||
if ($conn->connect_error) {
|
||||
die("DB Connection failed");
|
||||
}
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = intval($_GET['id']);
|
||||
|
||||
$sql = "UPDATE institutions SET status='active' WHERE id=$id";
|
||||
$conn->query($sql);
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
|
||||
// Redirect back to dashboard
|
||||
header("Location: admin_dashboard.php");
|
||||
exit();
|
||||
116
active_learners.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
// DB Connection
|
||||
$conn = new mysqli("localhost", "root", "", "rs_lab");
|
||||
if ($conn->connect_error) {
|
||||
die("Connection failed: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
/*
|
||||
ACTIVE LEARNERS:
|
||||
- Practiced at least once in last 7 days
|
||||
*/
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
pa.roll_no,
|
||||
MAX(pa.last_attempt_at) AS last_practice_date,
|
||||
DATEDIFF(NOW(), MAX(pa.last_attempt_at)) AS days_gap
|
||||
FROM practice_attempts pa
|
||||
GROUP BY pa.roll_no
|
||||
HAVING
|
||||
MAX(pa.last_attempt_at) >= NOW() - INTERVAL 7 DAY
|
||||
ORDER BY last_practice_date DESC
|
||||
";
|
||||
|
||||
$result = $conn->query($sql);
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Active Learners</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background: #0f172a;
|
||||
color: #e5e7eb;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
color: #22c55e;
|
||||
}
|
||||
.context {
|
||||
text-align: center;
|
||||
color: #9ca3af;
|
||||
margin-top: 10px;
|
||||
}
|
||||
table {
|
||||
width: 60%;
|
||||
margin: 30px auto;
|
||||
border-collapse: collapse;
|
||||
background: #020617;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.6);
|
||||
}
|
||||
th, td {
|
||||
padding: 14px;
|
||||
border-bottom: 1px solid #1e293b;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #14532d;
|
||||
color: #bbf7d0;
|
||||
}
|
||||
td {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
.gap-good {
|
||||
color: #22c55e;
|
||||
font-weight: bold;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>🟢 Active Learners (Last 7 Days)</h2>
|
||||
<div class="context">
|
||||
These students are maintaining learning continuity.
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Roll No</th>
|
||||
<th>Last Practice Date</th>
|
||||
<th>Days Since Last Practice</th>
|
||||
</tr>
|
||||
|
||||
<?php
|
||||
if ($result && $result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
echo "<tr>
|
||||
<td>{$row['roll_no']}</td>
|
||||
<td>{$row['last_practice_date']}</td>
|
||||
<td class='gap-good'>{$row['days_gap']} day(s)</td>
|
||||
</tr>";
|
||||
}
|
||||
} else {
|
||||
echo "<tr>
|
||||
<td colspan='3' class='empty'>No active learners yet</td>
|
||||
</tr>";
|
||||
}
|
||||
?>
|
||||
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<?php $conn->close(); ?>
|
||||
147
admin/admin_dashboard.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once("../config/db.php");
|
||||
|
||||
/*
|
||||
ASSUMPTION:
|
||||
- Founder already logged in
|
||||
- institutions table has:
|
||||
id, institution_name, email, username, password, institution_type, status
|
||||
*/
|
||||
|
||||
// 🔒 BASIC ADMIN PROTECTION (simple)
|
||||
if (!isset($_SESSION['admin_logged_in'])) {
|
||||
header("Location: admin_login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$message = "";
|
||||
|
||||
// 🔑 GENERATE CREDENTIALS
|
||||
if (isset($_POST['generate_credentials'])) {
|
||||
|
||||
$institution_id = $_POST['institution_id'];
|
||||
|
||||
// fetch institution
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT institution_name, institution_type
|
||||
FROM institutions
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$institution_id]);
|
||||
$inst = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($inst) {
|
||||
|
||||
// username pattern
|
||||
$username = "inst_" . $institution_id;
|
||||
|
||||
// temporary password
|
||||
$temp_password = strtoupper(substr(md5(time()), 0, 6));
|
||||
$hashed_password = password_hash($temp_password, PASSWORD_DEFAULT);
|
||||
|
||||
// update institution record
|
||||
$update = $pdo->prepare("
|
||||
UPDATE institutions
|
||||
SET username = ?, password = ?, status = 'active'
|
||||
WHERE id = ?
|
||||
");
|
||||
$update->execute([$username, $hashed_password, $institution_id]);
|
||||
|
||||
// login URL (FINAL)
|
||||
$login_url = "http://localhost/rs_lab/institution/login.php";
|
||||
|
||||
// activation message
|
||||
$message = "
|
||||
Hello 👋
|
||||
|
||||
Your institution has been activated on RS Learning Lab.
|
||||
|
||||
🏫 Institution Type: {$inst['institution_type']}
|
||||
|
||||
🔐 Institution Login Details:
|
||||
🌐 Login URL: {$login_url}
|
||||
👤 Username: {$username}
|
||||
🔑 Temporary Password: {$temp_password}
|
||||
|
||||
Please log in and change your password immediately after first login.
|
||||
|
||||
– RS Learning Lab Team
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
// fetch all institutions
|
||||
$list = $pdo->query("
|
||||
SELECT id, institution_name, email, institution_type, status
|
||||
FROM institutions
|
||||
ORDER BY id DESC
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Founder Dashboard | RS Learning Lab</title>
|
||||
<style>
|
||||
body { font-family: Arial; background:#f4f6f8; }
|
||||
table { border-collapse: collapse; width: 100%; background:#fff; }
|
||||
th, td { border:1px solid #ddd; padding:10px; text-align:left; }
|
||||
th { background:#0b8c9f; color:white; }
|
||||
.btn { padding:6px 12px; cursor:pointer; }
|
||||
.btn-gen { background:#ff7a00; color:white; border:none; }
|
||||
textarea { width:100%; height:180px; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h2>🧠 RS Learning Lab – Founder Dashboard</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Institution</th>
|
||||
<th>Email</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td><?= $row['id'] ?></td>
|
||||
<td><?= htmlspecialchars($row['institution_name']) ?></td>
|
||||
<td><?= htmlspecialchars($row['email']) ?></td>
|
||||
<td><?= ucfirst($row['institution_type']) ?></td>
|
||||
<td><?= $row['status'] ?></td>
|
||||
<td>
|
||||
<form method="POST" style="margin:0;">
|
||||
<input type="hidden" name="institution_id" value="<?= $row['id'] ?>">
|
||||
<button class="btn btn-gen" name="generate_credentials">
|
||||
Generate Credentials
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<h3>📩 Activation Message</h3>
|
||||
<textarea readonly><?= trim($message) ?></textarea><br><br>
|
||||
<button onclick="copyMsg()">Copy Message</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
function copyMsg() {
|
||||
const ta = document.querySelector("textarea");
|
||||
ta.select();
|
||||
document.execCommand("copy");
|
||||
alert("Message copied!");
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
53
admin/admin_login.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// demo login – no DB
|
||||
$_SESSION['institution_logged_in'] = true;
|
||||
$_SESSION['institution_name'] = "Demo Institution";
|
||||
header("Location: ../dashboard.php");
|
||||
exit;
|
||||
}
|
||||
include __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="grid" style="grid-template-columns: 1fr; max-width:420px; margin:0 auto;">
|
||||
|
||||
<div class="card">
|
||||
<h2 style="text-align:center; margin-bottom:6px;">Institution Admin Login</h2>
|
||||
<p style="text-align:center; color:var(--muted); margin-bottom:22px;">
|
||||
Secure access for registered schools and colleges
|
||||
</p>
|
||||
|
||||
<form method="post" action="">
|
||||
<div class="form-group">
|
||||
<label>Official Email ID</label>
|
||||
<input type="email" name="email" placeholder="admin@institution.edu" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" placeholder="Enter your password" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:18px;">
|
||||
<button class="btn btn-primary" style="width:100%;">
|
||||
Login to Dashboard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p style="text-align:center; margin-top:18px; font-size:13px; color:var(--muted);">
|
||||
Only registered institutions can access the dashboard.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
include __DIR__ . '/../includes/footer.php';
|
||||
?>
|
||||
229
admin/computer_quiz_setup.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
// admin/computer_quiz_setup.php
|
||||
session_start();
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
// 🔐 Auth guard
|
||||
if (!isset($_SESSION['institution_id']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$institutionId = $_SESSION['institution_id'];
|
||||
$institutionName = $_SESSION['institution_name'] ?? 'Your Institution';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
$quiz_name = trim($_POST['quiz_name']);
|
||||
$total_questions = 20; // FIXED for learning style
|
||||
|
||||
if ($quiz_name === '') {
|
||||
$error = "Quiz name is required.";
|
||||
} else {
|
||||
|
||||
try {
|
||||
// 1️⃣ Deactivate existing quizzes (only one active)
|
||||
$stmt = $conn->prepare(
|
||||
"UPDATE computer_quizzes
|
||||
SET is_active = 0
|
||||
WHERE institution_id = ?"
|
||||
);
|
||||
$stmt->bind_param("i", $institutionId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// 2️⃣ Create new active quiz
|
||||
$stmt = $conn->prepare(
|
||||
"INSERT INTO computer_quizzes
|
||||
(institution_id, quiz_name, total_questions, is_active, created_at)
|
||||
VALUES (?, ?, ?, 1, NOW())"
|
||||
);
|
||||
$stmt->bind_param("isi", $institutionId, $quiz_name, $total_questions);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$message = "Computer quiz started successfully.";
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$error = "Something went wrong. Please try again.";
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Start Computer Quiz | RS Learning Lab</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
*{box-sizing:border-box;font-family:'Inter',system-ui}
|
||||
body{margin:0;background:#0b1220;color:#e5e7eb}
|
||||
|
||||
.wrapper{display:flex;min-height:100vh}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar{
|
||||
width:240px;
|
||||
background:#0f172a;
|
||||
padding:24px 18px;
|
||||
}
|
||||
.logo{
|
||||
font-size:20px;
|
||||
font-weight:700;
|
||||
color:#22d3ee;
|
||||
margin-bottom:32px;
|
||||
}
|
||||
.nav a{
|
||||
display:block;
|
||||
padding:12px 14px;
|
||||
margin-bottom:6px;
|
||||
border-radius:10px;
|
||||
color:#cbd5f5;
|
||||
text-decoration:none;
|
||||
font-size:14px;
|
||||
}
|
||||
.nav a.active,
|
||||
.nav a:hover{
|
||||
background:#1e293b;
|
||||
color:#22d3ee;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main{flex:1;display:flex;flex-direction:column}
|
||||
|
||||
/* Topbar */
|
||||
.topbar{
|
||||
background:#0f172a;
|
||||
padding:18px 28px;
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:center;
|
||||
border-bottom:1px solid #1e293b;
|
||||
}
|
||||
.topbar .title{font-size:18px;font-weight:600}
|
||||
.topbar .right{font-size:13px;color:#94a3b8}
|
||||
.logout{
|
||||
margin-left:16px;
|
||||
padding:8px 12px;
|
||||
background:#1e293b;
|
||||
border-radius:8px;
|
||||
text-decoration:none;
|
||||
color:#e5e7eb;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content{padding:36px;max-width:720px}
|
||||
|
||||
/* Card */
|
||||
.card{
|
||||
background:#0f172a;
|
||||
border:1px solid #1e293b;
|
||||
border-radius:16px;
|
||||
padding:32px;
|
||||
}
|
||||
.card h2{margin-top:0;margin-bottom:6px}
|
||||
.card p{color:#94a3b8;margin-bottom:26px}
|
||||
|
||||
label{display:block;margin-top:18px;color:#cbd5f5}
|
||||
input{
|
||||
width:100%;
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
border:1px solid #1e293b;
|
||||
background:#020617;
|
||||
color:#e5e7eb;
|
||||
margin-top:6px;
|
||||
}
|
||||
|
||||
.note{
|
||||
margin-top:10px;
|
||||
font-size:13px;
|
||||
color:#64748b;
|
||||
}
|
||||
|
||||
button{
|
||||
margin-top:28px;
|
||||
padding:14px 20px;
|
||||
border-radius:14px;
|
||||
border:0;
|
||||
background:#22d3ee;
|
||||
color:#001018;
|
||||
font-weight:700;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.success{margin-top:16px;color:#86efac}
|
||||
.error{margin-top:16px;color:#fca5a5}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="logo">RS Learning Lab</div>
|
||||
<nav class="nav">
|
||||
<a href="index.php">Dashboard</a>
|
||||
<a class="active" href="#">Computer Quiz</a>
|
||||
<a href="#">Manual Quiz</a>
|
||||
<a href="#">Students</a>
|
||||
<a href="#">Reports</a>
|
||||
<a href="#">Coding Challenges</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
|
||||
<!-- Topbar -->
|
||||
<div class="topbar">
|
||||
<div class="title">Start Computer Quiz</div>
|
||||
<div class="right">
|
||||
<?= htmlspecialchars($institutionName) ?> · Admin
|
||||
<a class="logout" href="logout.php">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="content">
|
||||
<div class="card">
|
||||
<h2>Create Learning Style Quiz</h2>
|
||||
<p>
|
||||
This quiz identifies learning patterns and behaviour.<br>
|
||||
No marks. No answer key. No subject.
|
||||
</p>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="success"><?= htmlspecialchars($message) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post">
|
||||
<label>Quiz Name</label>
|
||||
<input name="quiz_name" placeholder="Learning Style Assessment – Class 8" required>
|
||||
|
||||
<div class="note">
|
||||
• Total questions fixed at 20<br>
|
||||
• Only one active quiz per institution
|
||||
</div>
|
||||
|
||||
<button type="submit">Start Computer Quiz</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
107
admin/create_credentials.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once '../db_config.php';
|
||||
|
||||
if (!isset($_SESSION['admin_logged_in'])) {
|
||||
header("Location: admin_login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
die("Invalid request");
|
||||
}
|
||||
|
||||
$institution_id = (int) $_GET['id'];
|
||||
|
||||
/* Fetch institution */
|
||||
$stmt = $pdo->prepare("SELECT institution_name, institution_type FROM institutions WHERE id = ?");
|
||||
$stmt->execute([$institution_id]);
|
||||
$inst = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$inst) {
|
||||
die("Institution not found");
|
||||
}
|
||||
|
||||
/* Generate credentials */
|
||||
$username = strtoupper(substr($inst['institution_type'], 0, 3)) . "_RS_" . $institution_id;
|
||||
$password_plain = substr(str_shuffle("ABCDEFGHJKLMNPQRSTUVWXYZ23456789"), 0, 8);
|
||||
$password_hash = password_hash($password_plain, PASSWORD_DEFAULT);
|
||||
|
||||
/* Save credentials */
|
||||
$update = $pdo->prepare("
|
||||
UPDATE institutions
|
||||
SET username = ?, password_hash = ?, credentials_created = 1
|
||||
WHERE id = ?
|
||||
");
|
||||
$update->execute([$username, $password_hash, $institution_id]);
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Credentials Generated</title>
|
||||
< style >
|
||||
body {
|
||||
background: #050b14;
|
||||
color: #eaf2ff;
|
||||
font-family: Inter, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 30px 40px;
|
||||
border-radius: 14px;
|
||||
text-align: center;
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #b8c6de;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cred {
|
||||
margin-top: 20px;
|
||||
font-size: 15px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
color: #7fd4ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
</ style >
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="card">
|
||||
<h2>Credentials Created</h2>
|
||||
<p>Send these credentials to the institution</p>
|
||||
|
||||
<div class="cred">
|
||||
<strong>Username:</strong><br><?= $username ?><br><br>
|
||||
<strong>Password:</strong><br><?= $password_plain ?>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:14px;font-size:12px;">
|
||||
Institution will be forced to change password on first login.
|
||||
</p>
|
||||
|
||||
<a href="admin_dashboard.php">← Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
126
admin/index.php
@ -1,66 +1,70 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.php">Admin Panel</a>
|
||||
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="logout.php">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<div class="container py-5">
|
||||
<h1 class="display-4 fw-bold">Admin Dashboard</h1>
|
||||
<p class="fs-5 text-muted">Welcome to the admin panel. Here you can manage the content of the application.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Challenges</h5>
|
||||
<p class="card-text">Manage coding challenges.</p>
|
||||
<a href="challenges.php" class="btn btn-primary">Go to Challenges</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Competitions</h5>
|
||||
<p class="card-text">Manage coding competitions.</p>
|
||||
<a href="competitions.php" class="btn btn-primary">Go to Competitions</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Users</h5>
|
||||
<p class="card-text">Manage users.</p>
|
||||
<a href="users.php" class="btn btn-primary">Go to Users</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Dashboard Header -->
|
||||
<div style="margin-bottom:28px;">
|
||||
<h1 style="font-size:28px; font-weight:800; margin-bottom:6px;">
|
||||
Institution Dashboard
|
||||
</h1>
|
||||
<p style="color:var(--muted);">
|
||||
Manage learning insights, coding challenges, and certifications from one place.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Dashboard Cards -->
|
||||
<div class="grid">
|
||||
|
||||
<!-- Learning Insight -->
|
||||
<div class="card">
|
||||
<h3>Learning Style Assessment</h3>
|
||||
<p>
|
||||
Understand how students learn through pattern-based assessments
|
||||
without marks or exam pressure.
|
||||
</p>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="../manual_mode_setup.php" class="btn btn-primary">
|
||||
Start Assessment
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Coding Challenges -->
|
||||
<div class="card">
|
||||
<h3>Coding Challenges</h3>
|
||||
<p>
|
||||
Structured coding tracks designed to build logical thinking
|
||||
and real-world problem-solving skills.
|
||||
</p>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="../coding/tracks.php" class="btn btn-primary">
|
||||
View Coding Tracks
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certificates -->
|
||||
<div class="card">
|
||||
<h3>Certificates</h3>
|
||||
<p>
|
||||
Generate professional participation certificates
|
||||
recognizing student effort and demonstrated skills.
|
||||
</p>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="../certificates/generate_certificate_pdf.php" class="btn btn-primary">
|
||||
Preview Certificate
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
include __DIR__ . '/../includes/footer.php';
|
||||
?>
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
$errors = [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if ($username === 'admin' && $password === 'password') {
|
||||
$_SESSION['admin_logged_in'] = true;
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
$errors[] = 'Invalid username or password';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Login</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">Admin Login</div>
|
||||
<div class="card-body">
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<p><?php echo $error; ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form action="login.php" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
196
admin_dashboard.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost", "root", "", "rs_lab");
|
||||
if ($conn->connect_error) {
|
||||
die("DB Connection failed");
|
||||
}
|
||||
|
||||
/* GET VIEW FILTER */
|
||||
$view = $_GET['view'] ?? 'active';
|
||||
|
||||
/* COUNTS */
|
||||
$total = $conn->query("SELECT COUNT(*) c FROM institutions WHERE deleted_at IS NULL")->fetch_assoc()['c'];
|
||||
$active = $conn->query("SELECT COUNT(*) c FROM institutions WHERE status='active' AND deleted_at IS NULL")->fetch_assoc()['c'];
|
||||
$inactive = $conn->query("SELECT COUNT(*) c FROM institutions WHERE status='inactive' AND deleted_at IS NULL")->fetch_assoc()['c'];
|
||||
$deleted = $conn->query("SELECT COUNT(*) c FROM institutions WHERE deleted_at IS NOT NULL")->fetch_assoc()['c'];
|
||||
|
||||
/* FILTER QUERY */
|
||||
if($view == 'active'){
|
||||
$result = $conn->query("SELECT * FROM institutions WHERE status='active' AND deleted_at IS NULL");
|
||||
}
|
||||
elseif($view == 'inactive'){
|
||||
$result = $conn->query("SELECT * FROM institutions WHERE status='inactive' AND deleted_at IS NULL");
|
||||
}
|
||||
elseif($view == 'deleted'){
|
||||
$result = $conn->query("SELECT * FROM institutions WHERE deleted_at IS NOT NULL");
|
||||
}
|
||||
else{
|
||||
$result = $conn->query("SELECT * FROM institutions WHERE deleted_at IS NULL");
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Founder Dashboard | RS Learning Lab</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial;
|
||||
background: radial-gradient(circle at top, #020617, #0f172a);
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex: 1;
|
||||
padding: 25px;
|
||||
border-radius: 16px;
|
||||
background: #020617;
|
||||
cursor: pointer;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.total { border-left: 4px solid #60a5fa; }
|
||||
.active { border-left: 4px solid #22c55e; }
|
||||
.inactive { border-left: 4px solid #facc15; }
|
||||
.deleted { border-left: 4px solid #ef4444; }
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,td {
|
||||
padding:12px;
|
||||
border-bottom:1px solid #1e293b;
|
||||
}
|
||||
|
||||
.badge {
|
||||
cursor:pointer;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.badge.active { color:#22c55e; }
|
||||
.badge.inactive { color:#ef4444; }
|
||||
|
||||
.delete-btn{
|
||||
background:red;
|
||||
color:white;
|
||||
border:none;
|
||||
padding:6px 10px;
|
||||
border-radius:6px;
|
||||
cursor:pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>Founder Dashboard</h1>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<a href="?view=all" style="flex:1;text-decoration:none;color:white;">
|
||||
<div class="card total">
|
||||
<h2><?= $total ?></h2>
|
||||
<p>Total</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="?view=active" style="flex:1;text-decoration:none;color:white;">
|
||||
<div class="card active">
|
||||
<h2><?= $active ?></h2>
|
||||
<p>Active</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="?view=inactive" style="flex:1;text-decoration:none;color:white;">
|
||||
<div class="card inactive">
|
||||
<h2><?= $inactive ?></h2>
|
||||
<p>Inactive</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="?view=deleted" style="flex:1;text-decoration:none;color:white;">
|
||||
<div class="card deleted">
|
||||
<h2><?= $deleted ?></h2>
|
||||
<p>Deleted</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Contact</th>
|
||||
<th>Phone</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
|
||||
<?php while($row = $result->fetch_assoc()): ?>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a href="institution_detail.php?id=<?= $row['id'] ?>"
|
||||
style="color:#60a5fa;text-decoration:none;">
|
||||
<?= htmlspecialchars($row['institution_name']) ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?= $row['contact_person'] ?></td>
|
||||
<td><?= $row['phone'] ?></td>
|
||||
<td><?= $row['email'] ?></td>
|
||||
|
||||
<td>
|
||||
<?php if($row['deleted_at']): ?>
|
||||
<span style="color:red;">Deleted</span>
|
||||
<?php else: ?>
|
||||
<a href="toggle_institution_status.php?id=<?= $row['id'] ?>&view=<?= $view ?>">
|
||||
<span class="badge <?= $row['status'] ?>">
|
||||
<?= ucfirst($row['status']) ?>
|
||||
</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<?php if(!$row['deleted_at']): ?>
|
||||
<form method="POST" action="delete_institution.php">
|
||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
||||
<input type="hidden" name="view" value="<?= $view ?>">
|
||||
<button class="delete-btn">Delete</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<?php endwhile; ?>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<?php $conn->close(); ?>
|
||||
187
admin_login.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
// admin_login.php
|
||||
session_start();
|
||||
|
||||
$error = "";
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// Demo credentials
|
||||
if ($email === "admin@rslearninglab.in" && $password === "admin123") {
|
||||
$_SESSION['admin_logged_in'] = true;
|
||||
header("Location: dashboard.php");
|
||||
exit;
|
||||
} else {
|
||||
$error = "Invalid admin credentials";
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin Login | RS Learning Lab</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
background: radial-gradient(circle at top, #0b1a2a, #05080f);
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-wrapper {
|
||||
background: rgba(255,255,255,0.04);
|
||||
backdrop-filter: blur(14px);
|
||||
border-radius: 18px;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 30px 80px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.brand {
|
||||
text-align: center;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.brand img {
|
||||
width: 64px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.brand h1 {
|
||||
font-size: 28px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.brand p {
|
||||
font-size: 14px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #0b8c9f, #1fd1f9);
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 30px rgba(31,209,249,0.4);
|
||||
}
|
||||
|
||||
.error {
|
||||
background: rgba(255,0,0,0.15);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-box {
|
||||
margin-top: 24px;
|
||||
background: rgba(255,255,255,0.06);
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.demo-box strong {
|
||||
color: #1fd1f9;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="login-wrapper">
|
||||
|
||||
<div class="brand">
|
||||
<!-- Optional logo -->
|
||||
<!-- <img src="assets/logo.png" alt="RS Learning Lab"> -->
|
||||
<h1>RS Learning Lab</h1>
|
||||
<p>Institution Admin Login</p>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" name="email" placeholder="admin@rslearninglab.in" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" placeholder="••••••••" required>
|
||||
</div>
|
||||
|
||||
<button type="submit">Login as Admin</button>
|
||||
</form>
|
||||
|
||||
<div class="demo-box">
|
||||
<strong>Demo Credentials</strong><br>
|
||||
Email: admin@rslearninglab.in<br>
|
||||
Password: admin123
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
© RS Learning Lab · Learning Behaviour Platform
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
71
api_scan_omr.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// api_scan_omr.php
|
||||
// RS Learning Lab – OMR Scan API (Silent Mode)
|
||||
|
||||
$pythonPath = "python";
|
||||
$scriptPath = __DIR__ . "/omr_read_sheet.py";
|
||||
$uploadDir = __DIR__ . "/uploads/";
|
||||
|
||||
// Create uploads folder
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
|
||||
// Student context
|
||||
$roll_no = $_GET['roll'] ?? $_POST['roll'] ?? null;
|
||||
$student_name = $_GET['name'] ?? $_POST['name'] ?? null;
|
||||
|
||||
// Validate upload
|
||||
if (!isset($_FILES['omr_image'])) {
|
||||
return ""; // 🔥 changed
|
||||
}
|
||||
|
||||
$tmpName = $_FILES['omr_image']['tmp_name'];
|
||||
$originalName = basename($_FILES['omr_image']['name']);
|
||||
$ext = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
|
||||
// Allow only images
|
||||
$allowed = ['jpg', 'jpeg', 'png'];
|
||||
if (!in_array(strtolower($ext), $allowed)) {
|
||||
return ""; // 🔥 changed
|
||||
}
|
||||
|
||||
// Save image
|
||||
$filename = time() . "_" . uniqid() . "." . $ext;
|
||||
$targetPath = $uploadDir . $filename;
|
||||
|
||||
if (!move_uploaded_file($tmpName, $targetPath)) {
|
||||
return ""; // 🔥 changed
|
||||
}
|
||||
|
||||
// Call Python script
|
||||
$command = escapeshellcmd("$pythonPath \"$scriptPath\" \"$targetPath\"");
|
||||
$raw_output = shell_exec($command);
|
||||
|
||||
// Clean output
|
||||
if (!$raw_output) {
|
||||
return ""; // 🔥 changed
|
||||
}
|
||||
|
||||
$raw_output = trim($raw_output);
|
||||
$raw_output = str_replace(["\n", "\r", " "], "", $raw_output);
|
||||
|
||||
$clean_answers = preg_replace("/[^A-D,]/", "", strtoupper($raw_output));
|
||||
$clean_answers = rtrim($clean_answers, ",");
|
||||
|
||||
// Logging (optional)
|
||||
$logEntry = [
|
||||
'timestamp' => date("Y-m-d H:i:s"),
|
||||
'roll_no' => $roll_no,
|
||||
'student_name' => $student_name,
|
||||
'answers' => $clean_answers
|
||||
];
|
||||
|
||||
file_put_contents(
|
||||
__DIR__ . "/omr_log.txt",
|
||||
json_encode($logEntry) . PHP_EOL,
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
// 🔥 FINAL CHANGE: RETURN instead of echo
|
||||
return $clean_answers;
|
||||
@ -1,13 +0,0 @@
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
max-width: 400px;
|
||||
margin: 100px auto;
|
||||
padding: 30px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
:root {
|
||||
--primary-color: #20C997;
|
||||
--secondary-color: #FD7E14;
|
||||
--dark-color: #212529;
|
||||
--light-color: #F8F9FA;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.btn-primary, .btn-primary:hover, .btn-primary:active, .btn-primary:visited {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(32, 201, 151, 0.2);
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-img-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-img-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0));
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-img-overlay .card-title a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card-img-overlay .badge {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
background: linear-gradient(45deg, var(--primary-color), #5be3c2);
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const difficultyFilter = document.getElementById('difficulty-filter');
|
||||
const learningStyleFilter = document.getElementById('learning-style-filter');
|
||||
const sortBy = document.getElementById('sort-by');
|
||||
const challengeList = document.querySelector('.challenge-list');
|
||||
|
||||
if (difficultyFilter) {
|
||||
function fetchChallenges() {
|
||||
const difficulty = difficultyFilter.value;
|
||||
const learningStyle = learningStyleFilter.value;
|
||||
const sort = sortBy.value;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('difficulty', difficulty);
|
||||
formData.append('learning_style', learningStyle);
|
||||
formData.append('sort_by', sort);
|
||||
|
||||
fetch('api/filter_challenges.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
challengeList.innerHTML = data;
|
||||
});
|
||||
}
|
||||
|
||||
difficultyFilter.addEventListener('change', fetchChallenges);
|
||||
learningStyleFilter.addEventListener('change', fetchChallenges);
|
||||
sortBy.addEventListener('change', fetchChallenges);
|
||||
}
|
||||
|
||||
const runCodeBtn = document.getElementById('run-code');
|
||||
if (runCodeBtn) {
|
||||
runCodeBtn.addEventListener('click', function() {
|
||||
const solution = document.getElementById('solution').value;
|
||||
const language = document.getElementById('language').value;
|
||||
const challengeId = document.querySelector('input[name="challenge_id"]').value;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('solution', solution);
|
||||
formData.append('language', language);
|
||||
formData.append('challenge_id', challengeId);
|
||||
|
||||
fetch('api/run_code.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
const outputDiv = document.getElementById('output');
|
||||
outputDiv.innerHTML = data;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 36 KiB |
72
auto_route_engine.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
// auto_route_engine.php
|
||||
// RS Learning Lab – Momentum-based Auto Routing Engine
|
||||
// Decides next learning track automatically
|
||||
|
||||
session_start();
|
||||
|
||||
/* ===============================
|
||||
DB CONFIG
|
||||
================================ */
|
||||
|
||||
require_once "db_config.php";
|
||||
|
||||
|
||||
/* ===============================
|
||||
INPUT
|
||||
================================ */
|
||||
$roll_no = $_GET['roll'] ?? $_SESSION['roll'] ?? null;
|
||||
$name = $_GET['name'] ?? $_SESSION['name'] ?? null;
|
||||
$concept_id = $_GET['concept'] ?? 1;
|
||||
|
||||
if (!$roll_no || !$name) {
|
||||
die("Student context missing");
|
||||
}
|
||||
|
||||
$_SESSION['roll'] = $roll_no;
|
||||
$_SESSION['name'] = $name;
|
||||
|
||||
/* ===============================
|
||||
FETCH MOMENTUM
|
||||
================================ */
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT momentum_state
|
||||
FROM learning_momentum
|
||||
WHERE roll_no = :roll
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':roll' => $roll_no]);
|
||||
$data = $stmt->fetch();
|
||||
|
||||
$momentum_state = $data['momentum_state'] ?? "Building";
|
||||
|
||||
/* ===============================
|
||||
ROUTING RULE ENGINE
|
||||
================================ */
|
||||
switch ($momentum_state) {
|
||||
|
||||
case "Applying":
|
||||
$next_track = "final_track.php";
|
||||
$reason = "You are ready to apply what you’ve learned calmly.";
|
||||
break;
|
||||
|
||||
case "Stabilizing":
|
||||
$next_track = "reinforcement_track.php";
|
||||
$reason = "Let’s stabilize your understanding before final application.";
|
||||
break;
|
||||
|
||||
case "Reset":
|
||||
case "Building":
|
||||
default:
|
||||
$next_track = "practice_track.php";
|
||||
$reason = "Practice builds confidence. Let’s continue calmly.";
|
||||
break;
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
AUTO REDIRECT
|
||||
================================ */
|
||||
header("Location: $next_track?roll=" . urlencode($roll_no) .
|
||||
"&name=" . urlencode($name) .
|
||||
"&concept=" . urlencode($concept_id));
|
||||
exit;
|
||||
70
bulk_download.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
$folder = "generated_pdfs/";
|
||||
|
||||
$files = glob($folder . "*.pdf");
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Download Reports</title>
|
||||
|
||||
<style>
|
||||
body{
|
||||
background:#0b1020;
|
||||
color:white;
|
||||
font-family:Arial;
|
||||
padding:30px;
|
||||
}
|
||||
|
||||
.card{
|
||||
background:#111827;
|
||||
padding:25px;
|
||||
border-radius:10px;
|
||||
max-width:700px;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
a{
|
||||
display:block;
|
||||
margin:10px 0;
|
||||
padding:10px;
|
||||
background:#0ea5e9;
|
||||
color:white;
|
||||
text-decoration:none;
|
||||
border-radius:6px;
|
||||
width:300px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="card">
|
||||
|
||||
<h2>Learning Style Reports</h2>
|
||||
|
||||
<?php
|
||||
|
||||
if(count($files)==0){
|
||||
echo "No reports generated yet.";
|
||||
}
|
||||
|
||||
foreach($files as $file){
|
||||
|
||||
$name = basename($file);
|
||||
|
||||
echo "<a href='$file' download>$name</a>";
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
48
certificate.html
Normal file
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
border: 3px solid teal;
|
||||
padding: 40px;
|
||||
}
|
||||
.center { text-align: center; }
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="center">
|
||||
<img src="../assets/logo.png" width="120"><br><br>
|
||||
<h1>CERTIFICATE OF PARTICIPATION</h1>
|
||||
</div>
|
||||
|
||||
<p class="center">This is to certify that</p>
|
||||
|
||||
<h2 class="center">STUDENT NAME</h2>
|
||||
|
||||
<p class="center">
|
||||
has successfully completed the coding challenge track
|
||||
<b>Python Foundations</b><br>
|
||||
conducted by <b>RS Learning Lab</b>
|
||||
</p>
|
||||
|
||||
<div class="footer">
|
||||
<div>
|
||||
<img src="../assets/signature.png" width="150"><br>
|
||||
<b>Gokula Krishnan</b><br>
|
||||
Founder – RS Learning Lab
|
||||
</div>
|
||||
<div>
|
||||
Verified Learning Certificate<br>
|
||||
www.rslearninglab.in
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
137
certificate_verify.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
// ============================================
|
||||
// RS Learning Lab - Certificate Verification
|
||||
// ============================================
|
||||
|
||||
require_once 'db_config.php';
|
||||
|
||||
$verified = false;
|
||||
$error = '';
|
||||
$data = [];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
$roll_no = $_POST['roll_no'] ?? '';
|
||||
$concept_id = isset($_POST['concept_id']) ? (int)$_POST['concept_id'] : 0;
|
||||
|
||||
if ($roll_no === '' || $concept_id === 0) {
|
||||
$error = "Please enter valid details.";
|
||||
} else {
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
qa.roll_no,
|
||||
SUM(qa.is_correct) AS correct_count,
|
||||
COUNT(*) AS total_count
|
||||
FROM question_attempts qa
|
||||
WHERE qa.roll_no = ?
|
||||
AND qa.track = 'final'
|
||||
");
|
||||
$stmt->execute([$roll_no]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row && $row['total_count'] > 0) {
|
||||
$percent = round(($row['correct_count'] / $row['total_count']) * 100);
|
||||
|
||||
if ($percent >= 60) {
|
||||
$verified = true;
|
||||
$data = [
|
||||
'roll_no' => $roll_no,
|
||||
'concept' => $concept_id,
|
||||
'score' => $percent,
|
||||
'date' => date("d M Y")
|
||||
];
|
||||
} else {
|
||||
$error = "Certificate not eligible (final not passed).";
|
||||
}
|
||||
} else {
|
||||
$error = "No final assessment found.";
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Certificate Verification</title>
|
||||
<style>
|
||||
body {
|
||||
background:#020617;
|
||||
color:#e5e7eb;
|
||||
font-family:Arial;
|
||||
}
|
||||
.box {
|
||||
max-width:520px;
|
||||
margin:80px auto;
|
||||
background:#020617;
|
||||
padding:30px;
|
||||
border-radius:16px;
|
||||
border:1px solid #1e293b;
|
||||
}
|
||||
h2 { color:#38bdf8; text-align:center; }
|
||||
input {
|
||||
width:100%;
|
||||
padding:10px;
|
||||
margin:10px 0;
|
||||
border-radius:8px;
|
||||
border:none;
|
||||
}
|
||||
button {
|
||||
width:100%;
|
||||
padding:12px;
|
||||
background:#0b8c9f;
|
||||
color:white;
|
||||
border:none;
|
||||
border-radius:8px;
|
||||
font-weight:bold;
|
||||
cursor:pointer;
|
||||
}
|
||||
.success {
|
||||
margin-top:20px;
|
||||
padding:15px;
|
||||
background:#022c22;
|
||||
border-radius:10px;
|
||||
color:#22c55e;
|
||||
}
|
||||
.error {
|
||||
margin-top:20px;
|
||||
padding:15px;
|
||||
background:#3f1d1d;
|
||||
border-radius:10px;
|
||||
color:#f87171;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="box">
|
||||
|
||||
<h2>Certificate Verification</h2>
|
||||
|
||||
<form method="post">
|
||||
<input type="text" name="roll_no" placeholder="Student Roll No" required>
|
||||
<input type="number" name="concept_id" placeholder="Concept ID" required>
|
||||
<button type="submit">Verify Certificate</button>
|
||||
</form>
|
||||
|
||||
<?php if ($verified): ?>
|
||||
<div class="success">
|
||||
✅ Certificate Verified<br><br>
|
||||
Roll No: <strong><?=htmlspecialchars($data['roll_no'])?></strong><br>
|
||||
Concept: <strong><?=htmlspecialchars($data['concept'])?></strong><br>
|
||||
Score: <strong><?=$data['score']?>%</strong><br>
|
||||
Status: <strong>VALID</strong><br>
|
||||
Date: <strong><?=$data['date']?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error">
|
||||
❌ <?=$error?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
certificates/assets/certificate_bg.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
certificates/assets/logo.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
certificates/assets/signature.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
130
certificates/certificate_template.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
function base64img($path) {
|
||||
$type = pathinfo($path, PATHINFO_EXTENSION);
|
||||
$data = file_get_contents($path);
|
||||
return 'data:image/' . $type . ';base64,' . base64_encode($data);
|
||||
}
|
||||
|
||||
$logo = base64img(__DIR__ . '/../assets/logo.png');
|
||||
$sign = base64img(__DIR__ . '/../assets/signature.png');
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<style>
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 12mm;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Georgia, serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.certificate {
|
||||
border: 2px solid #0b8c9f;
|
||||
padding: 15mm;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header img {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
letter-spacing: 2px;
|
||||
margin: 8mm 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.student {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.track {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 15mm;
|
||||
display: table;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
display: table-cell;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.signature img {
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.verify {
|
||||
text-align: center;
|
||||
margin-top: 6mm;
|
||||
font-size: 11px;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="certificate">
|
||||
|
||||
<div class="header">
|
||||
<img src="<?= $logo ?>">
|
||||
<div class="title">CERTIFICATE OF PARTICIPATION</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>This is to certify that</p>
|
||||
|
||||
<div class="student"><?= htmlspecialchars($student_name) ?></div>
|
||||
|
||||
<p>has successfully participated in the</p>
|
||||
|
||||
<div class="track"><?= htmlspecialchars($track_name) ?></div>
|
||||
|
||||
<p>conducted by <b>RS Learning Lab</b></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-left signature">
|
||||
<img src="<?= $sign ?>"><br>
|
||||
<b>Gokula Krishnan</b><br>
|
||||
Founder – RS Learning Lab
|
||||
</div>
|
||||
|
||||
<div class="footer-right">
|
||||
Certificate ID: <?= htmlspecialchars($certificate_id) ?><br>
|
||||
Issued on: <?= date("d M Y", strtotime($issued_at)) ?><br>
|
||||
<small>Verify at: rslearninglab.com/verify</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
72
certificates/certificate_tmp.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
}
|
||||
|
||||
/* FULL BACKGROUND IMAGE */
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
}
|
||||
|
||||
/* STUDENT NAME */
|
||||
.name {
|
||||
position: absolute;
|
||||
top: 150mm; /* 🔥 adjust small up/down if needed */
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
/* CERTIFICATE ID */
|
||||
.cert-id {
|
||||
position: absolute;
|
||||
top: 35mm;
|
||||
right: 30mm;
|
||||
font-size: 12px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
/* ISSUE DATE */
|
||||
.issue-date {
|
||||
position: absolute;
|
||||
top: 42mm;
|
||||
right: 30mm;
|
||||
font-size: 12px;
|
||||
color: #334155;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<img class="bg" src="assets/certificate_bg.png">
|
||||
|
||||
<div class="cert-id">Certificate ID: {{CERT_ID}}</div>
|
||||
<div class="issue-date">Issue Date: {{ISSUE_DATE}}</div>
|
||||
|
||||
<div class="name">{{NAME}}</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
25
certificates/generate_certificate_pdf.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
|
||||
$name = $_GET['name'] ?? 'STUDENT NAME';
|
||||
$certId = 'RSL-' . date('Y') . '-' . rand(10000,99999);
|
||||
$issueDate = date('F Y');
|
||||
|
||||
$html = file_get_contents(__DIR__ . '/certificate_tmp.html');
|
||||
|
||||
$html = str_replace('{{NAME}}', htmlspecialchars($name), $html);
|
||||
$html = str_replace('{{CERT_ID}}', $certId, $html);
|
||||
$html = str_replace('{{ISSUE_DATE}}', $issueDate, $html);
|
||||
|
||||
$options = new Options();
|
||||
$options->set('isRemoteEnabled', true);
|
||||
$options->set('chroot', realpath(__DIR__));
|
||||
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html);
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
$dompdf->stream("certificate_$name.pdf", ["Attachment" => false]);
|
||||
53
certificates/preview_certificate.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
include __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<div style="margin-bottom:26px;">
|
||||
<h1 style="font-size:26px; font-weight:800;">Certificate Preview</h1>
|
||||
<p style="color:var(--muted);">
|
||||
This is how the official certificate will appear for students.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="certificate">
|
||||
<img src="/rs_lab/assets/rs_logo.png" style="height:60px; margin-bottom:20px;">
|
||||
|
||||
<h2 style="letter-spacing:1px;">Certificate of Participation</h2>
|
||||
|
||||
<p style="margin-top:16px;">This is to certify that</p>
|
||||
|
||||
<div class="student-name">Gokul</div>
|
||||
|
||||
<p>
|
||||
has actively participated in the RS Learning Lab Coding Challenge Program
|
||||
and demonstrated consistent learning effort.
|
||||
</p>
|
||||
|
||||
<h4 style="margin-top:24px;">Demonstrated Skills</h4>
|
||||
<p>Logical Thinking · Problem Solving · Coding Fundamentals</p>
|
||||
|
||||
<div style="margin-top:40px; text-align:left;">
|
||||
<img src="/rs_lab/assets/signature.png" style="height:50px;">
|
||||
<div class="signature-name">Gokula Krishnan</div>
|
||||
<div style="font-size:13px; color:var(--muted);">
|
||||
Founder – RS Learning Lab
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="margin-top:26px;">
|
||||
<a href="generate_certificate_pdf.php" class="btn btn-primary">
|
||||
Download Official Certificate (PDF)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
include __DIR__ . '/../includes/footer.php';
|
||||
?>
|
||||
59
certificates/verify.php
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Certificate Verification | RS Learning Lab</title>
|
||||
<style>
|
||||
body {
|
||||
background:#0f172a;
|
||||
color:#ffffff;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.card {
|
||||
width:420px;
|
||||
margin:120px auto;
|
||||
background:#020617;
|
||||
padding:30px;
|
||||
border-radius:14px;
|
||||
box-shadow:0 0 25px rgba(0,0,0,0.6);
|
||||
}
|
||||
h2 {
|
||||
text-align:center;
|
||||
}
|
||||
input {
|
||||
width:100%;
|
||||
padding:12px;
|
||||
margin-top:15px;
|
||||
border-radius:8px;
|
||||
border:none;
|
||||
font-size:15px;
|
||||
}
|
||||
button {
|
||||
width:100%;
|
||||
padding:12px;
|
||||
margin-top:20px;
|
||||
background:#0b8c9f;
|
||||
color:#fff;
|
||||
border:none;
|
||||
border-radius:8px;
|
||||
font-size:16px;
|
||||
cursor:pointer;
|
||||
}
|
||||
button:hover {
|
||||
background:#0aa0b5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="card">
|
||||
<h2>Certificate Verification</h2>
|
||||
<p style="text-align:center;">Enter Certificate ID</p>
|
||||
|
||||
<form action="verify_action.php" method="POST">
|
||||
<input type="text" name="certificate_id" placeholder="Eg: RSL-2025-00123" required>
|
||||
<button type="submit">Verify Certificate</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
31
certificates/verify_action.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
session_start();
|
||||
include_once __DIR__ . '/../includes/db.php';
|
||||
|
||||
// Allow only POST request
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
die("Invalid Request");
|
||||
}
|
||||
|
||||
// Check required input
|
||||
if (!isset($_POST['certificate_id']) || empty($_POST['certificate_id'])) {
|
||||
die("Invalid Request");
|
||||
}
|
||||
|
||||
$certificate_id = trim($_POST['certificate_id']);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM certificates WHERE certificate_id = ?");
|
||||
$stmt->execute([$certificate_id]);
|
||||
|
||||
$cert = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$cert) {
|
||||
echo "Certificate Not Found";
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<h2>✅ Certificate Verified</h2>
|
||||
<p><strong>Student:</strong> <?= htmlspecialchars($cert['student_name']) ?></p>
|
||||
<p><strong>Course:</strong> <?= htmlspecialchars($cert['course']) ?></p>
|
||||
<p><strong>Date:</strong> <?= htmlspecialchars($cert['issued_on']) ?></p>
|
||||
164
challenge.php
@ -1,164 +0,0 @@
|
||||
<?php
|
||||
include 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||
header('Location: challenges.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$challenge_id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare('SELECT * FROM challenges WHERE id = ? AND deleted_at IS NULL');
|
||||
$stmt->execute([$challenge_id]);
|
||||
$challenge = $stmt->fetch();
|
||||
|
||||
if (!$challenge) {
|
||||
header('Location: challenges.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
$submissions = [];
|
||||
if ($user_id) {
|
||||
$stmt = $pdo->prepare('SELECT * FROM challenge_submissions WHERE user_id = ? AND challenge_id = ? ORDER BY submitted_at DESC');
|
||||
$stmt->execute([$user_id, $challenge_id]);
|
||||
$submissions = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Submit Your Solution</h5>
|
||||
<form action="submit_solution.php" method="post">
|
||||
<input type="hidden" name="challenge_id" value="<?php echo $challenge['id']; ?>">
|
||||
<div class="mb-3">
|
||||
<label for="language" class="form-label">Language</label>
|
||||
<select class="form-select" id="language" name="language" required>
|
||||
<option value="python">Python</option>
|
||||
<option value="javascript">JavaScript</option>
|
||||
<option value="php">PHP</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="css">CSS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="solution" class="form-label">Your Code</label>
|
||||
<div id="editor"></div>
|
||||
<textarea class="form-control d-none" id="solution" name="solution" rows="10" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit Solution</button>
|
||||
<button type="button" class="btn btn-secondary" id="run-code">Run Code</button>
|
||||
</form>
|
||||
<div id="output" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h1 class="display-4 fw-bold"><?php echo htmlspecialchars($challenge['title']); ?></h1>
|
||||
<p class="fs-5 text-muted"><strong>Difficulty:</strong> <?php echo htmlspecialchars($challenge['difficulty']); ?> | <strong>Learning Style:</strong> <?php echo htmlspecialchars($challenge['learning_style']); ?></p>
|
||||
<hr>
|
||||
<ul class="nav nav-tabs" id="challengeTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="description-tab" data-bs-toggle="tab" data-bs-target="#description" type="button" role="tab" aria-controls="description" aria-selected="true">Description</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="submissions-tab" data-bs-toggle="tab" data-bs-target="#submissions" type="button" role="tab" aria-controls="submissions" aria-selected="false">My Submissions</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="challengeTabContent">
|
||||
<div class="tab-pane fade show active" id="description" role="tabpanel" aria-labelledby="description-tab">
|
||||
<div class="pt-4">
|
||||
<h2>Description</h2>
|
||||
<p><?php echo nl2br(htmlspecialchars($challenge['description'])); ?></p>
|
||||
<?php if (!empty($challenge['sample_cases_json'])) : ?>
|
||||
<h2>Sample Cases</h2>
|
||||
<pre class="bg-light p-3 rounded"><code><?php
|
||||
$samples = json_decode($challenge['sample_cases_json'], true);
|
||||
foreach ($samples as $sample) {
|
||||
echo "<strong>Input:</strong>\n" . htmlspecialchars($sample['input']) . "\n";
|
||||
echo "<strong>Output:</strong>\n" . htmlspecialchars($sample['output']) . "\n\n";
|
||||
}
|
||||
?></code></pre>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="submissions" role="tabpanel" aria-labelledby="submissions-tab">
|
||||
<div class="pt-4">
|
||||
<h2>My Submissions</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attempt</th>
|
||||
<th>Language</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($submissions) > 0) : ?>
|
||||
<?php foreach ($submissions as $submission) : ?>
|
||||
<tr>
|
||||
<td><?php echo $submission['attempt_number']; ?></td>
|
||||
<td><?php echo htmlspecialchars($submission['language']); ?></td>
|
||||
<td><span class="badge bg-<?php echo get_status_badge_class($submission['status']); ?>"><?php echo htmlspecialchars($submission['status']); ?></span></td>
|
||||
<td><?php echo $submission['submitted_at']; ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">You have not made any submissions for this challenge yet.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
function get_status_badge_class($status) {
|
||||
switch ($status) {
|
||||
case 'passed':
|
||||
return 'success';
|
||||
case 'failed':
|
||||
return 'danger';
|
||||
case 'pending':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'secondary';
|
||||
}
|
||||
}
|
||||
include 'includes/footer.php';
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var editor = CodeMirror(document.getElementById('editor'), {
|
||||
lineNumbers: true,
|
||||
mode: 'python',
|
||||
theme: 'default'
|
||||
});
|
||||
var solutionTextarea = document.getElementById('solution');
|
||||
editor.on('change', function() {
|
||||
solutionTextarea.value = editor.getValue();
|
||||
});
|
||||
|
||||
var languageSelect = document.getElementById('language');
|
||||
languageSelect.addEventListener('change', function() {
|
||||
var mode = this.value;
|
||||
if (mode === 'html' || mode === 'xml') {
|
||||
mode = 'htmlmixed';
|
||||
}
|
||||
editor.setOption('mode', mode);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
203
challenges.php
@ -1,114 +1,103 @@
|
||||
<?php
|
||||
include 'includes/header.php';
|
||||
require_once 'includes/pexels.php';
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
session_start();
|
||||
|
||||
/*
|
||||
Progress State
|
||||
(default everything NOT started)
|
||||
*/
|
||||
$language = $_SESSION['language'] ?? null;
|
||||
|
||||
$practice_status = $_SESSION['practice_status'] ?? 'not_started';
|
||||
$reinforcement_status = $_SESSION['reinforcement_status'] ?? 'locked';
|
||||
$final_status = $_SESSION['final_status'] ?? 'locked';
|
||||
|
||||
$class = $_SESSION['class'] ?? 'college';
|
||||
$applied_allowed = in_array($class, ['11', '12']);
|
||||
?>
|
||||
|
||||
<div class="container py-5">
|
||||
<h1 class="display-4 fw-bold">Coding Challenges</h1>
|
||||
<p class="fs-5 text-muted">Test your skills with our coding challenges.</p>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Coding Challenges | RS Learning Lab</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])) : ?>
|
||||
<div class="alert alert-success">
|
||||
<?php
|
||||
echo $_SESSION['success_message'];
|
||||
unset($_SESSION['success_message']);
|
||||
?>
|
||||
</div>
|
||||
<h1>🏆 Coding Challenges</h1>
|
||||
<p>Base to advanced learning path.</p>
|
||||
|
||||
<!-- LANGUAGE SELECTION -->
|
||||
<?php if (!$language): ?>
|
||||
<div class="card">
|
||||
<h2>Select Programming Language</h2>
|
||||
<form method="post" action="select_language.php">
|
||||
<select name="language" required>
|
||||
<option value="">-- Choose Language --</option>
|
||||
<option value="python">Python</option>
|
||||
<option value="c">C</option>
|
||||
<option value="java">Java</option>
|
||||
</select>
|
||||
<button class="btn">Start Practice Track</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- PRACTICE TRACK -->
|
||||
<div class="card">
|
||||
<h2>Track 1: Practice Track</h2>
|
||||
<p>Focus: Syntax, basics, confidence building.</p>
|
||||
|
||||
<?php if ($practice_status === 'not_started'): ?>
|
||||
<span class="pending">Not Started</span><br><br>
|
||||
<a href="practice_track.php" class="btn">Start Practice</a>
|
||||
<?php elseif ($practice_status === 'completed'): ?>
|
||||
<span class="completed">Completed</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error_message'])) : ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php
|
||||
echo $_SESSION['error_message'];
|
||||
unset($_SESSION['error_message']);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['info_message'])) : ?>
|
||||
<div class="alert alert-info">
|
||||
<?php
|
||||
echo $_SESSION['info_message'];
|
||||
unset($_SESSION['info_message']);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="difficulty-filter">Difficulty</label>
|
||||
<select class="form-select" id="difficulty-filter">
|
||||
<option value="all">All</option>
|
||||
<option value="easy">Easy</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="advanced">Advanced</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="learning-style-filter">Learning Style</label>
|
||||
<select class="form-select" id="learning-style-filter">
|
||||
<option value="all">All</option>
|
||||
<option value="kinesthetic">Kinesthetic</option>
|
||||
<option value="visual">Visual</option>
|
||||
<option value="auditory">Auditory</option>
|
||||
<option value="reading/writing">Reading/Writing</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="sort-by">Sort By</label>
|
||||
<select class="form-select" id="sort-by">
|
||||
<option value="date_desc">Newest</option>
|
||||
<option value="date_asc">Oldest</option>
|
||||
<option value="popularity">Popularity</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query('SELECT * FROM challenges WHERE deleted_at IS NULL ORDER BY created_at DESC');
|
||||
$challenges = $stmt->fetchAll();
|
||||
|
||||
if (count($challenges) > 0) {
|
||||
foreach ($challenges as $challenge) {
|
||||
$image_data = pexels_get('https://api.pexels.com/v1/search?query=' . urlencode($challenge['title']) . '&per_page=1');
|
||||
if ($image_data && isset($image_data['photos'][0])) {
|
||||
$image_url = $image_data['photos'][0]['src']['large'];
|
||||
} else {
|
||||
$image_url = 'https://via.placeholder.com/600x400'; // Default placeholder
|
||||
}
|
||||
|
||||
echo '<div class="col-md-4 mb-4">';
|
||||
echo '<div class="card h-100">';
|
||||
echo '<div class="card-img-container">';
|
||||
echo '<img src="' . $image_url . '" class="card-img-top" alt="' . htmlspecialchars($challenge['title']) . '">';
|
||||
echo '<div class="card-img-overlay d-flex flex-column justify-content-end">';
|
||||
echo '<h5 class="card-title text-white"><a href="challenge.php?id=' . $challenge['id'] . '">' . htmlspecialchars($challenge['title']) . '</a></h5>';
|
||||
echo '<span class="badge bg-primary">' . htmlspecialchars($challenge['difficulty']) . '</span>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '<div class="card-body d-flex flex-column">';
|
||||
echo '<p class="card-text flex-grow-1">' . htmlspecialchars(substr($challenge['description'], 0, 100)) . '...</p>';
|
||||
echo '<div class="d-flex justify-content-between align-items-center">';
|
||||
echo '<small class="text-muted">' . htmlspecialchars($challenge['learning_style']) . '</small>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<div class="alert alert-info">No challenges available yet. Please check back later.</div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<!-- REINFORCEMENT TRACK -->
|
||||
<div class="card">
|
||||
<h2>Track 2: Reinforcement Track</h2>
|
||||
<p>Focus: Logic strengthening.</p>
|
||||
|
||||
<?php if ($reinforcement_status === 'locked'): ?>
|
||||
<span class="locked">Locked</span>
|
||||
<?php elseif ($reinforcement_status === 'active'): ?>
|
||||
<a href="practice_reinforcement.php" class="btn">Start Reinforcement</a>
|
||||
<?php elseif ($reinforcement_status === 'completed'): ?>
|
||||
<span class="completed">Completed</span><br>
|
||||
🏅 Learning Badge Earned
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- FINAL TRACK -->
|
||||
<div class="card">
|
||||
<h2>Track 3: Final Track</h2>
|
||||
<p>Focus: End-to-end problem solving.</p>
|
||||
|
||||
<?php if ($final_status === 'locked'): ?>
|
||||
<span class="locked">Locked</span>
|
||||
<?php elseif ($final_status === 'active'): ?>
|
||||
<a href="practice_final.php" class="btn">Start Final Track</a>
|
||||
<?php elseif ($final_status === 'completed'): ?>
|
||||
<span class="completed">Completed</span><br>
|
||||
📜 Track Completion Certificate Unlocked
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- APPLIED THINKING -->
|
||||
<div class="card">
|
||||
<h2>Applied Thinking (Class 11–12 CS)</h2>
|
||||
|
||||
<?php if ($applied_allowed): ?>
|
||||
<a href="applied_thinking.php" class="btn">Start Applied Thinking</a>
|
||||
<?php else: ?>
|
||||
<span class="locked">Only for Class 11–12 CS</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<a href="dashboard.php" class="btn">← Back to Dashboard</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
63
change_password.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once("config/db.php");
|
||||
|
||||
if (!isset($_SESSION['institution_id'])) {
|
||||
header("Location: /rs_lab/institution/login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = "";
|
||||
$success = "";
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
|
||||
$new = $_POST['new_password'];
|
||||
$confirm = $_POST['confirm_password'];
|
||||
|
||||
if ($new === "" || $confirm === "") {
|
||||
$error = "All fields required";
|
||||
} elseif ($new !== $confirm) {
|
||||
$error = "Passwords do not match";
|
||||
} else {
|
||||
|
||||
$hash = password_hash($new, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE institutions
|
||||
SET password = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$hash, $_SESSION['institution_id']]);
|
||||
|
||||
$success = "Password updated successfully";
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Change Password | RS Learning Lab</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Change Password</h2>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<p style="color:red;"><?php echo $error; ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<p style="color:green;"><?php echo $success; ?></p>
|
||||
<a href="/rs_lab/institution/dashboard.php">Go to Dashboard</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<input type="password" name="new_password" placeholder="New Password" required><br><br>
|
||||
<input type="password" name="confirm_password" placeholder="Confirm Password" required><br><br>
|
||||
<button type="submit">Update Password</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
40
check_certificate_unlock.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
require_once "../config.php";
|
||||
|
||||
$student_id = $_SESSION['student_id'];
|
||||
$track = "Python Basics Track";
|
||||
|
||||
// Count completed challenges
|
||||
$stmt = $conn->prepare("
|
||||
SELECT COUNT(DISTINCT challenge_id) AS completed
|
||||
FROM challenge_submissions
|
||||
WHERE student_id = ? AND is_correct = 1
|
||||
");
|
||||
$stmt->bind_param("i", $student_id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result()->fetch_assoc();
|
||||
|
||||
$completed = $result['completed'];
|
||||
|
||||
// Rule: 5 challenges
|
||||
if ($completed >= 5) {
|
||||
|
||||
// Check already issued?
|
||||
$check = $conn->prepare("
|
||||
SELECT id FROM certificates
|
||||
WHERE student_id = ? AND certificate_type = 'Participation'
|
||||
");
|
||||
$check->bind_param("i", $student_id);
|
||||
$check->execute();
|
||||
$checkResult = $check->get_result();
|
||||
|
||||
if ($checkResult->num_rows == 0) {
|
||||
// Issue certificate
|
||||
$insert = $conn->prepare("
|
||||
INSERT INTO certificates (student_id, certificate_type, track_name)
|
||||
VALUES (?, 'Participation', ?)
|
||||
");
|
||||
$insert->bind_param("is", $student_id, $track);
|
||||
$insert->execute();
|
||||
}
|
||||
}
|
||||
24
check_professional_certificate.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// ❌ session_start() remove pannrom
|
||||
// session_start();
|
||||
|
||||
if (!isset($_SESSION)) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// DEFAULT
|
||||
$eligible = false;
|
||||
|
||||
// DEMO LOGIC (ippo)
|
||||
$class = $_SESSION['class'] ?? null;
|
||||
$stream = $_SESSION['stream'] ?? null;
|
||||
$applied_thinking_completed = $_SESSION['track3_completed'] ?? false;
|
||||
|
||||
// REAL RULE
|
||||
if (
|
||||
in_array($class, [11, 12]) &&
|
||||
$stream === 'CS' &&
|
||||
$applied_thinking_completed === true
|
||||
) {
|
||||
$eligible = true;
|
||||
}
|
||||
4
check_session.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
session_start();
|
||||
echo "<pre>";
|
||||
print_r($_SESSION);
|
||||
103
choose_assessment_mode.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
session_start();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Choose Assessment Mode | RS Learning Lab</title>
|
||||
|
||||
<style>
|
||||
body{
|
||||
margin:0;
|
||||
min-height:100vh;
|
||||
background:radial-gradient(circle at top,#0f172a,#020617);
|
||||
font-family:Segoe UI,sans-serif;
|
||||
color:#e5e7eb;
|
||||
}
|
||||
.wrap{
|
||||
max-width:1100px;
|
||||
margin:80px auto;
|
||||
padding:20px;
|
||||
}
|
||||
h1{
|
||||
font-size:34px;
|
||||
margin-bottom:6px;
|
||||
}
|
||||
.sub{
|
||||
opacity:.7;
|
||||
margin-bottom:45px;
|
||||
}
|
||||
.cards{
|
||||
display:grid;
|
||||
grid-template-columns:1fr 1fr;
|
||||
gap:30px;
|
||||
}
|
||||
.card{
|
||||
background:linear-gradient(180deg,#111827,#020617);
|
||||
border:1px solid #1f2937;
|
||||
border-radius:20px;
|
||||
padding:32px;
|
||||
transition:.3s;
|
||||
}
|
||||
.card:hover{
|
||||
border-color:#0ea5e9;
|
||||
transform:translateY(-6px);
|
||||
}
|
||||
.title{
|
||||
font-size:22px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
.desc{
|
||||
opacity:.8;
|
||||
line-height:1.6;
|
||||
}
|
||||
a{
|
||||
text-decoration:none;
|
||||
color:inherit;
|
||||
}
|
||||
.back{
|
||||
display:inline-block;
|
||||
margin-top:40px;
|
||||
color:#38bdf8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="wrap">
|
||||
<h1>Choose Assessment Mode</h1>
|
||||
<div class="sub">
|
||||
Select how you want to conduct the learning style assessment.
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<a href="manual.php">
|
||||
<div class="card">
|
||||
<div class="title">👨🏫 Facilitated Mode</div>
|
||||
<div class="desc">
|
||||
Teacher conducts the assessment.
|
||||
Upload class list and guide students
|
||||
in classroom or lab.
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="self_assessment_start.php">
|
||||
<div class="card">
|
||||
<div class="title">💻 Self-Assessment Mode</div>
|
||||
<div class="desc">
|
||||
Students attempt the assessment independently
|
||||
using name or roll number.
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<a href="dashboard.php" class="back">← Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
116
class_report.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/config/db.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: teacher_login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
FETCH CLASS REPORT DATA
|
||||
-------------------------------- */
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT student_roll, student_name, primary_style, secondary_style, pdf_path
|
||||
FROM learning_style_results
|
||||
ORDER BY student_roll ASC
|
||||
");
|
||||
|
||||
$stmt->execute();
|
||||
$students = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<div class="dashboard-wrap">
|
||||
|
||||
<h2>Class Learning Style Report</h2>
|
||||
|
||||
<p class="sub">
|
||||
Teacher: <?php echo htmlspecialchars($_SESSION['teacher_name'] ?? ''); ?> |
|
||||
Class: <?php echo htmlspecialchars($_SESSION['class_name'] ?? ''); ?>
|
||||
</p>
|
||||
|
||||
|
||||
<table style="width:100%; margin-top:30px; border-collapse:collapse;">
|
||||
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th align="left">Roll No</th>
|
||||
<th align="left">Student Name</th>
|
||||
<th align="left">Primary Style</th>
|
||||
<th align="left">Secondary Style</th>
|
||||
<th align="left">Report</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
<?php if(count($students) > 0): ?>
|
||||
|
||||
<?php foreach ($students as $s): ?>
|
||||
|
||||
<tr>
|
||||
|
||||
<td><?php echo htmlspecialchars($s['student_roll']); ?></td>
|
||||
|
||||
<td><?php echo htmlspecialchars($s['student_name']); ?></td>
|
||||
|
||||
<td><?php echo htmlspecialchars($s['primary_style']); ?></td>
|
||||
|
||||
<td><?php echo htmlspecialchars($s['secondary_style']); ?></td>
|
||||
|
||||
<td>
|
||||
|
||||
<?php if(!empty($s['pdf_path'])): ?>
|
||||
|
||||
<a href="<?php echo $s['pdf_path']; ?>" target="_blank" style="color:#38bdf8;">
|
||||
Download PDF
|
||||
</a>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<span style="color:#ef4444;">Not Generated</span>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<tr>
|
||||
<td colspan="5">No assessment data available</td>
|
||||
</tr>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
<form action="generate_class_pdf.php" method="POST" style="margin-top:30px;">
|
||||
|
||||
<button type="submit">
|
||||
⬇ Download Class Report (PDF)
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
<a href="dashboard.php" class="back-link">
|
||||
← Back to Dashboard
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
44
class_summary.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/config/db.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT learning_style, COUNT(*) as count
|
||||
FROM learning_styles
|
||||
WHERE user_id = ?
|
||||
GROUP BY learning_style
|
||||
");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$summary = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<div class="dashboard-wrap">
|
||||
|
||||
<h2>Class Learning Style Summary</h2>
|
||||
<p class="sub">
|
||||
Overview of how students in your class prefer to learn.
|
||||
</p>
|
||||
|
||||
<div class="dashboard-cards">
|
||||
|
||||
<?php foreach ($summary as $row): ?>
|
||||
<div class="dash-card">
|
||||
<h3><?php echo $row['learning_style']; ?></h3>
|
||||
<p><?php echo $row['count']; ?> students</p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<a href="dashboard.php" class="back-link">← Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
9
code_arena/.env.example
Normal file
@ -0,0 +1,9 @@
|
||||
# GEMINI_API_KEY: Required for Gemini AI API calls.
|
||||
# AI Studio automatically injects this at runtime from user secrets.
|
||||
# Users configure this via the Secrets panel in the AI Studio UI.
|
||||
GEMINI_API_KEY="MY_GEMINI_API_KEY"
|
||||
|
||||
# APP_URL: The URL where this applet is hosted.
|
||||
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
|
||||
# Used for self-referential links, OAuth callbacks, and API endpoints.
|
||||
APP_URL="MY_APP_URL"
|
||||
8
code_arena/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
coverage/
|
||||
.DS_Store
|
||||
*.log
|
||||
.env*
|
||||
!.env.example
|
||||
20
code_arena/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
<div align="center">
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
|
||||
# Run and deploy your AI Studio app
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/d9855fda-dc07-40f0-9cd7-f15e32e565e5
|
||||
|
||||
## Run Locally
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
|
||||
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
14
code_arena/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Playfair+Display:wght@700&family=Dancing+Script:wght@700&display=swap" rel="stylesheet">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>RS LEARNING LAB</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
5
code_arena/metadata.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "RS Learning Lab - Coding Challenge System",
|
||||
"description": "A gamified coding challenge platform with levels, tracks, mini-projects, and dynamic certificate generation.",
|
||||
"requestFramePermissions": []
|
||||
}
|
||||
4795
code_arena/package-lock.json
generated
Normal file
45
code_arena/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.29.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
"clsx": "^2.1.1",
|
||||
"docx": "^9.6.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^12.38.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^4.2.1",
|
||||
"lucide-react": "^0.546.0",
|
||||
"motion": "^12.23.24",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
||||
179
code_arena/server.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import express from "express";
|
||||
import path from "path";
|
||||
import { createServer as createViteServer } from "vite";
|
||||
|
||||
async function startServer() {
|
||||
const app = express();
|
||||
const PORT = 3000;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// In-memory "database"
|
||||
const db = {
|
||||
users: [
|
||||
{ id: "admin1", username: "admin", password: "password", role: "admin", name: "Administrator" },
|
||||
{ id: "teacher1", username: "teacher1", password: "password", role: "teacher", name: "Gokula Krishnan", assignedGrade: "12" },
|
||||
{ id: "teacher2", username: "teacher2", password: "password", role: "teacher", name: "Arun Kumar", assignedGrade: "10" },
|
||||
{ id: "teacher3", username: "teacher3", password: "password", role: "teacher", name: "Meera Devi", assignedGrade: "11" },
|
||||
{ id: "teacher4", username: "teacher4", password: "password", role: "teacher", name: "Senthil Nathan", assignedGrade: "9" }
|
||||
],
|
||||
submissions: [] as any[],
|
||||
students: [] as any[],
|
||||
studentStats: {} as Record<string, any> // rollNumber -> latest LEI stats
|
||||
};
|
||||
|
||||
// Auth
|
||||
app.post("/api/login", (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const user = db.users.find(u => u.username === username && u.password === password);
|
||||
if (user) {
|
||||
res.json({ id: user.id, username: user.username, role: user.role, name: user.name, assignedGrade: (user as any).assignedGrade });
|
||||
} else {
|
||||
res.status(401).json({ success: false, message: "Invalid credentials" });
|
||||
}
|
||||
});
|
||||
|
||||
// Submissions
|
||||
app.post("/api/submissions", (req, res) => {
|
||||
const submission = {
|
||||
id: Date.now().toString(),
|
||||
...req.body,
|
||||
status: "pending",
|
||||
submittedAt: new Date().toISOString(),
|
||||
documentationMarks: 0,
|
||||
vivaScore: 0,
|
||||
understandingLevel: 0,
|
||||
explanationAbility: 0,
|
||||
teacherRemarks: "",
|
||||
lei: req.body.lei || 0,
|
||||
leiCategory: req.body.leiCategory || 'Stalled Learner',
|
||||
leiInsight: req.body.leiInsight || '',
|
||||
retries: req.body.retries || 0,
|
||||
streak: req.body.streak || 0,
|
||||
completionRate: req.body.completionRate || 0
|
||||
};
|
||||
db.submissions.push(submission);
|
||||
res.json({ success: true, submission });
|
||||
});
|
||||
|
||||
app.get("/api/submissions", (req, res) => {
|
||||
const { grade } = req.query;
|
||||
let filtered = db.submissions;
|
||||
if (grade) {
|
||||
filtered = filtered.filter(s => s.grade.includes(grade as string));
|
||||
}
|
||||
res.json(filtered);
|
||||
});
|
||||
|
||||
app.post("/api/student/lei", (req, res) => {
|
||||
const { rollNumber, lei, leiCategory, leiInsight, studentName, grade } = req.body;
|
||||
if (!rollNumber) return res.status(400).json({ success: false, message: "Roll number required" });
|
||||
|
||||
db.studentStats[rollNumber] = {
|
||||
lei,
|
||||
leiCategory,
|
||||
leiInsight,
|
||||
studentName,
|
||||
grade,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get("/api/students/lei", (req, res) => {
|
||||
const { grade } = req.query;
|
||||
let stats = Object.values(db.studentStats);
|
||||
if (grade) {
|
||||
stats = stats.filter(s => s.grade.includes(grade as string));
|
||||
}
|
||||
res.json(stats);
|
||||
});
|
||||
|
||||
app.post("/api/student/viva", (req, res) => {
|
||||
const { rollNumber, session, vivaScore } = req.body;
|
||||
if (!rollNumber) return res.status(400).json({ success: false, message: "Roll number required" });
|
||||
|
||||
if (db.studentStats[rollNumber]) {
|
||||
if (!db.studentStats[rollNumber].vivaSessions) {
|
||||
db.studentStats[rollNumber].vivaSessions = [];
|
||||
}
|
||||
db.studentStats[rollNumber].vivaSessions.push(session);
|
||||
db.studentStats[rollNumber].vivaSummary = session.summary;
|
||||
db.studentStats[rollNumber].vivaScore = vivaScore;
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.patch("/api/submissions/:id", (req, res) => {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
documentationMarks, vivaScore, teacherVivaMarks, vivaRequested,
|
||||
teacherRemarks, status, marks, totalScore
|
||||
} = req.body;
|
||||
const index = db.submissions.findIndex(s => s.id === id);
|
||||
if (index !== -1) {
|
||||
if (documentationMarks !== undefined) db.submissions[index].documentationMarks = documentationMarks;
|
||||
if (vivaScore !== undefined) db.submissions[index].vivaScore = vivaScore;
|
||||
if (teacherVivaMarks !== undefined) db.submissions[index].teacherVivaMarks = teacherVivaMarks;
|
||||
if (vivaRequested !== undefined) db.submissions[index].vivaRequested = vivaRequested;
|
||||
if (teacherRemarks !== undefined) db.submissions[index].teacherRemarks = teacherRemarks;
|
||||
if (marks !== undefined) db.submissions[index].marks = marks;
|
||||
if (totalScore !== undefined) db.submissions[index].totalScore = totalScore;
|
||||
db.submissions[index].status = status || "reviewed";
|
||||
|
||||
res.json({ success: true, submission: db.submissions[index] });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: "Submission not found" });
|
||||
}
|
||||
});
|
||||
|
||||
// Leaderboard
|
||||
app.get("/api/leaderboard", (req, res) => {
|
||||
const { grade } = req.query;
|
||||
let filtered = db.submissions;
|
||||
if (grade) {
|
||||
filtered = filtered.filter(s => s.grade.includes(grade as string));
|
||||
}
|
||||
const leaderboard = filtered
|
||||
.sort((a, b) => (b.lei || 0) - (a.lei || 0))
|
||||
.slice(0, 10)
|
||||
.map(s => {
|
||||
let label = "Active Learner";
|
||||
if (s.lei > 100) label = "Top Contributor";
|
||||
else if (s.lei > 50) label = "Most Consistent";
|
||||
else if (s.lei > 20) label = "Rising Star";
|
||||
|
||||
return {
|
||||
name: s.studentName,
|
||||
lei: s.lei || 0,
|
||||
grade: s.grade,
|
||||
rollNumber: s.rollNumber,
|
||||
label: label
|
||||
};
|
||||
});
|
||||
res.json(leaderboard);
|
||||
});
|
||||
|
||||
// Vite middleware for development
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
const vite = await createViteServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: "spa",
|
||||
});
|
||||
app.use(vite.middlewares);
|
||||
} else {
|
||||
const distPath = path.join(process.cwd(), 'dist');
|
||||
app.use(express.static(distPath));
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(distPath, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, "0.0.0.0", () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
startServer();
|
||||
2563
code_arena/src/App.tsx
Normal file
BIN
code_arena/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
185
code_arena/src/components/ErrorAnalyzerView.tsx
Normal file
@ -0,0 +1,185 @@
|
||||
import React from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { AlertTriangle, Lightbulb, CheckCircle2, ChevronRight, RefreshCw, Code2, Brain, Target } from 'lucide-react';
|
||||
import { ErrorAnalysis } from '../lib/errorAnalyzer';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface ErrorAnalyzerViewProps {
|
||||
analysis: ErrorAnalysis;
|
||||
onRetry: () => void;
|
||||
currentHintLevel: number;
|
||||
onShowNextHint: () => void;
|
||||
}
|
||||
|
||||
export function ErrorAnalyzerView({ analysis, onRetry, currentHintLevel, onShowNextHint }: ErrorAnalyzerViewProps) {
|
||||
const [activeStep, setActiveStep] = React.useState(1);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mt-8 space-y-6"
|
||||
>
|
||||
{/* Error Header */}
|
||||
<div className="bg-red-500/10 border border-red-500/20 rounded-2xl p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-red-500/20 rounded-lg text-red-500">
|
||||
<AlertTriangle size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-white">{analysis.errorType} Error Detected</h3>
|
||||
<p className="text-sm text-red-400/80">Don't worry! Mistakes are the best way to learn.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="p-4 bg-black/20 rounded-xl border border-white/5">
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1">What happened?</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{analysis.explanation.what}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-black/20 rounded-xl border border-white/5">
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1">Why is it wrong?</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{analysis.explanation.why}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-black/20 rounded-xl border border-white/5">
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1">Where is it?</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed font-medium text-[#0b8c9f]">{analysis.explanation.where}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step-by-Step Learning */}
|
||||
<div className="bg-dark-card border border-dark-border rounded-2xl overflow-hidden">
|
||||
<div className="p-4 bg-slate-800/50 border-b border-dark-border flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Brain size={18} className="text-purple-400" />
|
||||
<span className="text-xs font-bold text-white uppercase tracking-widest">Step-by-Step Learning</span>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{[1, 2, 3].map(step => (
|
||||
<button
|
||||
key={step}
|
||||
onClick={() => setActiveStep(step)}
|
||||
className={cn(
|
||||
"w-8 h-8 rounded-lg text-xs font-bold transition-all",
|
||||
activeStep === step
|
||||
? "bg-[#0b8c9f] text-white"
|
||||
: "bg-slate-800 text-slate-500 hover:text-slate-300"
|
||||
)}
|
||||
>
|
||||
{step}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeStep}
|
||||
initial={{ opacity: 0, x: 10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -10 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 rounded-xl bg-[#0b8c9f]/10 flex items-center justify-center text-[#0b8c9f] shrink-0">
|
||||
{activeStep === 1 ? <Target size={20} /> : activeStep === 2 ? <Brain size={20} /> : <CheckCircle2 size={20} />}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-white mb-1">
|
||||
{activeStep === 1 ? "Identify the Mistake" : activeStep === 2 ? "Understand the Concept" : "The Correct Approach"}
|
||||
</h4>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
{activeStep === 1 ? analysis.steps.step1 : activeStep === 2 ? analysis.steps.step2 : analysis.steps.step3}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{activeStep < 3 && (
|
||||
<button
|
||||
onClick={() => setActiveStep(prev => prev + 1)}
|
||||
className="text-xs font-bold text-[#0b8c9f] hover:underline flex items-center gap-1 ml-14"
|
||||
>
|
||||
Next Step <ChevronRight size={14} />
|
||||
</button>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hint System */}
|
||||
<div className="bg-yellow-500/5 border border-yellow-500/20 rounded-2xl p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Lightbulb size={20} className="text-yellow-500" />
|
||||
<span className="text-xs font-bold text-yellow-500 uppercase tracking-widest">Hint System</span>
|
||||
</div>
|
||||
{currentHintLevel < 3 && (
|
||||
<button
|
||||
onClick={onShowNextHint}
|
||||
className="text-[10px] font-bold text-yellow-500 hover:underline uppercase tracking-widest"
|
||||
>
|
||||
{currentHintLevel === 0 ? "Get a Hint" : "Need more help?"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{currentHintLevel >= 1 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="p-3 bg-yellow-500/10 rounded-xl border border-yellow-500/20 text-xs text-yellow-200/80 italic"
|
||||
>
|
||||
<span className="font-bold mr-2">Hint 1:</span> {analysis.hints[0]}
|
||||
</motion.div>
|
||||
)}
|
||||
{currentHintLevel >= 2 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="p-3 bg-yellow-500/10 rounded-xl border border-yellow-500/20 text-xs text-yellow-200/80 italic"
|
||||
>
|
||||
<span className="font-bold mr-2">Hint 2:</span> {analysis.hints[1]}
|
||||
</motion.div>
|
||||
)}
|
||||
{currentHintLevel >= 3 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="p-4 bg-green-500/10 rounded-xl border border-green-500/20 space-y-3"
|
||||
>
|
||||
<div className="text-xs text-green-400 font-bold uppercase tracking-widest">Final Explanation</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">{analysis.hints[2]}</p>
|
||||
<div className="pt-3 border-t border-green-500/20">
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase mb-2">Corrected Code Snippet</div>
|
||||
<pre className="text-[10px] font-mono text-green-400 bg-black/40 p-3 rounded-lg overflow-x-auto">
|
||||
{analysis.correctedCode}
|
||||
</pre>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Suggested Fix & Retry */}
|
||||
<div className="flex flex-col sm:flex-row items-center gap-4">
|
||||
<div className="flex-1 p-4 bg-[#0b8c9f]/5 border border-[#0b8c9f]/20 rounded-xl flex items-center gap-3">
|
||||
<div className="p-2 bg-[#0b8c9f]/10 rounded-lg text-[#0b8c9f]">
|
||||
<Code2 size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-0.5">Quick Fix</div>
|
||||
<p className="text-sm font-bold text-white">{analysis.suggestedFix}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onRetry}
|
||||
className="w-full sm:w-auto bg-white text-black px-8 py-4 rounded-xl font-bold hover:bg-slate-200 transition-all flex items-center justify-center gap-2 shadow-xl"
|
||||
>
|
||||
<RefreshCw size={20} /> Try Again
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
156
code_arena/src/components/Leaderboard.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'motion/react';
|
||||
import { Trophy, Medal, Star, ArrowRight, Search, Filter } from 'lucide-react';
|
||||
import { type LeaderboardEntry, type User } from '../types';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface LeaderboardProps {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export function Leaderboard({ user }: LeaderboardProps) {
|
||||
const [entries, setEntries] = React.useState<LeaderboardEntry[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [filter, setFilter] = React.useState<string>('all');
|
||||
const [search, setSearch] = React.useState('');
|
||||
|
||||
const fetchLeaderboard = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/leaderboard');
|
||||
const data = await res.json();
|
||||
setEntries(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch leaderboard:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchLeaderboard();
|
||||
}, []);
|
||||
|
||||
const filteredEntries = entries.filter(e => {
|
||||
const matchesFilter = filter === 'all' || e.grade === filter;
|
||||
const matchesSearch = e.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
e.rollNumber.toLowerCase().includes(search.toLowerCase());
|
||||
return matchesFilter && matchesSearch;
|
||||
});
|
||||
|
||||
const grades = Array.from(new Set(entries.map(e => e.grade))).sort();
|
||||
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div className="text-center max-w-2xl mx-auto">
|
||||
<h2 className="text-4xl font-black text-white mb-4">Class Leaderboard 🏆</h2>
|
||||
<p className="text-slate-400">Celebrate the top performers of RS Learning Lab. Keep learning to climb the ranks!</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-dark-card rounded-3xl border border-dark-border overflow-hidden shadow-2xl">
|
||||
<div className="p-6 border-b border-dark-border flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-slate-800/30">
|
||||
<div className="relative w-full sm:w-64">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search students..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 rounded-xl bg-dark-bg border border-dark-border text-white text-sm focus:ring-2 focus:ring-[#0b8c9f] outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter size={18} className="text-slate-500" />
|
||||
<select
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
className="bg-dark-bg border border-dark-border text-white text-sm rounded-xl px-3 py-2 outline-none focus:ring-2 focus:ring-[#0b8c9f]"
|
||||
>
|
||||
<option value="all">All Classes</option>
|
||||
{grades.map(g => <option key={g} value={g}>{g}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="bg-slate-800/50 border-b border-dark-border">
|
||||
<th className="px-8 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest">Rank</th>
|
||||
<th className="px-8 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest">Student</th>
|
||||
<th className="px-8 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest">Class</th>
|
||||
<th className="px-8 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest">Recognition</th>
|
||||
{user?.role === 'teacher' && (
|
||||
<th className="px-8 py-4 text-xs font-bold text-slate-500 uppercase tracking-widest text-right">Learning Effective Index (LEI)</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-dark-border">
|
||||
{loading ? (
|
||||
<tr><td colSpan={user?.role === 'teacher' ? 5 : 4} className="px-8 py-12 text-center text-slate-500">Loading leaderboard...</td></tr>
|
||||
) : filteredEntries.length === 0 ? (
|
||||
<tr><td colSpan={user?.role === 'teacher' ? 5 : 4} className="px-8 py-12 text-center text-slate-500">No entries found.</td></tr>
|
||||
) : (
|
||||
filteredEntries.map((entry, index) => (
|
||||
<motion.tr
|
||||
key={entry.rollNumber}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className={cn(
|
||||
"group hover:bg-slate-800/50 transition-all",
|
||||
index === 0 && "bg-yellow-500/5",
|
||||
index === 1 && "bg-slate-400/5",
|
||||
index === 2 && "bg-orange-500/5"
|
||||
)}
|
||||
>
|
||||
<td className="px-8 py-6">
|
||||
<div className="flex items-center gap-3">
|
||||
{index === 0 ? (
|
||||
<div className="w-8 h-8 bg-yellow-500 rounded-lg flex items-center justify-center text-white shadow-lg shadow-yellow-500/20">
|
||||
<Trophy size={16} />
|
||||
</div>
|
||||
) : index === 1 ? (
|
||||
<div className="w-8 h-8 bg-slate-400 rounded-lg flex items-center justify-center text-white shadow-lg shadow-slate-400/20">
|
||||
<Medal size={16} />
|
||||
</div>
|
||||
) : index === 2 ? (
|
||||
<div className="w-8 h-8 bg-orange-500 rounded-lg flex items-center justify-center text-white shadow-lg shadow-orange-500/20">
|
||||
<Medal size={16} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="w-8 h-8 flex items-center justify-center font-bold text-slate-500">{index + 1}</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-8 py-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 bg-slate-800 rounded-full flex items-center justify-center text-white font-bold text-sm">
|
||||
{entry.name.charAt(0)}
|
||||
</div>
|
||||
<span className="font-bold text-white group-hover:text-[#0b8c9f] transition-colors">{entry.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-8 py-6 text-slate-400 font-medium">{entry.grade}</td>
|
||||
<td className="px-8 py-6">
|
||||
<span className="px-3 py-1 rounded-full bg-[#0b8c9f]/10 text-[#0b8c9f] text-[10px] font-bold uppercase tracking-widest border border-[#0b8c9f]/20">
|
||||
{entry.label || 'Active Learner'}
|
||||
</span>
|
||||
</td>
|
||||
{user?.role === 'teacher' && (
|
||||
<td className="px-8 py-6 text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Star size={16} className="text-[#ff7a00]" fill="#ff7a00" />
|
||||
<span className="text-xl font-black text-white">{entry.lei}</span>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</motion.tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
695
code_arena/src/components/TeacherDashboard.tsx
Normal file
@ -0,0 +1,695 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'motion/react';
|
||||
import {
|
||||
Users, FileText, CheckCircle2, Clock, AlertCircle,
|
||||
Search, Filter, ArrowRight, MessageSquare, Award, Download, Trophy
|
||||
} from 'lucide-react';
|
||||
import { type Submission, type User } from '../types';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface TeacherDashboardProps {
|
||||
user: User;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
export function TeacherDashboard({ user, onLogout }: TeacherDashboardProps) {
|
||||
const [submissions, setSubmissions] = React.useState<Submission[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [filter, setFilter] = React.useState<'all' | 'pending' | 'reviewed'>('all');
|
||||
const [search, setSearch] = React.useState('');
|
||||
const [selectedSubmission, setSelectedSubmission] = React.useState<Submission | null>(null);
|
||||
const [vivaScore, setVivaScore] = React.useState<number>(0);
|
||||
const [teacherVivaMarks, setTeacherVivaMarks] = React.useState<number>(0);
|
||||
const [docMarks, setDocMarks] = React.useState({
|
||||
objective: 0,
|
||||
concepts: 0,
|
||||
explanation: 0,
|
||||
learningOutcome: 0
|
||||
});
|
||||
const [codeMarks, setCodeMarks] = React.useState({
|
||||
logic: 0,
|
||||
output: 0,
|
||||
concepts: 0
|
||||
});
|
||||
const [teacherRemarks, setTeacherRemarks] = React.useState<string>('');
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
const [showSettings, setShowSettings] = React.useState(false);
|
||||
const [showReport, setShowReport] = React.useState(false);
|
||||
const [schoolName, setSchoolName] = React.useState(() => localStorage.getItem('rs_school_name') || '');
|
||||
const [principalName, setPrincipalName] = React.useState(() => localStorage.getItem('rs_principal_name') || '');
|
||||
|
||||
const fetchSubmissions = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/submissions');
|
||||
const data = await res.json();
|
||||
// Filter by teacher's assigned grade if applicable
|
||||
const filtered = user.assignedGrade
|
||||
? data.filter((s: Submission) => s.grade === user.assignedGrade)
|
||||
: data;
|
||||
setSubmissions(filtered);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch submissions:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchSubmissions();
|
||||
}, []);
|
||||
|
||||
const handleUpdateScore = async (submissionId: string) => {
|
||||
setIsUpdating(true);
|
||||
const docTotal = docMarks.objective + docMarks.concepts + docMarks.explanation + docMarks.learningOutcome;
|
||||
const codeTotal = codeMarks.logic + codeMarks.output + codeMarks.concepts;
|
||||
const vivaTotal = vivaScore + teacherVivaMarks;
|
||||
const totalScore = docTotal + codeTotal + vivaTotal;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/submissions/${submissionId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
marks: {
|
||||
documentation: { ...docMarks, total: docTotal },
|
||||
code: { ...codeMarks, total: codeTotal },
|
||||
viva: { aiScore: vivaScore, teacherScore: teacherVivaMarks, total: vivaTotal },
|
||||
total: totalScore
|
||||
},
|
||||
documentationMarks: docTotal,
|
||||
vivaScore: vivaScore,
|
||||
teacherVivaMarks: teacherVivaMarks,
|
||||
teacherRemarks,
|
||||
totalScore,
|
||||
status: 'reviewed'
|
||||
})
|
||||
});
|
||||
if (res.ok) {
|
||||
await fetchSubmissions();
|
||||
setSelectedSubmission(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update score:', error);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredSubmissions = submissions.filter(s => {
|
||||
const matchesFilter = filter === 'all' || s.status === filter;
|
||||
const matchesSearch = s.studentName.toLowerCase().includes(search.toLowerCase()) ||
|
||||
s.rollNumber.toLowerCase().includes(search.toLowerCase());
|
||||
return matchesFilter && matchesSearch;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6">
|
||||
<div>
|
||||
<h2 className="text-3xl font-black text-white">Teacher Dashboard 👩🏫</h2>
|
||||
<p className="text-slate-400 mt-1">Reviewing submissions for {user.assignedGrade || 'All Classes'}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setShowReport(true)}
|
||||
className="bg-orange-500/10 text-orange-500 px-4 py-2 rounded-xl font-bold border border-orange-500/20 hover:bg-orange-500 hover:text-white transition-all flex items-center gap-2"
|
||||
>
|
||||
<Award size={18} /> Class Report
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowSettings(true)}
|
||||
className="bg-[#0b8c9f]/10 text-[#0b8c9f] px-4 py-2 rounded-xl font-bold border border-[#0b8c9f]/20 hover:bg-[#0b8c9f] hover:text-white transition-all flex items-center gap-2"
|
||||
>
|
||||
<FileText size={18} /> School Settings
|
||||
</button>
|
||||
<div className="bg-dark-card px-4 py-2 rounded-xl border border-dark-border flex items-center gap-3">
|
||||
<Users size={20} className="text-[#0b8c9f]" />
|
||||
<span className="font-bold text-white">{submissions.length} Students</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={onLogout}
|
||||
className="bg-red-500/10 text-red-500 px-4 py-2 rounded-xl font-bold border border-red-500/20 hover:bg-red-500 hover:text-white transition-all"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showReport && (
|
||||
<div className="fixed inset-0 bg-dark-bg/90 backdrop-blur-xl z-[100] flex items-center justify-center p-6 overflow-y-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-dark-card rounded-3xl p-10 border border-dark-border max-w-4xl w-full shadow-2xl my-auto"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h3 className="text-3xl font-black text-white">Class Performance Report 📊</h3>
|
||||
<p className="text-slate-400">Comprehensive analysis for {user.assignedGrade || 'All Classes'}</p>
|
||||
</div>
|
||||
<button onClick={() => setShowReport(false)} className="p-2 hover:bg-slate-800 rounded-xl text-slate-500 transition-colors">
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10">
|
||||
<div className="bg-dark-bg p-6 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">Avg LEI</div>
|
||||
<div className="text-3xl font-black text-[#0b8c9f]">
|
||||
{submissions.length > 0
|
||||
? Math.round(submissions.reduce((acc, s) => acc + (s.lei || 0), 0) / submissions.length)
|
||||
: 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-dark-bg p-6 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">Total Submissions</div>
|
||||
<div className="text-3xl font-black text-white">{submissions.length}</div>
|
||||
</div>
|
||||
<div className="bg-dark-bg p-6 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">Reviewed</div>
|
||||
<div className="text-3xl font-black text-green-500">
|
||||
{submissions.filter(s => s.status === 'reviewed').length}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-dark-bg p-6 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">Avg Time</div>
|
||||
<div className="text-3xl font-black text-orange-500">
|
||||
{submissions.length > 0
|
||||
? Math.round(submissions.reduce((acc, s) => acc + (s.timeSpent || 0), 0) / submissions.length)
|
||||
: 0}m
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-lg font-bold text-white flex items-center gap-2">
|
||||
<Trophy size={20} className="text-yellow-500" /> Top Performers (by LEI)
|
||||
</h4>
|
||||
<div className="bg-dark-bg rounded-2xl border border-dark-border overflow-hidden">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="bg-slate-800/50">
|
||||
<tr>
|
||||
<th className="px-6 py-4 font-bold text-slate-400">Student</th>
|
||||
<th className="px-6 py-4 font-bold text-slate-400">Roll No</th>
|
||||
<th className="px-6 py-4 font-bold text-slate-400">Level</th>
|
||||
<th className="px-6 py-4 font-bold text-slate-400 text-right">LEI Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-dark-border">
|
||||
{[...submissions]
|
||||
.sort((a, b) => (b.lei || 0) - (a.lei || 0))
|
||||
.slice(0, 5)
|
||||
.map((s, i) => (
|
||||
<tr key={s.id} className="hover:bg-slate-800/30 transition-colors">
|
||||
<td className="px-6 py-4 text-white font-medium">{s.studentName}</td>
|
||||
<td className="px-6 py-4 text-slate-400">{s.rollNumber}</td>
|
||||
<td className="px-6 py-4 text-slate-400">Level {s.level}</td>
|
||||
<td className="px-6 py-4 text-right font-black text-[#0b8c9f]">{s.lei}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 pt-8 border-t border-dark-border flex justify-between items-center">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="bg-white p-1 rounded-lg">
|
||||
<img src="https://raw.githubusercontent.com/RS-Learning-Lab/assets/main/logo.png" alt="Logo" className="h-6" />
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">
|
||||
RS Learning Lab Official Report
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => window.print()}
|
||||
className="flex items-center gap-2 text-sm font-bold text-[#0b8c9f] hover:underline"
|
||||
>
|
||||
<Download size={16} /> Print Report
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSettings && (
|
||||
<div className="fixed inset-0 bg-dark-bg/80 backdrop-blur-md z-[100] flex items-center justify-center p-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="bg-dark-card rounded-3xl p-8 border border-dark-border max-w-md w-full shadow-2xl"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-2xl font-bold text-white">School Settings</h3>
|
||||
<button onClick={() => setShowSettings(false)} className="text-slate-500 hover:text-white">
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-300 mb-1">School Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={schoolName}
|
||||
onChange={(e) => setSchoolName(e.target.value)}
|
||||
placeholder="Enter school name"
|
||||
className="w-full px-4 py-3 rounded-xl bg-dark-bg border border-dark-border text-white focus:ring-2 focus:ring-[#0b8c9f] outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-300 mb-1">Principal Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={principalName}
|
||||
onChange={(e) => setPrincipalName(e.target.value)}
|
||||
placeholder="Enter principal name"
|
||||
className="w-full px-4 py-3 rounded-xl bg-dark-bg border border-dark-border text-white focus:ring-2 focus:ring-[#0b8c9f] outline-none"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
// In a real app, we'd save this to the backend
|
||||
localStorage.setItem('rs_school_name', schoolName);
|
||||
localStorage.setItem('rs_principal_name', principalName);
|
||||
setShowSettings(false);
|
||||
}}
|
||||
className="w-full bg-[#0b8c9f] text-white py-4 rounded-xl font-bold hover:bg-[#097a8a] transition-all shadow-lg shadow-[#0b8c9f]/20"
|
||||
>
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Submissions List */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<div className="bg-dark-card rounded-3xl border border-dark-border overflow-hidden">
|
||||
<div className="p-6 border-b border-dark-border flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<div className="relative w-full sm:w-64">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search students..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 rounded-xl bg-dark-bg border border-dark-border text-white text-sm focus:ring-2 focus:ring-[#0b8c9f] outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter size={18} className="text-slate-500" />
|
||||
<select
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value as any)}
|
||||
className="bg-dark-bg border border-dark-border text-white text-sm rounded-xl px-3 py-2 outline-none focus:ring-2 focus:ring-[#0b8c9f]"
|
||||
>
|
||||
<option value="all">All Status</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="reviewed">Reviewed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-dark-border">
|
||||
{loading ? (
|
||||
<div className="p-12 text-center text-slate-500">Loading submissions...</div>
|
||||
) : filteredSubmissions.length === 0 ? (
|
||||
<div className="p-12 text-center text-slate-500">No submissions found.</div>
|
||||
) : (
|
||||
filteredSubmissions.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => {
|
||||
setSelectedSubmission(s);
|
||||
setDocMarks({
|
||||
objective: s.marks?.documentation?.objective || 0,
|
||||
concepts: s.marks?.documentation?.concepts || 0,
|
||||
explanation: s.marks?.documentation?.explanation || 0,
|
||||
learningOutcome: s.marks?.documentation?.learningOutcome || 0
|
||||
});
|
||||
setCodeMarks({
|
||||
logic: s.marks?.code?.logic || 0,
|
||||
output: s.marks?.code?.output || 0,
|
||||
concepts: s.marks?.code?.concepts || 0
|
||||
});
|
||||
setVivaScore(s.vivaScore || 0);
|
||||
setTeacherVivaMarks(s.teacherVivaMarks || 0);
|
||||
setTeacherRemarks(s.teacherRemarks || '');
|
||||
}}
|
||||
className={cn(
|
||||
"w-full p-6 flex items-center justify-between hover:bg-slate-800/50 transition-all text-left group",
|
||||
selectedSubmission?.id === s.id && "bg-slate-800/80"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-slate-800 rounded-xl flex items-center justify-center text-white font-bold">
|
||||
{s.studentName.charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-white group-hover:text-[#0b8c9f] transition-colors">{s.studentName}</h4>
|
||||
<div className="flex items-center gap-3 text-xs text-slate-500 mt-1">
|
||||
<span>Roll: {s.rollNumber}</span>
|
||||
<span>•</span>
|
||||
<span>{s.grade}</span>
|
||||
<span>•</span>
|
||||
<span className="text-[#0b8c9f] font-bold">Lvl {s.level}</span>
|
||||
<span>•</span>
|
||||
<span className="text-orange-500">{s.language}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right hidden sm:block">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest">Total Score</div>
|
||||
<div className="text-lg font-black text-white">{s.marks?.total || 0} <span className="text-[10px] text-slate-500">/ 50</span></div>
|
||||
<div className="text-[10px] font-bold text-[#0b8c9f] uppercase">LEI: {s.lei || 0}</div>
|
||||
</div>
|
||||
<div className="text-right hidden sm:block">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest">Effort</div>
|
||||
<div className="text-lg font-black text-white">{s.timeSpent}m / {s.attempts}a</div>
|
||||
</div>
|
||||
<div className={cn(
|
||||
"px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest",
|
||||
s.status === 'reviewed' ? "bg-green-500/10 text-green-500" : "bg-yellow-500/10 text-yellow-500"
|
||||
)}>
|
||||
{s.status}
|
||||
</div>
|
||||
<ArrowRight size={20} className="text-slate-700 group-hover:text-white transition-all" />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Evaluation Panel */}
|
||||
<div className="space-y-6">
|
||||
{selectedSubmission ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="bg-dark-card rounded-3xl border border-dark-border p-8 sticky top-28"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-xl font-bold text-white">Evaluation</h3>
|
||||
<button onClick={() => setSelectedSubmission(null)} className="text-slate-500 hover:text-white">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="p-4 bg-slate-800/50 rounded-2xl border border-dark-border">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest">Performance Metrics</div>
|
||||
<div className="text-sm font-black text-[#0b8c9f]">LEI: {selectedSubmission.lei}</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="p-2 bg-dark-bg rounded-lg text-center">
|
||||
<div className="text-[10px] text-slate-500 uppercase">Time</div>
|
||||
<div className="text-sm font-bold text-white">{selectedSubmission.timeSpent}m</div>
|
||||
</div>
|
||||
<div className="p-2 bg-dark-bg rounded-lg text-center">
|
||||
<div className="text-[10px] text-slate-500 uppercase">Attempts</div>
|
||||
<div className="text-sm font-bold text-white">{selectedSubmission.attempts}</div>
|
||||
</div>
|
||||
<div className="p-2 bg-dark-bg rounded-lg text-center">
|
||||
<div className="text-[10px] text-slate-500 uppercase">Hints</div>
|
||||
<div className="text-sm font-bold text-white">{selectedSubmission.hintsUsed}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-slate-800/50 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">Project Details</div>
|
||||
<div className="text-sm font-bold text-white mb-1">{selectedSubmission.track === 'project' ? 'Final Project' : 'Track Challenge'}</div>
|
||||
<div className="text-xs text-[#0b8c9f] font-mono">{selectedSubmission.language}</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-[#0b8c9f]/5 rounded-2xl border border-[#0b8c9f]/20">
|
||||
<div className="text-xs font-bold text-[#0b8c9f] uppercase tracking-widest mb-2">Project Documentation</div>
|
||||
<div className="text-xs text-slate-300 leading-relaxed whitespace-pre-wrap max-h-40 overflow-y-auto custom-scrollbar">
|
||||
{selectedSubmission.docText}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-[#0b8c9f]/5 rounded-2xl border border-[#0b8c9f]/20">
|
||||
<div className="text-xs font-bold text-[#0b8c9f] uppercase tracking-widest mb-2">AI Feedback</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed italic">"{selectedSubmission.aiFeedback}"</p>
|
||||
</div>
|
||||
|
||||
{selectedSubmission.vivaSummary && (
|
||||
<div className="p-4 bg-yellow-500/5 rounded-2xl border border-yellow-500/20">
|
||||
<div className="text-xs font-bold text-yellow-500 uppercase tracking-widest mb-4">AI Viva Practice Summary</div>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase mb-1">Overall Rating</div>
|
||||
<p className="text-xs text-white font-bold">{selectedSubmission.vivaSummary.finalQualitative}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase mb-1">Strengths</div>
|
||||
<ul className="space-y-1">
|
||||
{selectedSubmission.vivaSummary.strengths.map((s, i) => (
|
||||
<li key={i} className="text-[10px] text-slate-300 flex items-center gap-2">
|
||||
<div className="w-1 h-1 bg-green-500 rounded-full" /> {s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase mb-1">Areas to Improve</div>
|
||||
<ul className="space-y-1">
|
||||
{selectedSubmission.vivaSummary.improvements.map((s, i) => (
|
||||
<li key={i} className="text-[10px] text-slate-300 flex items-center gap-2">
|
||||
<div className="w-1 h-1 bg-orange-500 rounded-full" /> {s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedSubmission.plagiarismReport && (
|
||||
<div className={cn(
|
||||
"p-4 rounded-2xl border",
|
||||
selectedSubmission.plagiarismReport.status === 'Safe' ? "bg-green-500/5 border-green-500/20" :
|
||||
selectedSubmission.plagiarismReport.status === 'Needs Review' ? "bg-yellow-500/5 border-yellow-500/20" :
|
||||
"bg-red-500/5 border-red-500/20"
|
||||
)}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className={cn(
|
||||
"text-xs font-bold uppercase tracking-widest",
|
||||
selectedSubmission.plagiarismReport.status === 'Safe' ? "text-green-500" :
|
||||
selectedSubmission.plagiarismReport.status === 'Needs Review' ? "text-yellow-500" :
|
||||
"text-red-500"
|
||||
)}>
|
||||
Anti-Copy Report: {selectedSubmission.plagiarismReport.status}
|
||||
</div>
|
||||
<div className="text-xl font-black text-white">
|
||||
{selectedSubmission.plagiarismReport.score}%
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed mb-4 italic">
|
||||
"{selectedSubmission.plagiarismReport.explanation}"
|
||||
</p>
|
||||
|
||||
{selectedSubmission.plagiarismReport.status !== 'Safe' && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await fetch(`/api/submissions/${selectedSubmission.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ vivaRequested: true })
|
||||
});
|
||||
alert('Viva explanation requested from student.');
|
||||
} catch (err) {
|
||||
console.error("Failed to request viva:", err);
|
||||
}
|
||||
}}
|
||||
className="w-full mb-4 py-2 bg-yellow-500/10 hover:bg-yellow-500/20 text-yellow-500 rounded-xl text-[10px] font-bold border border-yellow-500/20 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<MessageSquare size={14} /> Request Viva Explanation
|
||||
</button>
|
||||
)}
|
||||
|
||||
{selectedSubmission.plagiarismReport.similarSubmissions.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase">Similar Submissions</div>
|
||||
{selectedSubmission.plagiarismReport.similarSubmissions.map((s, i) => (
|
||||
<div key={i} className="p-2 bg-black/20 rounded-lg flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-white">{s.studentName} ({s.rollNumber})</div>
|
||||
<div className="text-[8px] text-slate-500">{s.reason}</div>
|
||||
</div>
|
||||
<div className="text-[10px] font-bold text-slate-400">{s.similarityScore}%</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedSubmission.code && (
|
||||
<div className="p-4 bg-slate-950 rounded-2xl border border-dark-border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest">Submitted Code</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
const blob = new Blob([selectedSubmission.code || ''], { type: 'text/javascript' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `solution_${selectedSubmission.studentName.replace(/\s+/g, '_')}.js`;
|
||||
a.click();
|
||||
}}
|
||||
className="text-[10px] text-[#0b8c9f] hover:underline flex items-center gap-1"
|
||||
>
|
||||
<Download size={12} /> Download Code
|
||||
</button>
|
||||
</div>
|
||||
<pre className="text-[10px] text-slate-400 font-mono bg-black/30 p-3 rounded-lg overflow-x-auto max-h-40 custom-scrollbar">
|
||||
{selectedSubmission.code}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-slate-800/50 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-4">Documentation (Max 20)</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Objective (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={docMarks.objective}
|
||||
onChange={(e) => setDocMarks({...docMarks, objective: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Concepts (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={docMarks.concepts}
|
||||
onChange={(e) => setDocMarks({...docMarks, concepts: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Explanation (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={docMarks.explanation}
|
||||
onChange={(e) => setDocMarks({...docMarks, explanation: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Outcome (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={docMarks.learningOutcome}
|
||||
onChange={(e) => setDocMarks({...docMarks, learningOutcome: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-slate-800/50 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-4">Code Implementation (Max 15)</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Logic (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={codeMarks.logic}
|
||||
onChange={(e) => setCodeMarks({...codeMarks, logic: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Output (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={codeMarks.output}
|
||||
onChange={(e) => setCodeMarks({...codeMarks, output: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Concept (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={codeMarks.concepts}
|
||||
onChange={(e) => setCodeMarks({...codeMarks, concepts: Number(e.target.value)})}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-slate-800/50 rounded-2xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-4">Viva (Max 15)</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">AI Viva (5)</label>
|
||||
<input
|
||||
type="number" max={5} value={vivaScore}
|
||||
onChange={(e) => setVivaScore(Number(e.target.value))}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] text-slate-500 uppercase mb-1">Teacher (10)</label>
|
||||
<input
|
||||
type="number" max={10} value={teacherVivaMarks}
|
||||
onChange={(e) => setTeacherVivaMarks(Number(e.target.value))}
|
||||
className="w-full px-3 py-2 rounded-lg bg-dark-bg border border-dark-border text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">Teacher Remarks</label>
|
||||
<textarea
|
||||
value={teacherRemarks}
|
||||
onChange={(e) => setTeacherRemarks(e.target.value)}
|
||||
placeholder="Enter qualitative feedback..."
|
||||
className="w-full px-4 py-3 rounded-xl bg-dark-bg border border-dark-border text-white outline-none focus:ring-2 focus:ring-[#0b8c9f] min-h-[80px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<button
|
||||
disabled={isUpdating}
|
||||
onClick={() => handleUpdateScore(selectedSubmission.id)}
|
||||
className="w-full bg-[#0b8c9f] text-white py-4 rounded-xl font-bold hover:bg-[#097a8a] transition-all flex items-center justify-center gap-2 shadow-lg shadow-[#0b8c9f]/20"
|
||||
>
|
||||
{isUpdating ? 'Updating...' : 'Save Evaluation'} <Award size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="bg-dark-card rounded-3xl border border-dark-border p-12 text-center flex flex-col items-center justify-center h-full min-h-[400px]">
|
||||
<div className="w-20 h-20 bg-slate-800 rounded-full flex items-center justify-center text-slate-600 mb-6">
|
||||
<MessageSquare size={40} />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-white mb-2">Select a Submission</h3>
|
||||
<p className="text-sm text-slate-500 max-w-[200px]">Click on a student to start the evaluation process.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function X({ size }: { size: number }) {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
343
code_arena/src/components/VivaSimulator.tsx
Normal file
@ -0,0 +1,343 @@
|
||||
import React from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import {
|
||||
MessageSquare, Send, CheckCircle2, AlertCircle,
|
||||
ArrowRight, Trophy, Brain, Target, RefreshCw,
|
||||
ChevronRight, Sparkles, User, Award
|
||||
} from 'lucide-react';
|
||||
import { VivaQuestion, VivaEvaluation, VivaSession, UserProgress } from '../types';
|
||||
import { generateVivaQuestions, evaluateVivaAnswer, generateVivaSummary } from '../lib/vivaSimulator';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface VivaSimulatorProps {
|
||||
progress: UserProgress;
|
||||
level: number;
|
||||
onComplete: (session: VivaSession) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function VivaSimulator({ progress, level, onComplete, onClose }: VivaSimulatorProps) {
|
||||
const [step, setStep] = React.useState<'start' | 'questioning' | 'feedback' | 'summary'>('start');
|
||||
const [questions, setQuestions] = React.useState<VivaQuestion[]>([]);
|
||||
const [currentQuestionIndex, setCurrentQuestionIndex] = React.useState(0);
|
||||
const [answer, setAnswer] = React.useState('');
|
||||
const [isEvaluating, setIsEvaluating] = React.useState(false);
|
||||
const [lastEvaluation, setLastEvaluation] = React.useState<VivaEvaluation | null>(null);
|
||||
const [session, setSession] = React.useState<Partial<VivaSession>>({
|
||||
id: Date.now().toString(),
|
||||
level,
|
||||
language: progress.language || 'JavaScript',
|
||||
questions: []
|
||||
});
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const startViva = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const qs = await generateVivaQuestions(progress, level, 5);
|
||||
setQuestions(qs);
|
||||
setStep('questioning');
|
||||
} catch (error) {
|
||||
console.error("Failed to start viva:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitAnswer = async () => {
|
||||
if (!answer.trim() || isEvaluating) return;
|
||||
|
||||
setIsEvaluating(true);
|
||||
try {
|
||||
const evaluation = await evaluateVivaAnswer(
|
||||
questions[currentQuestionIndex],
|
||||
answer,
|
||||
progress.language || 'JavaScript'
|
||||
);
|
||||
|
||||
setLastEvaluation(evaluation);
|
||||
setSession(prev => ({
|
||||
...prev,
|
||||
questions: [
|
||||
...(prev.questions || []),
|
||||
{
|
||||
question: questions[currentQuestionIndex],
|
||||
answer,
|
||||
evaluation
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
setStep('feedback');
|
||||
} catch (error) {
|
||||
console.error("Failed to evaluate answer:", error);
|
||||
} finally {
|
||||
setIsEvaluating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const nextQuestion = async () => {
|
||||
if (currentQuestionIndex < questions.length - 1) {
|
||||
setCurrentQuestionIndex(prev => prev + 1);
|
||||
setAnswer('');
|
||||
setLastEvaluation(null);
|
||||
setStep('questioning');
|
||||
} else {
|
||||
setLoading(true);
|
||||
try {
|
||||
const summary = await generateVivaSummary(session);
|
||||
const finalSession: VivaSession = {
|
||||
...session as VivaSession,
|
||||
summary,
|
||||
completedAt: new Date().toISOString()
|
||||
};
|
||||
setSession(finalSession);
|
||||
setStep('summary');
|
||||
} catch (error) {
|
||||
console.error("Failed to generate summary:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/90 backdrop-blur-sm">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
className="bg-dark-card w-full max-w-2xl rounded-3xl border border-dark-border shadow-2xl overflow-hidden flex flex-col max-h-[90vh]"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b border-dark-border flex items-center justify-between bg-dark-card/50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-[#0b8c9f]/10 rounded-xl flex items-center justify-center text-[#0b8c9f]">
|
||||
<MessageSquare size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-black text-white">AI Viva Simulator</h3>
|
||||
<p className="text-xs text-slate-400 font-bold uppercase tracking-widest">Level {level} • {progress.language}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-500 hover:text-white transition-colors"
|
||||
>
|
||||
<AlertCircle size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-8 custom-scrollbar">
|
||||
<AnimatePresence mode="wait">
|
||||
{step === 'start' && (
|
||||
<motion.div
|
||||
key="start"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="text-center space-y-8 py-8"
|
||||
>
|
||||
<div className="w-20 h-20 bg-[#0b8c9f]/10 rounded-3xl flex items-center justify-center text-[#0b8c9f] mx-auto">
|
||||
<Brain size={40} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-2xl font-black text-white mb-2">Ready for your Viva?</h4>
|
||||
<p className="text-slate-400 max-w-md mx-auto">
|
||||
I'll ask you 5 questions about what you've learned. Practice explaining your logic clearly and confidently!
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 max-w-lg mx-auto">
|
||||
{[
|
||||
{ icon: Target, label: "Conceptual Clarity" },
|
||||
{ icon: MessageSquare, label: "Logical Explanation" },
|
||||
{ icon: Award, label: "Confidence Building" }
|
||||
].map((item, i) => (
|
||||
<div key={i} className="p-4 bg-slate-900/50 rounded-2xl border border-dark-border">
|
||||
<item.icon className="mx-auto mb-2 text-[#0b8c9f]" size={20} />
|
||||
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={startViva}
|
||||
disabled={loading}
|
||||
className="bg-[#0b8c9f] hover:bg-[#0b8c9f]/90 text-white px-8 py-4 rounded-2xl font-black shadow-lg shadow-[#0b8c9f]/20 transition-all flex items-center gap-2 mx-auto disabled:opacity-50"
|
||||
>
|
||||
{loading ? <RefreshCw className="animate-spin" /> : <Sparkles size={20} />}
|
||||
Start Viva Session
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{step === 'questioning' && (
|
||||
<motion.div
|
||||
key="questioning"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
{/* Progress */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest">Question {currentQuestionIndex + 1} of {questions.length}</div>
|
||||
<div className="flex gap-1">
|
||||
{questions.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
"h-1.5 rounded-full transition-all duration-500",
|
||||
i === currentQuestionIndex ? "w-8 bg-[#0b8c9f]" :
|
||||
i < currentQuestionIndex ? "w-4 bg-green-500/50" : "w-4 bg-slate-800"
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Question */}
|
||||
<div className="bg-slate-900/50 p-8 rounded-3xl border border-dark-border relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-1 h-full bg-[#0b8c9f]" />
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<span className="px-2 py-1 bg-[#0b8c9f]/10 text-[#0b8c9f] text-[10px] font-bold rounded uppercase tracking-widest">
|
||||
{questions[currentQuestionIndex].type}
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-white leading-relaxed">
|
||||
{questions[currentQuestionIndex].text}
|
||||
</h4>
|
||||
{questions[currentQuestionIndex].context && (
|
||||
<div className="mt-6 p-4 bg-slate-950 rounded-xl border border-dark-border font-mono text-sm text-slate-300 overflow-x-auto">
|
||||
<pre>{questions[currentQuestionIndex].context}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Answer Input */}
|
||||
<div className="space-y-4">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest">Your Explanation</div>
|
||||
<textarea
|
||||
value={answer}
|
||||
onChange={(e) => setAnswer(e.target.value)}
|
||||
placeholder="Type your answer here... Be as descriptive as possible!"
|
||||
className="w-full h-32 bg-slate-900 border border-dark-border rounded-2xl p-4 text-white placeholder:text-slate-600 focus:outline-none focus:border-[#0b8c9f] transition-colors resize-none custom-scrollbar"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSubmitAnswer}
|
||||
disabled={!answer.trim() || isEvaluating}
|
||||
className="w-full bg-[#0b8c9f] hover:bg-[#0b8c9f]/90 text-white py-4 rounded-2xl font-black shadow-lg shadow-[#0b8c9f]/20 transition-all flex items-center justify-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
{isEvaluating ? <RefreshCw className="animate-spin" /> : <Send size={20} />}
|
||||
Submit Answer
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{step === 'feedback' && lastEvaluation && (
|
||||
<motion.div
|
||||
key="feedback"
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className={cn(
|
||||
"w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg",
|
||||
lastEvaluation.isCorrect ? "bg-green-500/10 text-green-500 shadow-green-500/10" : "bg-orange-500/10 text-orange-500 shadow-orange-500/10"
|
||||
)}>
|
||||
{lastEvaluation.isCorrect ? <CheckCircle2 size={32} /> : <Brain size={32} />}
|
||||
</div>
|
||||
<h4 className="text-xl font-black text-white">{lastEvaluation.qualitativeScore}</h4>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="p-6 bg-green-500/5 rounded-2xl border border-green-500/20">
|
||||
<div className="text-[10px] font-bold text-green-500 uppercase tracking-widest mb-2">What was correct</div>
|
||||
<p className="text-sm text-slate-300 leading-relaxed">{lastEvaluation.feedback.correct}</p>
|
||||
</div>
|
||||
<div className="p-6 bg-orange-500/5 rounded-2xl border border-orange-500/20">
|
||||
<div className="text-[10px] font-bold text-orange-500 uppercase tracking-widest mb-2">What was missing</div>
|
||||
<p className="text-sm text-slate-300 leading-relaxed">{lastEvaluation.feedback.missing}</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#0b8c9f]/5 rounded-2xl border border-[#0b8c9f]/20">
|
||||
<div className="text-[10px] font-bold text-[#0b8c9f] uppercase tracking-widest mb-2">How to improve</div>
|
||||
<p className="text-sm text-slate-300 leading-relaxed">{lastEvaluation.feedback.improvement}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={nextQuestion}
|
||||
className="w-full bg-slate-800 hover:bg-slate-700 text-white py-4 rounded-2xl font-black transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
{currentQuestionIndex < questions.length - 1 ? "Next Question" : "View Final Summary"}
|
||||
<ArrowRight size={20} />
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{step === 'summary' && session.summary && (
|
||||
<motion.div
|
||||
key="summary"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-8 py-4"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="w-20 h-20 bg-yellow-500/10 rounded-3xl flex items-center justify-center text-yellow-500 mx-auto mb-6 shadow-xl shadow-yellow-500/10">
|
||||
<Trophy size={40} />
|
||||
</div>
|
||||
<h4 className="text-3xl font-black text-white mb-2">Viva Completed!</h4>
|
||||
<div className="inline-block px-4 py-1.5 bg-yellow-500/10 text-yellow-500 rounded-full text-sm font-black uppercase tracking-widest border border-yellow-500/20">
|
||||
{session.summary.finalQualitative}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900/50 p-8 rounded-3xl border border-dark-border">
|
||||
<div className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-4">Overall Performance</div>
|
||||
<p className="text-slate-300 leading-relaxed italic">"{session.summary.overall}"</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="p-6 bg-green-500/5 rounded-2xl border border-green-500/20">
|
||||
<div className="text-[10px] font-bold text-green-500 uppercase tracking-widest mb-4 flex items-center gap-2">
|
||||
<Award size={14} /> Key Strengths
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{session.summary.strengths.map((s, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-xs text-slate-300">
|
||||
<CheckCircle2 size={14} className="text-green-500 mt-0.5 flex-shrink-0" />
|
||||
{s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="p-6 bg-orange-500/5 rounded-2xl border border-orange-500/20">
|
||||
<div className="text-[10px] font-bold text-orange-500 uppercase tracking-widest mb-4 flex items-center gap-2">
|
||||
<Target size={14} /> Areas to Improve
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{session.summary.improvements.map((s, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-xs text-slate-300">
|
||||
<ChevronRight size={14} className="text-orange-500 mt-0.5 flex-shrink-0" />
|
||||
{s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => onComplete(session as VivaSession)}
|
||||
className="w-full bg-[#0b8c9f] hover:bg-[#0b8c9f]/90 text-white py-4 rounded-2xl font-black shadow-lg shadow-[#0b8c9f]/20 transition-all"
|
||||
>
|
||||
Save & Finish Session
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
code_arena/src/index.css
Normal file
@ -0,0 +1,65 @@
|
||||
@import "tailwindcss";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Root Variables */
|
||||
:root {
|
||||
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-serif: "Playfair Display", serif;
|
||||
--font-cursive: "Dancing Script", cursive;
|
||||
|
||||
/* Brand Colors */
|
||||
--color-primary: #0b8c9f;
|
||||
--color-secondary: #ff7a00;
|
||||
|
||||
/* Dark Theme */
|
||||
--color-dark-bg: #020617;
|
||||
--color-dark-card: #0f172a;
|
||||
--color-dark-border: #1e293b;
|
||||
|
||||
/* Slate Colors */
|
||||
--color-slate-50: #f8fafc;
|
||||
--color-slate-100: #f1f5f9;
|
||||
--color-slate-200: #e2e8f0;
|
||||
--color-slate-300: #cbd5e1;
|
||||
--color-slate-400: #94a3b8;
|
||||
--color-slate-500: #64748b;
|
||||
--color-slate-600: #475569;
|
||||
--color-slate-700: #334155;
|
||||
--color-slate-800: #1e293b;
|
||||
--color-slate-900: #0f172a;
|
||||
--color-slate-950: #020617;
|
||||
}
|
||||
|
||||
/* Apply Base Styles */
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background-color: var(--color-dark-bg);
|
||||
color: var(--color-slate-50);
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-dark-bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-dark-border);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Glass Card */
|
||||
.glass-card {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--color-dark-border);
|
||||
}
|
||||
111
code_arena/src/lib/certificate.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { jsPDF } from 'jspdf';
|
||||
import html2canvas from 'html2canvas';
|
||||
|
||||
export const generateCertificate = async (
|
||||
studentName: string,
|
||||
level: number,
|
||||
track: string,
|
||||
skills: string[],
|
||||
isPremium: boolean,
|
||||
grade: string,
|
||||
rollNumber: string,
|
||||
projectName?: string,
|
||||
effortLevel?: string,
|
||||
schoolName?: string,
|
||||
principalName?: string
|
||||
) => {
|
||||
const element = document.createElement('div');
|
||||
element.style.width = '800px';
|
||||
element.style.padding = '40px';
|
||||
element.style.background = '#fff';
|
||||
element.style.position = 'absolute';
|
||||
element.style.left = '-9999px';
|
||||
element.style.fontFamily = "'Inter', sans-serif";
|
||||
|
||||
const certificateId = `RS-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
||||
const date = new Date().toLocaleDateString();
|
||||
const logoUrl = "https://raw.githubusercontent.com/RS-Learning-Lab/assets/main/logo.png";
|
||||
|
||||
element.innerHTML = `
|
||||
<div style="border: 15px solid #0b8c9f; padding: 40px; position: relative; background: #fff; box-sizing: border-box; height: 560px;">
|
||||
<!-- Watermark -->
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); opacity: 0.05; pointer-events: none; width: 400px;">
|
||||
<img src="${logoUrl}" style="width: 100%;" />
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; position: relative; z-index: 1;">
|
||||
<div style="display: flex; justify-content: center; margin-bottom: 20px;">
|
||||
<img src="${logoUrl}" style="height: 60px;" />
|
||||
</div>
|
||||
<div style="color: #0b8c9f; font-weight: 800; font-size: 28px; margin-bottom: 5px; letter-spacing: 2px;">RS LEARNING LAB</div>
|
||||
<div style="height: 2px; width: 100px; background: #ff7a00; margin: 0 auto 20px;"></div>
|
||||
|
||||
<h1 style="font-size: 36px; margin: 10px 0; color: #1a1a1a; text-transform: uppercase; letter-spacing: 1px;">Certificate of Achievement</h1>
|
||||
<p style="font-size: 16px; color: #666; margin-bottom: 10px;">This prestigious award is presented to</p>
|
||||
|
||||
<h2 style="font-size: 42px; color: #ff7a00; margin: 5px 0; font-family: 'Playfair Display', serif; font-weight: 700; border-bottom: 2px solid #f0f0f0; display: inline-block; padding: 0 40px;">${studentName}</h2>
|
||||
|
||||
<div style="margin: 5px 0; font-size: 14px; color: #444; font-weight: 600;">
|
||||
Roll No: ${rollNumber} | Class: ${grade}
|
||||
</div>
|
||||
|
||||
<div style="margin: 5px 0; font-size: 14px; color: #0b8c9f; font-weight: 700; text-transform: uppercase;">
|
||||
${schoolName || 'RS Learning Lab'}
|
||||
</div>
|
||||
|
||||
<p style="font-size: 15px; color: #666; max-width: 600px; margin: 15px auto;">
|
||||
for outstanding performance and successful completion of <br/>
|
||||
<strong style="color: #0b8c9f;">Level ${level} - ${track}</strong>
|
||||
${projectName ? `<br/>and the <strong style="color: #0b8c9f;">${projectName}</strong> project` : ''}
|
||||
${effortLevel ? `<br/><span style="font-size: 13px; color: #ff7a00; font-weight: bold;">Effort Recognition: ${effortLevel} Learner</span>` : ''}
|
||||
</p>
|
||||
|
||||
<div style="margin-top: 20px; text-align: left; display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div style="flex: 1;">
|
||||
<p style="font-size: 11px; color: #888; margin-bottom: 6px; font-weight: bold; text-transform: uppercase;">Skills Mastered:</p>
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
||||
${skills.map(s => `<span style="background: #f0fdfa; color: #0b8c9f; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; border: 1px solid #0b8c9f;">${s}</span>`).join('')}
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<p style="font-size: 10px; color: #aaa; margin: 0;">Certificate ID: ${certificateId}</p>
|
||||
<p style="font-size: 10px; color: #aaa; margin: 0;">Issued on: ${date}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: right; flex: 1;">
|
||||
<div style="display: flex; gap: 40px; justify-content: flex-end;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-family: 'Dancing Script', cursive; font-size: 24px; color: #1a1a1a; border-bottom: 2px solid #0b8c9f; padding: 0 15px 3px;">Gokula Krishnan</div>
|
||||
<p style="font-size: 10px; color: #0b8c9f; font-weight: bold; margin-top: 6px; text-transform: uppercase; letter-spacing: 1px;">Founder</p>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 120px; border-bottom: 2px solid #ccc; padding: 0 15px 3px; height: 30px;"></div>
|
||||
<p style="font-size: 10px; color: #888; font-weight: bold; margin-top: 6px; text-transform: uppercase; letter-spacing: 1px;">School Signature</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${isPremium ? `
|
||||
<div style="position: absolute; top: 30px; right: 30px; width: 100px; height: 100px; background: #ff7a00; border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: white; font-weight: 900; font-size: 11px; text-align: center; transform: rotate(15deg); box-shadow: 0 6px 15px rgba(255, 122, 0, 0.4); border: 4px double white;">
|
||||
<span style="font-size: 14px;">PREMIUM</span>
|
||||
<span>EXCELLENCE</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(element);
|
||||
const canvas = await html2canvas(element, { scale: 3, useCORS: true });
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'landscape',
|
||||
unit: 'px',
|
||||
format: [800, 560]
|
||||
});
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, 0, 800, 560);
|
||||
pdf.save(`RS_Certificate_${studentName.replace(/\s+/g, '_')}.pdf`);
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
402
code_arena/src/lib/docGenerator.ts
Normal file
@ -0,0 +1,402 @@
|
||||
import { Document, Packer, Paragraph, TextRun, AlignmentType, HeadingLevel, ImageRun } from 'docx';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export const generateProjectDoc = async (
|
||||
studentName: string,
|
||||
grade: string,
|
||||
rollNumber: string,
|
||||
projectTitle: string,
|
||||
projectId: string,
|
||||
schoolName?: string,
|
||||
principalName?: string,
|
||||
docContent?: {
|
||||
objective: string;
|
||||
conceptsUsed: string;
|
||||
howItWorks: string;
|
||||
whatILearned: string;
|
||||
}
|
||||
) => {
|
||||
const logoUrl = "https://raw.githubusercontent.com/RS-Learning-Lab/assets/main/logo.png";
|
||||
let logoImage;
|
||||
try {
|
||||
const response = await fetch(logoUrl);
|
||||
const buffer = await response.arrayBuffer();
|
||||
logoImage = new ImageRun({
|
||||
data: new Uint8Array(buffer),
|
||||
transformation: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
} as any);
|
||||
} catch (error) {
|
||||
console.error("Failed to load logo for document:", error);
|
||||
}
|
||||
|
||||
const doc = new Document({
|
||||
sections: [
|
||||
{
|
||||
properties: {},
|
||||
children: [
|
||||
// Front Page Logo
|
||||
...(logoImage ? [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [logoImage],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
] : []),
|
||||
|
||||
// Front Page
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "RS LEARNING LAB",
|
||||
bold: true,
|
||||
size: 56,
|
||||
color: "0b8c9f",
|
||||
font: "Calibri",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "Innovation through Code",
|
||||
italics: true,
|
||||
size: 24,
|
||||
color: "666666",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 1200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "PROJECT DOCUMENTATION",
|
||||
bold: true,
|
||||
size: 40,
|
||||
underline: {},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 800 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: projectTitle.toUpperCase(),
|
||||
bold: true,
|
||||
size: 32,
|
||||
color: "333333",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 1200 } }),
|
||||
|
||||
// Front Page Details (Centered)
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "Project ID: ", bold: true, size: 24 }),
|
||||
new TextRun({ text: `RS-PROJ-${projectId}`, size: 24 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "Student Name: ", bold: true, size: 24 }),
|
||||
new TextRun({ text: studentName, size: 24 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "Class: ", bold: true, size: 24 }),
|
||||
new TextRun({ text: grade, size: 24 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "School Name: ", bold: true, size: 24 }),
|
||||
new TextRun({ text: schoolName || "N/A", size: 24 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "Founder Name: ", bold: true, size: 24 }),
|
||||
new TextRun({ text: "Gokula Krishnan", size: 24 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "Date: ", bold: true, size: 24 }),
|
||||
new TextRun({ text: new Date().toLocaleDateString(), size: 24 }),
|
||||
],
|
||||
}),
|
||||
|
||||
// Page Break
|
||||
new Paragraph({ children: [new TextRun({ text: "", break: 1 })] }),
|
||||
|
||||
// Acknowledgement Page
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "ACKNOWLEDGEMENT", bold: true, size: 36, color: "0b8c9f" }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 800 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "I would like to extend my heartfelt gratitude to my school for their unwavering support and for providing the necessary resources to undertake this project. I am particularly grateful to RS Learning Lab and my mentor, Mr. Gokula Krishnan, for his exceptional guidance, technical expertise, and constant encouragement throughout the project's lifecycle. His mentorship has been instrumental in the successful completion of this endeavor. Furthermore, I would like to express my sincere appreciation to my parents and friends for their continuous motivation and support, which played a vital role in my academic journey.",
|
||||
size: 24,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 1200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [
|
||||
new TextRun({ text: "__________________________", bold: true }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [
|
||||
new TextRun({ text: "Student Signature", size: 18, color: "666666" }),
|
||||
],
|
||||
}),
|
||||
|
||||
// Page Break
|
||||
new Paragraph({ children: [new TextRun({ text: "", break: 1 })] }),
|
||||
|
||||
// Certificate
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "CERTIFICATE OF COMPLETION", bold: true, size: 36, color: "0b8c9f" }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 600 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: `This is to formally certify that `,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: studentName,
|
||||
bold: true,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: ` of `,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: grade,
|
||||
bold: true,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: `, `,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: schoolName || "N/A",
|
||||
bold: true,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: ` has successfully designed, developed, and documented the project titled `,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: `"${projectTitle}"`,
|
||||
bold: true,
|
||||
size: 24,
|
||||
}),
|
||||
new TextRun({
|
||||
text: `. This project was completed as part of the curriculum at RS Learning Lab, demonstrating proficiency in logical reasoning and technical implementation.`,
|
||||
size: 24,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 1600 } }),
|
||||
|
||||
// Signatures
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "__________________________", bold: true }),
|
||||
new TextRun({ text: "\t\t\t\t\t__________________________", bold: true }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "Gokula Krishnan", bold: true, size: 22 }),
|
||||
new TextRun({ text: "\t\t\t\t\t", bold: true, size: 22 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "Founder, RS Learning Lab", size: 18, color: "666666" }),
|
||||
new TextRun({ text: "\t\t\t\t\tSchool Signature", size: 18, color: "666666" }),
|
||||
],
|
||||
}),
|
||||
|
||||
// Page Break
|
||||
new Paragraph({ children: [new TextRun({ text: "", break: 1 })] }),
|
||||
|
||||
// Project Technical Content
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "PROJECT CONTENT", bold: true, size: 36, color: "0b8c9f" }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 600 } }),
|
||||
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: "1. Objective", bold: true, size: 28 })],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: docContent?.objective || "The primary objective of this project is to develop a functional application that demonstrates the practical application of programming logic in a real-world context.",
|
||||
size: 22,
|
||||
color: "444444"
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
new Paragraph({ spacing: { before: 400 } }),
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: "2. Concepts Used", bold: true, size: 28 })],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: docContent?.conceptsUsed || "Core logic, variables, and algorithmic thinking were applied to build this solution.",
|
||||
size: 22,
|
||||
color: "444444"
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
new Paragraph({ spacing: { before: 400 } }),
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: "3. How It Works", bold: true, size: 28 })],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: docContent?.howItWorks || "The implementation follows a structured approach to process input and generate the desired output.",
|
||||
size: 22,
|
||||
color: "444444"
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
new Paragraph({ spacing: { before: 400 } }),
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: "4. What I Learned", bold: true, size: 28 })],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: docContent?.whatILearned || "Through this project, I have gained significant insights into problem-solving and technical implementation.",
|
||||
size: 22,
|
||||
color: "444444"
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
new Paragraph({ spacing: { before: 1200 } }),
|
||||
|
||||
// Declaration
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "DECLARATION",
|
||||
bold: true,
|
||||
size: 24,
|
||||
color: "333333",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 200 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "I hereby declare that this project is a result of my own efforts and has been completed with integrity under the academic guidance of RS Learning Lab. This is my own work.",
|
||||
italics: true,
|
||||
size: 20,
|
||||
color: "666666",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 1200 } }),
|
||||
|
||||
// Evaluation Section
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "OFFICIAL EVALUATION", bold: true, size: 24, color: "0b8c9f" }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 400 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "Performance Score: ", bold: true, size: 22 }),
|
||||
new TextRun({ text: "__________ / 100", size: 22 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({ spacing: { before: 400 } }),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "Instructor Remarks: ", bold: true, size: 22 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.LEFT,
|
||||
children: [
|
||||
new TextRun({ text: "__________________________________________________________________________", size: 18, color: "999999" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const blob = await Packer.toBlob(doc);
|
||||
saveAs(blob, `${projectTitle.replace(/\s+/g, '_')}_Documentation.docx`);
|
||||
};
|
||||
120
code_arena/src/lib/errorAnalyzer.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { GoogleGenAI, Type } from "@google/genai";
|
||||
import { ProgrammingLanguage } from "../types";
|
||||
|
||||
export interface ErrorAnalysis {
|
||||
errorType: 'Syntax' | 'Logic' | 'Runtime' | 'Conceptual';
|
||||
explanation: {
|
||||
what: string;
|
||||
why: string;
|
||||
where: string;
|
||||
};
|
||||
steps: {
|
||||
step1: string; // Identify mistake
|
||||
step2: string; // Explain concept
|
||||
step3: string; // Show correct approach
|
||||
};
|
||||
suggestedFix: string;
|
||||
correctLogic: string;
|
||||
correctedCode?: string;
|
||||
hints: string[]; // [Small clue, More guidance, Full explanation]
|
||||
}
|
||||
|
||||
export async function analyzeError(
|
||||
studentCode: string,
|
||||
expectedOutput: string,
|
||||
actualOutput: string,
|
||||
language: ProgrammingLanguage,
|
||||
problemDescription: string
|
||||
): Promise<ErrorAnalysis> {
|
||||
const apiKey = process.env.GEMINI_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("Gemini API key is missing. Please configure it in the settings.");
|
||||
}
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey });
|
||||
|
||||
try {
|
||||
const response = await ai.models.generateContent({
|
||||
model: "gemini-3-flash-preview",
|
||||
contents: `You are an expert AI Coding Tutor for school students (Classes 6-12).
|
||||
Analyze the following incorrect code submission and provide a student-friendly explanation.
|
||||
|
||||
Problem Description: ${problemDescription}
|
||||
Programming Language: ${language}
|
||||
Student Code:
|
||||
\`\`\`${language.toLowerCase()}
|
||||
${studentCode}
|
||||
\`\`\`
|
||||
Expected Output: ${expectedOutput}
|
||||
Actual Output: ${actualOutput}
|
||||
|
||||
Rules:
|
||||
1. Keep it simple and encouraging. No complex jargon.
|
||||
2. Identify the error type: Syntax, Logic, Runtime, or Conceptual.
|
||||
3. Provide a 3-step learning breakdown.
|
||||
4. Generate 3 progressive hints:
|
||||
- Hint 1: A very small clue to nudge them.
|
||||
- Hint 2: More specific guidance.
|
||||
- Hint 3: A full explanation of how to fix it.
|
||||
|
||||
Return a JSON object with the following structure:
|
||||
{
|
||||
"errorType": "Syntax" | "Logic" | "Runtime" | "Conceptual",
|
||||
"explanation": {
|
||||
"what": "What exactly went wrong in simple terms",
|
||||
"why": "Why this is a mistake in this language",
|
||||
"where": "Where the mistake is (e.g., 'on the line where you defined the loop')"
|
||||
},
|
||||
"steps": {
|
||||
"step1": "Identify the mistake clearly",
|
||||
"step2": "Explain the underlying concept simply",
|
||||
"step3": "Show the correct approach without necessarily giving the full code yet"
|
||||
},
|
||||
"suggestedFix": "A short, actionable instruction to fix the code",
|
||||
"correctLogic": "Explain the correct logic that should have been used",
|
||||
"correctedCode": "The full corrected code snippet",
|
||||
"hints": ["Hint 1", "Hint 2", "Hint 3"]
|
||||
}`,
|
||||
config: {
|
||||
responseMimeType: "application/json",
|
||||
responseSchema: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
errorType: { type: Type.STRING },
|
||||
explanation: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
what: { type: Type.STRING },
|
||||
why: { type: Type.STRING },
|
||||
where: { type: Type.STRING }
|
||||
},
|
||||
required: ["what", "why", "where"]
|
||||
},
|
||||
steps: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
step1: { type: Type.STRING },
|
||||
step2: { type: Type.STRING },
|
||||
step3: { type: Type.STRING }
|
||||
},
|
||||
required: ["step1", "step2", "step3"]
|
||||
},
|
||||
suggestedFix: { type: Type.STRING },
|
||||
correctLogic: { type: Type.STRING },
|
||||
correctedCode: { type: Type.STRING },
|
||||
hints: {
|
||||
type: Type.ARRAY,
|
||||
items: { type: Type.STRING }
|
||||
}
|
||||
},
|
||||
required: ["errorType", "explanation", "steps", "suggestedFix", "correctLogic", "correctedCode", "hints"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.parse(response.text) as ErrorAnalysis;
|
||||
} catch (error) {
|
||||
console.error("Error Analysis failed:", error);
|
||||
throw new Error("Failed to analyze the error. Please try again.");
|
||||
}
|
||||
}
|
||||
124
code_arena/src/lib/lei.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { GoogleGenAI, Type } from "@google/genai";
|
||||
import { UserProgress } from "../types";
|
||||
|
||||
export interface LEIResult {
|
||||
score: number;
|
||||
category: 'Active Learner' | 'Consistent Learner' | 'Needs Support' | 'Stalled Learner';
|
||||
insight: string;
|
||||
}
|
||||
|
||||
export async function calculateLEI(progress: UserProgress): Promise<LEIResult> {
|
||||
const apiKey = process.env.GEMINI_API_KEY;
|
||||
if (!apiKey) {
|
||||
// Fallback if no API key
|
||||
const score = calculateManualLEI(progress);
|
||||
return {
|
||||
score,
|
||||
category: getLEICategory(score),
|
||||
insight: "AI insight unavailable. Manual calculation used."
|
||||
};
|
||||
}
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey });
|
||||
|
||||
const completedTracksCount = Object.values(progress.completedTracks).filter(Boolean).length;
|
||||
const metrics = {
|
||||
attempts: progress.totalAttempts,
|
||||
timeSpent: progress.timeSpent,
|
||||
completedTracks: completedTracksCount,
|
||||
retries: progress.retries || 0,
|
||||
hintsUsed: progress.hintsUsed,
|
||||
streak: progress.streak,
|
||||
xp: progress.xp
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await ai.models.generateContent({
|
||||
model: "gemini-3-flash-preview",
|
||||
contents: `Analyze the following student learning metrics and calculate a Learning Efficiency Index (LEI) score (0-100).
|
||||
|
||||
Metrics:
|
||||
- Total Coding Attempts: ${metrics.attempts}
|
||||
- Total Time Spent (minutes): ${metrics.timeSpent}
|
||||
- Completed Learning Tracks: ${metrics.completedTracks} (out of 12)
|
||||
- Retry Count: ${metrics.retries}
|
||||
- Hints Used: ${metrics.hintsUsed}
|
||||
- Daily Activity Streak: ${metrics.streak}
|
||||
|
||||
Calculation Rules:
|
||||
- Completion (Basics + Tracks) -> 30% weight
|
||||
- Effort (Attempts + Retries) -> 25% weight
|
||||
- Consistency (Daily usage / streak) -> 20% weight
|
||||
- Time Spent -> 15% weight
|
||||
- Hint usage -> -10% penalty
|
||||
|
||||
Categories:
|
||||
- 80-100: Active Learner
|
||||
- 60-79: Consistent Learner
|
||||
- 40-59: Needs Support
|
||||
- 0-39: Stalled Learner
|
||||
|
||||
Return a JSON object with:
|
||||
{
|
||||
"score": number (0-100),
|
||||
"category": "Active Learner" | "Consistent Learner" | "Needs Support" | "Stalled Learner",
|
||||
"insight": "A short (1-2 sentence) insight about the student's learning behavior"
|
||||
}`,
|
||||
config: {
|
||||
responseMimeType: "application/json",
|
||||
responseSchema: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
score: { type: Type.NUMBER },
|
||||
category: { type: Type.STRING },
|
||||
insight: { type: Type.STRING }
|
||||
},
|
||||
required: ["score", "category", "insight"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const result = JSON.parse(response.text);
|
||||
return {
|
||||
score: Math.min(100, Math.max(0, result.score)),
|
||||
category: result.category,
|
||||
insight: result.insight
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("LEI Calculation failed:", error);
|
||||
const score = calculateManualLEI(progress);
|
||||
return {
|
||||
score,
|
||||
category: getLEICategory(score),
|
||||
insight: "Error generating AI insight. Manual calculation used."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function calculateManualLEI(progress: UserProgress): number {
|
||||
const completedTracksCount = Object.values(progress.completedTracks).filter(Boolean).length;
|
||||
|
||||
// Normalization factors (estimated)
|
||||
const completionScore = (completedTracksCount / 12) * 100;
|
||||
const effortScore = Math.min(100, ((progress.totalAttempts + (progress.retries || 0)) / 40) * 100);
|
||||
const consistencyScore = Math.min(100, (progress.streak / 7) * 100);
|
||||
const timeScore = Math.min(100, (progress.timeSpent / 300) * 100);
|
||||
const hintPenalty = Math.min(100, (progress.hintsUsed / 10) * 100);
|
||||
|
||||
const finalScore = (
|
||||
(completionScore * 0.3) +
|
||||
(effortScore * 0.25) +
|
||||
(consistencyScore * 0.2) +
|
||||
(timeScore * 0.15) -
|
||||
(hintPenalty * 0.1)
|
||||
);
|
||||
|
||||
return Math.round(Math.min(100, Math.max(0, finalScore)));
|
||||
}
|
||||
|
||||
function getLEICategory(score: number): LEIResult['category'] {
|
||||
if (score >= 80) return 'Active Learner';
|
||||
if (score >= 60) return 'Consistent Learner';
|
||||
if (score >= 40) return 'Needs Support';
|
||||
return 'Stalled Learner';
|
||||
}
|
||||
9
code_arena/src/lib/logo.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
||||
|
||||
export const getLogoUrl = async (base64Image: string) => {
|
||||
// In a real app, you might upload this to a storage service.
|
||||
// For this environment, we'll just return the base64 string as the URL.
|
||||
return `data:image/png;base64,${base64Image}`;
|
||||
};
|
||||
62
code_arena/src/lib/plagiarismDetector.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { PlagiarismReport } from "../types";
|
||||
|
||||
export async function detectPlagiarism(
|
||||
newCode: string,
|
||||
existingSubmissions: { studentName: string; rollNumber: string; code: string }[],
|
||||
problemStatement: string,
|
||||
language: string
|
||||
): Promise<PlagiarismReport> {
|
||||
|
||||
if (existingSubmissions.length === 0) {
|
||||
return {
|
||||
score: 0,
|
||||
status: 'Safe',
|
||||
explanation: "No other submissions found for comparison.",
|
||||
similarSubmissions: []
|
||||
};
|
||||
}
|
||||
|
||||
// 🔥 SAFE MODE (NO AI)
|
||||
// Basic dummy comparison logic
|
||||
|
||||
let highestScore = 0;
|
||||
let similarSubmissions: any[] = [];
|
||||
|
||||
existingSubmissions.forEach(sub => {
|
||||
// simple similarity check (length + includes)
|
||||
let similarity = 0;
|
||||
|
||||
if (newCode.trim() === sub.code.trim()) {
|
||||
similarity = 90;
|
||||
} else if (newCode.includes(sub.code.substring(0, 20))) {
|
||||
similarity = 50;
|
||||
} else {
|
||||
similarity = 10;
|
||||
}
|
||||
|
||||
if (similarity > highestScore) {
|
||||
highestScore = similarity;
|
||||
}
|
||||
|
||||
if (similarity > 30) {
|
||||
similarSubmissions.push({
|
||||
studentName: sub.studentName,
|
||||
rollNumber: sub.rollNumber,
|
||||
similarityScore: similarity,
|
||||
reason: "Basic similarity detected (AI disabled mode)"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let status: 'Safe' | 'Needs Review' | 'High Similarity' = 'Safe';
|
||||
|
||||
if (highestScore >= 60) status = 'High Similarity';
|
||||
else if (highestScore >= 30) status = 'Needs Review';
|
||||
|
||||
return {
|
||||
score: highestScore,
|
||||
status,
|
||||
explanation: "Basic plagiarism check (AI disabled mode)",
|
||||
similarSubmissions
|
||||
};
|
||||
}
|
||||
6
code_arena/src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
79
code_arena/src/lib/vivaSimulator.ts
Normal file
@ -0,0 +1,79 @@
|
||||
// AI DISABLED VERSION (SAFE)
|
||||
|
||||
import { UserProgress, VivaQuestion, VivaEvaluation, VivaSession } from "../types";
|
||||
|
||||
// ------------------ DOCUMENT ANALYSIS ------------------
|
||||
export async function analyzeDocumentation(submission: any, language: string): Promise<any> {
|
||||
return {
|
||||
marks: {
|
||||
documentation: {
|
||||
objective: 4,
|
||||
concepts: 4,
|
||||
explanation: 4,
|
||||
learningOutcome: 4,
|
||||
total: 16
|
||||
},
|
||||
code: {
|
||||
logic: 4,
|
||||
output: 4,
|
||||
concepts: 4,
|
||||
total: 12
|
||||
}
|
||||
},
|
||||
aiFeedback: "Good effort. AI disabled mode."
|
||||
};
|
||||
}
|
||||
|
||||
// ------------------ VIVA QUESTIONS ------------------
|
||||
export async function generateVivaQuestions(
|
||||
progress: UserProgress,
|
||||
level: number,
|
||||
count: number = 5
|
||||
): Promise<VivaQuestion[]> {
|
||||
return [
|
||||
{
|
||||
id: "q1",
|
||||
text: "Explain your project.",
|
||||
type: "Conceptual"
|
||||
},
|
||||
{
|
||||
id: "q2",
|
||||
text: "What concepts did you use?",
|
||||
type: "Conceptual"
|
||||
},
|
||||
{
|
||||
id: "q3",
|
||||
text: "What is the output of your program?",
|
||||
type: "Output Prediction"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// ------------------ ANSWER EVALUATION ------------------
|
||||
export async function evaluateVivaAnswer(
|
||||
question: VivaQuestion,
|
||||
answer: string,
|
||||
language: string
|
||||
): Promise<VivaEvaluation> {
|
||||
return {
|
||||
isCorrect: true,
|
||||
feedback: {
|
||||
correct: "Good explanation",
|
||||
missing: "Could improve clarity",
|
||||
improvement: "Practice more"
|
||||
},
|
||||
qualitativeScore: "Good understanding"
|
||||
};
|
||||
}
|
||||
|
||||
// ------------------ SUMMARY ------------------
|
||||
export async function generateVivaSummary(
|
||||
session: Partial<VivaSession>
|
||||
): Promise<VivaSession['summary']> {
|
||||
return {
|
||||
overall: "Good performance",
|
||||
strengths: ["Basic understanding", "Effort"],
|
||||
improvements: ["More clarity needed"],
|
||||
finalQualitative: "Good"
|
||||
};
|
||||
}
|
||||
10
code_arena/src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import {StrictMode} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
1931
code_arena/src/types.ts
Normal file
26
code_arena/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
24
code_arena/vite.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import {defineConfig, loadEnv} from 'vite';
|
||||
|
||||
export default defineConfig(({mode}) => {
|
||||
const env = loadEnv(mode, '.', '');
|
||||
return {
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '.'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
// HMR is disabled in AI Studio via DISABLE_HMR env var.
|
||||
// Do not modifyâfile watching is disabled to prevent flickering during agent edits.
|
||||
hmr: process.env.DISABLE_HMR !== 'true',
|
||||
},
|
||||
};
|
||||
});
|
||||
39
coding.php
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RS Learning Lab - Coding Challenge</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background: #020617;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #0b8c9f;
|
||||
color: white;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: calc(100vh - 50px);
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
RS Learning Lab - Coding Challenge
|
||||
</div>
|
||||
|
||||
<iframe src="https://ai.studio/apps/d9855fda-dc07-40f0-9cd7-f15e32e565e5"></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
30
coding/attempt.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
if (!isset($_GET['track_id'], $_GET['challenge_no'])) {
|
||||
die('Invalid access');
|
||||
}
|
||||
|
||||
$track_id = (int)$_GET['track_id'];
|
||||
$challenge_no = (int)$_GET['challenge_no'];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Challenge <?= $challenge_no ?></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Challenge <?= $challenge_no ?></h2>
|
||||
|
||||
<p><b>Question:</b> Write code to complete this task.</p>
|
||||
|
||||
<form method="post" action="submit.php">
|
||||
<input type="hidden" name="track_id" value="<?= $track_id ?>">
|
||||
<input type="hidden" name="challenge_no" value="<?= $challenge_no ?>">
|
||||
|
||||
<input type="text" name="answer" placeholder="Your Answer" required>
|
||||
<br><br>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
53
coding/challenges.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['student_name'] = $_POST['student_name'];
|
||||
$_SESSION['class'] = $_POST['class'];
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Coding Challenges</title>
|
||||
<style>
|
||||
body { font-family: Arial; background:#f4f6f8; }
|
||||
.card {
|
||||
background:#fff;
|
||||
padding:20px;
|
||||
margin:20px auto;
|
||||
width:500px;
|
||||
border-radius:8px;
|
||||
}
|
||||
a {
|
||||
text-decoration:none;
|
||||
color:#0b8c9f;
|
||||
font-weight:bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2 style="text-align:center;">Coding Challenges</h2>
|
||||
|
||||
<?php
|
||||
$challenges = [
|
||||
1 => "Identify the Output",
|
||||
2 => "Logical Sequence",
|
||||
3 => "Condition Based Thinking",
|
||||
4 => "Pattern Recognition",
|
||||
5 => "Step-by-Step Logic"
|
||||
];
|
||||
|
||||
foreach ($challenges as $id => $title) {
|
||||
echo "
|
||||
<div class='card'>
|
||||
<h3>$title</h3>
|
||||
<a href='attempt.php?id=$id'>Start Challenge</a>
|
||||
</div>";
|
||||
}
|
||||
?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
44
coding/check_project_unlock.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once(__DIR__ . "/../config/db.php");
|
||||
|
||||
$student_id = $_SESSION['student_id'];
|
||||
$topic_id = $_GET['topic_id'];
|
||||
$class = $_GET['class'];
|
||||
|
||||
/* CHECK COMPLETED TRACKS */
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COUNT(*) as completed_tracks
|
||||
FROM student_progress
|
||||
WHERE student_id = ?
|
||||
AND topic_id = ?
|
||||
AND completed = 1
|
||||
");
|
||||
|
||||
$stmt->execute([$student_id, $topic_id]);
|
||||
$result = $stmt->fetch();
|
||||
|
||||
if ($result['completed_tracks'] == 3 && $class >= 11) {
|
||||
|
||||
/* CHECK ALREADY UNLOCKED */
|
||||
$check = $pdo->prepare("
|
||||
SELECT * FROM project_unlock
|
||||
WHERE student_id=? AND topic_id=?
|
||||
");
|
||||
$check->execute([$student_id, $topic_id]);
|
||||
|
||||
if ($check->rowCount() == 0) {
|
||||
|
||||
/* INSERT UNLOCK */
|
||||
$insert = $pdo->prepare("
|
||||
INSERT INTO project_unlock
|
||||
(student_id, topic_id, class, unlocked)
|
||||
VALUES (?, ?, ?, 1)
|
||||
");
|
||||
$insert->execute([$student_id, $topic_id, $class]);
|
||||
}
|
||||
|
||||
echo "unlocked";
|
||||
} else {
|
||||
echo "locked";
|
||||
}
|
||||
?>
|
||||
204
coding/mini_project.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
$name = $_SESSION['student_name'] ?? 'Student';
|
||||
$class = $_SESSION['student_class'] ?? '';
|
||||
$topic_id = $_GET['topic_id'] ?? 1;
|
||||
|
||||
/* 🔥 PROJECT QUESTIONS (TOPIC BASED) */
|
||||
$projects = [
|
||||
1 => [
|
||||
"title" => "Student Grade Calculator",
|
||||
"desc" => "Take 3 subject marks and print grade based on average.",
|
||||
"rules" => [
|
||||
"Take 3 inputs",
|
||||
"Find average",
|
||||
">=90 → A",
|
||||
">=75 → B",
|
||||
">=50 → C",
|
||||
"<50 → Fail"
|
||||
],
|
||||
"example" => "Input: 80 90 70\nOutput: Grade B"
|
||||
],
|
||||
2 => [
|
||||
"title" => "Even or Odd Analyzer",
|
||||
"desc" => "Take a number and print whether it is Even or Odd.",
|
||||
"rules" => [
|
||||
"Take input",
|
||||
"Use condition",
|
||||
"Print result"
|
||||
],
|
||||
"example" => "Input: 5\nOutput: Odd"
|
||||
]
|
||||
];
|
||||
|
||||
$project = $projects[$topic_id] ?? $projects[1];
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mini Project | RS Learning Lab</title>
|
||||
|
||||
<style>
|
||||
body{
|
||||
background: radial-gradient(circle at top,#020617,#0f172a);
|
||||
color:#e5e7eb;
|
||||
font-family:'Segoe UI', Arial;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
/* 🔥 HEADER */
|
||||
.header{
|
||||
width:85%;
|
||||
margin:20px auto;
|
||||
color:#94a3b8;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
.container{
|
||||
width:85%;
|
||||
margin:20px auto;
|
||||
display:flex;
|
||||
gap:30px;
|
||||
}
|
||||
|
||||
.left{
|
||||
width:42%;
|
||||
background: rgba(15,23,42,0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
padding:28px;
|
||||
border-radius:18px;
|
||||
box-shadow:0 0 25px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.right{
|
||||
width:58%;
|
||||
background: rgba(2,6,23,0.8);
|
||||
padding:28px;
|
||||
border-radius:18px;
|
||||
box-shadow:0 0 25px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
h2{
|
||||
margin-top:0;
|
||||
font-size:24px;
|
||||
}
|
||||
|
||||
textarea{
|
||||
width:100%;
|
||||
height:320px;
|
||||
background:#020617;
|
||||
color:#22c55e;
|
||||
border:1px solid #1f2937;
|
||||
padding:15px;
|
||||
border-radius:12px;
|
||||
font-family:monospace;
|
||||
font-size:14px;
|
||||
outline:none;
|
||||
}
|
||||
|
||||
textarea:focus{
|
||||
border:1px solid #22c55e;
|
||||
box-shadow:0 0 10px rgba(34,197,94,0.3);
|
||||
}
|
||||
|
||||
button{
|
||||
margin-top:20px;
|
||||
padding:14px 40px;
|
||||
border:none;
|
||||
border-radius:999px;
|
||||
font-weight:bold;
|
||||
font-size:15px;
|
||||
background:linear-gradient(135deg,#22c55e,#06b6d4);
|
||||
color:#020617;
|
||||
cursor:pointer;
|
||||
transition:0.3s;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
transform:scale(1.05);
|
||||
box-shadow:0 0 20px rgba(34,197,94,0.4);
|
||||
}
|
||||
|
||||
.rule{
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
/* 🔥 TIMER */
|
||||
.timer{
|
||||
color:#facc15;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 🔥 STUDENT HEADER -->
|
||||
<div class="header">
|
||||
👤 <?= htmlspecialchars($name) ?> | 🎓 Class <?= htmlspecialchars($class) ?>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- 🔥 LEFT SIDE -->
|
||||
<div class="left">
|
||||
<h2>🚀 <?= $project['title'] ?></h2>
|
||||
|
||||
<p><?= $project['desc'] ?></p>
|
||||
|
||||
<h3>📌 Requirements:</h3>
|
||||
<?php foreach($project['rules'] as $r): ?>
|
||||
<div class="rule">✔ <?= $r ?></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<h3>🧪 Example:</h3>
|
||||
<pre><?= $project['example'] ?></pre>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 🔥 RIGHT SIDE -->
|
||||
<div class="right">
|
||||
|
||||
<h2>💻 Write Your Code</h2>
|
||||
|
||||
<div class="timer" id="timer">
|
||||
⏳ Time Left: 10:00
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:10px;color:#22c55e;">
|
||||
💻 Language: Python
|
||||
</div>
|
||||
<input type="hidden" name="start_time" id="start_time">
|
||||
<form method="POST" action="/rs_lab/coding/submit_project.php">
|
||||
|
||||
<textarea name="code" placeholder="Write your code here..." required></textarea>
|
||||
|
||||
<input type="hidden" name="topic_id" value="<?= $topic_id ?>">
|
||||
|
||||
<button type="submit">🚀 Submit Project</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 🔥 TIMER SCRIPT -->
|
||||
<script>
|
||||
let time = 600;
|
||||
setInterval(()=>{
|
||||
let m = Math.floor(time/60);
|
||||
let s = time%60;
|
||||
document.getElementById("timer").innerText =
|
||||
"⏳ Time Left: " + m + ":" + (s<10?"0":"") + s;
|
||||
time--;
|
||||
},1000);
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById("start_time").value = Date.now();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
19
coding/submit.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once "../config.php";
|
||||
|
||||
$student_name = $_SESSION['student_name'];
|
||||
$class = $_SESSION['class'];
|
||||
$track_id = $_POST['track_id'];
|
||||
$challenge_no = $_POST['challenge_no'];
|
||||
|
||||
$stmt = $conn->prepare(
|
||||
"INSERT INTO coding_submissions
|
||||
(student_name, class, track_id, challenge_no)
|
||||
VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->bind_param("ssii", $student_name, $class, $track_id, $challenge_no);
|
||||
$stmt->execute();
|
||||
|
||||
header("Location: track_challenges.php?track_id=$track_id");
|
||||
exit;
|
||||
90
coding/submit_project.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once(__DIR__ . "/../config/db.php");
|
||||
|
||||
$name = $_SESSION['student_name'] ?? 'Student';
|
||||
$roll = $_SESSION['student_id'] ?? 'NA';
|
||||
$class = $_SESSION['student_class'] ?? '';
|
||||
|
||||
$code = $_POST['code'] ?? '';
|
||||
|
||||
/* 🔥 TIME TRACKING */
|
||||
$start_time = $_POST['start_time'] ?? round(microtime(true) * 1000);
|
||||
$end_time = round(microtime(true) * 1000);
|
||||
$time_taken = ($end_time - $start_time) / 1000; // seconds
|
||||
|
||||
$topic_id = $_POST['topic_id'] ?? 1;
|
||||
|
||||
if(strlen(trim($code)) < 20){
|
||||
die("❌ Write proper logic (too short)");
|
||||
}
|
||||
|
||||
/* 🔥 SMART CHECK */
|
||||
$passed = false;
|
||||
$score = 0;
|
||||
|
||||
if($topic_id == 1){
|
||||
if(strpos($code, 'if') !== false && strpos($code, 'average') !== false){
|
||||
$passed = true;
|
||||
$score = 90;
|
||||
} else {
|
||||
$score = 40;
|
||||
}
|
||||
}
|
||||
|
||||
/* 🔥 THINKING TYPE LOGIC */
|
||||
if($time_taken < 30){
|
||||
$thinking_type = "⚡ Quick Thinker";
|
||||
}
|
||||
elseif($time_taken > 120){
|
||||
$thinking_type = "🧠 Deep Thinker";
|
||||
}
|
||||
else{
|
||||
$thinking_type = "🚶 Balanced Learner";
|
||||
}
|
||||
|
||||
/* SAVE TO DB */
|
||||
$status = $passed ? 'approved' : 'rejected';
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO project_submissions
|
||||
(student_name, student_id, class, topic_id, score, status, thinking_type)
|
||||
VALUES (?,?,?,?,?,?,?)
|
||||
");
|
||||
|
||||
$stmt->execute([$name, $roll, $class, $topic_id, $score, $status, $thinking_type]);
|
||||
|
||||
/* RESULT UI */
|
||||
if($passed){
|
||||
echo "
|
||||
<div style='text-align:center;margin-top:100px'>
|
||||
<h2 style='color:#22c55e'>✅ Project Approved 🎉</h2>
|
||||
|
||||
<h3 style='color:#facc15'>$thinking_type</h3>
|
||||
|
||||
<p style='color:#94a3b8'>
|
||||
You solved this in ".round($time_taken)." seconds
|
||||
</p>
|
||||
|
||||
<a href='/rs_lab/student/leaderboard.php'
|
||||
style='display:inline-block;margin-top:20px;padding:12px 30px;
|
||||
background:#facc15;color:#020617;border-radius:999px;
|
||||
text-decoration:none;font-weight:bold'>
|
||||
🏆 View Leaderboard
|
||||
</a>
|
||||
</div>
|
||||
";
|
||||
}else{
|
||||
echo "
|
||||
<div style='text-align:center;margin-top:100px'>
|
||||
<h2 style='color:#f87171'>❌ Improve your logic</h2>
|
||||
|
||||
<h3 style='color:#38bdf8'>$thinking_type</h3>
|
||||
|
||||
<p style='color:#94a3b8'>
|
||||
Try again. You spent ".round($time_taken)." seconds
|
||||
</p>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
?>
|
||||
52
coding/track_challenges.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
include __DIR__ . '/../includes/header.php';
|
||||
|
||||
$track_id = $_GET['track_id'] ?? 0;
|
||||
|
||||
$trackNames = [
|
||||
1 => "Logic & Problem Solving",
|
||||
2 => "Python Foundations",
|
||||
3 => "Applied Thinking"
|
||||
];
|
||||
|
||||
$trackTitle = $trackNames[$track_id] ?? "Coding Challenges";
|
||||
?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<!-- Header -->
|
||||
<div style="margin-bottom:28px;">
|
||||
<h1 style="font-size:26px; font-weight:800; margin-bottom:6px;">
|
||||
<?php echo htmlspecialchars($trackTitle); ?>
|
||||
</h1>
|
||||
<p style="color:var(--muted); max-width:720px;">
|
||||
Complete the challenges in sequence to unlock participation
|
||||
recognition and skill validation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Challenges -->
|
||||
<div class="grid">
|
||||
|
||||
<?php for ($i = 1; $i <= 5; $i++): ?>
|
||||
<div class="card">
|
||||
<h3>Challenge <?php echo $i; ?></h3>
|
||||
<p>
|
||||
This challenge focuses on strengthening core concepts
|
||||
and applying logical reasoning step by step.
|
||||
</p>
|
||||
<div style="margin-top:14px;">
|
||||
<button class="btn btn-outline">Start Challenge</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
include __DIR__ . '/../includes/footer.php';
|
||||
?>
|
||||
71
coding/tracks.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
include __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<!-- Page Header -->
|
||||
<div style="margin-bottom:28px;">
|
||||
<h1 style="font-size:28px; font-weight:800; margin-bottom:6px;">
|
||||
Coding Challenge Tracks
|
||||
</h1>
|
||||
<p style="color:var(--muted); max-width:720px;">
|
||||
Professionally designed learning tracks to develop logical thinking,
|
||||
programming fundamentals, and real-world problem-solving skills.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tracks Grid -->
|
||||
<div class="grid">
|
||||
|
||||
<!-- Track 1 -->
|
||||
<div class="card feature-card">
|
||||
<h3>Logic & Problem Solving</h3>
|
||||
<p>
|
||||
Strengthen reasoning skills using puzzles, patterns,
|
||||
and step-by-step thinking exercises.
|
||||
</p>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="track_challenges.php?track_id=1" class="btn btn-primary">
|
||||
View Challenges
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Track 2 -->
|
||||
<div class="card feature-card">
|
||||
<h3>Python Foundations</h3>
|
||||
<p>
|
||||
Learn programming basics with beginner-friendly
|
||||
challenges focused on logic and clarity.
|
||||
</p>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="track_challenges.php?track_id=2" class="btn btn-primary">
|
||||
View Challenges
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Track 3 -->
|
||||
<div class="card feature-card">
|
||||
<h3>Applied Thinking</h3>
|
||||
<p>
|
||||
Apply concepts to simple real-life problems
|
||||
and scenario-based coding tasks.
|
||||
</p>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="track_challenges.php?track_id=3" class="btn btn-primary">
|
||||
View Challenges
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
include __DIR__ . '/../includes/footer.php';
|
||||
?>
|
||||
@ -1,46 +1,57 @@
|
||||
<?php
|
||||
session_start();
|
||||
include 'includes/header.php';
|
||||
require_once 'includes/pexels.php';
|
||||
// competitions.php
|
||||
// Ensure DB config loaded BEFORE using db()
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// Start session if not already (header will also try, but safe to ensure)
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// get PDO connection via helper
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query('SELECT * FROM competitions WHERE deleted_at IS NULL ORDER BY start_date DESC');
|
||||
$competitions = $stmt->fetchAll();
|
||||
|
||||
// fetch competitions - prepared statement, safe
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT id, title, description, start_date, end_date, registration_link FROM competitions ORDER BY start_date DESC");
|
||||
$stmt->execute();
|
||||
$competitions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $e) {
|
||||
// If query fails, show friendly message (we enabled display_errors during debug)
|
||||
die("Database query failed: " . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
|
||||
// include header (will not start a new session because header uses safe check)
|
||||
include __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container py-5">
|
||||
<h1 class="display-4 fw-bold">Coding Competitions</h1>
|
||||
<p class="fs-5 text-muted">Join our coding competitions and win exciting prizes.</p>
|
||||
<h2>Competitions</h2>
|
||||
|
||||
<div class="row">
|
||||
<?php if (count($competitions) > 0) : ?>
|
||||
<?php foreach ($competitions as $competition) : ?>
|
||||
<?php
|
||||
$image_url = 'https://via.placeholder.com/600x400'; // Default placeholder
|
||||
$query = preg_replace('/[^a-zA-Z0-9\s]/', '', $competition['title']);
|
||||
$pexels_data = pexels_get('https://api.pexels.com/v1/search?query=' . urlencode($query) . '&per_page=1');
|
||||
if ($pexels_data && !empty($pexels_data['photos'])) {
|
||||
$image_url = $pexels_data['photos'][0]['src']['large'];
|
||||
}
|
||||
?>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<img src="<?php echo $image_url; ?>" class="card-img-top" alt="<?php echo htmlspecialchars($competition['title']); ?>">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title"><a href="competition.php?id=<?php echo $competition['id']; ?>"><?php echo htmlspecialchars($competition['title']); ?></a></h5>
|
||||
<p class="card-text flex-grow-1"><?php echo htmlspecialchars(substr($competition['description'], 0, 100)); ?>...</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">Starts: <?php echo date('M d, Y', strtotime($competition['start_date'])); ?></small>
|
||||
<small class="text-muted">Ends: <?php echo date('M d, Y', strtotime($competition['end_date'])); ?></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<div class="alert alert-info">No competitions available yet. Please check back later.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (empty($competitions)): ?>
|
||||
<p>No competitions right now. Check back later.</p>
|
||||
<?php else: ?>
|
||||
<table style="width:100%; border-collapse:collapse;" border="1" cellpadding="8">
|
||||
<thead style="background:#0b8c9f; color:#fff;">
|
||||
<tr>
|
||||
<th style="text-align:left; width:30%;">Title</th>
|
||||
<th style="text-align:left; width:40%;">Description</th>
|
||||
<th style="text-align:center; width:15%;">Start</th>
|
||||
<th style="text-align:center; width:15%;">End</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($competitions as $c): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($c['title']); ?></td>
|
||||
<td><?php echo nl2br(htmlspecialchars($c['description'])); ?></td>
|
||||
<td style="text-align:center;"><?php echo htmlspecialchars(date('Y-m-d', strtotime($c['start_date']))); ?></td>
|
||||
<td style="text-align:center;"><?php echo htmlspecialchars(date('Y-m-d', strtotime($c['end_date']))); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php
|
||||
// optional footer close (if you want to keep layout)
|
||||
echo "</div></body></html>";
|
||||
|
||||
5
composer.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"dompdf/dompdf": "^3.1"
|
||||
}
|
||||
}
|
||||
307
composer.lock
generated
Normal file
@ -0,0 +1,307 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "00da199c24f69d4e39903c3676bfe248",
|
||||
"packages": [
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
"version": "v3.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/dompdf.git",
|
||||
"reference": "db712c90c5b9868df3600e64e68da62e78a34623"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623",
|
||||
"reference": "db712c90c5b9868df3600e64e68da62e78a34623",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dompdf/php-font-lib": "^1.0.0",
|
||||
"dompdf/php-svg-lib": "^1.0.0",
|
||||
"ext-dom": "*",
|
||||
"ext-mbstring": "*",
|
||||
"masterminds/html5": "^2.0",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"mockery/mockery": "^1.3",
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "Needed to process images",
|
||||
"ext-gmagick": "Improves image processing performance",
|
||||
"ext-imagick": "Improves image processing performance",
|
||||
"ext-zlib": "Needed for pdf stream compression"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dompdf\\": "src/"
|
||||
},
|
||||
"classmap": [
|
||||
"lib/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Dompdf Community",
|
||||
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
|
||||
"homepage": "https://github.com/dompdf/dompdf",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/dompdf/issues",
|
||||
"source": "https://github.com/dompdf/dompdf/tree/v3.1.4"
|
||||
},
|
||||
"time": "2025-10-29T12:43:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/php-font-lib",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-font-lib.git",
|
||||
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
|
||||
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FontLib\\": "src/FontLib"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The FontLib Community",
|
||||
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse, export and make subsets of different types of font files.",
|
||||
"homepage": "https://github.com/dompdf/php-font-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-font-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.1"
|
||||
},
|
||||
"time": "2024-12-02T14:37:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/php-svg-lib",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-svg-lib.git",
|
||||
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
|
||||
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabberworm/php-css-parser": "^8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Svg\\": "src/Svg"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The SvgLib Community",
|
||||
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse and export to PDF SVG files.",
|
||||
"homepage": "https://github.com/dompdf/php-svg-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-svg-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0"
|
||||
},
|
||||
"time": "2024-04-29T13:26:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
"version": "2.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Masterminds/html5-php.git",
|
||||
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
|
||||
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Masterminds\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matt Butcher",
|
||||
"email": "technosophos@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Matt Farina",
|
||||
"email": "matt@mattfarina.com"
|
||||
},
|
||||
{
|
||||
"name": "Asmir Mustafic",
|
||||
"email": "goetas@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "An HTML5 parser and serializer.",
|
||||
"homepage": "http://masterminds.github.io/html5-php",
|
||||
"keywords": [
|
||||
"HTML5",
|
||||
"dom",
|
||||
"html",
|
||||
"parser",
|
||||
"querypath",
|
||||
"serializer",
|
||||
"xml"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Masterminds/html5-php/issues",
|
||||
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
|
||||
},
|
||||
"time": "2025-07-25T09:04:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabberworm/php-css-parser",
|
||||
"version": "v8.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
||||
"reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9",
|
||||
"reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41",
|
||||
"rawr/cross-data-providers": "^2.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "9.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabberworm\\CSS\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Raphael Schweikert"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake.github@qzdesign.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "Parser for CSS Files written in PHP",
|
||||
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
|
||||
"keywords": [
|
||||
"css",
|
||||
"parser",
|
||||
"stylesheet"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
|
||||
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0"
|
||||
},
|
||||
"time": "2025-07-11T13:20:48+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
156
computer_quiz.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
// computer_quiz.php
|
||||
session_start();
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
// 1️⃣ Find active quiz
|
||||
$quiz = null;
|
||||
|
||||
$result = $conn->query(
|
||||
"SELECT * FROM computer_quizzes
|
||||
WHERE is_active = 1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
$quiz = $result->fetch_assoc();
|
||||
}
|
||||
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if (!$quiz) {
|
||||
$error = "Quiz not started yet.";
|
||||
} else {
|
||||
$name = trim($_POST['student_name']);
|
||||
$class = trim($_POST['class']);
|
||||
$roll = trim($_POST['roll_number']);
|
||||
|
||||
if ($name === '' || $class === '' || $roll === '') {
|
||||
$error = "All fields are required.";
|
||||
} else {
|
||||
|
||||
// Save attempt
|
||||
$stmt = $conn->prepare(
|
||||
"INSERT INTO computer_quiz_attempts
|
||||
(quiz_id, student_name, class, roll_number)
|
||||
VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->bind_param(
|
||||
"isss",
|
||||
$quiz['id'],
|
||||
$name,
|
||||
$class,
|
||||
$roll
|
||||
);
|
||||
$stmt->execute();
|
||||
$attempt_id = $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
// Store attempt in session
|
||||
$_SESSION['quiz_attempt_id'] = $attempt_id;
|
||||
$_SESSION['quiz_id'] = $quiz['id'];
|
||||
|
||||
header("Location: computer_quiz_questions.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Computer Quiz | RS Learning Lab</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
*{box-sizing:border-box;font-family:'Inter',system-ui}
|
||||
body{
|
||||
margin:0;
|
||||
background:#0b1220;
|
||||
color:#e5e7eb;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
min-height:100vh;
|
||||
}
|
||||
|
||||
.card{
|
||||
width:100%;
|
||||
max-width:420px;
|
||||
background:#0f172a;
|
||||
border:1px solid #1e293b;
|
||||
border-radius:18px;
|
||||
padding:32px;
|
||||
}
|
||||
h1{margin-top:0;font-size:22px}
|
||||
p{color:#94a3b8}
|
||||
|
||||
label{display:block;margin-top:18px}
|
||||
input{
|
||||
width:100%;
|
||||
margin-top:6px;
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
border:1px solid #1e293b;
|
||||
background:#020617;
|
||||
color:#e5e7eb;
|
||||
}
|
||||
|
||||
button{
|
||||
margin-top:26px;
|
||||
width:100%;
|
||||
padding:14px;
|
||||
border-radius:14px;
|
||||
border:0;
|
||||
background:#22d3ee;
|
||||
color:#001018;
|
||||
font-weight:700;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.error{
|
||||
margin-top:14px;
|
||||
color:#fca5a5;
|
||||
font-size:14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="card">
|
||||
|
||||
<h1>Learning Style Quiz</h1>
|
||||
|
||||
<?php if ($quiz): ?>
|
||||
<p>Please enter your details to begin.</p>
|
||||
<?php else: ?>
|
||||
<p>No active quiz at the moment.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($quiz): ?>
|
||||
<form method="post">
|
||||
<label>Student Name</label>
|
||||
<input name="student_name" required>
|
||||
|
||||
<label>Class</label>
|
||||
<input name="class" required>
|
||||
|
||||
<label>Roll Number</label>
|
||||
<input name="roll_number" required>
|
||||
|
||||
<button type="submit">Start Quiz</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
147
computer_quiz_questions.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
// computer_quiz_questions.php
|
||||
session_start();
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
// 🔐 Guard: must come from entry page
|
||||
if (!isset($_SESSION['quiz_attempt_id']) || !isset($_SESSION['quiz_id'])) {
|
||||
header("Location: computer_quiz.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$attemptId = $_SESSION['quiz_attempt_id'];
|
||||
|
||||
// 🔹 20 FIXED QUESTIONS (Learning-style based)
|
||||
$questions = [
|
||||
1 => "When learning something new, what helps you most?",
|
||||
2 => "If you don’t understand a topic, what do you do first?",
|
||||
3 => "You remember things best when you…",
|
||||
4 => "In exams, you usually prefer questions that are…",
|
||||
5 => "While studying, you are more comfortable with…",
|
||||
6 => "When solving problems, you usually…",
|
||||
7 => "You feel confident when learning involves…",
|
||||
8 => "During group study, you usually…",
|
||||
9 => "You understand faster when lessons include…",
|
||||
10 => "When instructions are given, you prefer them to be…",
|
||||
11 => "You feel bored quickly if learning is…",
|
||||
12 => "You prefer teachers who…",
|
||||
13 => "While revising, you usually…",
|
||||
14 => "You feel learning is effective when it is…",
|
||||
15 => "When facing a difficult question, you…",
|
||||
16 => "You like assignments that involve…",
|
||||
17 => "You stay focused when tasks are…",
|
||||
18 => "You learn better from mistakes when…",
|
||||
19 => "You feel confident if learning materials are…",
|
||||
20 => "Overall, you feel learning is best when it is…"
|
||||
];
|
||||
|
||||
// Options (same pattern for all)
|
||||
$options = [
|
||||
'A' => 'Using diagrams, images or videos',
|
||||
'B' => 'Understanding logic and steps',
|
||||
'C' => 'Practicing with real examples',
|
||||
'D' => 'Thinking deeply and quietly'
|
||||
];
|
||||
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
foreach ($questions as $qNo => $qText) {
|
||||
if (!isset($_POST['q'][$qNo])) {
|
||||
$error = "Please answer all questions.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($error === '') {
|
||||
|
||||
foreach ($_POST['q'] as $qNo => $answer) {
|
||||
$stmt = $conn->prepare(
|
||||
"INSERT INTO computer_quiz_answers
|
||||
(attempt_id, question_no, selected_option)
|
||||
VALUES (?, ?, ?)"
|
||||
);
|
||||
$stmt->bind_param("iis", $attemptId, $qNo, $answer);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
header("Location: generate_learning_pdf.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Learning Style Quiz</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
*{box-sizing:border-box;font-family:'Inter',system-ui}
|
||||
body{margin:0;background:#0b1220;color:#e5e7eb}
|
||||
.container{max-width:860px;margin:40px auto;padding:0 20px}
|
||||
h1{text-align:center;margin-bottom:26px}
|
||||
|
||||
.question{
|
||||
background:#0f172a;
|
||||
border:1px solid #1e293b;
|
||||
border-radius:16px;
|
||||
padding:22px;
|
||||
margin-bottom:18px;
|
||||
}
|
||||
.question h3{margin-top:0;font-size:16px}
|
||||
|
||||
.option{margin-top:10px}
|
||||
.option label{cursor:pointer}
|
||||
|
||||
button{
|
||||
margin-top:28px;
|
||||
width:100%;
|
||||
padding:16px;
|
||||
border-radius:16px;
|
||||
border:0;
|
||||
background:#22d3ee;
|
||||
color:#001018;
|
||||
font-weight:700;
|
||||
font-size:16px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.error{text-align:center;color:#fca5a5;margin-bottom:14px}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>Learning Style Assessment</h1>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post">
|
||||
<?php foreach ($questions as $no => $text): ?>
|
||||
<div class="question">
|
||||
<h3><?= $no ?>. <?= htmlspecialchars($text) ?></h3>
|
||||
<?php foreach ($options as $key => $label): ?>
|
||||
<div class="option">
|
||||
<label>
|
||||
<input type="radio" name="q[<?= $no ?>]" value="<?= $key ?>" required>
|
||||
<?= $key ?>. <?= htmlspecialchars($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<button type="submit">Submit Quiz</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
19
config.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// RS Learning Lab - single DB config
|
||||
|
||||
$host = "localhost";
|
||||
$user = "root";
|
||||
$pass = ""; // XAMPP default empty password
|
||||
$db = "rs_lab"; // IMPORTANT: your phpMyAdmin DB name
|
||||
|
||||
// Create connection
|
||||
$conn = new mysqli($host, $user, $pass, $db);
|
||||
|
||||
// Check connection
|
||||
if ($conn->connect_error) {
|
||||
die("DB Connection Failed: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
// Optional: show errors while developing
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
?>
|
||||
19
config/db.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
$host = "localhost";
|
||||
$db = "rs_lab";
|
||||
$user = "root";
|
||||
$pass = "";
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$host;dbname=$db;charset=utf8mb4",
|
||||
$user,
|
||||
$pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("Database connection failed: " . $e->getMessage());
|
||||
}
|
||||
29
dashboard.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['role'])) {
|
||||
echo "Unauthorized access";
|
||||
exit;
|
||||
}
|
||||
|
||||
switch ($_SESSION['role']) {
|
||||
case 'student':
|
||||
header("Location: /rs_lab/student/student_dashboard.php");
|
||||
break;
|
||||
|
||||
case 'teacher':
|
||||
header("Location: /rs_lab/teacher/dashboard.php");
|
||||
break;
|
||||
|
||||
case 'school':
|
||||
header("Location: /rs_lab/school/dashboard.php");
|
||||
break;
|
||||
|
||||
case 'institution':
|
||||
header("Location: /rs_lab/institution/dashboard.php");
|
||||
break;
|
||||
|
||||
default:
|
||||
echo "Invalid role";
|
||||
}
|
||||
exit;
|
||||
292
dashboard_learning_passport.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
// dashboard_learning_passport.php
|
||||
// RS Learning Lab – Student Dashboard with Momentum Card
|
||||
|
||||
/* ===============================
|
||||
DB CONFIG
|
||||
================================ */
|
||||
$db_host = "localhost";
|
||||
$db_name = "rs_lab";
|
||||
$db_user = "root";
|
||||
$db_pass = "";
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
|
||||
$db_user,
|
||||
$db_pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("DB connection failed");
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
INPUT
|
||||
================================ */
|
||||
$roll_no = $_GET['roll'] ?? null;
|
||||
|
||||
if (!$roll_no) {
|
||||
die("Student not specified");
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
FETCH LEARNING PROFILE
|
||||
================================ */
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT *
|
||||
FROM learning_profiles
|
||||
WHERE roll_no = :roll
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':roll' => $roll_no]);
|
||||
$profile = $stmt->fetch();
|
||||
|
||||
if (!$profile) {
|
||||
die("Learning profile not found");
|
||||
}
|
||||
|
||||
$style_scores = json_decode($profile['style_scores'], true) ?? [];
|
||||
$learning_signals = json_decode($profile['learning_signals'], true) ?? [];
|
||||
|
||||
/* ===============================
|
||||
FETCH MOMENTUM
|
||||
================================ */
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT momentum_score, momentum_state, updated_at
|
||||
FROM learning_momentum
|
||||
WHERE roll_no = :roll
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':roll' => $roll_no]);
|
||||
$momentum = $stmt->fetch();
|
||||
|
||||
$momentum_score = $momentum['momentum_score'] ?? 50;
|
||||
$momentum_state = $momentum['momentum_state'] ?? "Building";
|
||||
$momentum_updated = $momentum['updated_at'] ?? null;
|
||||
|
||||
/* ===============================
|
||||
MOMENTUM UI MAPPING
|
||||
================================ */
|
||||
$momentum_color = [
|
||||
"Applying" => "#22c55e",
|
||||
"Stabilizing" => "#38bdf8",
|
||||
"Building" => "#facc15",
|
||||
"Reset" => "#ef4444"
|
||||
];
|
||||
|
||||
$momentum_text = [
|
||||
"Applying" => "Applying concepts confidently",
|
||||
"Stabilizing" => "Understanding is settling steadily",
|
||||
"Building" => "Learning habit is forming",
|
||||
"Reset" => "Needs calm practice reinforcement"
|
||||
];
|
||||
|
||||
$bar_color = $momentum_color[$momentum_state] ?? "#38bdf8";
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Student Dashboard – RS Learning Lab</title>
|
||||
<style>
|
||||
body {
|
||||
background:#0b1020;
|
||||
color:#e5e7eb;
|
||||
font-family:Arial, sans-serif;
|
||||
padding:30px;
|
||||
}
|
||||
.container {
|
||||
max-width:1100px;
|
||||
margin:auto;
|
||||
}
|
||||
h1 {
|
||||
color:#38bdf8;
|
||||
}
|
||||
.grid {
|
||||
display:grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap:20px;
|
||||
}
|
||||
.card {
|
||||
background:#111827;
|
||||
padding:22px;
|
||||
border-radius:14px;
|
||||
}
|
||||
.label {
|
||||
color:#93c5fd;
|
||||
font-size:14px;
|
||||
}
|
||||
.value {
|
||||
font-size:16px;
|
||||
margin-bottom:8px;
|
||||
}
|
||||
.badge {
|
||||
display:inline-block;
|
||||
padding:6px 14px;
|
||||
border-radius:20px;
|
||||
background:#0b8c9f;
|
||||
color:#fff;
|
||||
font-size:14px;
|
||||
}
|
||||
.badge.secondary {
|
||||
background:#334155;
|
||||
}
|
||||
.badge.lei {
|
||||
background:#022c22;
|
||||
color:#6ee7b7;
|
||||
}
|
||||
.progress {
|
||||
height:10px;
|
||||
background:#020617;
|
||||
border-radius:8px;
|
||||
overflow:hidden;
|
||||
margin-top:10px;
|
||||
}
|
||||
.progress-fill {
|
||||
height:100%;
|
||||
}
|
||||
ul {
|
||||
padding-left:20px;
|
||||
}
|
||||
.footer-note {
|
||||
margin-top:30px;
|
||||
font-size:13px;
|
||||
color:#94a3b8;
|
||||
}
|
||||
.btn {
|
||||
display:inline-block;
|
||||
background:#0ea5e9;
|
||||
padding:10px 16px;
|
||||
border-radius:8px;
|
||||
color:#fff;
|
||||
text-decoration:none;
|
||||
font-size:14px;
|
||||
margin-top:15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>Student Learning Dashboard</h1>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<!-- STUDENT INFO -->
|
||||
<div class="card">
|
||||
<p class="label">Student Name</p>
|
||||
<p class="value"><?php echo htmlspecialchars($profile['student_name']); ?></p>
|
||||
|
||||
<p class="label">Roll No</p>
|
||||
<p class="value"><?php echo htmlspecialchars($profile['roll_no']); ?></p>
|
||||
|
||||
<p class="label">Class</p>
|
||||
<p class="value"><?php echo htmlspecialchars($profile['class']); ?></p>
|
||||
</div>
|
||||
|
||||
<!-- LEARNING STYLE -->
|
||||
<div class="card">
|
||||
<h3>Learning Style</h3>
|
||||
<span class="badge"><?php echo $profile['primary_style']; ?></span>
|
||||
<span class="badge secondary"><?php echo $profile['secondary_style']; ?></span>
|
||||
</div>
|
||||
|
||||
<!-- LEI -->
|
||||
<div class="card">
|
||||
<h3>Learning Effectiveness Index</h3>
|
||||
<span class="badge lei"><?php echo $profile['lei_signal']; ?></span>
|
||||
<p class="footer-note">
|
||||
LEI reflects effort, improvement, and consistency.
|
||||
It is not a score or rank.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 🚀 MOMENTUM CARD (NEW) -->
|
||||
<div class="card">
|
||||
<h3>Learning Momentum</h3>
|
||||
|
||||
<p>
|
||||
<strong><?php echo $momentum_state; ?></strong><br>
|
||||
<span class="label"><?php echo $momentum_text[$momentum_state]; ?></span>
|
||||
</p>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-fill"
|
||||
style="width: <?php echo $momentum_score; ?>%;
|
||||
background: <?php echo $bar_color; ?>;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="footer-note">
|
||||
Momentum shows persistence and recovery during learning.
|
||||
<?php if ($momentum_updated): ?>
|
||||
<br>Last updated: <?php echo date("d M Y, h:i A", strtotime($momentum_updated)); ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<a class="btn"
|
||||
href="momentum_timeline.php?roll=<?php echo urlencode($roll_no); ?>&name=<?php echo urlencode($profile['student_name']); ?>">
|
||||
View Momentum Timeline
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- LEARNING SIGNALS -->
|
||||
<div class="card" style="margin-top:20px;">
|
||||
<h3>Learning Signals</h3>
|
||||
<ul>
|
||||
<?php foreach ($learning_signals as $signal): ?>
|
||||
<li><?php echo htmlspecialchars($signal); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- STYLE PATTERN -->
|
||||
<div class="card" style="margin-top:20px;">
|
||||
<h3>Learning Pattern Overview</h3>
|
||||
<div class="grid">
|
||||
<?php foreach ($style_scores as $style => $score): ?>
|
||||
<div class="card">
|
||||
<strong><?php echo $style; ?></strong><br>
|
||||
<span><?php echo $score; ?> signal units</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PASSPORT -->
|
||||
<div class="card" style="margin-top:20px;">
|
||||
<h3>Learning Passport</h3>
|
||||
<p class="footer-note">
|
||||
This passport evolves as the student practices, reinforces,
|
||||
and applies learning over time.
|
||||
</p>
|
||||
|
||||
<a class="btn"
|
||||
href="learning_passport.php?
|
||||
name=<?php echo urlencode($profile['student_name']); ?>&
|
||||
roll=<?php echo urlencode($profile['roll_no']); ?>&
|
||||
class=<?php echo urlencode($profile['class']); ?>&
|
||||
primary=<?php echo urlencode($profile['primary_style']); ?>&
|
||||
secondary=<?php echo urlencode($profile['secondary_style']); ?>&
|
||||
lei=<?php echo urlencode($profile['lei_signal']); ?>&
|
||||
signals=<?php echo urlencode(implode(',', $learning_signals)); ?>">
|
||||
View Learning Passport PDF
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer-note">
|
||||
© 2026 RS Learning Lab · Learning · Momentum · Growth
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,17 +1,20 @@
|
||||
<?php
|
||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||
define('DB_HOST', '127.0.0.1');
|
||||
define('DB_NAME', 'app_36459');
|
||||
define('DB_USER', 'app_36459');
|
||||
define('DB_PASS', 'dcb62e1f-58c2-4988-8f8c-4094d3b743c0');
|
||||
// RS Learning Lab - basic DB config
|
||||
// NOTE: change db_name, username, password based on your XAMPP setup
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
return $pdo;
|
||||
$host = 'localhost';
|
||||
$db_name = 'rs_lab'; // create this DB in phpMyAdmin
|
||||
$db_user = 'root'; // default XAMPP user
|
||||
$db_pass = ''; // default XAMPP password (empty)
|
||||
|
||||
// Create connection
|
||||
$conn = new mysqli($host, $db_user, $db_pass, $db_name);
|
||||
|
||||
// Check connection
|
||||
if ($conn->connect_error) {
|
||||
die("Database connection failed: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
// Set charset
|
||||
$conn->set_charset("utf8mb4");
|
||||
?>
|
||||
|
||||
17
db/config.php.bak
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||
define('DB_HOST', '127.0.0.1');
|
||||
define('DB_NAME', 'app_36459');
|
||||
define('DB_USER', 'app_36459');
|
||||
define('DB_PASS', 'dcb62e1f-58c2-4988-8f8c-4094d3b743c0');
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
43
db/config.php.bak.txt
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// db/config.php
|
||||
// RS Learning Lab — database configuration for XAMPP (MySQL root, blank password)
|
||||
|
||||
// Database connection settings
|
||||
$DB_HOST = 'localhost';
|
||||
$DB_USER = 'root';
|
||||
$DB_PASS = '';
|
||||
$DB_NAME = 'rs_lab';
|
||||
$DB_PORT = 3306; // usually 3306
|
||||
|
||||
// Create MySQLi connection and check
|
||||
$mysqli = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_PORT);
|
||||
|
||||
// Check connection
|
||||
if ($mysqli->connect_errno) {
|
||||
// If you enabled display_errors (see below) you'll see this on the page.
|
||||
error_log("MySQL connection failed: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error);
|
||||
die("Database connection failed. Please check config.php settings.");
|
||||
}
|
||||
|
||||
// Set charset to UTF-8
|
||||
$mysqli->set_charset("utf8mb4");
|
||||
|
||||
// Optional: a PDO connection if any part of app expects PDO (safe to have both)
|
||||
try {
|
||||
$dsn = "mysql:host={$DB_HOST};dbname={$DB_NAME};port={$DB_PORT};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $DB_USER, $DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("PDO connection failed: " . $e->getMessage());
|
||||
// don't reveal raw error to users in production
|
||||
// but during debugging it's okay to show:
|
||||
// die("PDO DB connection failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// If your app expects a single $db or $conn variable, create alias:
|
||||
$db = $mysqli; // mysqli object
|
||||
$conn = $mysqli; // some files may use $conn
|
||||
|
||||
// End of config.php
|
||||
6
db/students.csv
Normal file
@ -0,0 +1,6 @@
|
||||
roll_number,student_name
|
||||
2173,Gokul Krishnan
|
||||
2174,Arun Kumar
|
||||
2175,Karthik Raja
|
||||
2176,Manoj Kumar
|
||||
2177,Suresh Babu
|
||||
|
21
db_config.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// db_config.php – Common DB connection for RS Learning Lab
|
||||
|
||||
$db_host = "localhost";
|
||||
$db_name = "rs_lab"; // ✅ CONFIRMED DB NAME
|
||||
$db_user = "root";
|
||||
$db_pass = "";
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
|
||||
$db_user,
|
||||
$db_pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("DB connection failed");
|
||||
}
|
||||
137
db_save_learning_profile.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
// db_save_learning_profile.php
|
||||
// RS Learning Lab – Save Learning Profile (Production Ready)
|
||||
|
||||
// ------------------------------------
|
||||
// DB CONFIG (CHANGE AS PER YOUR SETUP)
|
||||
// ------------------------------------
|
||||
$db_host = "localhost";
|
||||
$db_name = "rs_learning_lab";
|
||||
$db_user = "root";
|
||||
$db_pass = "";
|
||||
|
||||
// ------------------------------------
|
||||
// CONNECT TO DATABASE (PDO)
|
||||
// ------------------------------------
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
|
||||
$db_user,
|
||||
$db_pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Database connection failed"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// INPUT DATA (POST / SESSION / API)
|
||||
// ------------------------------------
|
||||
$student_name = $_POST['name'] ?? null;
|
||||
$roll_no = $_POST['roll'] ?? null;
|
||||
$class = $_POST['class'] ?? null;
|
||||
|
||||
$primary_style = $_POST['primary'] ?? null;
|
||||
$secondary_style = $_POST['secondary'] ?? null;
|
||||
|
||||
$lei_signal = $_POST['lei'] ?? "Baseline";
|
||||
|
||||
// Scores JSON (internal, no marks)
|
||||
$scores_json = $_POST['scores'] ?? null;
|
||||
|
||||
// Signals array → JSON
|
||||
$signals = $_POST['signals'] ?? [];
|
||||
$signals_json = json_encode($signals);
|
||||
|
||||
// ------------------------------------
|
||||
// BASIC VALIDATION
|
||||
// ------------------------------------
|
||||
if (
|
||||
!$student_name ||
|
||||
!$roll_no ||
|
||||
!$primary_style ||
|
||||
!$scores_json
|
||||
) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Missing required learning profile data"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// INSERT OR UPDATE LOGIC
|
||||
// ------------------------------------
|
||||
// One active learning profile per student
|
||||
$sql = "
|
||||
INSERT INTO learning_profiles
|
||||
(
|
||||
student_name,
|
||||
roll_no,
|
||||
class,
|
||||
primary_style,
|
||||
secondary_style,
|
||||
lei_signal,
|
||||
style_scores,
|
||||
learning_signals,
|
||||
created_at
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:student_name,
|
||||
:roll_no,
|
||||
:class,
|
||||
:primary_style,
|
||||
:secondary_style,
|
||||
:lei_signal,
|
||||
:style_scores,
|
||||
:learning_signals,
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
primary_style = VALUES(primary_style),
|
||||
secondary_style = VALUES(secondary_style),
|
||||
lei_signal = VALUES(lei_signal),
|
||||
style_scores = VALUES(style_scores),
|
||||
learning_signals = VALUES(learning_signals),
|
||||
updated_at = NOW()
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
// ------------------------------------
|
||||
// EXECUTE
|
||||
// ------------------------------------
|
||||
try {
|
||||
$stmt->execute([
|
||||
':student_name' => $student_name,
|
||||
':roll_no' => $roll_no,
|
||||
':class' => $class,
|
||||
':primary_style' => $primary_style,
|
||||
':secondary_style' => $secondary_style,
|
||||
':lei_signal' => $lei_signal,
|
||||
':style_scores' => $scores_json,
|
||||
':learning_signals'=> $signals_json
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Learning profile saved successfully"
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Failed to save learning profile"
|
||||
]);
|
||||
}
|
||||
17
deactivate_institution.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost", "root", "", "rs_lab");
|
||||
if ($conn->connect_error) {
|
||||
die("DB Connection failed");
|
||||
}
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = intval($_GET['id']);
|
||||
|
||||
$sql = "UPDATE institutions SET status='inactive' WHERE id=$id";
|
||||
$conn->query($sql);
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
|
||||
header("Location: admin_dashboard.php");
|
||||
exit();
|
||||
9
delete_institution.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost","root","","rs_lab");
|
||||
|
||||
$id = $_POST['id'];
|
||||
|
||||
$conn->query("UPDATE institutions SET deleted_at = NOW() WHERE id=$id");
|
||||
|
||||
header("Location: admin_dashboard.php");
|
||||
exit;
|
||||
57
export_teacher_excel.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// export_teacher_excel.php
|
||||
// RS Learning Lab – Teacher Dashboard Excel Export
|
||||
|
||||
$db_host = "localhost";
|
||||
$db_name = "rs_learning_lab";
|
||||
$db_user = "root";
|
||||
$db_pass = "";
|
||||
|
||||
$class = $_GET['class'] ?? '10-A';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
|
||||
$db_user,
|
||||
$db_pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("DB connection failed");
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
lp.roll_no,
|
||||
lp.student_name,
|
||||
lp.class,
|
||||
lm.momentum_state,
|
||||
lm.momentum_score,
|
||||
lm.updated_at
|
||||
FROM learning_profiles lp
|
||||
LEFT JOIN learning_momentum lm
|
||||
ON lp.roll_no = lm.roll_no
|
||||
WHERE lp.class = :class
|
||||
ORDER BY lp.roll_no ASC
|
||||
");
|
||||
$stmt->execute([':class' => $class]);
|
||||
$data = $stmt->fetchAll();
|
||||
|
||||
header("Content-Type: application/vnd.ms-excel");
|
||||
header("Content-Disposition: attachment; filename=Class_{$class}_Momentum_Report.xls");
|
||||
|
||||
echo "Roll No\tStudent Name\tClass\tMomentum State\tMomentum Score\tLast Activity\n";
|
||||
|
||||
foreach ($data as $row) {
|
||||
echo
|
||||
$row['roll_no'] . "\t" .
|
||||
$row['student_name'] . "\t" .
|
||||
$row['class'] . "\t" .
|
||||
($row['momentum_state'] ?? 'Building') . "\t" .
|
||||
($row['momentum_score'] ?? 50) . "\t" .
|
||||
($row['updated_at'] ?? '-') . "\n";
|
||||
}
|
||||
exit;
|
||||
76
export_teacher_pdf.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// export_teacher_pdf.php
|
||||
// RS Learning Lab – Teacher Dashboard PDF Export (wkhtmltopdf)
|
||||
|
||||
$db_host = "localhost";
|
||||
$db_name = "rs_learning_lab";
|
||||
$db_user = "root";
|
||||
$db_pass = "";
|
||||
|
||||
$class = $_GET['class'] ?? '10-A';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
|
||||
$db_user,
|
||||
$db_pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("DB connection failed");
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
lp.roll_no,
|
||||
lp.student_name,
|
||||
lm.momentum_state,
|
||||
lm.momentum_score
|
||||
FROM learning_profiles lp
|
||||
LEFT JOIN learning_momentum lm
|
||||
ON lp.roll_no = lm.roll_no
|
||||
WHERE lp.class = :class
|
||||
ORDER BY lp.roll_no ASC
|
||||
");
|
||||
$stmt->execute([':class' => $class]);
|
||||
$students = $stmt->fetchAll();
|
||||
|
||||
$html = "
|
||||
<h2>RS Learning Lab – Class Momentum Report</h2>
|
||||
<p>Class: {$class}</p>
|
||||
<table border='1' cellpadding='8' cellspacing='0' width='100%'>
|
||||
<tr>
|
||||
<th>Roll No</th>
|
||||
<th>Student Name</th>
|
||||
<th>Momentum State</th>
|
||||
<th>Momentum Score</th>
|
||||
</tr>
|
||||
";
|
||||
|
||||
foreach ($students as $s) {
|
||||
$html .= "
|
||||
<tr>
|
||||
<td>{$s['roll_no']}</td>
|
||||
<td>{$s['student_name']}</td>
|
||||
<td>".($s['momentum_state'] ?? 'Building')."</td>
|
||||
<td>".($s['momentum_score'] ?? 50)."</td>
|
||||
</tr>
|
||||
";
|
||||
}
|
||||
|
||||
$html .= "</table>";
|
||||
|
||||
$tmp = __DIR__ . "/teacher_report.html";
|
||||
$pdf = __DIR__ . "/Class_{$class}_Momentum_Report.pdf";
|
||||
|
||||
file_put_contents($tmp, $html);
|
||||
|
||||
shell_exec("wkhtmltopdf \"$tmp\" \"$pdf\"");
|
||||
|
||||
header("Content-Type: application/pdf");
|
||||
header("Content-Disposition: inline; filename=Class_Momentum_Report.pdf");
|
||||
readfile($pdf);
|
||||
exit;
|
||||
170
facilitated_assessment.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . "/config/db.php"; // 🔥 ADDED
|
||||
|
||||
$csvFile = __DIR__ . "/uploads/student_list.csv";
|
||||
|
||||
$students = [];
|
||||
$teacher = $_SESSION["teacher_name"] ?? "";
|
||||
$class = $_SESSION["class"] ?? "";
|
||||
$section = $_SESSION["section"] ?? "";
|
||||
|
||||
/* 🔥 FETCH PDF STATUS FROM DB */
|
||||
$stmt = $pdo->prepare("SELECT student_roll, pdf_path FROM learning_style_results");
|
||||
$stmt->execute();
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$pdfMap = [];
|
||||
foreach ($results as $row) {
|
||||
$pdfMap[$row['student_roll']] = $row['pdf_path'];
|
||||
}
|
||||
|
||||
if (file_exists($csvFile)) {
|
||||
if (($handle = fopen($csvFile, "r")) !== FALSE) {
|
||||
$header = fgetcsv($handle);
|
||||
|
||||
while (($row = fgetcsv($handle)) !== FALSE) {
|
||||
$students[] = [
|
||||
"roll" => $row[0],
|
||||
"name" => $row[1]
|
||||
];
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Assessment Session | RS Learning Lab</title>
|
||||
|
||||
<style>
|
||||
body{
|
||||
margin:0;
|
||||
background:radial-gradient(circle at top,#0f172a,#020617);
|
||||
font-family:Segoe UI,sans-serif;
|
||||
color:#e5e7eb;
|
||||
}
|
||||
.wrap{
|
||||
max-width:1100px;
|
||||
margin:60px auto;
|
||||
padding:20px;
|
||||
}
|
||||
h1{
|
||||
margin-bottom:6px;
|
||||
}
|
||||
.sub{
|
||||
opacity:.7;
|
||||
margin-bottom:30px;
|
||||
}
|
||||
table{
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
background:#020617;
|
||||
border-radius:14px;
|
||||
overflow:hidden;
|
||||
}
|
||||
th,td{
|
||||
padding:16px;
|
||||
border-bottom:1px solid #1f2937;
|
||||
}
|
||||
th{
|
||||
text-align:left;
|
||||
color:#9ca3af;
|
||||
font-size:14px;
|
||||
}
|
||||
tr:hover{
|
||||
background:#020617;
|
||||
}
|
||||
.btn{
|
||||
padding:8px 14px;
|
||||
background:#0ea5e9;
|
||||
color:#000;
|
||||
font-size:13px;
|
||||
border-radius:8px;
|
||||
text-decoration:none;
|
||||
font-weight:600;
|
||||
}
|
||||
.btn:hover{
|
||||
background:#38bdf8;
|
||||
}
|
||||
.success{
|
||||
color:#22c55e;
|
||||
font-weight:bold;
|
||||
}
|
||||
.empty{
|
||||
opacity:.6;
|
||||
padding:30px;
|
||||
text-align:center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="wrap">
|
||||
<h1>📋 Assessment Session</h1>
|
||||
<div class="sub">
|
||||
Class <?= htmlspecialchars($class) ?> ·
|
||||
Section <?= htmlspecialchars($section) ?> ·
|
||||
Facilitator: <?= htmlspecialchars($teacher) ?>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%">Roll No</th>
|
||||
<th>Student Name</th>
|
||||
<th width="25%">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<?php if (count($students) > 0): ?>
|
||||
<?php foreach ($students as $s): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($s["roll"]) ?></td>
|
||||
<td><?= htmlspecialchars($s["name"]) ?></td>
|
||||
<td>
|
||||
|
||||
<?php if(isset($pdfMap[$s['roll']]) && !empty($pdfMap[$s['roll']])): ?>
|
||||
|
||||
<span class="success">✅ PDF Generated</span><br><br>
|
||||
|
||||
<a href="<?= $pdfMap[$s['roll']] ?>" target="_blank" class="btn">
|
||||
View PDF
|
||||
</a>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<a href="omr_test.php?roll=<?= urlencode($s['roll']) ?>&name=<?= urlencode($s['name']) ?>" class="btn">
|
||||
Scan OMR
|
||||
</a>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="3" class="empty">
|
||||
No student list found. Please upload CSV to begin.
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<a href="bulk_download.php" class="btn">
|
||||
Download All Reports
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||