E-Learning Gamifikasi SMKN 1 Tapen

This commit is contained in:
RetasyaSalsabila 2026-06-02 13:49:01 +07:00
parent 5785ad88fc
commit 1498c6e762
240 changed files with 7574 additions and 4198 deletions

View File

@ -7,16 +7,43 @@
use App\Models\Kelas;
use App\Models\Mapel;
use App\Models\Challenge;
use App\Models\Leaderboard;
use Illuminate\Support\Facades\Auth;
class AdminController extends Controller {
public function dashboard(){
$totalGuru = Guru::count();
$totalGuru = Guru::count();
$totalSiswa = Siswa::count();
$totalKelas = Kelas::count();
$totalMapel = Mapel::count();
$chartData = Kelas::withCount('siswa')->get();
$chartData = Kelas::withCount('siswa')->get();
$latestChallenges = Challenge::latest()->take(3)->get();
return view('admin.dashboard', compact('totalGuru','totalSiswa','totalKelas','totalMapel','chartData','latestChallenges'));
// Leaderboard top 10
$leaderboard = Leaderboard::with(['siswa', 'kelas'])
->orderBy('total_exp', 'desc')
->take(10)
->get()
->map(function ($lb, $i) {
return [
'ranking' => $i + 1,
'nama' => optional($lb->siswa)->nama ?? '-',
'kelas' => optional($lb->kelas)->nama_kelas ?? '-',
'total_exp' => $lb->total_exp,
'foto' => optional($lb->siswa)->foto_profil,
'semester' => $lb->semester,
'tahun' => $lb->tahun_ajaran,
];
});
$firstLb = Leaderboard::orderBy('total_exp', 'desc')->first();
$semester = $firstLb->semester ?? '-';
$tahunAjaran = $firstLb->tahun_ajaran ?? '-';
return view('admin.dashboard', compact(
'totalGuru','totalSiswa','totalKelas','totalMapel',
'chartData','latestChallenges',
'leaderboard','semester','tahunAjaran'
));
}
}
}

View File

@ -4,188 +4,48 @@
use App\Http\Controllers\Controller;
use App\Models\Challenge;
use App\Models\SoalChallenge;
use App\Models\Kelas;
use App\Models\Badge;
use App\Models\PesertaChallenge;
use App\Models\Guru;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class ChallengeController extends Controller
{
public function index(Request $request)
{
$query = Challenge::with(['kelas', 'soal'])
->withCount('soal');
{
$query = Challenge::with(['guru', 'kelas'])
->withCount('soal as soal_count');
if ($request->filled('search')) {
$query->where('judul_challenge', 'like', '%' . $request->search . '%');
}
$challenges = $query->orderBy('created_at', 'desc')
->paginate(10)
->appends($request->all());
$kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$badges = Badge::all();
return view('admin.challenge.index', compact('challenges', 'kelas', 'badges'));
// Filter by guru
if ($request->filled('id_guru')) {
$query->where('id_guru', $request->id_guru);
}
public function store(Request $request)
{
$request->validate([
'judul_challenge' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'exp' => 'required|integer|min:0',
'tenggat_waktu' => 'required|date|after:now',
'durasi_pengerjaan' => 'required|integer|min:1|max:360',
'id_kelas' => 'required|array|min:1',
'id_kelas.*' => 'exists:kelas,id_kelas',
// Soal
'pertanyaan' => 'required|array|min:1',
'pertanyaan.*' => 'required|string',
'opsi_a.*' => 'required|string',
'opsi_b.*' => 'required|string',
'opsi_c.*' => 'required|string',
'opsi_d.*' => 'required|string',
'jawaban_benar.*' => 'required|in:A,B,C,D',
'exp_per_soal.*' => 'required|integer|min:0',
], [
'tenggat_waktu.after' => 'Tenggat waktu harus lebih dari sekarang.',
'pertanyaan.required' => 'Minimal harus ada 1 soal.',
'id_kelas.required' => 'Pilih minimal 1 kelas.',
]);
DB::transaction(function () use ($request) {
$admin = Auth::guard('admin')->user();
$challenge = Challenge::create([
'id_admin' => $admin->id_admin,
'judul_challenge' => $request->judul_challenge,
'deskripsi' => $request->deskripsi,
'exp' => $request->exp,
'id_badge' => $request->id_badge,
'tenggat_waktu' => $request->tenggat_waktu,
'durasi_pengerjaan' => $request->durasi_pengerjaan,
]);
// Attach ke kelas
$challenge->kelas()->sync($request->id_kelas);
$challenge->soal()->delete();
// Simpan soal
foreach ($request->pertanyaan as $i => $pertanyaan) {
SoalChallenge::create([
'id_challenge' => $challenge->id_challenge,
'pertanyaan' => $pertanyaan,
'opsi_a' => $request->opsi_a[$i],
'opsi_b' => $request->opsi_b[$i],
'opsi_c' => $request->opsi_c[$i],
'opsi_d' => $request->opsi_d[$i],
'jawaban_benar' => $request->jawaban_benar[$i],
'exp_per_soal' => $request->exp_per_soal[$i],
]);
}
});
return redirect()->route('admin.challenge.index')
->with('success', 'Challenge berhasil dibuat!');
// Search judul
if ($request->filled('search')) {
$query->where('judul_challenge', 'like', '%' . $request->search . '%');
}
$challenges = $query->latest()
->paginate(10)
->appends($request->all()); // ganti withQueryString() → appends()
$guruList = Guru::orderBy('nama')->get(); // ambil semua guru, pakai model import
return view('admin.challenge.index', compact('challenges', 'guruList'));
}
public function show($id)
{
$challenge = Challenge::with(['kelas', 'soal'])->findOrFail($id);
$challenge = Challenge::with(['kelas', 'soal', 'guru', 'peserta.siswa'])
->findOrFail($id);
return view('admin.challenge.show', compact('challenge'));
}
public function edit($id)
{
$challenge = Challenge::with(['kelas', 'soal'])->findOrFail($id);
$kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$badges = Badge::all();
return view('admin.challenge.edit', compact('challenge', 'kelas', 'badges'));
}
public function update(Request $request, $id)
{
$challenge = Challenge::findOrFail($id);
$request->validate([
'judul_challenge' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'exp' => 'required|integer|min:0',
'tenggat_waktu' => 'required|date',
'durasi_pengerjaan' => 'required|integer|min:1|max:360',
'id_kelas' => 'required|array|min:1',
'id_kelas.*' => 'exists:kelas,id_kelas',
'pertanyaan' => 'required|array|min:1',
'pertanyaan.*' => 'required|string',
'opsi_a.*' => 'required|string',
'opsi_b.*' => 'required|string',
'opsi_c.*' => 'required|string',
'opsi_d.*' => 'required|string',
'jawaban_benar.*' => 'required|in:A,B,C,D',
'exp_per_soal.*' => 'required|integer|min:0',
]);
DB::transaction(function () use ($request, $challenge) {
$challenge->update([
'judul_challenge' => $request->judul_challenge,
'deskripsi' => $request->deskripsi,
'exp' => $request->exp,
'id_badge' => $request->id_badge,
'tenggat_waktu' => $request->tenggat_waktu,
'durasi_pengerjaan' => $request->durasi_pengerjaan,
]);
$challenge->kelas()->sync($request->id_kelas);
// Hapus soal lama, insert ulang
SoalChallenge::where('id_challenge', $challenge->id_challenge)->delete();
foreach ($request->pertanyaan as $i => $pertanyaan) {
SoalChallenge::create([
'id_challenge' => $challenge->id_challenge,
'pertanyaan' => $pertanyaan,
'opsi_a' => $request->opsi_a[$i],
'opsi_b' => $request->opsi_b[$i],
'opsi_c' => $request->opsi_c[$i],
'opsi_d' => $request->opsi_d[$i],
'jawaban_benar' => $request->jawaban_benar[$i],
'exp_per_soal' => $request->exp_per_soal[$i],
]);
}
});
return redirect()->route('admin.challenge.index')
->with('success', 'Challenge berhasil diupdate!');
}
public function destroy($id)
{
Challenge::findOrFail($id)->delete();
return redirect()->route('admin.challenge.index')
->with('success', 'Challenge berhasil dihapus.');
}
/**
* AJAX return data challenge untuk modal edit
*/
public function editData($id)
{
$challenge = Challenge::with(['kelas', 'soal'])->findOrFail($id);
return response()->json([
'judul_challenge' => $challenge->judul_challenge,
'deskripsi' => $challenge->deskripsi,
'exp' => $challenge->exp,
'tenggat_waktu' => $challenge->tenggat_waktu,
'durasi_pengerjaan' => $challenge->durasi_pengerjaan,
'kelas' => $challenge->kelas->pluck('id_kelas'),
'soal' => $challenge->soal,
]);
}
}

View File

@ -161,10 +161,22 @@ public function downloadPdf(Request $request)
}
$gurus = $query->get();
$pdf = Pdf::loadView('admin.guru.pdf', compact('gurus'))
->setPaper('a4', 'landscape');
// Override DomPDF options langsung
$options = new \Dompdf\Options();
$options->setChroot(base_path('public'));
$options->setIsRemoteEnabled(true);
return $pdf->download('daftar-guru-' . date('Ymd') . '.pdf');
$dompdf = new \Dompdf\Dompdf($options);
$html = view('admin.guru.pdf', compact('gurus'))->render();
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'landscape');
$dompdf->render();
return response($dompdf->output(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="daftar-guru-' . date('Ymd') . '.pdf"',
]);
}
public function downloadExcel(Request $request)

View File

@ -90,7 +90,7 @@ public function destroy($id_kelas)
->with('success', 'Data kelas berhasil dihapus!');
}
public function downloadPdf(Request $request)
public function downloadPdf(Request $request)
{
$query = Kelas::query();
if ($request->filled('search')) {
@ -99,11 +99,20 @@ public function downloadPdf(Request $request)
->orWhere('id_kelas', 'like', "%$search%");
}
$kelass = $query->get();
$pdf = Pdf::loadView('admin.kelas.pdf', compact('kelass'))
->setPaper('a4', 'portrait');
return $pdf->download('daftar-kelas-' . date('Ymd') . '.pdf');
$options = new \Dompdf\Options();
$options->setChroot(base_path('public'));
$options->setIsRemoteEnabled(true);
$dompdf = new \Dompdf\Dompdf($options);
$dompdf->loadHtml(view('admin.kelas.pdf', compact('kelass'))->render());
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
return response($dompdf->output(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="daftar-kelas-' . date('Ymd') . '.pdf"',
]);
}
public function downloadExcel(Request $request)

View File

@ -7,9 +7,72 @@
use App\Models\Kelas;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LeaderboardController extends Controller
{
private function hitungExpSiswa(\App\Models\Siswa $s, Carbon $now): int
{
// ── 1. EXP dari challenge ─────────────────────────────────────────
$expChallenge = 0;
$pesertaList = DB::table('peserta_challenges')
->where('id_siswa', $s->id_siswa)
->get();
foreach ($pesertaList as $peserta) {
$jawaban = json_decode($peserta->jawaban, true) ?? [];
$soalList = DB::table('soal_challenge')
->where('id_challenge', $peserta->id_challenge)
->get();
foreach ($soalList as $soal) {
$jwb = $jawaban[$soal->id_soal] ?? null;
if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) {
$expChallenge += 1; // +1 per soal benar
}
}
}
// ── 2. EXP dari tugas ─────────────────────────────────────────────
$semuaTugasIdKelas = DB::table('tugas')
->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar')
->where('mengajars.id_kelas', $s->id_kelas)
->select('tugas.id_tugas', 'tugas.deadline')
->get();
$expTugas = 0;
foreach ($semuaTugasIdKelas as $tugas) {
$deadline = Carbon::parse($tugas->deadline);
$pengumpulan = DB::table('pengumpulan_tugas')
->where('id_tugas', $tugas->id_tugas)
->where('id_siswa', $s->id_siswa)
->first();
if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) {
$tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit);
if ($tanggalSubmit->lessThanOrEqualTo($deadline)) {
$expTugas += 10; // Tepat waktu → +10
} else {
$hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik
if ($hariTerlambat <= 1) {
$expTugas += 5; // Terlambat ≤ 1 hari → +5
} elseif ($hariTerlambat <= 3) {
$expTugas += 1; // Terlambat 2-3 hari → +3
} else {
$expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul
}
}
} elseif ($now->greaterThan($deadline)) {
// Tidak kumpul sama sekali & deadline lewat → -5
$expTugas -= 5;
}
}
return max(0, $expChallenge + $expTugas);
}
public function index(Request $request)
{
$now = Carbon::now();
@ -17,35 +80,38 @@ public function index(Request $request)
$tahunAjaran = $request->input('tahun_ajaran', $now->month >= 7
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year);
$idKelas = $request->input('id_kelas');
$idKelas = $request->input('id_kelas');
$kelasList = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$query = Leaderboard::with(['siswa', 'kelas'])
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran);
if ($idKelas) {
$query->where('id_kelas', $idKelas);
}
$leaderboard = $query->orderBy('total_exp', 'desc')->get()
->map(function ($item, $i) {
return [
'ranking' => $i + 1,
'nama' => optional($item->siswa)->nama ?? '-',
'nisn' => optional($item->siswa)->nisn ?? '-',
'nama_kelas' => optional($item->kelas)->nama_kelas ?? '-',
'exp' => $item->total_exp,
];
});
// Tahun ajaran list untuk dropdown (5 tahun ke belakang)
$tahunList = [];
for ($y = $now->year; $y >= $now->year - 4; $y--) {
$tahunList[] = $y . '/' . ($y + 1);
}
$query = \App\Models\Siswa::with('kelas');
if ($idKelas) {
$query->where('id_kelas', $idKelas);
}
$semuaSiswa = $query->get();
$leaderboard = $semuaSiswa->map(function ($s) use ($now) {
return [
'id_siswa' => $s->id_siswa,
'nama' => $s->nama,
'nisn' => $s->nisn,
'foto_profil' => $s->foto_profil,
'nama_kelas' => $s->kelas->nama_kelas ?? '-',
'exp' => $this->hitungExpSiswa($s, $now),
];
})
->sortByDesc('exp')
->values()
->map(function ($item, $i) {
$item['ranking'] = $i + 1;
return $item;
});
return view('admin.leaderboard.index', compact(
'leaderboard', 'kelasList', 'semester', 'tahunAjaran', 'idKelas', 'tahunList'
));

