diff --git a/app/Http/Controllers/Siswa/DashboardController.php b/app/Http/Controllers/Siswa/DashboardController.php index dc33d7a..ac35bb0 100644 --- a/app/Http/Controllers/Siswa/DashboardController.php +++ b/app/Http/Controllers/Siswa/DashboardController.php @@ -6,18 +6,15 @@ use Illuminate\Support\Facades\Auth; use Carbon\Carbon; -// Sesuaikan nama model-model ini dengan yang kamu punya use App\Models\Tugas; -use App\Models\TugasSiswa; // tabel pengumpulan tugas +use App\Models\PengumpulanTugas; use App\Models\Challenge; -use App\Models\ChallengeSiswa; // tabel progress challenge siswa +use App\Models\PesertaChallenge; +use App\Models\Leaderboard; use App\Models\Siswa; class DashboardController extends Controller { - /** - * Pastikan hanya siswa yang sudah login yang bisa akses. - */ public function __construct() { $this->middleware('auth:siswa'); @@ -29,72 +26,83 @@ public function index() $siswa = Auth::guard('siswa')->user(); // ============================================= - // 1. TUGAS — ambil tugas yang belum dikumpulkan - // dan deadline-nya belum lewat, urutkan by deadline + // 1. TUGAS — yang belum dikumpulkan siswa ini + // dan deadline belum lewat // ============================================= - $tugasRaw = Tugas::with('mataPelajaran') // eager load relasi mapel - ->whereDoesntHave('pengumpulan', function ($q) use ($siswa) { - // tugas yang BELUM dikumpulkan oleh siswa ini - $q->where('siswa_id', $siswa->id); - }) + + // id_tugas yang sudah dikumpulkan siswa ini + $sudahDikumpulkan = PengumpulanTugas::where('id_siswa', $siswa->id_siswa) + ->pluck('id_tugas'); + + // Tugas untuk kelas siswa yang belum dikumpulkan + $tugasRaw = Tugas::with(['mengajar.mapel']) + ->whereNotIn('id_tugas', $sudahDikumpulkan) ->where('deadline', '>=', Carbon::now()) + ->whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); + }) ->orderBy('deadline', 'asc') - ->take(5) // tampilkan maks 5 tugas di dashboard + ->take(5) ->get(); - // Kelompokkan tugas berdasarkan tanggal deadline + // Kelompokkan per tanggal deadline $tugasList = []; foreach ($tugasRaw as $tugas) { $tgl = Carbon::parse($tugas->deadline) ->locale('id') - ->isoFormat('dddd, D MMMM YYYY'); // contoh: "Sabtu, 10 Mei 2025" + ->isoFormat('dddd, D MMMM YYYY'); + + $namaMapel = optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-'; $tugasList[$tgl][] = [ 'jam' => Carbon::parse($tugas->deadline)->format('H.i'), - 'nama' => $tugas->judul, - // sesuaikan nama kolom dengan model kamu - 'mapel' => 'Belum · ' . ($tugas->mataPelajaran->nama ?? '-'), + 'nama' => $tugas->judul_tugas, + 'mapel' => 'Belum · ' . $namaMapel, ]; } // ============================================= // 2. CHALLENGE MINGGUAN // ============================================= - $startMingguIni = Carbon::now()->startOfWeek(); - $endMingguIni = Carbon::now()->endOfWeek(); + $startMinggu = Carbon::now()->startOfWeek(); + $endMinggu = Carbon::now()->endOfWeek(); - // Total challenge aktif minggu ini - $challengeTotal = Challenge::whereBetween('created_at', [$startMingguIni, $endMingguIni]) - ->where('is_active', true) + // Challenge untuk kelas siswa yang masih aktif minggu ini + $challengeTotal = Challenge::whereHas('kelas', function ($q) use ($siswa) { + $q->where('challenge_kelas.id_kelas', $siswa->id_kelas); + }) + ->where('tenggat_waktu', '>=', Carbon::now()) + ->whereBetween('created_at', [$startMinggu, $endMinggu]) ->count(); - // Challenge yang sudah diselesaikan siswa minggu ini - $challengeDone = ChallengeSiswa::where('siswa_id', $siswa->id) + // Challenge yang sudah selesai dikerjakan siswa minggu ini + $challengeDone = PesertaChallenge::where('id_siswa', $siswa->id_siswa) ->where('status', 'selesai') - ->whereBetween('created_at', [$startMingguIni, $endMingguIni]) + ->whereBetween('waktu_submit', [$startMinggu, $endMinggu]) ->count(); // ============================================= - // 3. TUGAS SELESAI MINGGU INI (untuk speech bubble mascot) + // 3. TUGAS SELESAI MINGGU INI (mascot speech bubble) // ============================================= - $tugasSelesai = TugasSiswa::where('siswa_id', $siswa->id) - ->whereBetween('created_at', [$startMingguIni, $endMingguIni]) + $tugasSelesai = PengumpulanTugas::where('id_siswa', $siswa->id_siswa) + ->whereIn('status', ['dikumpulkan', 'terlambat']) + ->whereBetween('tanggal_submit', [$startMinggu, $endMinggu]) ->count(); // ============================================= - // 4. LEADERBOARD — top 3 siswa berdasarkan total EXP - // Asumsi: kolom 'exp' ada di tabel siswa - // Sesuaikan jika EXP disimpan di tabel lain + // 4. LEADERBOARD — top 3 kelas siswa yang login // ============================================= - $leaderboardRaw = Siswa::orderBy('exp', 'desc') + $leaderboardRaw = Leaderboard::with('siswa') + ->where('id_kelas', $siswa->id_kelas) + ->orderBy('total_exp', 'desc') ->take(3) ->get(); $leaderboard = $leaderboardRaw->map(function ($item, $index) { return [ 'rank' => $index + 1, - 'nama' => $item->nama, - 'exp' => $item->exp, + 'nama' => optional($item->siswa)->nama ?? '-', + 'exp' => $item->total_exp, ]; })->toArray(); diff --git a/app/Http/Controllers/Siswa/MateriController.php b/app/Http/Controllers/Siswa/MateriController.php new file mode 100644 index 0000000..748748d --- /dev/null +++ b/app/Http/Controllers/Siswa/MateriController.php @@ -0,0 +1,59 @@ +middleware('auth:siswa'); + } + + /** + * Halaman daftar mata pelajaran siswa + */ + public function index() + { + $siswa = Auth::guard('siswa')->user(); + + // Ambil semua mapel yang diajarkan di kelas siswa ini + $mapelList = Mengajar::with(['mapel', 'guru']) + ->where('id_kelas', $siswa->id_kelas) + ->get() + ->map(function ($mengajar) { + return [ + 'id_mengajar' => $mengajar->id_mengajar, + 'nama_mapel' => optional($mengajar->mapel)->nama_mapel ?? '-', + 'nama_guru' => optional($mengajar->guru)->nama ?? '-', + 'jumlah_materi' => Materi::where('id_mengajar', $mengajar->id_mengajar)->count(), + ]; + }); + + return view('siswa.materi.index', compact('mapelList')); + } + + /** + * Halaman daftar materi per mata pelajaran + */ + public function show($id_mengajar) + { + $siswa = Auth::guard('siswa')->user(); + + // Pastikan mengajar ini memang untuk kelas siswa + $mengajar = Mengajar::with(['mapel', 'guru']) + ->where('id_mengajar', $id_mengajar) + ->where('id_kelas', $siswa->id_kelas) + ->firstOrFail(); + + $materiList = Materi::where('id_mengajar', $id_mengajar) + ->orderBy('tanggal_upload', 'desc') + ->get(); + + return view('siswa.materi.show', compact('mengajar', 'materiList')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Siswa/TugasController.php b/app/Http/Controllers/Siswa/TugasController.php new file mode 100644 index 0000000..67463cf --- /dev/null +++ b/app/Http/Controllers/Siswa/TugasController.php @@ -0,0 +1,152 @@ +middleware('auth:siswa'); + } + + /** + * Daftar semua tugas untuk kelas siswa + */ + public function index() + { + $siswa = Auth::guard('siswa')->user(); + + // Ambil semua tugas untuk kelas siswa + $semuaTugas = Tugas::with(['mengajar.mapel', 'mengajar.guru']) + ->whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); + }) + ->orderBy('deadline', 'asc') + ->get(); + + // Tandai status tiap tugas untuk siswa ini + $tugasList = $semuaTugas->map(function ($tugas) use ($siswa) { + $pengumpulan = PengumpulanTugas::where('id_tugas', $tugas->id_tugas) + ->where('id_siswa', $siswa->id_siswa) + ->first(); + + $now = Carbon::now(); + $deadline = Carbon::parse($tugas->deadline); + + if ($pengumpulan) { + $status = $pengumpulan->status; // 'dikumpulkan' atau 'terlambat' + } else { + $status = $now->greaterThan($deadline) ? 'terlambat' : 'belum'; + } + + return [ + 'id_tugas' => $tugas->id_tugas, + 'judul' => $tugas->judul_tugas, + 'keterangan' => $tugas->keterangan, + 'deadline' => $deadline, + 'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-', + 'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-', + 'status' => $status, + 'sudah_kumpul'=> !is_null($pengumpulan), + 'lampiran' => $pengumpulan?->lampiran_tugas, + 'exp' => $pengumpulan?->exp ?? 0, + ]; + }); + + // Kelompokkan: belum & pending vs sudah dikumpulkan + $tugasBelum = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] !== 'terlambat'); + $tugasTerlambat = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] === 'terlambat'); + $tugasSelesai = $tugasList->filter(fn($t) => $t['sudah_kumpul']); + + return view('siswa.tugas.index', compact('tugasBelum', 'tugasTerlambat', 'tugasSelesai')); + } + + /** + * Detail tugas + form submit + */ + public function show($id_tugas) + { + $siswa = Auth::guard('siswa')->user(); + + $tugas = Tugas::with(['mengajar.mapel', 'mengajar.guru']) + ->whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); + }) + ->where('id_tugas', $id_tugas) + ->firstOrFail(); + + $pengumpulan = PengumpulanTugas::where('id_tugas', $id_tugas) + ->where('id_siswa', $siswa->id_siswa) + ->first(); + + $sudahKumpul = !is_null($pengumpulan); + $terlambat = Carbon::now()->greaterThan(Carbon::parse($tugas->deadline)); + + return view('siswa.tugas.show', compact('tugas', 'pengumpulan', 'sudahKumpul', 'terlambat')); + } + + /** + * Submit / upload jawaban tugas + */ + public function submit(Request $request, $id_tugas) + { + $siswa = Auth::guard('siswa')->user(); + + $request->validate([ + 'lampiran_tugas' => 'required|file|mimes:pdf,doc,docx,jpg,jpeg,png|max:5120', + ], [ + 'lampiran_tugas.required' => 'File jawaban wajib diupload.', + 'lampiran_tugas.mimes' => 'Format file harus pdf, doc, docx, jpg, atau png.', + 'lampiran_tugas.max' => 'Ukuran file maksimal 5MB.', + ]); + + // Cek tugas valid untuk kelas siswa + $tugas = Tugas::whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); + }) + ->where('id_tugas', $id_tugas) + ->firstOrFail(); + + // Cek sudah dikumpulkan belum + $sudahAda = PengumpulanTugas::where('id_tugas', $id_tugas) + ->where('id_siswa', $siswa->id_siswa) + ->exists(); + + if ($sudahAda) { + return back()->with('error', 'Kamu sudah mengumpulkan tugas ini.'); + } + + // Upload file + $file = $request->file('lampiran_tugas'); + $filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension(); + $path = $file->storeAs('pengumpulan_tugas', $filename, 'public'); + + // Tentukan status: terlambat atau dikumpulkan + $now = Carbon::now(); + $deadline = Carbon::parse($tugas->deadline); + $status = $now->greaterThan($deadline) ? 'terlambat' : 'dikumpulkan'; + + PengumpulanTugas::create([ + 'id_tugas' => $id_tugas, + 'id_siswa' => $siswa->id_siswa, + 'lampiran_tugas' => $path, + 'tanggal_submit' => $now, + 'exp' => 0, // exp diberikan guru saat menilai + 'status' => $status, + ]); + + $pesan = $status === 'terlambat' + ? 'Tugas berhasil dikumpulkan (terlambat).' + : 'Tugas berhasil dikumpulkan! 🎉'; + + return redirect()->route('siswa.tugas.index')->with('success', $pesan); + } +} \ No newline at end of file diff --git a/app/Models/Materi.php b/app/Models/Materi.php index 4f1b5d3..fd8be96 100644 --- a/app/Models/Materi.php +++ b/app/Models/Materi.php @@ -9,7 +9,7 @@ class Materi extends Model { use HasFactory; - protected $table = 'materis'; + protected $table = 'materis'; protected $primaryKey = 'id_materi'; protected $fillable = [ @@ -19,4 +19,13 @@ class Materi extends Model 'deskripsi', 'tanggal_upload', ]; + + protected $casts = [ + 'tanggal_upload' => 'datetime', + ]; + + public function mengajar() + { + return $this->belongsTo(Mengajar::class, 'id_mengajar', 'id_mengajar'); + } } \ No newline at end of file diff --git a/app/Models/Tugas.php b/app/Models/Tugas.php index 9302c06..8ddd25e 100644 --- a/app/Models/Tugas.php +++ b/app/Models/Tugas.php @@ -9,7 +9,7 @@ class Tugas extends Model { use HasFactory; - protected $table = 'tugas'; + protected $table = 'tugas'; protected $primaryKey = 'id_tugas'; protected $fillable = [ @@ -18,4 +18,24 @@ class Tugas extends Model 'keterangan', 'deadline', ]; + + protected $casts = [ + 'deadline' => 'datetime', + ]; + + /** + * Tugas dibuat melalui relasi mengajar (guru - mapel - kelas) + */ + public function mengajar() + { + return $this->belongsTo(Mengajar::class, 'id_mengajar', 'id_mengajar'); + } + + /** + * Satu tugas bisa punya banyak pengumpulan dari siswa + */ + public function pengumpulanTugas() + { + return $this->hasMany(PengumpulanTugas::class, 'id_tugas', 'id_tugas'); + } } \ No newline at end of file diff --git a/resources/views/siswa/dashboard.blade.php b/resources/views/siswa/dashboard.blade.php index 71b468b..416d0e4 100644 --- a/resources/views/siswa/dashboard.blade.php +++ b/resources/views/siswa/dashboard.blade.php @@ -1,4 +1,4 @@ -@extends('layouts.siswa.app') +@extends('siswa.layouts.app') @section('title', 'Dashboard Siswa') @@ -319,7 +319,7 @@

