diff --git a/app/Http/Controllers/NodeJS/NodeJSController.php b/app/Http/Controllers/NodeJS/NodeJSController.php new file mode 100644 index 0000000..a6ab6c4 --- /dev/null +++ b/app/Http/Controllers/NodeJS/NodeJSController.php @@ -0,0 +1,26 @@ +count(); + return view('dashboard_student', compact('topicCountt')); + } + + public function dashboardTeacher() { + $topicCount = DB::table('projects')->count(); + return view('dashboard_teacher', compact('topicCount')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/NodeJS/Teacher/DashboardController.php b/app/Http/Controllers/NodeJS/Teacher/DashboardController.php new file mode 100644 index 0000000..abd3997 --- /dev/null +++ b/app/Http/Controllers/NodeJS/Teacher/DashboardController.php @@ -0,0 +1,95 @@ +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' + )); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/NodeJS/Teacher/RankController.php b/app/Http/Controllers/NodeJS/Teacher/RankController.php new file mode 100644 index 0000000..e88471c --- /dev/null +++ b/app/Http/Controllers/NodeJS/Teacher/RankController.php @@ -0,0 +1,233 @@ +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); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/TeacherMiddleware.php b/app/Http/Middleware/TeacherMiddleware.php index abe57c5..b8c8abe 100644 --- a/app/Http/Middleware/TeacherMiddleware.php +++ b/app/Http/Middleware/TeacherMiddleware.php @@ -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."); } diff --git a/package-lock.json b/package-lock.json index bb0c951..bbed483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index e543e0d..bc981bc 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/resources/views/dashboard_student.blade.php b/resources/views/dashboard_student.blade.php index 2890a8f..2962705 100644 --- a/resources/views/dashboard_student.blade.php +++ b/resources/views/dashboard_student.blade.php @@ -116,7 +116,7 @@
- Start Learning + Start Learning
diff --git a/resources/views/dashboard_teacher.blade.php b/resources/views/dashboard_teacher.blade.php new file mode 100644 index 0000000..7dcf3c9 --- /dev/null +++ b/resources/views/dashboard_teacher.blade.php @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + iCLOP + + + + + + +@auth + + +
+

Choose your
Learning Materials

+ + + +
+
+ +
+
Android programming + with Java and Kotlin
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+
Mobile programming with Flutter
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+
Web application with Node.JS
+
+
+ +
+
+

{{ $topicCount ?? '0' }} learning topics

+
+
+
+ Start Learning +
+
+
+
+ + + + +
+
+ +
+
Python programming
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+
SQL Querying with MySQL
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+
SQL Querying with PostgreSQL
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+
+ + + + +
+
+ +
+
Network programming with Java
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+
Game programming with Unity
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+
Data Analytics with Python
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+
+
+
+ +
+
Database with PHP Programming
+
+
+ +
+
+

18 learning topics

+
+
+
+ Start Learning +
+
+
+
+ +
+
Learn React JS
+
+
+ +
+
+

6 learning topics

+
+
+
+ Start Learning +
+
+
+ +
+ +
+ + +
+
+
+
+ rocket +

Intelligent Computer Assisted
Programming Learning Platform.

+
+
+ + + + +
+
+ +
+
+

Company

+

Privacy Policy

+
+
+ +
+
+ +

Contact Info

+
+
+ +
+
+

Jl. Candi Mendut, RT.02/RW.08, Mojolangu, Kec. Lowokwaru, Kota Malang, Jawa Timur 65142

+
+
+ +
+
+ +
+
+

qulis@polinema.ac.id (Email)

+
+
+ +
+
+ +
+

© 2023 iCLOP. All rights reserved

+
+
+ + @endauth + + + + + + + + + + diff --git a/resources/views/nodejs/dashboard-teacher/index.blade.php b/resources/views/nodejs/dashboard-teacher/index.blade.php new file mode 100644 index 0000000..8a8d822 --- /dev/null +++ b/resources/views/nodejs/dashboard-teacher/index.blade.php @@ -0,0 +1,341 @@ + + +

+ {{ __('Teacher Dashboard') }} +

+
+ +
+
+ +
+ +
+
+
+
+ +
+
+

Total Projects

+

{{ $totalProyek }}

+
+
+
+
+ + +
+
+
+
+ +
+
+

Total Students

+

{{ $totalSiswa }}

+
+
+
+
+ + +
+
+
+
+ +
+
+

Total Submissions

+

{{ $totalSubmisi }}

+
+
+
+
+ + +
+
+
+
+ +
+
+

Completed Submissions

+

{{ $submissionSelesai }}

+
+
+
+
+ + +
+
+
+
+ +
+
+

Failed Submissions

+

{{ $submissionGagal }}

+
+
+
+
+ + +
+
+
+
+ +
+
+

Average Submissions Attempts

+

{{ $ratarataPecobaan }}

+
+
+
+
+
+ + +
+ +
+
+

Submission Status

+
+ +
+
+
+ + +
+
+

Projects with vs without Submissions

+
+ +
+
+
+
+ +
+ +
+
+

Student Participation

+
+
+

Active Students

+

{{ $siswaSubmisi }} / {{ $totalSiswa }}

+
+
+ +
+ {{ $tingkatPartisipasiSiswa }}% +
+
+
+
+
+ + +
+
+

Submission Success Rate

+
+
+

Completed Submissions

+

{{ $submissionSelesai }} / {{ $totalSubmisi }}

+
+
+ +
+ {{ $tingkatKeberhasilan ?? 0 }}% +
+
+
+
+
+
+ + + +
+
+
+

Detailed Statistics

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValueDescription
Projects with Submissions{{ $proyekDenganSubmisi }}Number of projects that have at least one submission
Projects without Submissions{{ $proyekTanpaSubmisi }}Number of projects that have no submissions yet
Processing Submissions{{ $submisiDalamProses }}Number of submissions currently being processed
Pending Submissions{{ $submisiTertunda }}Number of submissions awaiting action
+
+
+
+
+
+
+ + @section('scripts') + + + @endsection +
\ No newline at end of file diff --git a/resources/views/nodejs/layouts/navigation.blade.php b/resources/views/nodejs/layouts/navigation.blade.php index 6b3a8f8..e192b9c 100644 --- a/resources/views/nodejs/layouts/navigation.blade.php +++ b/resources/views/nodejs/layouts/navigation.blade.php @@ -1,76 +1,94 @@