View File

@ -33,6 +33,11 @@ public function loginAdmin(Request $request)
])->withInput($request->except('password'));
}
public function __construct()
{
$this->middleware('guest:admin')->only(['showLoginForm', 'loginAdmin']);
}
public function logout(Request $request)
{
Auth::guard('admin')->logout();

View File

@ -130,7 +130,7 @@ public function destroy($id)
->with('success', 'Data mata pelajaran berhasil dihapus!');
}
public function downloadPdf(Request $request)
public function downloadPdf(Request $request)
{
$query = Mapel::with('kelas');
if ($request->filled('search')) {
@ -145,11 +145,20 @@ public function downloadPdf(Request $request)
});
}
$mapels = $query->get();
$pdf = Pdf::loadView('admin.mapel.pdf', compact('mapels'))
->setPaper('a4', 'portrait');
return $pdf->download('daftar-mapel-' . date('Ymd') . '.pdf');
$options = new \Dompdf\Options();
$options->setChroot(base_path('public'));
$options->setIsRemoteEnabled(true);
$dompdf = new \Dompdf\Dompdf($options);
$dompdf->loadHtml(view('admin.mapel.pdf', compact('mapels'))->render());
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
return response($dompdf->output(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="daftar-mapel-' . date('Ymd') . '.pdf"',
]);
}
public function downloadExcel(Request $request)

View File

@ -5,6 +5,7 @@
use App\Http\Controllers\Controller;
use App\Models\Materi;
use App\Models\Tugas;
use App\Models\PesertaChallenge;
use Carbon\Carbon;
class NotifikasiController extends Controller
@ -41,7 +42,23 @@ public function index()
'time_raw'=> $t->created_at->toIso8601String(),
]);
$notifications = $materiBaru->concat($tugasBaru)
// Siswa yang mengumpulkan challenge
$challengeKumpul = PesertaChallenge::with(['siswa', 'challenge'])
->where('waktu_submit', '>=', $since)
->orderBy('waktu_submit', 'desc')
->get()
->map(fn($p) => [
'type' => 'challenge',
'title' => 'Challenge Dikumpulkan',
'message' => optional($p->siswa)->nama . ' mengumpulkan challenge',
'sub' => optional($p->challenge)->judul_challenge ?? '-',
'time' => Carbon::parse($p->waktu_submit)->diffForHumans(),
'time_raw'=> Carbon::parse($p->waktu_submit)->toIso8601String(),
]);
$notifications = $materiBaru
->concat($tugasBaru)
->concat($challengeKumpul)
->sortByDesc('time_raw')
->values();

View File

@ -5,6 +5,7 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
@ -16,47 +17,45 @@ public function edit()
return view('admin.profile.edit', compact('admin'));
}
public function updateAjax(Request $request)
{
$admin = Auth::guard('admin')->user();
public function updateAjax(Request $request)
{
$admin = Auth::guard('admin')->user();
$request->validate([
'username' => 'required|string|max:100|unique:admins,username,' . $admin->id_admin . ',id_admin',
'password' => 'nullable|min:6|confirmed',
'password_confirmation' => 'nullable',
'foto_profil' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
], [
'username.required' => 'Username wajib diisi.',
'username.unique' => 'Username sudah digunakan.',
'password.min' => 'Password minimal 6 karakter.',
'password.confirmed' => 'Konfirmasi password tidak cocok.',
'foto_profil.image' => 'File harus berupa gambar.',
'foto_profil.max' => 'Ukuran foto maksimal 2MB.',
]);
$request->validate([
'password' => 'nullable|min:6|confirmed',
'password_confirmation' => 'nullable',
'foto_profil' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
], [
'password.min' => 'Password minimal 6 karakter.',
'password.confirmed' => 'Konfirmasi password tidak cocok.',
'foto_profil.image' => 'File harus berupa gambar.',
'foto_profil.max' => 'Ukuran foto maksimal 2MB.',
]);
$data = ['username' => $request->username];
$data = [];
$fotoUrl = null;
if ($request->filled('password')) {
$data['password'] = Hash::make($request->password);
}
$fotoUrl = null;
if ($request->hasFile('foto_profil')) {
if ($admin->foto_profil && Storage::disk('public')->exists($admin->foto_profil)) {
Storage::disk('public')->delete($admin->foto_profil);
}
$path = $request->file('foto_profil')->store('foto_profil/admin', 'public');
$data['foto_profil'] = $path;
$fotoUrl = Storage::url($path);
}
$admin->update($data);
return response()->json([
'success' => true,
'message' => 'Profil berhasil diperbarui!',
'foto_url' => $fotoUrl ?? ($admin->foto_profil ? Storage::url($admin->foto_profil) : null),
'username' => $admin->fresh()->username,
]);
if ($request->filled('password')) {
$data['password'] = Hash::make($request->password);
}
if ($request->hasFile('foto_profil')) {
if ($admin->foto_profil && Storage::disk('public')->exists($admin->foto_profil)) {
Storage::disk('public')->delete($admin->foto_profil);
}
$path = $request->file('foto_profil')->store('foto_profil/admin', 'public');
$data['foto_profil'] = $path;
$fotoUrl = '/E31230356/storage/app/public/' . $path;
}
if (!empty($data)) {
$admin->update($data);
}
return response()->json([
'success' => true,
'message' => 'Profil berhasil diperbarui!',
'foto_url' => $fotoUrl ?? ($admin->foto_profil ? '/E31230356/storage/app/public/' . $admin->foto_profil : null),
]);
}
}

View File