Tugas

- LIHAT SEMUA + LIHAT SEMUA
@forelse($tugasList as $tanggal => $items) @@ -381,7 +381,7 @@
@@ -408,7 +408,7 @@

Leaderboard

- LIHAT SEMUA + LIHAT SEMUA
@forelse($leaderboard as $lb) diff --git a/resources/views/siswa/layouts/app.blade.php b/resources/views/siswa/layouts/app.blade.php index 4f53f55..5e83e89 100644 --- a/resources/views/siswa/layouts/app.blade.php +++ b/resources/views/siswa/layouts/app.blade.php @@ -218,13 +218,13 @@ class="sidebar-link {{ request()->routeIs('siswa.dashboard') ? 'active' : '' }}" Dashboard - Materi - Tugas diff --git a/resources/views/siswa/materi/index.blade.php b/resources/views/siswa/materi/index.blade.php new file mode 100644 index 0000000..4047fed --- /dev/null +++ b/resources/views/siswa/materi/index.blade.php @@ -0,0 +1,116 @@ +@extends('layouts.siswa.app') + +@section('title', 'Materi') + +@push('styles') + +@endpush + +@section('content') + +

📚 Mata Pelajaran

+ +@if($mapelList->isEmpty()) +
+
📭
+

Belum ada mata pelajaran untuk kelasmu.

