feat: teacer user

This commit is contained in:
BillieFaiqul 2025-05-16 08:05:09 +07:00
parent 5db88aa8c5
commit c7c2a6221d
13 changed files with 1481 additions and 62 deletions

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\NodeJS;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class NodeJSController extends Controller
{
public function index()
{
}
public function dashboardStudent() {
$topicCount = DB::table('projects')->count();
return view('dashboard_student', compact('topicCountt'));
}
public function dashboardTeacher() {
$topicCount = DB::table('projects')->count();
return view('dashboard_teacher', compact('topicCount'));
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\NodeJS\Teacher;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
public function index()
{
// Statistik yang sudah ada
$totalProyek = DB::table('projects')->count();
$totalSiswa = DB::table('users')
->where('role', 'student')
->count();
// Statistik baru
// 1. Hitung submission yang selesai (status 'completed' atau yang sesuai)
$submissionSelesai = DB::table('submissions')
->where('status', 'completed')
->count();
// 2. Hitung siswa yang telah mengirimkan minimal satu proyek
$siswaSubmisi = DB::table('submissions')
->join('users', 'users.id', '=', 'submissions.user_id')
->where('users.role', 'student')
->distinct('submissions.user_id')
->count('submissions.user_id');
// 3. Hitung total submission
$totalSubmisi = DB::table('submissions')->count();
// 4. Hitung submission yang gagal (status 'failed')
$submissionGagal = DB::table('submissions')
->where('status', 'failed')
->count();
// 5. Hitung tingkat keberhasilan (submission selesai vs total submission)
$tingkatKeberhasilan = $totalSubmisi > 0
? round(($submissionSelesai / $totalSubmisi) * 100)
: 0;
// 6. Hitung tingkat kegagalan (submission gagal vs total submission)
$tingkatKegagalan = $totalSubmisi > 0
? round(($submissionGagal / $totalSubmisi) * 100)
: 0;
// 7. Hitung proyek dengan minimal satu submission
$proyekDenganSubmisi = DB::table('submissions')
->distinct('project_id')
->count('project_id');
// 8. Hitung proyek tanpa submission
$proyekTanpaSubmisi = $totalProyek - $proyekDenganSubmisi;
// 9. Hitung persentase siswa yang telah membuat submission
$tingkatPartisipasiSiswa = $totalSiswa > 0
? round(($siswaSubmisi / $totalSiswa) * 100)
: 0;
// 10. Hitung submission yang sedang diproses
$submisiDalamProses = DB::table('submissions')
->where('status', 'processing')
->count();
// 11. Hitung submission yang tertunda
$submisiTertunda = DB::table('submissions')
->where('status', 'pending')
->count();
// 12. Hitung rata-rata percobaan per submission
$ratarataPecobaan = DB::table('submissions')
->avg('attempts');
$ratarataPecobaan = round($ratarataPecobaan, 1); // Bulatkan ke 1 angka desimal
return view('nodejs.dashboard-teacher.index', compact(
'totalProyek',
'totalSiswa',
'submissionSelesai',
'totalSubmisi',
'tingkatPartisipasiSiswa',
'submisiDalamProses',
'submisiTertunda',
'proyekDenganSubmisi',
'proyekTanpaSubmisi',
'siswaSubmisi',
'ratarataPecobaan',
'tingkatKeberhasilan',
'submissionGagal',
'tingkatKegagalan'
));
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace App\Http\Controllers\NodeJS\Teacher;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RankController extends Controller
{
/**
* Display the ranking page
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('nodejs.rank-teacher.index');
}
/**
* Get the ranking data for a specific project
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getRankingByProject(Request $request)
{
$projectId = $request->input('project_id');
if (!$projectId) {
return response()->json([
'status' => 'error',
'message' => 'Project ID is required'
], 400);
}
// Get all submissions for this project
$submissions = DB::table('submissions')
->join('users', 'submissions.user_id', '=', 'users.id')
->where('submissions.project_id', $projectId)
->select(
'submissions.id',
'submissions.user_id',
'users.name as user_name',
'submissions.attempts',
'submissions.results',
'submissions.updated_at'
)
->get();
// Process the submissions to calculate scores
$rankings = [];
$processedUsers = [];
foreach ($submissions as $submission) {
$userId = $submission->user_id;
// Parse the results JSON
$resultsData = json_decode($submission->results, true);
if (!$resultsData) {
continue; // Skip invalid results
}
// Calculate total tests and passed tests
$totalTests = 0;
$passedTests = 0;
foreach ($resultsData as $testSuite) {
if (isset($testSuite['testResults'])) {
foreach ($testSuite['testResults'] as $result) {
if (isset($result['totalTests']) && isset($result['passedTests'])) {
$totalTests += $result['totalTests'];
$passedTests += $result['passedTests'];
}
}
}
}
// Calculate percentage score
$score = $totalTests > 0 ? ($passedTests / $totalTests) * 100 : 0;
$score = round($score, 2); // Round to 2 decimal places
// If we already processed this user, only keep the higher score
if (isset($processedUsers[$userId])) {
if ($score > $processedUsers[$userId]['score']) {
$processedUsers[$userId] = [
'user_id' => $userId,
'user_name' => $submission->user_name,
'attempts' => $submission->attempts,
'score' => $score,
'total_tests' => $totalTests,
'passed_tests' => $passedTests,
'submission_date' => $submission->updated_at,
];
}
} else {
$processedUsers[$userId] = [
'user_id' => $userId,
'user_name' => $submission->user_name,
'attempts' => $submission->attempts,
'score' => $score,
'total_tests' => $totalTests,
'passed_tests' => $passedTests,
'submission_date' => $submission->updated_at,
];
}
}
// Convert to array and sort by score (descending)
$rankings = array_values($processedUsers);
usort($rankings, function($a, $b) {
if ($a['score'] == $b['score']) {
// If scores are equal, sort by submission date (earlier is better)
return strtotime($a['submission_date']) - strtotime($b['submission_date']);
}
return $b['score'] - $a['score'];
});
// Add rank information
$rank = 1;
foreach ($rankings as $key => $value) {
$rankings[$key]['rank'] = $rank++;
}
return response()->json([
'status' => 'success',
'data' => [
'rankings' => $rankings
]
]);
}
/**
* Get all projects for the ranking dropdown
*
* @return \Illuminate\Http\JsonResponse
*/
public function getProjects()
{
$projects = DB::table('projects')
->select('id', 'title') // Alias 'title' as 'name' to match frontend expectations
->orderBy('title')
->get();
return response()->json([
'status' => 'success',
'data' => [
'projects' => $projects
]
]);
}
/**
* Export ranking data to CSV
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function exportRanking(Request $request)
{
$projectId = $request->input('project_id');
if (!$projectId) {
return response()->json([
'status' => 'error',
'message' => 'Project ID is required'
], 400);
}
// Get project name
$project = DB::table('projects')
->where('id', $projectId)
->first();
if (!$project) {
return response()->json([
'status' => 'error',
'message' => 'Project not found'
], 404);
}
// Get rankings data by calling the existing method
$rankingResponse = $this->getRankingByProject($request);
$content = json_decode($rankingResponse->getContent(), true);
if (!isset($content['data']['rankings'])) {
return response()->json([
'status' => 'error',
'message' => 'No ranking data available'
], 404);
}
$rankings = $content['data']['rankings'];
$filename = 'ranking_' . \Illuminate\Support\Str::slug($project->title) . '_' . date('Y-m-d') . '.csv';
// Create CSV headers
$headers = [
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=$filename",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
];
// Create the CSV file
$callback = function() use ($rankings) {
$file = fopen('php://output', 'w');
// Add CSV header row
fputcsv($file, ['Rank', 'Name', 'Attempts', 'Score (%)', 'Passed Tests', 'Total Tests', 'Submission Date']);
// Add data rows
foreach ($rankings as $ranking) {
fputcsv($file, [
$ranking['rank'],
$ranking['user_name'],
$ranking['attempts'],
$ranking['score'],
$ranking['passed_tests'],
$ranking['total_tests'],
$ranking['submission_date']
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@ -18,7 +18,8 @@ class TeacherMiddleware
public function handle(Request $request, Closure $next): Response
{
if ( Auth::user()->teacher !== "teacher" ) {
if ( Auth::user()->role !== "teacher" ) {
abort(403, "Unauthorized action.");
}

41
package-lock.json generated
View File

@ -4,6 +4,11 @@
"requires": true,
"packages": {
"": {
"dependencies": {
"alpinejs": "^3.14.9",
"select2": "^4.1.0-rc.0",
"tailwindcss": "^4.1.7"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
@ -362,6 +367,30 @@
"node": ">=12"
}
},
"node_modules/@vue/reactivity": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.1.5"
}
},
"node_modules/@vue/shared": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"license": "MIT"
},
"node_modules/alpinejs": {
"version": "3.14.9",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.9.tgz",
"integrity": "sha512-gqSOhTEyryU9FhviNqiHBHzgjkvtukq9tevew29fTj+ofZtfsYriw4zPirHHOAy9bw8QoL3WGhyk7QqCh5AYlw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "~3.1.1"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -608,6 +637,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/select2": {
"version": "4.1.0-rc.0",
"resolved": "https://registry.npmjs.org/select2/-/select2-4.1.0-rc.0.tgz",
"integrity": "sha512-Hr9TdhyHCZUtwznEH2CBf7967mEM0idtJ5nMtjvk3Up5tPukOLXbHUNmh10oRfeNIhj+3GD3niu+g6sVK+gK0A==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
@ -617,6 +652,12 @@
"node": ">=0.10.0"
}
},
"node_modules/tailwindcss": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz",
"integrity": "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==",
"license": "MIT"
},
"node_modules/vite": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",

View File

@ -9,5 +9,10 @@
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"vite": "^4.0.0"
},
"dependencies": {
"alpinejs": "^3.14.9",
"select2": "^4.1.0-rc.0",
"tailwindcss": "^4.1.7"
}
}

View File

@ -116,7 +116,7 @@
</div>
</div>
<div style="margin-top: auto;">
<a href="/nodejs" class="btn btn-primary">Start Learning</a>
<a href="/nodejs/dashboard" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>

View File

@ -0,0 +1,413 @@
<!doctype html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<link href="style.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<title>iCLOP</title>
<link rel="icon" href={{asset("./images/logo.png")}} type="image/png">
</head>
<body>
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg" style="background-color: #FEFEFE;">
<div class="container-fluid">
<!-- <a class="navbar-brand" href="#">Navbar</a> -->
<img src={{asset("./images/logo.png")}} alt="logo" width="104" height="65">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="mx-auto">
<ul class="navbar-nav mb-2 mb-lg-0 justify-content-center">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Dashboard Teacher</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Tutorials</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Contact Us</a>
</li>
</ul>
</div>
<div class="dropdown">
<p style="margin-top: 10px; margin-right: 10px;">{{auth()->user()->name}}
<img src="{{ asset('./images/Group.png') }}" alt="Group" style="height: 50px; margin-right: 10px;">
<i class="fas fa-chevron-down" style="color: #0079FF;"></i>
<div class="dropdown-content" id="dropdownContent">
<form id="logout-form" action="{{ route('logoutt') }}" method="POST">
@csrf
<a href="#" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">Logout</a>
</form>
</div>
</p>
</div>
<!-- <button class="btn btn-primary custom-button-sign-up" onclick="window.location.href='register.html'">Sign Up</button> -->
</div>
</div>
</nav>
@auth
<!-- CONTENT -->
<div class="container" style="margin-top: 70px; justify-content: center; align-items: center;">
<p style="font-size: 22px;">Choose your<br><span style="font-size: 35px; font-weight: 600; color: #34364A;">Learning Materials</span></p>
<!-- CARD 1 -->
<!-- <div class="row" style="margin-top: 45px; display: flex; justify-content: center; align-items: center;"> -->
<div class="row" style="margin-top: 45px;">
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src={{asset("./images/cards/Android.png")}} class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Android programming
with Java and Kotlin</h5>
<div class="row align-items-start">
<div class="col-1">
<img src={{asset("./images/book.png")}} style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="/android23/topic" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src={{asset("./images/cards/Flutter.png")}} class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Mobile programming with Flutter</h5>
<div class="row align-items-start">
<div class="col-1">
<img src={{asset("./images/book.png")}} style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="/flutter/start" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height:375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src={{asset("./images/cards/Node.js.png")}} class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Web application with Node.JS</h5>
<div class="row align-items-start">
<div class="col-1">
<img src={{asset("./images/book.png ")}} style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>{{ $topicCount ?? '0' }} learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('dashboard.nodejs.teacher') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
</div>
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- CARD 2 -->
<!-- <div class="row" style="margin-top: 45px; display: flex; justify-content: center; align-items: center;"> -->
<div class="row" style="margin-top: 45px;">
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/Python.png")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Python programming</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('learning_student') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/MySQL.png")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">SQL Querying with MySQL</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('learning_student') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/PostgreSQL.png")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">SQL Querying with PostgreSQL</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('learning_student') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
</div>
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- CARD 3 -->
<!-- <div class="row" style="margin-top: 45px; display: flex; justify-content: center; align-items: center;"> -->
<div class="row" style="margin-top: 45px;">
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/Network.png ")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Network programming with Java</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('learning_student') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/Unity.png")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Game programming with Unity</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('learning_student') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/Data analytic.png ")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Data Analytics with Python</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('learning_student') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 45px;">
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/DB.png ")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Database with PHP Programming</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('welcome') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/React.jpg")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Learn React JS</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>6 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('react_welcome') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div>
<!-- <div class="card p-0" style="width: 305px; height: 375px; margin-left: 25px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
<img src="{{asset("./images/cards/DB.png ")}}" class="card-img-top" style="width: auto; height: 200px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Database Programming with PHP</h5>
<div class="row align-items-start">
<div class="col-1">
<img src="{{asset("./images/book.png ")}}" style="width: 13px; height: 16px;">
</div>
<div class="col">
<p>18 learning topics</p>
</div>
</div>
<div style="margin-top: auto;">
<a href="{{ route('welcome') }}" class="btn btn-primary">Start Learning</a>
</div>
</div>
</div> -->
</div>
</div>
<!-- FOOTER -->
<div class="container text-align" style="margin-top: 100px; background-color: #FAFAFA; padding-right: 50px; padding-left: 50px; height: 300px;">
<div class="row">
<div class="col">
<div class="container" style="margin-top: 40px;">
<img src="{{asset("./images/logo.png ")}}" alt="rocket" style="height: 60px;">
<p style="font-size: 16px; color: #636363;">Intelligent Computer Assisted<br>Programming Learning Platform.</p>
</div>
<div class="container">
<i class="fab fa-instagram fa-lg" style="padding-right: 2px; color: #636363;"></i>
<i class="fab fa-github fa-lg" style="padding-right: 2px; color: #636363;"></i>
<i class="fab fa-linkedin fa-lg" style="padding-right: 2px; color: #636363;"></i>
<i class="fab fa-youtube fa-lg" style="padding-right: 2px; color: #636363;"></i>
</div>
</div>
<div class="col">
<div class="container">
<p style="font-size: 22px; font-weight: 600; color: #34364A; margin-top: 40px; margin-left: 55px;">Company</p>
<p style="font-size: 16px; color: #636363; margin-left: 55px;">Privacy Policy</p>
</div>
</div>
<div class="col">
<div class="container">
<p style="font-size: 22px; font-weight: 600; color: #34364A; margin-top: 40px;">Contact Info</p>
<div class="row align-items-start">
<div class="col-1">
<i class="fas fa-map-marker-alt fa-lg" style="color: #636363; margin-top: 5px;"></i>
</div>
<div class="col">
<p style="font-size: 16px; color: #636363;">Jl. Candi Mendut, RT.02/RW.08, Mojolangu, Kec. Lowokwaru, Kota Malang, Jawa Timur 65142</p>
</div>
</div>
<div class="row align-items-start">
<div class="col-1">
<i class="fas fa-envelope" style="color: #636363; margin-top: 5px;"></i>
</div>
<div class="col">
<p style="font-size: 16px; color: #636363;">qulispolinema.ac.id (Email)</p>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<p style="font-size: 16px; color: #636363; text-align: center; margin-top: 16px; margin-bottom: 16px;">© 2023 iCLOP. All rights reserved</p>
</div>
</div>
</div>
@endauth
</body>
<script src="script.js"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script>
$(document).ready(function() {
$("#dropdownContainer").click(function() {
$("#dropdownContainer").toggleClass("active");
});
$("#dropdownContent").click(function(e) {
e.stopPropagation();
});
$(document).click(function() {
$("#dropdownContainer").removeClass("active");
});
});
</script>
<style>
.dropdown {
position: relative;
display: inline-block;
cursor: pointer;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #fff;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
border-radius: 5px;
overflow: hidden;
transition: 0.3s;
opacity: 0;
transform: translateY(-10px);
}
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
transition: 0.3s;
}
.dropdown-content a:hover {
background-color: #f1f1f1;
}
.dropdown:hover .dropdown-content {
display: block;
opacity: 1;
transform: translateY(0);
}
.dropdown.active .dropdown-content {
display: block;
opacity: 1;
transform: translateY(0);
}
</style>
</html>

View File

@ -0,0 +1,341 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Teacher Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 pb-12">
<!-- Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-16">
<!-- Total Projects -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-indigo-500 bg-opacity-75">
<i class="fas fa-project-diagram text-white text-2xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Projects</p>
<p class="text-lg font-semibold">{{ $totalProyek }}</p>
</div>
</div>
</div>
</div>
<!-- Total Students -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-500 bg-opacity-75">
<i class="fas fa-users text-white text-2xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Students</p>
<p class="text-lg font-semibold">{{ $totalSiswa }}</p>
</div>
</div>
</div>
</div>
<!-- Total Submissions -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-500 bg-opacity-75">
<i class="fas fa-file-code text-white text-2xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Submissions</p>
<p class="text-lg font-semibold">{{ $totalSubmisi }}</p>
</div>
</div>
</div>
</div>
<!-- Completed Submissions -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-500 bg-opacity-75">
<i class="fas fa-check-circle text-white text-2xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Completed Submissions</p>
<p class="text-lg font-semibold">{{ $submissionSelesai }}</p>
</div>
</div>
</div>
</div>
<!-- Failed Submissions -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-500 bg-opacity-75">
<i class="fas fa-times-circle text-white text-2xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Failed Submissions</p>
<p class="text-lg font-semibold">{{ $submissionGagal }}</p>
</div>
</div>
</div>
</div>
<!-- Average Submissions -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-500 bg-opacity-75">
<i class="fas fa-calculator text-white text-2xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Average Submissions Attempts</p>
<p class="text-lg font-semibold">{{ $ratarataPecobaan }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-8 mt-4">
<!-- Submission Status Chart -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Submission Status</h3>
<div class="relative h-64">
<canvas id="submissionStatusChart"></canvas>
</div>
</div>
</div>
<!-- Project Distribution Chart -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Projects with vs without Submissions</h3>
<div class="relative h-64">
<canvas id="projectDistributionChart"></canvas>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4 mb-8 mt-4">
<!-- Student Participation Rate -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Student Participation</h3>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Active Students</p>
<p class="text-2xl font-bold">{{ $siswaSubmisi }} / {{ $totalSiswa }}</p>
</div>
<div class="relative w-24 h-24">
<canvas id="participationChart"></canvas>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-lg font-bold text-gray-900 dark:text-gray-100">{{ $tingkatPartisipasiSiswa }}%</span>
</div>
</div>
</div>
</div>
</div>
<!-- Success Rate -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Submission Success Rate</h3>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Completed Submissions</p>
<p class="text-2xl font-bold">{{ $submissionSelesai }} / {{ $totalSubmisi }}</p>
</div>
<div class="relative w-24 h-24">
<canvas id="successRateChart"></canvas>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-lg font-bold text-gray-900 dark:text-gray-100">{{ $tingkatKeberhasilan ?? 0 }}%</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Detailed Statistics -->
<div class="grid grid-cols-1 gap-4 mb-8 mt-4 w-full">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg w-full">
<div class="p-6 w-full">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Detailed Statistics</h3>
<div class="overflow-x-auto w-full">
<table class="w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead>
<tr>
<th class="px-6 py-3 bg-gray-50 dark:bg-gray-700 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Metric</th>
<th class="px-6 py-3 bg-gray-50 dark:bg-gray-700 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Value</th>
<th class="px-6 py-3 bg-gray-50 dark:bg-gray-700 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Description</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">Projects with Submissions</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{ $proyekDenganSubmisi }}</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">Number of projects that have at least one submission</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">Projects without Submissions</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{ $proyekTanpaSubmisi }}</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">Number of projects that have no submissions yet</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">Processing Submissions</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{ $submisiDalamProses }}</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">Number of submissions currently being processed</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">Pending Submissions</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{ $submisiTertunda }}</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">Number of submissions awaiting action</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
@section('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Submission Status Chart
const submissionStatusCtx = document.getElementById('submissionStatusChart').getContext('2d');
const submissionStatusChart = new Chart(submissionStatusCtx, {
type: 'bar',
data: {
labels: ['Completed', 'Processing', 'Pending', 'Failed'], // Tambah label Failed
datasets: [{
label: 'Submission Status',
data: [{{ $submissionSelesai }}, {{ $submisiDalamProses }}, {{ $submisiTertunda }}, {{ $submissionGagal }}], // Tambah data failed
backgroundColor: [
'rgba(72, 187, 120, 0.7)', // Green for completed
'rgba(66, 153, 225, 0.7)', // Blue for processing
'rgba(237, 137, 54, 0.7)', // Orange for pending
'rgba(220, 53, 69, 0.7)' // Red for failed
],
borderColor: [
'rgba(72, 187, 120, 1)',
'rgba(66, 153, 225, 1)',
'rgba(237, 137, 54, 1)',
'rgba(220, 53, 69, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
// Project Distribution Chart
const projectDistributionCtx = document.getElementById('projectDistributionChart').getContext('2d');
const projectDistributionChart = new Chart(projectDistributionCtx, {
type: 'pie',
data: {
labels: ['Projects with Submissions', 'Projects without Submissions'],
datasets: [{
data: [{{ $proyekDenganSubmisi }}, {{ $proyekTanpaSubmisi }}],
backgroundColor: [
'rgba(79, 209, 197, 0.7)', // Teal
'rgba(159, 122, 234, 0.7)' // Purple
],
borderColor: [
'rgba(79, 209, 197, 1)',
'rgba(159, 122, 234, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
// Participation Rate Chart
const participationCtx = document.getElementById('participationChart').getContext('2d');
const participationChart = new Chart(participationCtx, {
type: 'doughnut',
data: {
labels: ['Active', 'Inactive'],
datasets: [{
data: [{{ $siswaSubmisi }}, {{ $totalSiswa - $siswaSubmisi }}],
backgroundColor: [
'rgba(104, 211, 145, 0.7)', // Green
'rgba(226, 232, 240, 0.7)' // Light gray
],
borderColor: [
'rgba(104, 211, 145, 1)',
'rgba(226, 232, 240, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
cutout: '70%',
plugins: {
legend: {
display: false
}
}
}
});
// Success Rate Chart
const successRateCtx = document.getElementById('successRateChart').getContext('2d');
const successRateChart = new Chart(successRateCtx, {
type: 'doughnut',
data: {
labels: ['Completed', 'Incomplete'],
datasets: [{
data: [{{ $submissionSelesai }}, {{ $totalSubmisi - $submissionSelesai }}],
backgroundColor: [
'rgba(72, 187, 120, 0.7)', // Green
'rgba(226, 232, 240, 0.7)' // Light gray
],
borderColor: [
'rgba(72, 187, 120, 1)',
'rgba(226, 232, 240, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
cutout: '70%',
plugins: {
legend: {
display: false
}
}
}
});
</script>
@endsection
</x-app-layout>

View File

@ -1,40 +1,48 @@
<nav x-data="{ open: false }" class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="flex justify-between items-center h-16">
<!-- Left side: Logo and Navigation Links -->
<div class="flex items-center">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard.nodejs') }}">
<a href="#">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('dashboard.nodejs')" :active="request()->routeIs('dashboard*')">
@if (Auth::user()->role === 'student')
<x-nav-link :href="route('dashboard.nodejs')" :active="request()->routeIs('dashboard-student')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link :href="route('projects')" :active="request()->routeIs('projects*')">
<x-nav-link :href="route('projects')" :active="request()->routeIs('projects.student')">
{{ __('Projects') }}
</x-nav-link>
<x-nav-link :href="route('submissions')" :active="request()->routeIs('submissions*')">
<x-nav-link :href="route('submissions')" :active="request()->routeIs('submissions.student')">
{{ __('Submissions') }}
</x-nav-link>
@elseif (Auth::user()->role === 'teacher')
<x-nav-link :href="route('dashboard.nodejs.teacher')" :active="request()->routeIs('dashboard-teacher')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link :href="route('nodejs.teacher.rank')" :active="request()->routeIs('nodejs.teacher.rank')">
{{ __('Rank') }}
</x-nav-link>
@endif
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<!-- Right side: User Dropdown -->
<div class="hidden sm:flex sm:items-center ml-auto">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" />
@ -44,24 +52,33 @@ class="inline-flex items-center px-3 py-2 border border-transparent text-sm lead
</x-slot>
<x-slot name="content">
{{-- Profile Link (Bisa ditampilkan untuk semua role) --}}
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
<!-- Authentication -->
{{-- Authentication / Logout Berdasarkan Role --}}
@if (Auth::user()->role === 'student')
<form method="GET" action="{{ route('dashboard-student') }}">
@csrf
<x-dropdown-link :href="route('dashboard-student')" onclick="event.preventDefault();
this.closest('form').submit();">
<x-dropdown-link href="{{ route('dashboard-student') }}" onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
@elseif (Auth::user()->role === 'teacher')
<form method="GET" action="{{ route('dashboard-teacher') }}">
@csrf
<x-dropdown-link href="{{ route('dashboard-teacher') }}" onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
@endif
</x-slot>
</x-dropdown>
</div>
<!-- Hamburger -->
<!-- Hamburger (Mobile) -->
<div class="-mr-2 flex items-center sm:hidden">
<button @click="open = ! open"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
@ -69,8 +86,9 @@ class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
@ -80,6 +98,7 @@ class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
@if (Auth::user()->role === 'student')
<x-responsive-nav-link :href="route('dashboard.nodejs')" :active="request()->routeIs('dashboard*')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
@ -89,6 +108,14 @@ class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark
<x-responsive-nav-link :href="route('submissions')" :active="request()->routeIs('submissions*')">
{{ __('Submissions') }}
</x-responsive-nav-link>
@elseif (Auth::user()->role === 'teacher')
<x-responsive-nav-link :href="route('dashboard.nodejs.teacher')" :active="request()->routeIs('dashboard*')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('nodejs.teacher.rank')" :active="request()->routeIs('nodejs.teacher.rank')">
{{ __('Rank') }}
</x-responsive-nav-link>
@endif
</div>
<!-- Responsive Settings Options -->
@ -103,15 +130,21 @@ class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark
{{ __('Profile') }}
</x-responsive-nav-link>
<!-- Authentication -->
@if (Auth::user()->role === 'student')
<form method="GET" action="{{ route('dashboard-student') }}">
@csrf
<x-responsive-nav-link :href="route('dashboard-student')" onclick="event.preventDefault();
this.closest('form').submit();">
<x-responsive-nav-link :href="route('dashboard-student')" onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</form>
@elseif (Auth::user()->role === 'teacher')
<form method="GET" action="{{ route('dashboard-teacher') }}">
@csrf
<x-responsive-nav-link :href="route('dashboard-teacher')" onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</form>
@endif
</div>
</div>
</div>

View File

@ -0,0 +1,212 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Student Rankings') }}
</h2>
</x-slot>
<div class="py-6">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8">
<div class=" dark:bg-slate-800 overflow-hidden shadow-xl sm:rounded-lg border dark:border-gray-700">
<!-- Header with project selector -->
<div class="flex flex-col md:flex-row justify-between items-center dark:bg-slate-900 p-4 border-b border-gray-200 dark:border-gray-700">
<span class="text-xl font-bold text-gray-800 dark:text-white mb-4 md:mb-0">Student Rankings</span>
<div class="w-full md:w-96">
<select id="project-selector" class= "bg-gray-50 border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-secondary-500 focus:border-secondary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-secondary-500 dark:focus:border-secondary-500">
<option value="">Select Project</option>
</select>
</div>
</div>
<div class="p-6">
<!-- Loading indicator -->
<div id="loading" class="text-center my-8 hidden">
<div class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
<p class="mt-4 text-gray-600 dark:text-gray-400">Loading data...</p>
</div>
<!-- No project selected message -->
<div id="no-project-selected" class="text-center my-12">
<svg class="mx-auto h-16 w-16 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
<p class="mt-4 text-lg font-medium text-gray-600 dark:text-gray-400">Please select a project to view rankings</p>
</div>
<!-- Rankings data container -->
<div id="ranking-data" class="hidden">
<!-- Export button -->
<div class="flex justify-end mb-5">
<button id="export-csv" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Export to CSV
</button>
</div>
<!-- Rankings table -->
<div class="overflow-x-auto dark:bg-slate-800 rounded-lg shadow-md">
<table class="w-full table-fixed divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-300">
<tr>
<th scope="col" class="w-1/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Rank</th>
<th scope="col" class="w-3/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Name</th>
<th scope="col" class="w-1/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Attempts</th>
<th scope="col" class="w-1/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Score</th>
<th scope="col" class="w-1/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Passed</th>
<th scope="col" class="w-1/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Total</th>
<th scope="col" class="w-2/12 px-6 py-4 text-left text-xs font-bold text-gray-600 dark:text-white uppercase tracking-wider">Last Submission</th>
</tr>
</thead>
<tbody id="ranking-table-body" class="divide-y divide-gray-200 dark:divide-gray-700">
<!-- Rankings will be populated here -->
</tbody>
</table>
</div>
</div>
<!-- No data message -->
<div id="no-data" class="text-center my-12 hidden">
<svg class="mx-auto h-16 w-16 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M12 14h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="mt-4 text-lg font-medium text-gray-600 dark:text-gray-400">No submissions found for this project</p>
</div>
</div>
</div>
</div>
</div>
@section('scripts')
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
$(document).ready(function() {
// Load projects for dropdown
loadProjects();
// Project selection change event
$('#project-selector').change(function() {
const projectId = $(this).val();
if (projectId) {
loadRankings(projectId);
} else {
$('#ranking-data').hide();
$('#no-project-selected').show();
$('#no-data').hide();
}
});
// Export to CSV button click event
$('#export-csv').click(function() {
const projectId = $('#project-selector').val();
if (projectId) {
window.location.href = `{{ route('nodejs.teacher.rank.export') }}?project_id=${projectId}`;
}
});
});
function loadProjects() {
$.ajax({
url: "{{ route('nodejs.teacher.rank.projects') }}",
type: "GET",
beforeSend: function() {
$('#loading').show();
},
success: function(response) {
if (response.status === 'success') {
const projects = response.data.projects;
let options = '<option value="">Pilih Proyek</option>';
projects.forEach(project => {
options += `<option value="${project.id}">${project.title}</option>`;
});
$('#project-selector').html(options);
}
},
error: function(xhr) {
console.error('Error loading projects', xhr);
alert('Gagal memuat proyek. Silakan coba lagi.');
},
complete: function() {
$('#loading').hide();
}
});
}
function loadRankings(projectId) {
$.ajax({
url: "{{ route('nodejs.teacher.rank.by-project') }}",
type: "GET",
data: {
project_id: projectId
},
beforeSend: function() {
$('#ranking-data').hide();
$('#no-project-selected').hide();
$('#no-data').hide();
$('#loading').show();
},
success: function(response) {
if (response.status === 'success') {
const rankings = response.data.rankings;
if (rankings.length > 0) {
populateRankingsTable(rankings);
$('#ranking-data').show();
} else {
$('#no-data').show();
}
}
},
error: function(xhr) {
console.error('Error loading rankings', xhr);
alert('Gagal memuat peringkat. Silakan coba lagi.');
},
complete: function() {
$('#loading').hide();
}
});
}
function populateRankingsTable(rankings) {
let tableRows = '';
rankings.forEach(student => {
const submissionDate = new Date(student.submission_date).toLocaleString();
// Adding special class for top 3 performers with improved contrast
let rowClass = '';
if (student.rank <= 3) {
rowClass = 'top-performer';
}
tableRows += `
<tr class="${rowClass}">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
${student.rank <= 3 ?
`<span class="inline-flex items-center justify-center w-6 h-6 rounded-full ${
student.rank === 1 ? 'bg-yellow-400 gold-medal' :
student.rank === 2 ? 'bg-gray-300 silver-medal' :
'bg-amber-600 bronze-medal'} text-white font-bold">
${student.rank}
</span>` :
student.rank
}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">${student.user_name}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">${student.attempts}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white font-medium">${student.score}%</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">${student.passed_tests}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">${student.total_tests}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">${submissionDate}</td>
</tr>
`;
});
$('#ranking-table-body').html(tableRows);
}
</script>
@endsection
</x-app-layout>

View File

@ -5,6 +5,8 @@
use App\Http\Controllers\NodeJS\Student\SubmissionController;
use App\Http\Controllers\NodeJS\Student\WelcomeController;
use App\Http\Controllers\NodeJS\Student\ProfileController;
use App\Http\Controllers\NodeJS\Teacher\DashboardController as TeacherDashboardController;
use App\Http\Controllers\NodeJS\Teacher\RankController;
use Illuminate\Support\Facades\Route;
@ -14,11 +16,14 @@
Route::middleware('auth')->group(function () {
// Dashboard
Route::get('/dashboard/teacher', [TeacherDashboardController::class, 'index'])->name('dashboard.nodejs.teacher');
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard.nodejs');
// Profile
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// Projects
Route::prefix('projects')->controller(ProjectController::class)->group(function () {
Route::get('/', 'index')->name('projects');
@ -26,6 +31,7 @@
Route::get('/project/{project_id}/download', 'download')->name('projects.download');
Route::get('pdf', 'showPDF')->name('projects.pdf');
});
// Submissions
Route::prefix('submissions')->group(function () {
// show all the submission based on the projects
@ -57,5 +63,17 @@
// update source code based on the submission id
Route::post('/update/submission', [SubmissionController::class, 'update'])->name('submissions.update');
});
// Teacher Rankings
Route::prefix('teacher/rank')->controller(RankController::class)->group(function () {
// Display ranking page
Route::get('/', 'index')->name('nodejs.teacher.rank');
// Get ranking data for a specific project
Route::get('/by-project', 'getRankingByProject')->name('nodejs.teacher.rank.by-project');
// Get all projects for the ranking dropdown
Route::get('/projects', 'getProjects')->name('nodejs.teacher.rank.projects');
// Export ranking data to CSV
Route::get('/export', 'exportRanking')->name('nodejs.teacher.rank.export');
});
});
});

View File

@ -4,6 +4,7 @@
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\DataController;
use App\Http\Controllers\NodeJS\NodeJSController;
use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\SocialController;
@ -50,13 +51,13 @@
// Route::group(["prefix" => 'test', 'middleware' => ['login'], 'as' => 'test.'], function(){
Route::get('/dashboard-student', function () {
return view('dashboard_student');
})->name('dashboard-student')->middleware('auth');
Route::get('/dashboard_teacher', [NodeJSController::class, 'dashboardTeacher'])
->name('dashboard-teacher')
->middleware('auth');
Route::get('/dashboard_teacher', function () {
return view('dashboard_student');
})->name('dashboard-teacher')->middleware('auth');
Route::get('/dashboard-student', [NodeJSController::class, 'dashboardStudent'])
->name('dashboard-student')
->middleware('auth');
Route::get('/learning-student', function () {
return view('learning_student');