@ -7,6 +7,7 @@
use App\Models\Kelas;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Barryvdh\DomPDF\Facade\Pdf;
class SiswaController extends Controller
@ -15,25 +16,21 @@ public function index(Request $request)
{
$query = Siswa::with('kelas');
// SEARCH
if ($request->has('search')) {
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
$query->where(function($q) use ($search) {
$q->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
});
}
// FILTER BY KELAS
if ($request->has('filter_kelas') && $request->filter_kelas != '') {
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
// SHOW PER PAGE
$perPage = $request->get('perPage', 10);
$siswas = $query->paginate($perPage)->appends($request->all());
// Ambil semua kelas untuk dropdown filter
$kelass = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$siswas = $query->paginate($perPage)->appends($request->all());
$kelass = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
return view('admin.siswa.index', compact('siswas', 'kelass'));
}
@ -41,31 +38,31 @@ public function index(Request $request)
public function store(Request $request)
{
$validated = $request->validate([
'nisn' => 'required|string|max:20|unique:siswas,nisn',
'nama' => 'required|string|max:100',
'tempat_lahir' => 'required|string|max:50',
'nisn' => 'required|string|max:20|unique:siswas,nisn',
'nama' => 'required|string|max:100',
'tempat_lahir' => 'required|string|max:50',
'tanggal_lahir' => 'required|date',
'id_kelas' => 'required|exists:kelas,id_kelas',
'password' => 'required|string|min:6',
'id_kelas' => 'required|exists:kelas,id_kelas',
'password' => 'required|string|min:6',
], [
'nisn.required' => 'NISN wajib diisi',
'nisn.unique' => 'NISN sudah terdaftar',
'nama.required' => 'Nama wajib diisi',
'tempat_lahir.required' => 'Tempat lahir wajib diisi',
'nisn.required' => 'NISN wajib diisi',
'nisn.unique' => 'NISN sudah terdaftar',
'nama.required' => 'Nama wajib diisi',
'tempat_lahir.required' => 'Tempat lahir wajib diisi',
'tanggal_lahir.required' => 'Tanggal lahir wajib diisi',
'id_kelas.required' => 'Kelas wajib dipilih',
'id_kelas.exists' => 'Kelas tidak valid',
'password.required' => 'Password wajib diisi',
'password.min' => 'Password minimal 6 karakter',
'id_kelas.required' => 'Kelas wajib dipilih',
'id_kelas.exists' => 'Kelas tidak valid',
'password.required' => 'Password wajib diisi',
'password.min' => 'Password minimal 6 karakter',
]);
Siswa::create([
'nisn' => $validated['nisn'],
'nama' => $validated['nama'],
'tempat_lahir' => $validated['tempat_lahir'],
'nisn' => $validated['nisn'],
'nama' => $validated['nama'],
'tempat_lahir' => $validated['tempat_lahir'],
'tanggal_lahir' => $validated['tanggal_lahir'],
'id_kelas' => $validated['id_kelas'],
'password' => Hash::make($validated['password']),
'id_kelas' => $validated['id_kelas'],
'password' => Hash::make($validated['password']),
]);
return redirect()->route('admin.siswa.index')
@ -76,28 +73,45 @@ public function update(Request $request, $id)
{
$siswa = Siswa::findOrFail($id);
$validated = $request->validate([
'nama' => 'required|string|max:100',
'tempat_lahir' => 'required|string|max:50',
$validator = Validator::make($request->all(), [
'nama' => 'required|string|max:100',
'tempat_lahir' => 'required|string|max:50',
'tanggal_lahir' => 'required|date',
'id_kelas' => 'required|exists:kelas,id_kelas',
'password' => 'nullable|string|min:6',
'id_kelas' => 'required|exists:kelas,id_kelas',
'password' => 'nullable|string|min:6',
], [
'nama.required' => 'Nama wajib diisi',
'tempat_lahir.required' => 'Tempat lahir wajib diisi',
'nama.required' => 'Nama wajib diisi',
'tempat_lahir.required' => 'Tempat lahir wajib diisi',
'tanggal_lahir.required' => 'Tanggal lahir wajib diisi',
'id_kelas.required' => 'Kelas wajib dipilih',
'id_kelas.exists' => 'Kelas tidak valid',
'password.min' => 'Password minimal 6 karakter',
'id_kelas.required' => 'Kelas wajib dipilih',
'id_kelas.exists' => 'Kelas tidak valid',
'password.min' => 'Password minimal 6 karakter',
]);
$siswa->nama = $validated['nama'];
$siswa->tempat_lahir = $validated['tempat_lahir'];
$siswa->tanggal_lahir = $validated['tanggal_lahir'];
$siswa->id_kelas = $validated['id_kelas'];
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput()
->with('error_from', 'edit')
// Simpan data siswa yang sedang diedit ke session
// supaya JavaScript bisa mengisi form edit secara otomatis
->with('edit_siswa', [
'id_siswa' => $siswa->id_siswa,
'nisn' => $siswa->nisn,
'nama' => $request->nama ?? $siswa->nama,
'tempat_lahir' => $request->tempat_lahir ?? $siswa->tempat_lahir,
'tanggal_lahir' => $request->tanggal_lahir ?? $siswa->tanggal_lahir,
'id_kelas' => $request->id_kelas ?? $siswa->id_kelas,
]);
}
$siswa->nama = $request->nama;
$siswa->tempat_lahir = $request->tempat_lahir;
$siswa->tanggal_lahir = $request->tanggal_lahir;
$siswa->id_kelas = $request->id_kelas;
if ($request->filled('password')) {
$siswa->password = Hash::make($validated['password']);
$siswa->password = Hash::make($request->password);
}
$siswa->save();
@ -116,60 +130,69 @@ public function destroy($id)
}
public function downloadPdf(Request $request)
{
$query = Siswa::with('kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
}
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
$siswas = $query->get();
$pdf = Pdf::loadView('admin.siswa.pdf', compact('siswas'))
->setPaper('a4', 'landscape');
return $pdf->download('daftar-siswa-' . date('Ymd') . '.pdf');
}
public function downloadExcel(Request $request)
{
$query = Siswa::with('kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
}
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
$siswas = $query->get();
$filename = 'daftar-siswa-' . date('Ymd') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
];
$callback = function () use ($siswas) {
$file = fopen('php://output', 'w');
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['No', 'NISN', 'Nama', 'Tempat Lahir', 'Tanggal Lahir', 'Kelas']);
foreach ($siswas as $i => $siswa) {
fputcsv($file, [
$i + 1,
$siswa->nisn,
$siswa->nama,
$siswa->tempat_lahir,
\Carbon\Carbon::parse($siswa->tanggal_lahir)->format('d M Y'),
$siswa->kelas->tingkat . ' - ' . $siswa->kelas->nama_kelas,
]);
{
$query = Siswa::with('kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
}
fclose($file);
};
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
$siswas = $query->get();
return response()->stream($callback, 200, $headers);
}
$options = new \Dompdf\Options();
$options->setChroot(base_path('public'));
$options->setIsRemoteEnabled(true);
$dompdf = new \Dompdf\Dompdf($options);
$dompdf->loadHtml(view('admin.siswa.pdf', compact('siswas'))->render());
$dompdf->setPaper('A4', 'landscape');
$dompdf->render();
return response($dompdf->output(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="daftar-siswa-' . date('Ymd') . '.pdf"',
]);
}
public function downloadExcel(Request $request)
{
$query = Siswa::with('kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
}
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
$siswas = $query->get();
$filename = 'daftar-siswa-' . date('Ymd') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
];
$callback = function () use ($siswas) {
$file = fopen('php://output', 'w');
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['No', 'NISN', 'Nama', 'Tempat Lahir', 'Tanggal Lahir', 'Kelas']);
foreach ($siswas as $i => $siswa) {
fputcsv($file, [
$i + 1,
$siswa->nisn,
$siswa->nama,
$siswa->tempat_lahir,
\Carbon\Carbon::parse($siswa->tanggal_lahir)->format('d M Y'),
$siswa->kelas->tingkat . ' - ' . $siswa->kelas->nama_kelas,
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace App\Http\Controllers\Guru;
use App\Http\Controllers\Controller;
use App\Models\Challenge;
use App\Models\SoalChallenge;
use App\Models\Kelas;
use App\Models\Badge;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class ChallengeController extends Controller
{
public function index(Request $request)
{
$guru = Auth::guard('guru')->user();
$query = Challenge::with(['kelas', 'soal'])
->withCount('soal')
->where('id_guru', $guru->id_guru);
if ($request->filled('search')) {
$query->where('judul_challenge', 'like', '%' . $request->search . '%');
}
$challenges = $query->orderBy('created_at', 'desc')
->paginate(10)
->appends($request->all());
$kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$badges = Badge::all();
return view('guru.challenge.index', compact('challenges', 'kelas', 'badges'));
}
public function store(Request $request)
{
$request->validate([
'judul_challenge' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'tenggat_waktu' => 'required|date|after:now',
'durasi_pengerjaan' => 'required|integer|min:1|max:360',
'id_kelas' => 'required|array|min:1',
'id_kelas.*' => 'exists:kelas,id_kelas',
'pertanyaan' => 'required|array|min:1',
'pertanyaan.*' => 'required|string',
'opsi_a.*' => 'required|string',
'opsi_b.*' => 'required|string',
'opsi_c.*' => 'required|string',
'opsi_d.*' => 'required|string',
'jawaban_benar.*' => 'required|in:A,B,C,D',
], [
'tenggat_waktu.after' => 'Tenggat waktu harus lebih dari sekarang.',
'pertanyaan.required' => 'Minimal harus ada 1 soal.',
'id_kelas.required' => 'Pilih minimal 1 kelas.',
]);
DB::transaction(function () use ($request) {
$guru = Auth::guard('guru')->user();
$challenge = Challenge::create([
'id_guru' => $guru->id_guru,
'judul_challenge' => $request->judul_challenge,
'deskripsi' => $request->deskripsi,
'id_badge' => $request->id_badge,
'tenggat_waktu' => $request->tenggat_waktu,
'durasi_pengerjaan' => $request->durasi_pengerjaan,
]);
$challenge->kelas()->sync($request->id_kelas);
foreach ($request->pertanyaan as $i => $pertanyaan) {
SoalChallenge::create([
'id_challenge' => $challenge->id_challenge,
'pertanyaan' => $pertanyaan,
'opsi_a' => $request->opsi_a[$i],
'opsi_b' => $request->opsi_b[$i],
'opsi_c' => $request->opsi_c[$i],
'opsi_d' => $request->opsi_d[$i],
'jawaban_benar' => $request->jawaban_benar[$i],
]);
}
});
return redirect()->route('guru.challenge.index')
->with('success', 'Challenge berhasil dibuat!');
}
public function show($id)
{
$guru = Auth::guard('guru')->user();
$challenge = Challenge::with(['kelas', 'soal'])
->where('id_guru', $guru->id_guru)
->findOrFail($id);
return view('guru.challenge.show', compact('challenge'));
}
public function edit($id)
{
$guru = Auth::guard('guru')->user();
$challenge = Challenge::with(['kelas', 'soal'])
->where('id_guru', $guru->id_guru)
->findOrFail($id);
$kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$badges = Badge::all();
return view('guru.challenge.edit', compact('challenge', 'kelas', 'badges'));
}
public function update(Request $request, $id)
{
$guru = Auth::guard('guru')->user();
$challenge = Challenge::where('id_guru', $guru->id_guru)->findOrFail($id);
$request->validate([
'judul_challenge' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'tenggat_waktu' => 'required|date',
'durasi_pengerjaan' => 'required|integer|min:1|max:360',
'id_kelas' => 'required|array|min:1',
'id_kelas.*' => 'exists:kelas,id_kelas',
'pertanyaan' => 'required|array|min:1',
'pertanyaan.*' => 'required|string',
'opsi_a.*' => 'required|string',
'opsi_b.*' => 'required|string',
'opsi_c.*' => 'required|string',
'opsi_d.*' => 'required|string',
'jawaban_benar.*' => 'required|in:A,B,C,D',
]);
DB::transaction(function () use ($request, $challenge) {
$challenge->update([
'judul_challenge' => $request->judul_challenge,
'deskripsi' => $request->deskripsi,
'id_badge' => $request->id_badge,
'tenggat_waktu' => $request->tenggat_waktu,
'durasi_pengerjaan' => $request->durasi_pengerjaan,
]);
$challenge->kelas()->sync($request->id_kelas);
SoalChallenge::where('id_challenge', $challenge->id_challenge)->delete();
foreach ($request->pertanyaan as $i => $pertanyaan) {
SoalChallenge::create([
'id_challenge' => $challenge->id_challenge,
'pertanyaan' => $pertanyaan,
'opsi_a' => $request->opsi_a[$i],
'opsi_b' => $request->opsi_b[$i],
'opsi_c' => $request->opsi_c[$i],
'opsi_d' => $request->opsi_d[$i],
'jawaban_benar' => $request->jawaban_benar[$i],
]);
}
});
return redirect()->route('guru.challenge.index')
->with('success', 'Challenge berhasil diupdate!');
}
public function destroy($id)
{
$guru = Auth::guard('guru')->user();
Challenge::where('id_guru', $guru->id_guru)->findOrFail($id)->delete();
return redirect()->route('guru.challenge.index')
->with('success', 'Challenge berhasil dihapus.');
}
public function editData($id)
{
$guru = Auth::guard('guru')->user();
$challenge = Challenge::with(['kelas', 'soal'])
->where('id_guru', $guru->id_guru)
->findOrFail($id);
return response()->json([
'judul_challenge' => $challenge->judul_challenge,
'deskripsi' => $challenge->deskripsi,
'tenggat_waktu' => $challenge->tenggat_waktu,
'durasi_pengerjaan' => $challenge->durasi_pengerjaan,
'kelas' => $challenge->kelas->pluck('id_kelas'),
'soal' => $challenge->soal,
]);
}
}

View File

@ -6,7 +6,9 @@
use App\Models\Mengajar;
use App\Models\Siswa;
use App\Models\Tugas;
use App\Models\Leaderboard;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class DashboardController extends Controller
{
@ -21,42 +23,85 @@ public function index()
$totalMapel = Mengajar::where('id_guru', $guru->id_guru)
->distinct('id_mapel')->count('id_mapel');
$kelasIds = Mengajar::where('id_guru', $guru->id_guru)
->pluck('id_kelas')->unique();
$kelasIds = Mengajar::where('id_guru', $guru->id_guru)
->pluck('id_kelas')->unique()->values()->toArray(); // fix: toArray()
$totalSiswa = Siswa::whereIn('id_kelas', $kelasIds)->count();
// Chart: pengumpulan tugas per tugas (6 tugas terbaru)
$idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar');
// ── Chart: Grouped Bar ──
// Tampilkan semua kombinasi mapel-kelas meskipun belum ada pengumpulan
$mengajars = Mengajar::with([
'mapel',
'kelas',
'tugas.pengumpulanTugas' // relasi sudah ditambahkan di model
])->where('id_guru', $guru->id_guru)->get();
$tugasList = Tugas::with(['mengajar.mapel', 'pengumpulanTugas'])
->whereIn('id_mengajar', $idMengajars)
->latest()->take(6)->get();
$chartLabels = [];
$chartTepat = [];
$chartTerlambat = [];
$chartLabels = [];
$chartSudah = [];
$chartBelum = [];
foreach ($mengajars as $m) {
$namaMapel = optional($m->mapel)->nama_mapel ?? 'Mapel';
$namaKelas = optional($m->kelas)->nama_kelas ?? 'Kelas';
$labelPendek = (strlen($namaMapel) > 12
? substr($namaMapel, 0, 12) . '…'
: $namaMapel) . ' · ' . $namaKelas;
foreach ($tugasList as $tugas) {
$namaMapel = optional($tugas->mengajar->mapel)->nama_mapel ?? 'Mapel';
$chartLabels[] = strlen($namaMapel) > 14 ? substr($namaMapel, 0, 14) . '…' : $namaMapel;
$sudah = $tugas->pengumpulanTugas->count();
$chartSudah[] = $sudah;
$chartBelum[] = max(0, $totalSiswa - $sudah);
$tepat = 0;
$terlambat = 0;
foreach ($m->tugas as $tugas) {
foreach ($tugas->pengumpulanTugas as $p) {
if ($p->status === 'dikumpulkan') $tepat++;
elseif ($p->status === 'terlambat') $terlambat++;
}
}
// Selalu push meskipun tepat=0 dan terlambat=0
$chartLabels[] = $labelPendek;
$chartTepat[] = $tepat;
$chartTerlambat[] = $terlambat;
}
// ── Leaderboard ──
$leaderboard = Leaderboard::with(['siswa', 'kelas'])
->whereIn('id_kelas', $kelasIds) // fix: sudah jadi array
->orderBy('total_exp', 'desc')
->take(10)
->get()
->map(function ($lb, $i) {
return [
'ranking' => $i + 1,
'nama' => optional($lb->siswa)->nama ?? '-',
'kelas' => optional($lb->kelas)->nama_kelas ?? '-',
'total_exp' => $lb->total_exp,
'foto' => optional($lb->siswa)->foto_profil,
'semester' => $lb->semester,
'tahun' => $lb->tahun_ajaran,
];
});
$firstLb = Leaderboard::whereIn('id_kelas', $kelasIds)
->orderBy('total_exp', 'desc')->first();
$semester = $firstLb->semester ?? '-';
$tahunAjaran = $firstLb->tahun_ajaran ?? '-';
} catch (\Exception $e) {
$totalKelas = 0;
$totalMapel = 0;
$totalSiswa = 0;
$chartLabels = [];
$chartSudah = [];
$chartBelum = [];
$totalKelas = 0;
$totalMapel = 0;
$totalSiswa = 0;
$chartLabels = [];
$chartTepat = [];
$chartTerlambat = [];
$leaderboard = collect();
$semester = '-';
$tahunAjaran = '-';
}
return view('guru.dashboard', compact(
'totalKelas', 'totalMapel', 'totalSiswa',
'chartLabels', 'chartSudah', 'chartBelum'
'chartLabels', 'chartTepat', 'chartTerlambat',
'leaderboard', 'semester', 'tahunAjaran'
));
}
}

View File

@ -3,15 +3,79 @@
namespace App\Http\Controllers\Guru;
use App\Http\Controllers\Controller;
use App\Models\Leaderboard;
use App\Models\Kelas;
use App\Models\Mengajar;
use App\Models\Siswa;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class LeaderboardController extends Controller
{
private function hitungExpSiswa(int $idSiswa, int $idKelas): int
{
$now = Carbon::now();
// ── 1. EXP dari challenge ─────────────────────────────────────────
$expChallenge = 0;
$pesertaList = DB::table('peserta_challenges')
->where('id_siswa', $idSiswa)
->get();
foreach ($pesertaList as $peserta) {
$jawaban = json_decode($peserta->jawaban, true) ?? [];
$soalList = DB::table('soal_challenge')
->where('id_challenge', $peserta->id_challenge)
->get();
foreach ($soalList as $soal) {
$jwb = $jawaban[$soal->id_soal] ?? null;
if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) {
$expChallenge += 1; // +1 per soal benar
}
}
}
// ── 2. EXP dari tugas ─────────────────────────────────────────────
$tugasList = DB::table('tugas')
->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar')
->where('mengajars.id_kelas', $idKelas)
->select('tugas.id_tugas', 'tugas.deadline')
->get();
$expTugas = 0;
foreach ($tugasList as $tugas) {
$deadline = Carbon::parse($tugas->deadline);
$pengumpulan = DB::table('pengumpulan_tugas')
->where('id_tugas', $tugas->id_tugas)
->where('id_siswa', $idSiswa)
->first();
if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) {
$tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit);
if ($tanggalSubmit->lessThanOrEqualTo($deadline)) {
$expTugas += 10; // Tepat waktu → +10
} else {
$hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik
if ($hariTerlambat <= 1) {
$expTugas += 5; // Terlambat ≤ 1 hari → +5
} elseif ($hariTerlambat <= 3) {
$expTugas += 1; // Terlambat 2-3 hari → +3
} else {
$expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul
}
}
} elseif ($now->greaterThan($deadline)) {
// Tidak kumpul sama sekali & deadline lewat → -5
$expTugas -= 5;
}
}
return max(0, $expChallenge + $expTugas);
}
public function index(Request $request)
{
/** @var \App\Models\Guru $guru */
@ -23,14 +87,12 @@ public function index(Request $request)
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year);
// Kelas yang diajar guru ini
$idKelasList = Mengajar::where('id_guru', $guru->id_guru)
->pluck('id_kelas')
->unique()
->toArray();
$idKelas = $request->input('id_kelas', $idKelasList[0] ?? null);
$idKelas = $request->input('id_kelas', $idKelasList[0] ?? null);
$kelasList = Kelas::whereIn('id_kelas', $idKelasList)
->orderBy('tingkat')->orderBy('nama_kelas')
->get();
@ -38,21 +100,24 @@ public function index(Request $request)
$leaderboard = collect();
if ($idKelas) {
$leaderboard = Leaderboard::with(['siswa', 'kelas'])
->where('id_kelas', $idKelas)
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran)
->orderBy('total_exp', 'desc')
->get()
->map(function ($item, $i) {
return [
'ranking' => $i + 1,
'nama' => optional($item->siswa)->nama ?? '-',
'nisn' => optional($item->siswa)->nisn ?? '-',
'nama_kelas' => optional($item->kelas)->nama_kelas ?? '-',
'exp' => $item->total_exp,
];
});
$semuaSiswa = Siswa::where('id_kelas', $idKelas)->get();
$leaderboard = $semuaSiswa->map(function ($s) {
return [
'id_siswa' => $s->id_siswa,
'nama' => $s->nama,
'nisn' => $s->nisn,
'foto_profil' => $s->foto_profil,
'nama_kelas' => $s->kelas->nama_kelas ?? '-',
'exp' => $this->hitungExpSiswa($s->id_siswa, $s->id_kelas),
];
})
->sortByDesc('exp')
->values()
->map(function ($item, $i) {
$item['ranking'] = $i + 1;
return $item;
});
}
$tahunList = [];

View File

@ -122,6 +122,59 @@ public function historyMateri()
return view('guru.materi.history', compact('materiList'));
}
public function editMateri($id)
{
$guru = Auth::guard('guru')->user();
$idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar');
$materi = Materi::whereIn('id_mengajar', $idMengajars)
->where('id_materi', $id)
->firstOrFail();
$mengajars = Mengajar::with(['mapel', 'kelas'])
->where('id_guru', $guru->id_guru)
->get();
return view('guru.materi.edit', compact('materi', 'mengajars'));
}
public function updateMateri(Request $request, $id)
{
$guru = Auth::guard('guru')->user();
$idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar');
$materi = Materi::whereIn('id_mengajar', $idMengajars)
->where('id_materi', $id)
->firstOrFail();
$request->validate([
// id_mengajar TIDAK divalidate karena tidak ada di form edit
'judul_materi' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'lampiran_materi' => 'nullable|file|mimes:pdf,doc,docx,jpg,jpeg,png,ppt,pptx|max:10240',
], [
'lampiran_materi.mimes' => 'Format file: pdf, doc, docx, jpg, png, ppt, pptx.',
'lampiran_materi.max' => 'Ukuran file maksimal 10MB.',
]);
if ($request->hasFile('lampiran_materi')) {
if ($materi->lampiran_materi && \Storage::disk('public')->exists($materi->lampiran_materi)) {
\Storage::disk('public')->delete($materi->lampiran_materi);
}
$file = $request->file('lampiran_materi');
$filename = 'materi_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension();
$materi->lampiran_materi = $file->storeAs('materi', $filename, 'public');
}
// id_mengajar TIDAK diupdate — kelas asal tetap
$materi->judul_materi = $request->judul_materi;
$materi->deskripsi = $request->deskripsi;
$materi->save();
return redirect()->route('guru.materi.history')
->with('success', 'Materi berhasil diperbarui.');
}
/**
* Hapus materi
*/
@ -184,6 +237,58 @@ public function detailTugas($id)
return view('guru.tugas.detail', compact('tugas'));
}
public function editTugas($id)
{
$guru = Auth::guard('guru')->user();
$idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar');
$tugas = Tugas::whereIn('id_mengajar', $idMengajars)
->where('id_tugas', $id)
->firstOrFail();
$mengajars = Mengajar::with(['mapel', 'kelas'])
->where('id_guru', $guru->id_guru)
->get();
return view('guru.tugas.edit', compact('tugas', 'mengajars'));
}
public function updateTugas(Request $request, $id)
{
$guru = Auth::guard('guru')->user();
$idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar');
$tugas = Tugas::whereIn('id_mengajar', $idMengajars)
->where('id_tugas', $id)
->firstOrFail();
$request->validate([
'judul_tugas' => 'required|string|max:200',
'keterangan' => 'nullable|string',
'deadline' => 'required|date', // ← hapus after:now biar edit deadline lama tetap bisa
'lampiran_tugas' => 'nullable|file|max:10240',
], [
'lampiran_tugas.max' => 'Ukuran file maksimal 10MB.',
]);
if ($request->hasFile('lampiran_tugas')) {
if ($tugas->lampiran && \Storage::disk('public')->exists($tugas->lampiran)) {
\Storage::disk('public')->delete($tugas->lampiran);
}
$file = $request->file('lampiran_tugas');
$filename = 'tugas_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension();
$tugas->lampiran = $file->storeAs('tugas', $filename, 'public');
}
$tugas->judul_tugas = $request->judul_tugas;
$tugas->keterangan = $request->keterangan;
$tugas->deadline = $request->deadline;
$tugas->save();
return redirect()->route('guru.tugas.history')
->with('success', 'Tugas berhasil diperbarui.');
}
/**
* Hapus tugas
*/