+
+@else +
+ @foreach($mapelList as $mapel) + +
📖
+
+

{{ $mapel['nama_mapel'] }}

+

{{ $mapel['nama_guru'] }}

+
+
+ 📄 {{ $mapel['jumlah_materi'] }} Materi +
+
+ @endforeach +
+@endif + +@endsection \ No newline at end of file diff --git a/resources/views/siswa/materi/show.blade.php b/resources/views/siswa/materi/show.blade.php new file mode 100644 index 0000000..b5c5c50 --- /dev/null +++ b/resources/views/siswa/materi/show.blade.php @@ -0,0 +1,222 @@ +@extends('layouts.siswa.app') + +@section('title', 'Materi - ' . optional(optional($mengajar)->mapel)->nama_mapel) + +@push('styles') + +@endpush + +@section('content') + +← Kembali ke Mata Pelajaran + +
+
📖
+
+

{{ optional($mengajar->mapel)->nama_mapel ?? '-' }}

+

Guru: {{ optional($mengajar->guru)->nama ?? '-' }} • {{ $materiList->count() }} Materi

+
+
+ +@if($materiList->isEmpty()) +
+
📭
+

Belum ada materi yang diupload untuk mata pelajaran ini.

+
+@else +
+ @foreach($materiList as $materi) + @php + $ext = $materi->lampiran_materi + ? strtolower(pathinfo($materi->lampiran_materi, PATHINFO_EXTENSION)) + : null; + + $iconClass = match(true) { + in_array($ext, ['pdf']) => 'pdf', + in_array($ext, ['doc','docx']) => 'doc', + in_array($ext, ['jpg','jpeg','png']) => 'img', + default => 'other', + }; + + $iconEmoji = match($iconClass) { + 'pdf' => '📄', + 'doc' => '📝', + 'img' => '🖼️', + default => '📎', + }; + @endphp + +
+
{{ $iconEmoji }}
+ +
+

{{ $materi->judul_materi }}

+

+ Diupload {{ \Carbon\Carbon::parse($materi->tanggal_upload)->locale('id')->isoFormat('D MMMM YYYY') }} +

+ @if($materi->deskripsi) +

{{ $materi->deskripsi }}

+ @endif +
+ + @if($materi->lampiran_materi) + + ⬇️ Unduh + + @else + Tidak ada file + @endif +
+ @endforeach +
+@endif + +@endsection \ No newline at end of file diff --git a/resources/views/siswa/tugas/index.blade.php b/resources/views/siswa/tugas/index.blade.php new file mode 100644 index 0000000..3b13345 --- /dev/null +++ b/resources/views/siswa/tugas/index.blade.php @@ -0,0 +1,326 @@ +@extends('layouts.siswa.app') + +@section('title', 'Tugas') + +@push('styles') + +@endpush + +@section('content') + +

📋 Tugas

+ +@if(session('success')) +
✅ {{ session('success') }}
+@endif + +@if(session('error')) +
❌ {{ session('error') }}
+@endif + +{{-- TABS --}} +
+ + + +
+ +{{-- TAB: BELUM --}} +
+ @if($tugasBelum->isEmpty()) +
+
🎉
+

Semua tugas sudah dikerjakan!

+
+ @else + + @endif +
+ +{{-- TAB: TERLAMBAT --}} +
+ @if($tugasTerlambat->isEmpty()) +
+
+