View File

@ -38,7 +38,7 @@ public function updateAjax(Request $request)
}
$path = $request->file('foto_profil')->store('foto_profil/guru', 'public');
$data['foto_profil'] = $path;
$fotoUrl = Storage::url($path);
$fotoUrl = '/E31230356/storage/app/public/' . $path; // ← diubah
}
if (!empty($data)) {
@ -48,7 +48,7 @@ public function updateAjax(Request $request)
return response()->json([
'success' => true,
'message' => 'Profil berhasil diperbarui!',
'foto_url' => $fotoUrl ?? ($guru->foto_profil ? Storage::url($guru->foto_profil) : null),
'foto_url' => $fotoUrl ?? ($guru->foto_profil ? '/E31230356/storage/app/public/' . $guru->foto_profil : null), // ← diubah
]);
}
}

View File

@ -93,14 +93,19 @@ public function submit(Request $request, $id_challenge)
}
$jawaban = $request->input('jawaban', []);
$totalExp = 0;
$jawabanJson = [];
// =============================================
// PERUBAHAN: +1 poin per soal yang dijawab BENAR
// (tidak lagi pakai exp_per_soal dari database)
// =============================================
$totalExp = 0;
foreach ($challenge->soal as $soal) {
$jwb = $jawaban[$soal->id_soal] ?? null;
$jawabanJson[$soal->id_soal] = $jwb;
if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) {
$totalExp += $soal->exp_per_soal;
$totalExp += 1; // +1 poin per soal benar
}
}
@ -110,7 +115,7 @@ public function submit(Request $request, $id_challenge)
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year;
// Snapshot badge sebelum submit — untuk deteksi badge baru
// Snapshot badge sebelum submit
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
@ -137,6 +142,7 @@ public function submit(Request $request, $id_challenge)
$lb->increment('total_exp', $totalExp);
// Update ranking semua siswa di kelas ini
Leaderboard::where('id_kelas', $siswa->id_kelas)
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran)
@ -148,14 +154,13 @@ public function submit(Request $request, $id_challenge)
// Cek & berikan badge challenge
app(BadgeService::class)->checkChallengeBadges($siswa->id_siswa);
// Deteksi badge yang baru didapat (selisih sebelum & sesudah)
// Deteksi badge baru
$badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
$idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum);
// Simpan ke session agar bisa ditampilkan di halaman hasil
if (!empty($idBadgeBaru)) {
$badgeBaru = Badge::whereIn('id_badge', $idBadgeBaru)->get();
session()->flash('badge_baru', $badgeBaru);
@ -185,7 +190,6 @@ public function hasil($id_challenge)
$totalSoal = $challenge->soal->count();
$persentase = $totalSoal > 0 ? round(($benar / $totalSoal) * 100) : 0;
// Ambil badge baru dari session (hanya ada jika baru saja submit)
$badgeBaru = session('badge_baru', collect());
return view('siswa.challenge.hasil', compact(

View File

@ -25,8 +25,7 @@ public function index()
/** @var \App\Models\Siswa $siswa */
$siswa = Auth::guard('siswa')->user();
// tugas
// Tugas pending (belum dikumpulkan, belum lewat deadline)
$sudahDikumpulkan = PengumpulanTugas::where('id_siswa', $siswa->id_siswa)
->pluck('id_tugas');
@ -49,40 +48,35 @@ public function index()
$namaMapel = optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-';
$tugasList[$tgl][] = [
'id_tugas' => $tugas->id_tugas, // tambah ini
'jam' => Carbon::parse($tugas->deadline)->format('H.i'),
'nama' => $tugas->judul_tugas,
'mapel' => 'Belum · ' . $namaMapel,
'id_tugas' => $tugas->id_tugas,
'jam' => Carbon::parse($tugas->deadline)->format('H.i'),
'nama' => $tugas->judul_tugas,
'mapel' => 'Belum · ' . $namaMapel,
];
}
// challenge mingguan
$startMinggu = Carbon::now()->startOfWeek();
$endMinggu = Carbon::now()->endOfWeek();
// 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);
// Progress bar tugas (semua tugas dari guru untuk kelas ini)
$tugasTotal = Tugas::whereHas('mengajar', function ($q) use ($siswa) {
$q->where('id_kelas', $siswa->id_kelas);
})
->where('tenggat_waktu', '>=', Carbon::now())
->whereBetween('created_at', [$startMinggu, $endMinggu])
->where('deadline', '>=', Carbon::now())
->count();
// Challenge yang sudah selesai dikerjakan siswa minggu ini
$challengeDone = PesertaChallenge::where('id_siswa', $siswa->id_siswa)
->where('status', 'selesai')
->whereBetween('waktu_submit', [$startMinggu, $endMinggu])
$tugasDikumpulkan = PengumpulanTugas::where('id_siswa', $siswa->id_siswa)
->whereIn('id_tugas', Tugas::whereHas('mengajar', function ($q) use ($siswa) {
$q->where('id_kelas', $siswa->id_kelas);
})->where('deadline', '>=', Carbon::now())->pluck('id_tugas'))
->count();
// mascot
// Mascot
$startMinggu = Carbon::now()->startOfWeek();
$endMinggu = Carbon::now()->endOfWeek();
$tugasSelesai = PengumpulanTugas::where('id_siswa', $siswa->id_siswa)
->whereIn('status', ['dikumpulkan', 'terlambat'])
->whereBetween('tanggal_submit', [$startMinggu, $endMinggu])
->count();
// leaderboard
// Leaderboard
$leaderboardRaw = Leaderboard::with('siswa')
->where('id_kelas', $siswa->id_kelas)
->orderBy('total_exp', 'desc')
@ -99,8 +93,8 @@ public function index()
return view('siswa.dashboard', compact(
'tugasList',
'challengeDone',
'challengeTotal',
'tugasTotal',
'tugasDikumpulkan',
'tugasSelesai',
'leaderboard',
));

View File

@ -4,15 +4,102 @@
use App\Http\Controllers\Controller;
use App\Models\Leaderboard;
use App\Models\Siswa;
use App\Models\SiswaBadge;
use App\Services\BadgeService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class LeaderboardController extends Controller
{
private function getData()
/**
* Hitung total EXP siswa secara dinamis langsung dari database.
*
* Formula:
* + 1 poin per soal benar di setiap challenge
* +10 poin per tugas yang dikumpulkan tepat waktu
* + 5 poin per tugas yang terlambat < 1 hari (grace period)
* + 3 poin per tugas yang terlambat 23 hari
* - 5 poin per tugas yang terlambat > 3 hari
* - 5 poin per tugas yang tidak dikumpulkan sama sekali (setelah deadline)
*/
private function hitungExpSiswa(int $idSiswa, int $idKelas): int
{
$now = Carbon::now();
// ── 1. EXP dari challenge (soal yang dijawab benar) ──────────────
$expChallenge = 0;
$pesertaList = DB::table('peserta_challenges')
->where('id_siswa', $idSiswa)
->get();
foreach ($pesertaList as $peserta) {
$jawaban = json_decode($peserta->jawaban, true) ?? [];
$soalList = DB::table('soal_challenge')
->where('id_challenge', $peserta->id_challenge)
->get();
foreach ($soalList as $soal) {
$jwb = $jawaban[$soal->id_soal] ?? null;
if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) {
$expChallenge += 1; // +1 per soal benar
}
}
}
// ── 2. EXP dari tugas ────────────────────────────────────────────
$semuaTugasIdKelas = DB::table('tugas')
->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar')
->where('mengajars.id_kelas', $idKelas)
->select('tugas.id_tugas', 'tugas.deadline')
->get();
$expTugas = 0;
foreach ($semuaTugasIdKelas as $tugas) {
$deadline = Carbon::parse($tugas->deadline);
$pengumpulan = DB::table('pengumpulan_tugas')
->where('id_tugas', $tugas->id_tugas)
->where('id_siswa', $idSiswa)
->first();
if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) {
$tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit);
if ($tanggalSubmit->lessThanOrEqualTo($deadline)) {
$expTugas += 10; // Tepat waktu → +10
} else {
$hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik
if ($hariTerlambat <= 1) {
$expTugas += 5; // Terlambat ≤ 1 hari → +5
} elseif ($hariTerlambat <= 3) {
$expTugas += 1; // Terlambat 2-3 hari → +3
} else {
$expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul
}
}
} else {
// Belum mengumpulkan file sama sekali
if ($now->greaterThan($deadline)) {
// Deadline sudah lewat → penalti -5
$expTugas -= 5;
}
// Deadline belum lewat → tidak ada penalti, tidak ada poin
}
}
// Total EXP tidak bisa negatif
return max(0, $expChallenge + $expTugas);
}
/**
* Ambil data leaderboard dengan EXP yang dihitung dinamis.
*/
private function getData(): array
{
$siswa = Auth::guard('siswa')->user();
@ -22,24 +109,42 @@ private function getData()
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year;
$leaderboard = Leaderboard::with('siswa')
->where('id_kelas', $siswa->id_kelas)
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran)
->orderBy('total_exp', 'desc')
->get()
->map(function ($item, $i) {
$fotoProfil = optional($item->siswa)->foto_profil;
return [
'ranking' => $i + 1,
'nama' => optional($item->siswa)->nama ?? '-',
'nisn' => optional($item->siswa)->nisn ?? '-',
'exp' => $item->total_exp,
'id_siswa' => $item->id_siswa,
'foto_profil' => $fotoProfil,
'foto_url' => $fotoProfil ? Storage::url($fotoProfil) : null,
];
});
// Ambil semua siswa di kelas yang sama
$semuaSiswa = Siswa::where('id_kelas', $siswa->id_kelas)->get();
// Hitung EXP tiap siswa secara dinamis
$leaderboard = $semuaSiswa->map(function ($s) {
return [
'id_siswa' => $s->id_siswa,
'nama' => $s->nama,
'nisn' => $s->nisn,
'foto_profil' => $s->foto_profil,
'foto_url' => $s->foto_profil ? Storage::url($s->foto_profil) : null,
'exp' => $this->hitungExpSiswa($s->id_siswa, $s->id_kelas),
];
})
->sortByDesc('exp')
->values()
->map(function ($item, $i) {
$item['ranking'] = $i + 1;
return $item;
});
// Sinkronisasi ke tabel leaderboards agar badge bisa dicek
foreach ($leaderboard as $item) {
Leaderboard::updateOrCreate(
[
'id_siswa' => $item['id_siswa'],
'id_kelas' => $siswa->id_kelas,
'semester' => $semester,
'tahun_ajaran' => $tahunAjaran,
],
[
'total_exp' => $item['exp'],
'ranking' => $item['ranking'],
]
);
}
$myRank = $leaderboard->firstWhere('id_siswa', $siswa->id_siswa);
@ -55,22 +160,18 @@ public function index()
/**
* Endpoint JSON untuk polling real-time.
* Mengevaluasi badge leaderboard siswa, lalu return badge yang dimiliki
* saat ini agar JS bisa mendeteksi badge baru via localStorage.
*/
public function json()
{
$data = $this->getData();
$siswa = $data['siswa'];
// Evaluasi badge leaderboard (grant/revoke)
// Evaluasi badge leaderboard berdasarkan data terbaru
app(BadgeService::class)->checkLeaderboardBadges(
$siswa->id_siswa,
$siswa->id_kelas
);
// Ambil semua badge leaderboard yang dimiliki siswa saat ini
// beserta detail badge (icon, nama, deskripsi) untuk ditampilkan di pop-up
$badgeSiswa = SiswaBadge::with('badge')
->where('id_siswa', $siswa->id_siswa)
->whereHas('badge', fn($q) => $q->whereIn('syarat', ['leaderboard_top5', 'leaderboard_top1']))

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Siswa;
use App\Http\Controllers\Controller;
use App\Models\Challenge;
use App\Models\Materi;
use App\Models\Tugas;
use App\Models\Mengajar;
@ -48,7 +49,23 @@ public function index()
'time_raw'=> $t->created_at->toIso8601String(),
]);
$notifications = $materiBaru->concat($tugasBaru)
// Challenge baru untuk kelas siswa
$challengeBaru = Challenge::whereHas('kelas', fn($q) =>
$q->where('challenge_kelas.id_kelas', $siswa->id_kelas))
->where('created_at', '>=', $since)
->orderBy('created_at', 'desc')
->get()
->map(fn($c) => [
'type' => 'challenge',
'title' => 'Challenge Baru!',
'message' => $c->judul_challenge,
'time' => $c->created_at->diffForHumans(),
'time_raw'=> $c->created_at->toIso8601String(),
]);
$notifications = $materiBaru
->concat($tugasBaru)
->concat($challengeBaru)
->sortByDesc('time_raw')
->values();