Tidak ada tugas terlambat!

+
+ @else + + @endif +
+ +{{-- TAB: SELESAI --}} +
+ @if($tugasSelesai->isEmpty()) +
+
📭
+

Belum ada tugas yang dikumpulkan.

+
+ @else + + @endif +
+ +@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/siswa/tugas/show.blade.php b/resources/views/siswa/tugas/show.blade.php new file mode 100644 index 0000000..6ba754f --- /dev/null +++ b/resources/views/siswa/tugas/show.blade.php @@ -0,0 +1,409 @@ +@extends('layouts.siswa.app') + +@section('title', $tugas->judul_tugas) + +@push('styles') + +@endpush + +@section('content') + +← Kembali ke Daftar Tugas + +@if(session('success')) +
✅ {{ session('success') }}
+@endif +@if(session('error')) +
❌ {{ session('error') }}
+@endif + +{{-- DETAIL TUGAS --}} +@php + $headerClass = $sudahKumpul ? 'selesai' : ($terlambat ? 'terlambat' : ''); + $deadlineClass = $terlambat ? '' : 'aman'; +@endphp + +
+

{{ $tugas->judul_tugas }}

+ +
+ + 📚 {{ optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-' }} + + + 👨‍🏫 {{ optional(optional($tugas->mengajar)->guru)->nama ?? '-' }} + + + ⏰ Deadline: {{ \Carbon\Carbon::parse($tugas->deadline)->format('d M Y, H:i') }} + +
+ + @if($tugas->keterangan) +
+ {!! nl2br(e($tugas->keterangan)) !!} +
+ @endif +
+ +{{-- WARNING TERLAMBAT --}} +@if($terlambat && !$sudahKumpul) +
+ ⚠️ +
+ Deadline sudah lewat!
+ Kamu masih bisa mengumpulkan tugas ini, tapi akan ditandai sebagai terlambat. +
+
+@endif + +{{-- SUDAH DIKUMPULKAN --}} +@if($sudahKumpul) +
+
🎉
+

Tugas sudah dikumpulkan!

+

+ Dikumpulkan pada {{ \Carbon\Carbon::parse($pengumpulan->tanggal_submit)->format('d M Y, H:i') }} + • Status: {{ ucfirst($pengumpulan->status) }} +

+ @if($pengumpulan->exp > 0) +
⭐ {{ $pengumpulan->exp }} EXP didapat
+ @else +

+ EXP akan diberikan setelah guru menilai tugasmu. +

+ @endif + + @if($pengumpulan->lampiran_tugas) + + 📎 Lihat File yang Dikumpulkan + + @endif +
+ +{{-- FORM SUBMIT --}} +@else +
+

📤 Upload Jawaban

+ +
+ @csrf + + @error('lampiran_tugas') +
❌ {{ $message }}
+ @enderror + +
+ +
☁️
+

Klik untuk pilih file atau drag & drop di sini

+

PDF, DOC, DOCX, JPG, PNG • Maks. 5MB

+
+ +
+ 📎 + - + - +
+ + +
+
+@endif + +@endsection + +@push('scripts') + +@endpush \ No newline at end of file