View File

@ -61,7 +61,7 @@ public function updateAjax(Request $request)
}
$path = $request->file('foto_profil')->store('foto_profil/siswa', 'public');
$data['foto_profil'] = $path;
$fotoUrl = Storage::url($path);
$fotoUrl = '/E31230356/storage/app/public/' . $path;
}
if (!empty($data)) {
@ -71,7 +71,7 @@ public function updateAjax(Request $request)
return response()->json([
'success' => true,
'message' => 'Profil berhasil diperbarui!',
'foto_url' => $fotoUrl ?? ($siswa->foto_profil ? Storage::url($siswa->foto_profil) : null),
'foto_url' => $fotoUrl ?? ($siswa->foto_profil ? '/E31230356/storage/app/public/' . $siswa->foto_profil : null),
]);
}
}

View File

@ -4,13 +4,14 @@
use App\Http\Controllers\Controller;
use App\Models\Badge;
use App\Models\PengumpulanTugas;
use App\Models\SiswaBadge;
use App\Services\BadgeService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
use App\Models\Tugas;
use App\Models\PengumpulanTugas;
class TugasController extends Controller
{
@ -19,9 +20,6 @@ public function __construct()
$this->middleware('auth:siswa');
}
/**
* Daftar semua tugas untuk kelas siswa
*/
public function index()
{
$siswa = Auth::guard('siswa')->user();
@ -33,18 +31,23 @@ public function index()
->orderBy('deadline', 'asc')
->get();
$tugasList = $semuaTugas->map(function ($tugas) use ($siswa) {
$now = Carbon::now();
$tugasList = $semuaTugas->map(function ($tugas) use ($siswa, $now) {
$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;
if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) {
$status = $pengumpulan->tanggal_submit && Carbon::parse($pengumpulan->tanggal_submit)->lessThanOrEqualTo($deadline)
? 'dikumpulkan'
: 'terlambat';
$sudahKumpul = true;
} else {
$status = $now->greaterThan($deadline) ? 'terlambat' : 'belum';
$status = $now->greaterThan($deadline) ? 'terlambat' : 'belum';
$sudahKumpul = false;
}
return [
@ -55,7 +58,7 @@ public function index()
'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-',
'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-',
'status' => $status,
'sudah_kumpul' => !is_null($pengumpulan),
'sudah_kumpul' => $sudahKumpul,
'lampiran' => $pengumpulan?->lampiran_tugas,
'exp' => $pengumpulan?->exp ?? 0,
'file_tugas' => $tugas->lampiran,
@ -69,9 +72,6 @@ public function index()
return view('siswa.tugas.index', compact('tugasBelum', 'tugasTerlambat', 'tugasSelesai'));
}
/**
* Detail tugas + form submit
*/
public function show($id_tugas)
{
$siswa = Auth::guard('siswa')->user();
@ -87,14 +87,18 @@ public function show($id_tugas)
->where('id_siswa', $siswa->id_siswa)
->first();
$sudahKumpul = !is_null($pengumpulan);
$sudahKumpul = $pengumpulan && $pengumpulan->lampiran_tugas !== null;
$terlambat = Carbon::now()->greaterThan(Carbon::parse($tugas->deadline));
return view('siswa.tugas.show', compact('tugas', 'pengumpulan', 'sudahKumpul', 'terlambat'));
}
/**
* Submit / upload jawaban tugas
* Submit tugas.
*
* EXP tidak lagi diupdate ke leaderboard di sini.
* EXP dihitung dinamis di LeaderboardController::hitungExpSiswa()
* berdasarkan tanggal_submit vs deadline secara real-time.
*/
public function submit(Request $request, $id_tugas)
{
@ -116,6 +120,7 @@ public function submit(Request $request, $id_tugas)
$sudahAda = PengumpulanTugas::where('id_tugas', $id_tugas)
->where('id_siswa', $siswa->id_siswa)
->whereNotNull('lampiran_tugas')
->exists();
if ($sudahAda) {
@ -126,45 +131,75 @@ public function submit(Request $request, $id_tugas)
$filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('pengumpulan_tugas', $filename, 'public');
$now = Carbon::now();
$deadline = Carbon::parse($tugas->deadline);
$status = $now->greaterThan($deadline) ? 'terlambat' : 'dikumpulkan';
$now = Carbon::now();
$deadline = Carbon::parse($tugas->deadline);
$terlambat = $now->greaterThan($deadline);
$status = $terlambat ? 'terlambat' : 'dikumpulkan';
PengumpulanTugas::create([
'id_tugas' => $id_tugas,
'id_siswa' => $siswa->id_siswa,
'lampiran_tugas' => $path,
'tanggal_submit' => $now,
'exp' => 0,
'status' => $status,
]);
// Cek & berikan badge tugas hanya jika tepat waktu
if ($status === 'dikumpulkan') {
// Snapshot badge sebelum pengecekan
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
PengumpulanTugas::updateOrCreate(
['id_tugas' => $id_tugas, 'id_siswa' => $siswa->id_siswa],
[
'lampiran_tugas' => $path,
'tanggal_submit' => $now,
'exp' => $terlambat ? -5 : 10, // catatan saja, tidak dipakai di leaderboard
'status' => $status,
]
);
if (!$terlambat) {
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray();
app(BadgeService::class)->checkTugasBadges($siswa->id_siswa);
// Deteksi badge yang baru didapat
$badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
$idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum);
$badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray();
$idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum);
if (!empty($idBadgeBaru)) {
$badgeBaru = Badge::whereIn('id_badge', $idBadgeBaru)->get();
session()->flash('badge_baru', $badgeBaru);
session()->flash('badge_baru', Badge::whereIn('id_badge', $idBadgeBaru)->get());
}
}
$pesan = $status === 'terlambat'
? 'Tugas berhasil dikumpulkan (terlambat).'
: 'Tugas berhasil dikumpulkan! 🎉';
$pesan = $terlambat
? 'Tugas berhasil dikumpulkan (terlambat). Kamu mendapat penalti -5 poin. 😔'
: 'Tugas berhasil dikumpulkan! Kamu mendapat +10 poin! 🎉';
return redirect()->route('siswa.tugas.index')->with('success', $pesan);
}
public function gantiFile(Request $request, $id_tugas)
{
$siswa = Auth::guard('siswa')->user();
$request->validate([
'lampiran_tugas' => 'required|file|mimes:pdf,doc,docx,jpg,jpeg,png|max:5120',
]);
$tugas = Tugas::whereHas('mengajar', function ($q) use ($siswa) {
$q->where('id_kelas', $siswa->id_kelas);
})
->where('id_tugas', $id_tugas)
->firstOrFail();
if (Carbon::now()->greaterThan(Carbon::parse($tugas->deadline))) {
return back()->with('error_ganti', 'Deadline sudah lewat, file tidak dapat diganti.');
}
$pengumpulan = PengumpulanTugas::where('id_tugas', $id_tugas)
->where('id_siswa', $siswa->id_siswa)
->firstOrFail();
if ($pengumpulan->lampiran_tugas) {
Storage::disk('public')->delete($pengumpulan->lampiran_tugas);
}
$file = $request->file('lampiran_tugas');
$filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('pengumpulan_tugas', $filename, 'public');
$pengumpulan->update([
'lampiran_tugas' => $path,
'tanggal_submit' => Carbon::now(),
'status' => 'dikumpulkan',
'exp' => 10,
]);
return back()->with('success', 'File jawaban berhasil diganti! ✅');
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AdminAuthenticate
{
public function handle(Request $request, Closure $next)
{
if (!Auth::guard('admin')->check()) {
return redirect()->route('admin.login');
}
return $next($request);
}
}

View File

@ -4,6 +4,8 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Guru;
use App\Models\PesertaChallenge;
class Challenge extends Model
{
@ -14,7 +16,7 @@ class Challenge extends Model
protected $primaryKey = 'id_challenge';
protected $fillable = [
'id_admin',
'id_guru',
'judul_challenge',
'deskripsi',
'exp',
@ -41,4 +43,14 @@ public function soal()
return $this->hasMany(SoalChallenge::class, 'id_challenge');
}
public function peserta()
{
return $this->hasMany(PesertaChallenge::class, 'id_challenge');
}
public function guru()
{
return $this->belongsTo(Guru::class, 'id_guru', 'id_guru');
}
}

View File

@ -9,25 +9,35 @@ class Guru extends Authenticatable
{
use HasFactory;
protected $table = 'gurus';
protected $table = 'gurus';
protected $primaryKey = 'id_guru';
public $incrementing = true;
protected $keyType = 'int';
public $incrementing = true;
protected $keyType = 'int';
protected $fillable = [
'nip',
'nama',
'password',
'foto_profil'
'foto_profil',
'remember_token',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'password' => 'hashed',
];
// Relasi ke Mengajar
public function mengajars()
{
return $this->hasMany(Mengajar::class, 'id_guru', 'id_guru');
}
}
public function challenges()
{
return $this->hasMany(Challenge::class, 'id_guru', 'id_guru');
}
}

View File

@ -35,4 +35,10 @@ public function kelas()
{
return $this->belongsTo(Kelas::class, 'id_kelas', 'id_kelas');
}
// Relasi ke Tugas
public function tugas()
{
return $this->hasMany(Tugas::class, 'id_mengajar', 'id_mengajar');
}
}

View File

@ -15,5 +15,6 @@ public function register(): void
public function boot(): void
{
Paginator::defaultView('vendor.pagination.bootstrap-5');
app()->usePublicPath(base_path('public'));
}
}

View File

@ -14,15 +14,14 @@ class BadgeService
{
/**
* Syarat (kolom `syarat` di tabel badges) yang dikenali sistem.
* Nilai ini harus sama persis dengan isi kolom `syarat` di DB.
*
* CHALLENGE
* challenge_1 selesaikan 1 challenge
* challenge_3 selesaikan 3 challenge
*
* TUGAS
* tugas_1 kumpulkan 1 tugas (tepat waktu)
* tugas_3 kumpulkan 3 tugas (tepat waktu)
* tugas_1 kumpulkan 1 tugas tepat waktu (tanggal_submit deadline)
* tugas_3 kumpulkan 3 tugas tepat waktu (tanggal_submit deadline)
*
* LEADERBOARD (dicek & dicabut secara real-time)
* leaderboard_top5 masuk top 5 leaderboard kelas aktif
@ -35,7 +34,6 @@ class BadgeService
/**
* Dipanggil setelah siswa submit challenge.
* Mengecek & memberikan badge challenge.
*/
public function checkChallengeBadges(int $idSiswa): void
{
@ -49,24 +47,28 @@ public function checkChallengeBadges(int $idSiswa): void
/**
* Dipanggil setelah siswa submit tugas.
* Mengecek & memberikan badge tugas (hanya status 'dikumpulkan' / tepat waktu).
* Badge hanya diberikan jika tugas dikumpulkan TEPAT WAKTU
* (tanggal_submit deadline tugas).
*/
public function checkTugasBadges(int $idSiswa): void
{
$jumlahKumpul = PengumpulanTugas::where('id_siswa', $idSiswa)
->where('status', 'dikumpulkan')
// Join pengumpulan_tugas dengan tugas untuk membandingkan
// tanggal_submit dengan deadline secara langsung di DB.
$jumlahTepatWaktu = DB::table('pengumpulan_tugas')
->join('tugas', 'pengumpulan_tugas.id_tugas', '=', 'tugas.id_tugas')
->where('pengumpulan_tugas.id_siswa', $idSiswa)
->whereNotNull('pengumpulan_tugas.lampiran_tugas')
->whereColumn('pengumpulan_tugas.tanggal_submit', '<=', 'tugas.deadline')
->count();
$this->grantIfEligible($idSiswa, 'tugas_1', $jumlahKumpul >= 1);
$this->grantIfEligible($idSiswa, 'tugas_3', $jumlahKumpul >= 3);
$this->grantIfEligible($idSiswa, 'tugas_1', $jumlahTepatWaktu >= 1);
$this->grantIfEligible($idSiswa, 'tugas_3', $jumlahTepatWaktu >= 3);
}
/**
* Dipanggil dari endpoint JSON leaderboard (polling real-time).
* Badge leaderboard DICABUT jika siswa tidak lagi memenuhi syarat,
* dan DIBERIKAN KEMBALI jika kembali memenuhi syarat.
*
* Hanya leaderboard semester & tahun ajaran aktif yang dievaluasi.
*/
public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void
{
@ -80,11 +82,9 @@ public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void
$ranking = $lb?->ranking ?? 0;
// top5: ranking 1-5, top1: hanya ranking 1
$isTop5 = $ranking >= 1 && $ranking <= 5;
$isTop1 = $ranking === 1;
// Untuk badge leaderboard: grant jika eligible, revoke jika tidak
$this->grantOrRevoke($idSiswa, 'leaderboard_top5', $isTop5);
$this->grantOrRevoke($idSiswa, 'leaderboard_top1', $isTop1);
}
@ -105,10 +105,9 @@ private function grantIfEligible(int $idSiswa, string $syarat, bool $eligible):
$badge = Badge::where('syarat', $syarat)->first();
if (!$badge) {
return; // badge belum di-seed di DB, skip
return;
}
// Idempoten: cek dulu sebelum insert
$sudahPunya = SiswaBadge::where('id_siswa', $idSiswa)
->where('id_badge', $badge->id_badge)
->exists();
@ -138,22 +137,18 @@ private function grantOrRevoke(int $idSiswa, string $syarat, bool $eligible): vo
->first();
if ($eligible && !$record) {
// Berikan badge
SiswaBadge::create([
'id_siswa' => $idSiswa,
'id_badge' => $badge->id_badge,
'tanggal_diberikan' => Carbon::now(),
]);
} elseif (!$eligible && $record) {
// Cabut badge
$record->delete();
}
// Jika sudah punya & masih eligible, atau tidak punya & tidak eligible → tidak perlu apa-apa
}
/**
* Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang,
* konsisten dengan logika di ChallengeController.
* Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang.
*/
private function semesterAktif(): array
{

View File

@ -10,8 +10,10 @@
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'admin.auth' => \App\Http\Middleware\AdminAuthenticate::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
build/manifest.json Normal file
View File

@ -0,0 +1,16 @@
{
"resources/css/app.css": {
"file": "assets/app-Cv_tjT6n.css",
"src": "resources/css/app.css",
"isEntry": true,
"names": [
"app.css"
]
},
"resources/js/app.js": {
"file": "assets/app-CXDpL9bK.js",
"name": "app",
"src": "resources/js/app.js",
"isEntry": true
}
}

View File

@ -65,7 +65,7 @@
|
*/
'timezone' => 'UTC',
'timezone' => 'Asia/Jakarta',
/*
|--------------------------------------------------------------------------

301
config/dompdf.php Normal file
View File

@ -0,0 +1,301 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Settings
|--------------------------------------------------------------------------
|
| Set some default values. It is possible to add all defines that can be set
| in dompdf_config.inc.php. You can also override the entire config file.
|
*/
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'public_path' => base_path('public'),
/*
* Dejavu Sans font is missing glyphs for converted entities, turn it off if you need to show and £.
*/
'convert_entities' => true,
'options' => [
/**
* The location of the DOMPDF font directory
*
* The location of the directory where DOMPDF will store fonts and font metrics
* Note: This directory must exist and be writable by the webserver process.
* *Please note the trailing slash.*
*
* Notes regarding fonts:
* Additional .afm font metrics can be added by executing load_font.php from command line.
*
* Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must
* be embedded in the pdf file or the PDF may not display correctly. This can significantly
* increase file size unless font subsetting is enabled. Before embedding a font please
* review your rights under the font license.
*
* Any font specification in the source HTML is translated to the closest font available
* in the font directory.
*
* The pdf standard "Base 14 fonts" are:
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats.
*/
'font_dir' => storage_path('fonts'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/**
* The location of the DOMPDF font cache directory
*
* This directory contains the cached font metrics for the fonts used by DOMPDF.
* This directory can be the same as DOMPDF_FONT_DIR
*
* Note: This directory must exist and be writable by the webserver process.
*/
'font_cache' => storage_path('fonts'),
/**
* The location of a temporary directory.
*
* The directory specified must be writeable by the webserver process.
* The temporary directory is required to download remote images and when
* using the PDFLib back end.
*/
'temp_dir' => sys_get_temp_dir(),
/**
* ==== IMPORTANT ====
*
* dompdf's "chroot": Prevents dompdf from accessing system files or other
* files on the webserver. All local files opened by dompdf must be in a
* subdirectory of this directory. DO NOT set it to '/' since this could
* allow an attacker to use dompdf to read any files on the server. This
* should be an absolute path.
* This is only checked on command line call by dompdf.php, but not by
* direct class use like:
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
*/
'chroot' => realpath(base_path()),
/**
* Protocol whitelist
*
* Protocols and PHP wrappers allowed in URIs, and the validation rules
* that determine if a resouce may be loaded. Full support is not guaranteed
* for the protocols/wrappers specified
* by this array.
*
* @var array
*/
'allowed_protocols' => [
'data://' => ['rules' => []],
'file://' => ['rules' => []],
'http://' => ['rules' => []],
'https://' => ['rules' => []],
],
/**
* Operational artifact (log files, temporary files) path validation
*/
'artifactPathValidation' => null,
/**
* @var string
*/
'log_output_file' => null,
/**
* Whether to enable font subsetting or not.
*/
'enable_font_subsetting' => false,
/**
* The PDF rendering backend to use
*
* Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and
* 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will
* fall back on CPDF. 'GD' renders PDFs to graphic files.
* {@link * Canvas_Factory} ultimately determines which rendering class to
* instantiate based on this setting.
*
* Both PDFLib & CPDF rendering backends provide sufficient rendering
* capabilities for dompdf, however additional features (e.g. object,
* image and font support, etc.) differ between backends. Please see
* {@link PDFLib_Adapter} for more information on the PDFLib backend
* and {@link CPDF_Adapter} and lib/class.pdf.php for more information
* on CPDF. Also see the documentation for each backend at the links
* below.
*
* The GD rendering backend is a little different than PDFLib and
* CPDF. Several features of CPDF and PDFLib are not supported or do
* not make any sense when creating image files. For example,
* multiple pages are not supported, nor are PDF 'objects'. Have a
* look at {@link GD_Adapter} for more information. GD support is
* experimental, so use it at your own risk.
*
* @link http://www.pdflib.com
* @link http://www.ros.co.nz/pdf
* @link http://www.php.net/image
*/
'pdf_backend' => 'CPDF',
/**
* html target media view which should be rendered into pdf.
* List of types and parsing rules for future extensions:
* http://www.w3.org/TR/REC-html40/types.html
* screen, tty, tv, projection, handheld, print, braille, aural, all
* Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3.
* Note, even though the generated pdf file is intended for print output,
* the desired content might be different (e.g. screen or projection view of html file).
* Therefore allow specification of content here.
*/
'default_media_type' => 'screen',
/**
* The default paper size.
*
* North America standard is "letter"; other countries generally "a4"
*
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
*/
'default_paper_size' => 'a4',
/**
* The default paper orientation.
*
* The orientation of the page (portrait or landscape).
*
* @var string
*/
'default_paper_orientation' => 'portrait',
/**
* The default font family
*
* Used if no suitable fonts can be found. This must exist in the font folder.
*
* @var string
*/
'default_font' => 'serif',
/**
* Image DPI setting
*
* This setting determines the default DPI setting for images and fonts. The
* DPI may be overridden for inline images by explictly setting the
* image's width & height style attributes (i.e. if the image's native
* width is 600 pixels and you specify the image's width as 72 points,
* the image will have a DPI of 600 in the rendered PDF. The DPI of
* background images can not be overridden and is controlled entirely
* via this parameter.
*
* For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
* If a size in html is given as px (or without unit as image size),
* this tells the corresponding size in pt.
* This adjusts the relative sizes to be similar to the rendering of the
* html page in a reference browser.
*
* In pdf, always 1 pt = 1/72 inch
*
* Rendering resolution of various browsers in px per inch:
* Windows Firefox and Internet Explorer:
* SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:?
* Linux Firefox:
* about:config *resolution: Default:96
* (xorg screen dimension in mm and Desktop font dpi settings are ignored)
*
* Take care about extra font/image zoom factor of browser.
*
* In images, <img> size in pixel attribute, img css style, are overriding
* the real image dimension in px for rendering.
*
* @var int
*/
'dpi' => 96,
/**
* Enable embedded PHP
*
* If this setting is set to true then DOMPDF will automatically evaluate embedded PHP contained
* within <script type="text/php"> ... </script> tags.
*
* ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages)
* is a security risk.
* Embedded scripts are run with the same level of system access available to dompdf.
* Set this option to false (recommended) if you wish to process untrusted documents.
* This setting may increase the risk of system exploit.
* Do not change this settings without understanding the consequences.
* Additional documentation is available on the dompdf wiki at:
* https://github.com/dompdf/dompdf/wiki
*
* @var bool
*/
'enable_php' => false,
/**
* Enable inline JavaScript
*
* If this setting is set to true then DOMPDF will automatically insert JavaScript code contained
* within <script type="text/javascript"> ... </script> tags as written into the PDF.
* NOTE: This is PDF-based JavaScript to be executed by the PDF viewer,
* not browser-based JavaScript executed by Dompdf.
*
* @var bool
*/
'enable_javascript' => true,
/**
* Enable remote file access
*
* If this setting is set to true, DOMPDF will access remote sites for
* images and CSS files as required.
*
* ==== IMPORTANT ====
* This can be a security risk, in particular in combination with isPhpEnabled and
* allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
* This allows anonymous users to download legally doubtful internet content which on
* tracing back appears to being downloaded by your server, or allows malicious php code
* in remote html pages to be executed by your server with your account privileges.
*
* This setting may increase the risk of system exploit. Do not change
* this settings without understanding the consequences. Additional
* documentation is available on the dompdf wiki at:
* https://github.com/dompdf/dompdf/wiki
*
* @var bool
*/
'enable_remote' => false,
/**
* List of allowed remote hosts
*
* Each value of the array must be a valid hostname.
*
* This will be used to filter which resources can be loaded in combination with
* isRemoteEnabled. If enable_remote is FALSE, then this will have no effect.
*
* Leave to NULL to allow any remote host.
*
* @var array|null
*/
'allowed_remote_hosts' => null,
/**
* A ratio applied to the fonts height to be more like browsers' line height
*/
'font_height_ratio' => 1.1,
/**
* Use the HTML5 Lib parser
*
* @deprecated This feature is now always on in dompdf 2.x
*
* @var bool
*/
'enable_html5_parser' => true,
],
];

View File

@ -8,6 +8,74 @@
padding: 0;
}
/*
CSS VARIABLES default: SISWA (biru)
*/
:root {
--blob1: rgba(100, 180, 255, 0.12);
--blob2: rgba(120, 80, 255, 0.1);
--badge-dot: #69f0ae;
--role-color: #1a5fb8;
--underline-from: #60b4ff;
--underline-to: #a78bfa;
--submit-from: #2b8ef3;
--submit-to: #1a7ae0;
--submit-shadow: rgba(43, 142, 243, 0.35);
--submit-shadow-hover: rgba(43, 142, 243, 0.45);
--check-color: #2b8ef3;
--focus-border: #2b8ef3;
--focus-shadow: rgba(43, 142, 243, 0.12);
--link-color: #1a5fb8;
--link-hover-bg: #e8f4ff;
}
/*
GURU Hijau (sesuai portal landing page)
Referensi: .pc-green / .bp-green / #accent2
*/
body.login-guru {
--blob1: rgba(34, 197, 94, 0.14);
--blob2: rgba(134, 239, 172, 0.12);
--badge-dot: #4ade80;
--role-color: #15803d;
--underline-from: #22c55e;
--underline-to: #86efac;
--submit-from: #22c55e;
--submit-to: #16a34a;
--submit-shadow: rgba(34, 197, 94, 0.35);
--submit-shadow-hover: rgba(34, 197, 94, 0.45);
--check-color: #22c55e;
--focus-border: #22c55e;
--focus-shadow: rgba(34, 197, 94, 0.12);
--link-color: #15803d;
--link-hover-bg: #f0fdf4;
}
/*
ADMIN Orange (sesuai portal landing page)
Referensi: .pc-orange / .bp-orange / #accent1
*/
body.login-admin {
--blob1: rgba(249, 115, 22, 0.14);
--blob2: rgba(251, 146, 60, 0.12);
--badge-dot: #fb923c;
--role-color: #c2410c;
--underline-from: #f97316;
--underline-to: #fdba74;
--submit-from: #f97316;
--submit-to: #ea580c;
--submit-shadow: rgba(249, 115, 22, 0.35);
--submit-shadow-hover: rgba(249, 115, 22, 0.45);
--check-color: #f97316;
--focus-border: #f97316;
--focus-shadow: rgba(249, 115, 22, 0.12);
--link-color: #c2410c;
--link-hover-bg: #fff7ed;
}
/*
BASE background SISWA (biru navy)
*/
body {
font-family: "Plus Jakarta Sans", sans-serif;
min-height: 100vh;
@ -26,12 +94,41 @@ body {
justify-content: center;
}
/*
GURU background hijau tua yang dalam & padu
Nada: forest / emerald, lebih kaya dari sekadar gelap
*/
body.login-guru {
background: linear-gradient(
135deg,
#052e16 0%,
#064e2a 30%,
#166534 60%,
#065f2d 85%,
#052e16 100%
) !important;
}
/*
ADMIN background oranye tua yang hangat & padu
Nada: ember / mahogany, lebih kaya & tidak terlalu merah
*/
body.login-admin {
background: linear-gradient(
135deg,
#431407 0%,
#6b2109 30%,
#9a3412 60%,
#7c2d12 85%,
#431407 100%
) !important;
}
/* ── DECORATIVE SHAPES ── */
.shape {
position: fixed;
pointer-events: none;
}
.shape-1 {
width: 320px;
height: 320px;
@ -41,7 +138,6 @@ .shape-1 {
left: 80px;
transform: rotate(20deg);
}
.shape-2 {
width: 220px;
height: 220px;
@ -51,7 +147,6 @@ .shape-2 {
left: 260px;
transform: rotate(-15deg);
}
.shape-3 {
width: 180px;
height: 180px;
@ -61,7 +156,6 @@ .shape-3 {
right: 320px;
transform: rotate(30deg);
}
.shape-4 {
width: 400px;
height: 400px;
@ -71,7 +165,6 @@ .shape-4 {
right: -80px;
transform: rotate(12deg);
}
.shape-5 {
width: 100px;
height: 100px;
@ -88,24 +181,22 @@ .blob {
pointer-events: none;
filter: blur(80px);
}
.blob-1 {
width: 500px;
height: 500px;
background: rgba(100, 180, 255, 0.12);
background: var(--blob1);
top: -200px;
left: -100px;
}
.blob-2 {
width: 350px;
height: 350px;
background: rgba(120, 80, 255, 0.1);
background: var(--blob2);
bottom: -100px;
right: 200px;
}
/* ── MAIN LAYOUT ── */
/* ── LAYOUT ── */
.page-wrap {
width: 100%;
max-width: 1100px;
@ -137,7 +228,6 @@ .school-tag {
align-items: center;
gap: 10px;
}
.school-tag::before {
content: "";
display: block;
@ -158,7 +248,6 @@ .left-logo {
justify-content: center;
margin-bottom: 32px;
}
.left-logo img {
width: 38px;
height: 38px;
@ -175,7 +264,6 @@ .left-welcome {
letter-spacing: -1px;
color: white;
}
.left-welcome span {
display: block;
color: rgba(255, 255, 255, 0.35);
@ -216,7 +304,6 @@ .btn-learn {
cursor: pointer;
transition: all 0.2s;
}
.btn-learn:hover {
background: rgba(255, 255, 255, 0.2);
}
@ -235,6 +322,24 @@ .form-card {
color: #1a1a2e;
}
/* GURU — card putih kehijauan lembut */
body.login-guru .form-card {
background: #f0fdf4;
border-color: rgba(134, 239, 172, 0.35);
box-shadow:
0 32px 64px rgba(0, 0, 0, 0.22),
0 0 0 1px rgba(255, 255, 255, 0.75);
}
/* ADMIN — card putih keoranyean lembut */
body.login-admin .form-card {
background: #fff7ed;
border-color: rgba(253, 186, 116, 0.35);
box-shadow:
0 32px 64px rgba(0, 0, 0, 0.22),
0 0 0 1px rgba(255, 255, 255, 0.75);
}
.card-title-wrap {
text-align: center;
margin-bottom: 28px;
@ -251,19 +356,27 @@ .role-badge {
padding: 5px 14px;
font-size: 10px;
font-weight: 700;
color: #5b3fc0;
color: var(--role-color);
letter-spacing: 1.5px;
text-transform: uppercase;
font-family: "Sora", sans-serif;
margin-bottom: 14px;
}
body.login-guru .role-badge {
border-color: rgba(134, 239, 172, 0.45);
}
body.login-admin .role-badge {
border-color: rgba(253, 186, 116, 0.45);
}
.badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #69f0ae;
box-shadow: 0 0 6px #69f0ae;
background: var(--badge-dot);
box-shadow: 0 0 6px var(--badge-dot);
}
.card-title {
@ -279,7 +392,11 @@ .title-underline {
display: inline-block;
width: 36px;
height: 3px;
background: linear-gradient(90deg, #60b4ff, #a78bfa);
background: linear-gradient(
90deg,
var(--underline-from),
var(--underline-to)
);
border-radius: 99px;
margin-top: 2px;
}
@ -288,7 +405,6 @@ .title-underline {
.field-group {
margin-bottom: 14px;
}
.field-label {
display: block;
font-size: 13px;
@ -296,13 +412,11 @@ .field-label {
color: #4a5568;
margin-bottom: 7px;
}
.field-wrap {
position: relative;
display: flex;
align-items: center;
}
.field-wrap .field-input {
padding-right: 44px;
}
@ -321,15 +435,22 @@ .field-input {
transition: all 0.2s;
}
body.login-guru .field-input {
border-color: #bbf7d0;
}
body.login-admin .field-input {
border-color: #fed7aa;
}
.field-input::placeholder {
color: #bbb5cc;
font-weight: 400;
}
.field-input:focus {
border-color: #7c5cbf;
border-color: var(--focus-border);
background: white;
box-shadow: 0 0 0 3px rgba(124, 92, 191, 0.1);
box-shadow: 0 0 0 3px var(--focus-shadow);
}
.toggle-password {
@ -342,7 +463,6 @@ .toggle-password {
display: flex;
align-items: center;
}
.eye-icon {
width: 17px;
height: 17px;
@ -350,7 +470,6 @@ .eye-icon {
pointer-events: none;
transition: opacity 0.2s;
}
.toggle-password:hover .eye-icon {
opacity: 0.6;
}
@ -375,10 +494,16 @@ .remember-check {
position: relative;
flex-shrink: 0;
}
body.login-guru .remember-check {
border-color: #86efac;
}
body.login-admin .remember-check {
border-color: #fdba74;
}
.remember-check:checked {
background: #7c5cbf;
border-color: #7c5cbf;
background: var(--check-color);
border-color: var(--check-color);
}
.remember-check:checked::after {
@ -393,7 +518,6 @@ .remember-check:checked::after {
border-left: none;
transform: rotate(45deg);
}
.remember-text {
font-size: 13px;
color: #6b7280;
@ -405,7 +529,7 @@ .remember-text {
/* ── SUBMIT BUTTON ── */
.submit-btn {
width: 100%;
background: linear-gradient(135deg, #7c5cbf, #5b3fc0);
background: linear-gradient(135deg, #2b8ef3, #1a7ae0);
color: white;
border: none;
border-radius: 12px;
@ -415,25 +539,47 @@ .submit-btn {
font-family: "Plus Jakarta Sans", sans-serif;
cursor: pointer;
transition: all 0.25s;
box-shadow: 0 8px 24px rgba(91, 63, 192, 0.3);
box-shadow: 0 8px 24px rgba(43, 142, 243, 0.35);
letter-spacing: 0.3px;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 12px 30px rgba(91, 63, 192, 0.4);
box-shadow: 0 12px 30px rgba(43, 142, 243, 0.45);
}
.submit-btn:active {
transform: translateY(0);
}
/* GURU — tombol hijau */
body.login-guru .submit-btn {
background: linear-gradient(135deg, #22c55e, #16a34a);
box-shadow: 0 8px 24px rgba(34, 197, 94, 0.35);
}
body.login-guru .submit-btn:hover {
box-shadow: 0 12px 30px rgba(34, 197, 94, 0.45);
}
/* ADMIN — tombol orange */
body.login-admin .submit-btn {
background: linear-gradient(135deg, #f97316, #ea580c);
box-shadow: 0 8px 24px rgba(249, 115, 22, 0.35);
}
body.login-admin .submit-btn:hover {
box-shadow: 0 12px 30px rgba(249, 115, 22, 0.45);
}
/* ── DIVIDER ── */
.card-divider {
border: none;
border-top: 1px solid #ede9fb;
margin: 20px 0 16px;
}
body.login-guru .card-divider {
border-top-color: #bbf7d0;
}
body.login-admin .card-divider {
border-top-color: #fed7aa;
}
/* ── OTHER PORTALS ── */
.other-portals {
@ -445,9 +591,8 @@ .other-portals {
justify-content: center;
gap: 6px;
}
.other-portals a {
color: #5b3fc0;
color: var(--link-color);
font-weight: 700;
text-decoration: none;
padding: 5px 12px;
@ -455,9 +600,8 @@ .other-portals a {
font-size: 15px;
transition: background 0.2s;
}
.other-portals a:hover {
background: #ede9fb;
background: var(--link-hover-bg);
}
/* ── TOAST ── */
@ -475,7 +619,6 @@ .toast-error {
toastIn 0.3s ease both,
toastOut 0.4s ease 3.5s forwards;
}
@keyframes toastIn {
from {
opacity: 0;
@ -508,7 +651,6 @@ .back-link {
transition: color 0.2s;
font-weight: 500;
}
.back-link:hover {
color: rgba(255, 255, 255, 0.7);
}

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
//
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Seeders;
use App\Models\Guru;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class GuruSeeder extends Seeder
{
public function run(): void
{
$gurus = [
['nip' => '2343762663300043', 'nama' => 'Barizatul Kamilah'],
['nip' => '2635762664200062', 'nama' => 'Beny Setiawan'],
['nip' => '2460770671130082', 'nama' => 'Hasan'],
['nip' => '6535747649200022', 'nama' => 'Fibrian Kartika Nuswanto'],
['nip' => '8637774675130152', 'nama' => 'Andy Nur Rachman'],
['nip' => '4447762663130173', 'nama' => 'Edi Hariyanto'],
['nip' => '3739766667130182', 'nama' => 'Fitriyah Arizkiyanti'],
['nip' => '4743763664300082', 'nama' => 'Uswatun Hasanah'],
['nip' => '6433772673230272', 'nama' => 'Alvi Hidayati'],
['nip' => '9651770671130122', 'nama' => 'Haris Nurtanio'],
['nip' => '8152760661300073', 'nama' => 'Firlya Citra Kurniawati'],
['nip' => '1957769670130032', 'nama' => 'Ahmad Timbul Sholeh'],
['nip' => '1949759659200002', 'nama' => 'Muhammad Sudarmaji'],
['nip' => '3044771672130253', 'nama' => 'Vishal Rahmat Dharmawan'],
['nip' => '5641769670230172', 'nama' => 'Ayu Ridhawati'],
['nip' => '8558757658200002', 'nama' => 'Adi Sumantri'],
['nip' => '4944762664200052', 'nama' => 'Sutin Rofikah'],
['nip' => '0043764665130213', 'nama' => 'Suwarno Arieska'],
['nip' => '8955757659300062', 'nama' => 'Ratnaningtyas Trisnasanti'],
];
foreach ($gurus as $guru) {
Guru::create([
'nip' => $guru['nip'],
'nama' => $guru['nama'],
'password' => Hash::make('password'),
]);
}
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Database\Seeders;
use App\Models\Siswa;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SiswaSeeder1 extends Seeder
{
public function run(): void
{
$siswas = [
['nisn' => '0092118525', 'nama' => 'Andika Pranata', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0094253436', 'nama' => 'Andra Sattiawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0092333773', 'nama' => 'Aryo Mahesa Tegar Hermawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0071760338', 'nama' => 'Dafid Anjasmara', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0091809817', 'nama' => 'Dirga Duwi Kusuma', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0095281507', 'nama' => 'Elena Risky Ananta', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0107707867', 'nama' => 'Fitriatul Jannah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0104289644', 'nama' => 'Michkel Stevano Afftafian', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0088998591', 'nama' => 'MOCH. Fido Haris Avian Kafabi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0095338610', 'nama' => 'Mohammad Aril Fatoni', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0099906862', 'nama' => 'Mohammad Felan Madali', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0107699211', 'nama' => 'Mohammad Vicky Safarudin Nisar', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0098431453', 'nama' => 'Muhammad Ardiansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0097601928', 'nama' => 'Muhammad Fiki Ardiyanto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '3107025595', 'nama' => 'Muhammad Haikal', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0089936758', 'nama' => 'Muhammad Kamil', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '3105815399', 'nama' => 'Panji Permana Putra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0091910408', 'nama' => 'Rizki Bachtiar Nadiansyah Ataullah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0099891074', 'nama' => 'Rofal Ardiyansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1],
['nisn' => '0095014424', 'nama' => 'Ahmad Yali', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0099370321', 'nama' => 'Ana Septian Nuraini Fajrih', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0103667815', 'nama' => 'Andini Arifatul Hasana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0091939551', 'nama' => 'Arkan Sakti Nusantara', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0117055954', 'nama' => 'Bela Ananda', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0099568560', 'nama' => 'Dewi Nur Arika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0108033407', 'nama' => 'Dinda Febriani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0083728858', 'nama' => 'Ella Nuryanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '3095151919', 'nama' => 'Enisa', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0108283354', 'nama' => 'Fina Ferianti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0095581495', 'nama' => 'Gabriel Ubaydillah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '3098215305', 'nama' => 'Halimatul Azkiah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0106567109', 'nama' => 'Innani Fajariyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0092219128', 'nama' => 'Kevin Ananda Pratama', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0098856597', 'nama' => 'Keysha Queen Salsabila', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0101865258', 'nama' => 'Lailatun Nasilah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0094527645', 'nama' => 'Ludfia Ayu Lestari', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0102553007', 'nama' => 'Maulia Feby Fajriyanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '3090856561', 'nama' => 'Meilandari Putri', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0092397923', 'nama' => 'Muchammad Jaka Firmansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0092084214', 'nama' => 'Nurul Aulia Ul Hasanah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0109110353', 'nama' => 'Qurrotul Aini', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0084601470', 'nama' => 'Saiful Rohman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0106960081', 'nama' => 'Selawati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '3092586488', 'nama' => 'Sitti Nasila', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0105183313', 'nama' => 'Valent Editya Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2],
['nisn' => '0091977438', 'nama' => 'Abdul Muavi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0082929164', 'nama' => 'Abimanyu Restu Darmawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
];
foreach ($siswas as $siswa) {
Siswa::create([
'nisn' => $siswa['nisn'],
'nama' => $siswa['nama'],
'tempat_lahir' => $siswa['tempat_lahir'],
'tanggal_lahir' => $siswa['tanggal_lahir'],
'id_kelas' => $siswa['id_kelas'],
'password' => Hash::make('dummy'),
]);
}
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Database\Seeders;
use App\Models\Siswa;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SiswaSeeder2 extends Seeder
{
public function run(): void
{
$siswas = [
['nisn' => '0091379890', 'nama' => 'Auli Maulidiya Khair', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0084775056', 'nama' => 'Balqies Nurcahyani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0078290039', 'nama' => 'Bima Ramando Fitriah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0086803610', 'nama' => 'Bramasta Baramuli Manulang', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0099480755', 'nama' => 'Desta Bramasta Finzhando', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0099455196', 'nama' => 'Desty Alika Gusman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0085225993', 'nama' => 'Diaz Dwi Putra Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0084181311', 'nama' => 'Moh Aldi Yansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0087864310', 'nama' => 'Mohammad Raihan Alfarezi Ramadan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0096867243', 'nama' => 'Mohammad Rendi Hardiansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0085980153', 'nama' => 'Muhammad Kevin Ardiansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0092255148', 'nama' => 'Muhammad Khalid Al Mahbubillah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0081831303', 'nama' => 'Muhammad Naufal Arif Ardiyansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0096212311', 'nama' => 'Muhammad Noer Alief', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0093508530', 'nama' => 'Muhammad Randy', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0089492385', 'nama' => 'Raihan Azammi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0084546336', 'nama' => 'Reztanza Tunggal Pragasta', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0086792897', 'nama' => 'Sri Quratul Aini', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0089736250', 'nama' => 'Teguh Raka Saputra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3],
['nisn' => '0082223414', 'nama' => 'Afrizal Malik Haromain', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0084044726', 'nama' => 'Aisyah Oktafia', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '3091019404', 'nama' => 'Alfa Nuril Hasanah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0095563022', 'nama' => 'Aprilia Suci Andini', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0096938889', 'nama' => 'Aruni Alfa Zulfikar', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0095427183', 'nama' => 'Fheby Isni Wagina', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0088298252', 'nama' => 'Halimatus Sadiya', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0084144682', 'nama' => 'Khofifah Dwi Ningsih', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0096432735', 'nama' => 'Kholifatun Nabilah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0089220766', 'nama' => 'Lina Hikmatul Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0082068509', 'nama' => 'Mochammad Farrel Raihan Firdaus', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0061008438', 'nama' => 'Mohammad Anggi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0098849761', 'nama' => 'Mohammad Arif Saifurrahman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '3087033551', 'nama' => 'Mohammad Faisal Sefti Andika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0086748054', 'nama' => 'Muhammad Bayu Purnama Putra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0096262155', 'nama' => 'Muhammad Izil Karim', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0095339031', 'nama' => 'Muhammad Maulana Riskianto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0085951656', 'nama' => 'Muhammad Novan Abdurahman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0085808002', 'nama' => 'Muhammad Ridlo Rofius Syan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0098590833', 'nama' => 'Nafiqotul Riskiyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0084753667', 'nama' => 'Nila Septi Ramadani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0087109093', 'nama' => 'Oktafia Fitri Ramadani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0071526400', 'nama' => 'Putri Wulandari', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0086502762', 'nama' => 'Rahmad Dwi Ramadhani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '3072074045', 'nama' => 'Siti Rofik Atul Amalia', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0071345906', 'nama' => 'Suryani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0095924117', 'nama' => 'Velani Egilika Elsa Putri', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0099036913', 'nama' => 'Vita Febri Virgiyanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
];
foreach ($siswas as $siswa) {
Siswa::create([
'nisn' => $siswa['nisn'],
'nama' => $siswa['nama'],
'tempat_lahir' => $siswa['tempat_lahir'],
'tanggal_lahir' => $siswa['tanggal_lahir'],
'id_kelas' => $siswa['id_kelas'],
'password' => Hash::make('dummy'),
]);
}
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Database\Seeders;
use App\Models\Siswa;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SiswaSeeder3 extends Seeder
{
public function run(): void
{
$siswas = [
['nisn' => '0087804840', 'nama' => 'Zaky Rahmad Romadhon', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4],
['nisn' => '0089349277', 'nama' => 'Abdul Abi Muit', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0085016633', 'nama' => 'Achmad Rifaldyn', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0072334482', 'nama' => 'Alan Okvan Prasetyo', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '3082900200', 'nama' => 'Bagas Dwi Adiansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0067011212', 'nama' => 'Dwi Alfian Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0085892435', 'nama' => 'Maulana Malik Ibrohim', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0072928557', 'nama' => 'Mohammad Ramadhani Nasrollah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0078577984', 'nama' => 'Mohammad Roni Hardiyanto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0087456391', 'nama' => 'Mohammad Sobri', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0089852561', 'nama' => 'Mohammad Yusuf', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0083326995', 'nama' => 'Muhammad Ainur Rizki', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0088090959', 'nama' => 'Muhammad Erik Saputra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0073155064', 'nama' => 'Muhammad Fikrul Ilmi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0082286825', 'nama' => 'Muhammad Giovanni Irsyad Daffa', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0142201337', 'nama' => 'Muhammad Niki', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0064715822', 'nama' => 'Reno Saputra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0073090750', 'nama' => 'Rici Candra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0075636251', 'nama' => 'Triyan Saiful Hidayah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0089983035', 'nama' => 'Yusril Andika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5],
['nisn' => '0086002173', 'nama' => 'Aditya Rizky Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0088596027', 'nama' => 'Ahmad Alfarizi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0072890707', 'nama' => 'Ahmad Farisi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0082953576', 'nama' => 'Alvin Kurniawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0078134661', 'nama' => 'Atikah Faizah Lailatus Saadah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0075610319', 'nama' => 'Barneth Ajib', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0074835788', 'nama' => 'Candra Ardiansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0078383354', 'nama' => 'Citra Megawati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0079070926', 'nama' => 'Desti Kiki Amelia', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0073492593', 'nama' => 'Desy Arisanty', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0078177505', 'nama' => 'Dwi Rohmatun Naysila', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0086141267', 'nama' => 'Dymas Pratama Soeprapto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0085573222', 'nama' => 'Herfina', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0067256961', 'nama' => 'Husnan Mutawakkil Ridwan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0082153364', 'nama' => 'Masturoh', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0077401869', 'nama' => 'Miftahul Arifin', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0081945314', 'nama' => 'Muhammad Adam Septian', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0073353544', 'nama' => 'Muhammad Haris Maksum', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0072499877', 'nama' => 'Muhammad Taufiqur Rohim', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0077906903', 'nama' => 'Naora Octa Cantika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0072860449', 'nama' => 'Nurul Amera', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0076195649', 'nama' => 'Rasya Luluk Rahmawati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0072695717', 'nama' => 'Raudatus Sarifah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0073232486', 'nama' => 'Sefina Dwi Arisandi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0071174401', 'nama' => 'Siti Fatmiati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0088603747', 'nama' => 'Siti Maimuna', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
['nisn' => '0082235835', 'nama' => 'Suci Susanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6],
];
foreach ($siswas as $siswa) {
Siswa::create([
'nisn' => $siswa['nisn'],
'nama' => $siswa['nama'],
'tempat_lahir' => $siswa['tempat_lahir'],
'tanggal_lahir' => $siswa['tanggal_lahir'],
'id_kelas' => $siswa['id_kelas'],
'password' => Hash::make('dummy'),
]);
}
}
}

View File

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

View File

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View File

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 373 B

View File

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 333 B

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 KiB

View File

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View File

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

BIN
images/badges/tugas_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 679 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 982 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 786 B

View File

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 882 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 695 B

After

Width:  |  Height:  |  Size: 695 B

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 918 B

After

Width:  |  Height:  |  Size: 918 B

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 297 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 939 B

After

Width:  |  Height:  |  Size: 939 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show More