Compare commits
10 Commits
7b0cb0350f
...
1498c6e762
| Author | SHA1 | Date |
|---|---|---|
|
|
1498c6e762 | |
|
|
5785ad88fc | |
|
|
67dacd6671 | |
|
|
8afad7d12b | |
|
|
3dfee40774 | |
|
|
be6db46930 | |
|
|
4f29a9ed24 | |
|
|
3b21f82135 | |
|
|
f84402aff7 | |
|
|
d06fafb7cc |
|
|
@ -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'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,184 +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',
|
||||
'id_badge' => 'nullable|exists:badges,id_badge',
|
||||
'tenggat_waktu' => 'required|date|after:now',
|
||||
'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,
|
||||
]);
|
||||
|
||||
// Attach ke kelas
|
||||
$challenge->kelas()->sync($request->id_kelas);
|
||||
|
||||
// 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',
|
||||
'id_badge' => 'nullable|exists:badges,id_badge',
|
||||
'tenggat_waktu' => 'required|date',
|
||||
'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,
|
||||
]);
|
||||
|
||||
$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,
|
||||
'id_badge' => $challenge->id_badge,
|
||||
'tenggat_waktu' => $challenge->tenggat_waktu,
|
||||
'kelas' => $challenge->kelas->pluck('id_kelas'),
|
||||
'soal' => $challenge->soal,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\Mengajar;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
class GuruController extends Controller
|
||||
{
|
||||
|
|
@ -62,15 +63,20 @@ public function store(Request $request)
|
|||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
// Tiap mapel berpasangan dengan kelas di index yang sama
|
||||
foreach ($request->id_mapel as $i => $idMapel) {
|
||||
$idKelas = $request->id_kelas[$i] ?? $request->id_kelas[0];
|
||||
Mengajar::create([
|
||||
'id_guru' => $guru->id_guru,
|
||||
$idKelas = $request->id_kelas[$i] ?? $request->id_kelas[0];
|
||||
|
||||
Mengajar::updateOrCreate(
|
||||
[
|
||||
'id_mapel' => $idMapel,
|
||||
'id_kelas' => $idKelas,
|
||||
]);
|
||||
}
|
||||
'id_guru' => null,
|
||||
],
|
||||
[
|
||||
'id_guru' => $guru->id_guru,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.guru.index')
|
||||
->with('success', 'Data guru berhasil ditambahkan.');
|
||||
|
|
@ -117,11 +123,17 @@ public function update(Request $request, string $id)
|
|||
|
||||
foreach ($request->id_mapel as $i => $idMapel) {
|
||||
$idKelas = $request->id_kelas[$i] ?? $request->id_kelas[0];
|
||||
Mengajar::create([
|
||||
'id_guru' => $guru->id_guru,
|
||||
'id_mapel' => $idMapel,
|
||||
'id_kelas' => $idKelas,
|
||||
]);
|
||||
|
||||
Mengajar::updateOrCreate(
|
||||
[
|
||||
'id_mapel' => $idMapel,
|
||||
'id_kelas' => $idKelas,
|
||||
'id_guru' => null,
|
||||
],
|
||||
[
|
||||
'id_guru' => $guru->id_guru,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.guru.index')
|
||||
|
|
@ -137,6 +149,70 @@ public function destroy(string $id)
|
|||
->with('success', 'Data guru berhasil dihapus.');
|
||||
}
|
||||
|
||||
public function downloadPdf(Request $request)
|
||||
{
|
||||
$query = Guru::with('mengajars.mapel', 'mengajars.kelas');
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('nama', 'like', "%$search%")
|
||||
->orWhere('nip', 'like', "%$search%");
|
||||
});
|
||||
}
|
||||
$gurus = $query->get();
|
||||
|
||||
// Override DomPDF options langsung
|
||||
$options = new \Dompdf\Options();
|
||||
$options->setChroot(base_path('public'));
|
||||
$options->setIsRemoteEnabled(true);
|
||||
|
||||
$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)
|
||||
{
|
||||
$query = Guru::with('mengajars.mapel', 'mengajars.kelas');
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('nama', 'like', "%$search%")
|
||||
->orWhere('nip', 'like', "%$search%");
|
||||
});
|
||||
}
|
||||
$gurus = $query->get();
|
||||
|
||||
$filename = 'daftar-guru-' . date('Ymd') . '.csv';
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => "attachment; filename=\"$filename\"",
|
||||
];
|
||||
|
||||
$callback = function () use ($gurus) {
|
||||
$file = fopen('php://output', 'w');
|
||||
// BOM agar Excel bisa baca UTF-8
|
||||
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||
fputcsv($file, ['No', 'Nama', 'NIP', 'Mata Pelajaran', 'Kelas']);
|
||||
foreach ($gurus as $i => $guru) {
|
||||
$mapels = $guru->mengajars->map(fn($m) => optional($m->mapel)->nama_mapel ?? '-')->join(', ');
|
||||
$kelas = $guru->mengajars->map(fn($m) => (optional($m->kelas)->tingkat . ' ' . optional($m->kelas)->nama_kelas))->join(', ');
|
||||
fputcsv($file, [$i + 1, $guru->nama, $guru->nip, $mapels, $kelas]);
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Ambil kelas yang memiliki mapel tertentu (lewat tabel mengajars)
|
||||
* Dipanggil via AJAX saat admin pilih mapel di modal
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
use App\Models\Kelas;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
|
||||
class KelasController extends Controller
|
||||
|
|
@ -66,7 +67,7 @@ public function update(Request $request, $id_kelas)
|
|||
'max:50',
|
||||
Rule::unique('kelas')->where(function ($query) use ($request) {
|
||||
return $query->where('tingkat', $request->tingkat);
|
||||
})->ignore($id, 'id_kelas'),
|
||||
})->ignore($id_kelas, 'id_kelas'),
|
||||
],
|
||||
'tingkat' => 'required|in:X,XI,XII',
|
||||
], [
|
||||
|
|
@ -88,4 +89,58 @@ public function destroy($id_kelas)
|
|||
return redirect()->route('admin.kelas.index')
|
||||
->with('success', 'Data kelas berhasil dihapus!');
|
||||
}
|
||||
|
||||
public function downloadPdf(Request $request)
|
||||
{
|
||||
$query = Kelas::query();
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('nama_kelas', 'like', "%$search%")
|
||||
->orWhere('id_kelas', 'like', "%$search%");
|
||||
}
|
||||
$kelass = $query->get();
|
||||
|
||||
$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)
|
||||
{
|
||||
$query = Kelas::query();
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('nama_kelas', 'like', "%$search%")
|
||||
->orWhere('id_kelas', 'like', "%$search%");
|
||||
}
|
||||
$kelass = $query->get();
|
||||
|
||||
$filename = 'daftar-kelas-' . date('Ymd') . '.csv';
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => "attachment; filename=\"$filename\"",
|
||||
];
|
||||
|
||||
$callback = function () use ($kelass) {
|
||||
$file = fopen('php://output', 'w');
|
||||
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||
fputcsv($file, ['No', 'ID Kelas', 'Nama Kelas', 'Tingkat']);
|
||||
foreach ($kelass as $i => $kelas) {
|
||||
fputcsv($file, [$i + 1, $kelas->id_kelas, $kelas->nama_kelas, $kelas->tingkat]);
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
use App\Models\Kelas;
|
||||
use App\Models\Mengajar;
|
||||
use Illuminate\Http\Request;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
class MapelController extends Controller
|
||||
{
|
||||
|
|
@ -44,31 +45,46 @@ public function index(Request $request)
|
|||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
{
|
||||
$request->validate([
|
||||
'nama_mapel' => 'required|max:100',
|
||||
'kelas' => 'required|array',
|
||||
]);
|
||||
|
||||
$request->validate([
|
||||
'nama_mapel' => 'required|max:100',
|
||||
'kelas' => 'required|array'
|
||||
]);
|
||||
// Cek duplikat nama mapel di tiap kelas yang dipilih
|
||||
foreach ($request->kelas as $idKelas) {
|
||||
$sudahAda = Mengajar::whereHas('mapel', function ($q) use ($request) {
|
||||
$q->where('nama_mapel', $request->nama_mapel);
|
||||
})
|
||||
->where('id_kelas', $idKelas)
|
||||
->exists();
|
||||
|
||||
$mapel = Mapel::create([
|
||||
'nama_mapel' => $request->nama_mapel
|
||||
]);
|
||||
|
||||
foreach ($request->kelas as $kelas) {
|
||||
|
||||
Mengajar::create([
|
||||
'id_mapel' => $mapel->id_mapel,
|
||||
'id_kelas' => $kelas,
|
||||
'id_guru' => null
|
||||
]);
|
||||
if ($sudahAda) {
|
||||
$namaKelas = \App\Models\Kelas::find($idKelas);
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error_from', 'tambah')
|
||||
->withErrors([
|
||||
'nama_mapel' => 'Mapel "' . $request->nama_mapel . '" sudah ada di kelas ' . optional($namaKelas)->nama_kelas . '!'
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.mapel.index')
|
||||
->with('success', 'Data mata pelajaran berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
$mapel = Mapel::create([
|
||||
'nama_mapel' => $request->nama_mapel,
|
||||
]);
|
||||
|
||||
foreach ($request->kelas as $kelas) {
|
||||
Mengajar::create([
|
||||
'id_mapel' => $mapel->id_mapel,
|
||||
'id_kelas' => $kelas,
|
||||
'id_guru' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.mapel.index')
|
||||
->with('success', 'Data mata pelajaran berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
|
|
@ -101,8 +117,6 @@ public function update(Request $request, $id)
|
|||
->with('success', 'Data mata pelajaran berhasil diupdate!');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
|
||||
|
|
@ -115,4 +129,71 @@ public function destroy($id)
|
|||
return redirect()->route('admin.mapel.index')
|
||||
->with('success', 'Data mata pelajaran berhasil dihapus!');
|
||||
}
|
||||
|
||||
public function downloadPdf(Request $request)
|
||||
{
|
||||
$query = Mapel::with('kelas');
|
||||
if ($request->filled('search')) {
|
||||
$query->where(function($q) use ($request) {
|
||||
$q->where('nama_mapel', 'like', "%$request->search%")
|
||||
->orWhere('id_mapel', 'like', "%$request->search%");
|
||||
});
|
||||
}
|
||||
if ($request->filled('filter_kelas')) {
|
||||
$query->whereHas('kelas', function($q) use ($request) {
|
||||
$q->where('kelas.id_kelas', $request->filter_kelas);
|
||||
});
|
||||
}
|
||||
$mapels = $query->get();
|
||||
|
||||
$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)
|
||||
{
|
||||
$query = Mapel::with('kelas');
|
||||
if ($request->filled('search')) {
|
||||
$query->where(function($q) use ($request) {
|
||||
$q->where('nama_mapel', 'like', "%$request->search%")
|
||||
->orWhere('id_mapel', 'like', "%$request->search%");
|
||||
});
|
||||
}
|
||||
if ($request->filled('filter_kelas')) {
|
||||
$query->whereHas('kelas', function($q) use ($request) {
|
||||
$q->where('kelas.id_kelas', $request->filter_kelas);
|
||||
});
|
||||
}
|
||||
$mapels = $query->get();
|
||||
|
||||
$filename = 'daftar-mapel-' . date('Ymd') . '.csv';
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => "attachment; filename=\"$filename\"",
|
||||
];
|
||||
|
||||
$callback = function () use ($mapels) {
|
||||
$file = fopen('php://output', 'w');
|
||||
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||
fputcsv($file, ['No', 'ID Mapel', 'Nama Mata Pelajaran', 'Kelas']);
|
||||
foreach ($mapels as $i => $mapel) {
|
||||
$kelas = $mapel->kelas->map(fn($k) => $k->tingkat . ' ' . $k->nama_kelas)->join(', ');
|
||||
fputcsv($file, [$i + 1, $mapel->id_mapel, $mapel->nama_mapel, $kelas]);
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
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
|
||||
{
|
||||
|
|
@ -14,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'));
|
||||
}
|
||||
|
|
@ -40,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')
|
||||
|
|
@ -75,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();
|
||||
|
|
@ -113,4 +128,71 @@ public function destroy($id)
|
|||
return redirect()->route('admin.siswa.index')
|
||||
->with('success', 'Data siswa berhasil dihapus!');
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,10 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
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
|
||||
{
|
||||
|
|
@ -15,25 +18,90 @@ public function index()
|
|||
|
||||
try {
|
||||
$totalKelas = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->distinct('id_kelas')
|
||||
->count('id_kelas');
|
||||
->distinct('id_kelas')->count('id_kelas');
|
||||
|
||||
$totalMapel = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->distinct('id_mapel')
|
||||
->count('id_mapel');
|
||||
->distinct('id_mapel')->count('id_mapel');
|
||||
|
||||
$kelasIds = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->pluck('id_kelas')
|
||||
->unique();
|
||||
->pluck('id_kelas')->unique()->values()->toArray(); // fix: toArray()
|
||||
|
||||
$totalSiswa = Siswa::whereIn('id_kelas', $kelasIds)->count();
|
||||
|
||||
// ── 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();
|
||||
|
||||
$chartLabels = [];
|
||||
$chartTepat = [];
|
||||
$chartTerlambat = [];
|
||||
|
||||
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;
|
||||
|
||||
$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;
|
||||
$totalKelas = 0;
|
||||
$totalMapel = 0;
|
||||
$totalSiswa = 0;
|
||||
$chartLabels = [];
|
||||
$chartTepat = [];
|
||||
$chartTerlambat = [];
|
||||
$leaderboard = collect();
|
||||
$semester = '-';
|
||||
$tahunAjaran = '-';
|
||||
}
|
||||
|
||||
return view('guru.dashboard', compact('totalKelas', 'totalMapel', 'totalSiswa'));
|
||||
return view('guru.dashboard', compact(
|
||||
'totalKelas', 'totalMapel', 'totalSiswa',
|
||||
'chartLabels', 'chartTepat', 'chartTerlambat',
|
||||
'leaderboard', 'semester', 'tahunAjaran'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Guru;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Guru;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GuruController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Guru::query();
|
||||
|
||||
// SEARCH
|
||||
if ($request->has('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('nama', 'like', "%$search%")
|
||||
->orWhere('nip', 'like', "%$search%");
|
||||
}
|
||||
|
||||
// SHOW PER PAGE
|
||||
$perPage = $request->get('perPage', 10);
|
||||
|
||||
$gurus = $query->paginate($perPage)->appends($request->all());
|
||||
|
||||
return view('guru.guru.index', compact('gurus'));
|
||||
}
|
||||
}
|
||||
|
|
@ -4,26 +4,38 @@
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Kelas;
|
||||
use App\Models\Mengajar;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class KelasController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Kelas::query();
|
||||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
// Ambil hanya id_kelas yang diajar guru ini
|
||||
$kelasIds = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->pluck('id_kelas')
|
||||
->unique();
|
||||
|
||||
$query = Kelas::whereIn('id_kelas', $kelasIds);
|
||||
|
||||
// SEARCH
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('nama_kelas', 'like', "%$search%")
|
||||
->orWhere('id_kelas', 'like', "%$search%");
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('nama_kelas', 'like', "%$search%")
|
||||
->orWhere('tingkat', 'like', "%$search%");
|
||||
});
|
||||
}
|
||||
|
||||
// SHOW PER PAGE
|
||||
$perPage = $request->get('perPage', 10);
|
||||
|
||||
$kelass = $query->paginate($perPage)->appends($request->all());
|
||||
$kelass = $query->orderBy('tingkat')
|
||||
->orderBy('nama_kelas')
|
||||
->paginate($perPage)
|
||||
->appends($request->all());
|
||||
|
||||
return view('guru.kelas.index', compact('kelass'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -16,12 +16,10 @@ public function index()
|
|||
{
|
||||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
// Ambil mengajar dengan relasi mapel & kelas
|
||||
// Group by id_mapel agar tidak duplikat jika guru ajar mapel sama di kelas berbeda
|
||||
$mengajars = Mengajar::with(['mapel', 'kelas'])
|
||||
->where('id_guru', $guru->id_guru)
|
||||
->get()
|
||||
->groupBy('id_mapel'); // group by mapel
|
||||
->groupBy('id_mapel');
|
||||
|
||||
return view('guru.mapel.index', compact('mengajars'));
|
||||
}
|
||||
|
|
@ -43,16 +41,15 @@ public function storeMateri(Request $request)
|
|||
'lampiran_materi.max' => 'Ukuran file maksimal 10MB.',
|
||||
]);
|
||||
|
||||
// Pastikan mengajar ini milik guru yang login
|
||||
$mengajar = Mengajar::where('id_mengajar', $request->id_mengajar)
|
||||
->where('id_guru', $guru->id_guru)
|
||||
->firstOrFail();
|
||||
|
||||
$path = null;
|
||||
if ($request->hasFile('lampiran_materi')) {
|
||||
$file = $request->file('lampiran_materi');
|
||||
$file = $request->file('lampiran_materi');
|
||||
$filename = 'materi_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs('materi', $filename, 'public');
|
||||
$path = $file->storeAs('materi', $filename, 'public');
|
||||
}
|
||||
|
||||
Materi::create([
|
||||
|
|
@ -74,24 +71,33 @@ public function storeTugas(Request $request)
|
|||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
$request->validate([
|
||||
'id_mengajar' => 'required|exists:mengajars,id_mengajar',
|
||||
'judul_tugas' => 'required|string|max:200',
|
||||
'keterangan' => 'nullable|string',
|
||||
'deadline' => 'required|date|after:now',
|
||||
'id_mengajar' => 'required|exists:mengajars,id_mengajar',
|
||||
'judul_tugas' => 'required|string|max:200',
|
||||
'keterangan' => 'nullable|string',
|
||||
'deadline' => 'required|date|after:now',
|
||||
'lampiran_tugas' => 'nullable|file|max:10240', // semua tipe file, maks 10MB
|
||||
], [
|
||||
'deadline.after' => 'Deadline harus lebih dari waktu sekarang.',
|
||||
'deadline.after' => 'Deadline harus lebih dari waktu sekarang.',
|
||||
'lampiran_tugas.max' => 'Ukuran file maksimal 10MB.',
|
||||
]);
|
||||
|
||||
// Pastikan mengajar ini milik guru yang login
|
||||
$mengajar = Mengajar::where('id_mengajar', $request->id_mengajar)
|
||||
->where('id_guru', $guru->id_guru)
|
||||
->firstOrFail();
|
||||
|
||||
$path = null;
|
||||
if ($request->hasFile('lampiran_tugas')) {
|
||||
$file = $request->file('lampiran_tugas');
|
||||
$filename = 'tugas_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs('tugas', $filename, 'public');
|
||||
}
|
||||
|
||||
Tugas::create([
|
||||
'id_mengajar' => $request->id_mengajar,
|
||||
'judul_tugas' => $request->judul_tugas,
|
||||
'keterangan' => $request->keterangan,
|
||||
'deadline' => $request->deadline,
|
||||
'lampiran' => $path,
|
||||
]);
|
||||
|
||||
return redirect()->route('guru.mapel.index')
|
||||
|
|
@ -105,7 +111,6 @@ public function historyMateri()
|
|||
{
|
||||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
// Ambil id_mengajar milik guru ini
|
||||
$idMengajars = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->pluck('id_mengajar');
|
||||
|
||||
|
|
@ -117,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
|
||||
*/
|
||||
|
|
@ -130,7 +188,6 @@ public function destroyMateri($id)
|
|||
->where('id_materi', $id)
|
||||
->firstOrFail();
|
||||
|
||||
// Hapus file jika ada
|
||||
if ($materi->lampiran_materi && \Storage::disk('public')->exists($materi->lampiran_materi)) {
|
||||
\Storage::disk('public')->delete($materi->lampiran_materi);
|
||||
}
|
||||
|
|
@ -180,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
|
||||
*/
|
||||
|
|
@ -193,6 +302,11 @@ public function destroyTugas($id)
|
|||
->where('id_tugas', $id)
|
||||
->firstOrFail();
|
||||
|
||||
// Hapus file lampiran jika ada
|
||||
if ($tugas->lampiran && \Storage::disk('public')->exists($tugas->lampiran)) {
|
||||
\Storage::disk('public')->delete($tugas->lampiran);
|
||||
}
|
||||
|
||||
$tugas->delete();
|
||||
|
||||
return redirect()->route('guru.tugas.history')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,34 +5,50 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Siswa;
|
||||
use App\Models\Kelas;
|
||||
use App\Models\Mengajar;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class SiswaController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Siswa::with('kelas');
|
||||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
// Ambil hanya id_kelas yang diajar guru ini
|
||||
$kelasIds = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->pluck('id_kelas')
|
||||
->unique();
|
||||
|
||||
$query = Siswa::with('kelas')
|
||||
->whereIn('id_kelas', $kelasIds);
|
||||
|
||||
// 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 != '') {
|
||||
$query->where('id_kelas', $request->filter_kelas);
|
||||
// FILTER BY KELAS (hanya kelas yang diajar guru ini)
|
||||
if ($request->filled('filter_kelas')) {
|
||||
// Pastikan filter kelas yang dipilih memang kelas yang diajar guru ini
|
||||
if ($kelasIds->contains($request->filter_kelas)) {
|
||||
$query->where('id_kelas', $request->filter_kelas);
|
||||
}
|
||||
}
|
||||
|
||||
// SHOW PER PAGE
|
||||
$perPage = $request->get('perPage', 10);
|
||||
$siswas = $query->paginate($perPage)->appends($request->all());
|
||||
|
||||
$siswas = $query->paginate($perPage)->appends($request->all());
|
||||
|
||||
// Ambil semua kelas untuk dropdown filter
|
||||
$kelass = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
|
||||
// Dropdown filter hanya tampilkan kelas yang diajar guru ini
|
||||
$kelass = Kelas::whereIn('id_kelas', $kelasIds)
|
||||
->orderBy('tingkat')
|
||||
->orderBy('nama_kelas')
|
||||
->get();
|
||||
|
||||
return view('guru.siswa.index', compact('siswas', 'kelass'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,12 @@
|
|||
namespace App\Http\Controllers\Siswa;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Badge;
|
||||
use App\Models\Challenge;
|
||||
use App\Models\PesertaChallenge;
|
||||
use App\Models\Leaderboard;
|
||||
use App\Models\SiswaBadge;
|
||||
use App\Services\BadgeService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
|
|
@ -90,24 +93,33 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// Semester & tahun ajaran otomatis
|
||||
$now = Carbon::now();
|
||||
$semester = $now->month >= 7 ? '1' : '2';
|
||||
$tahunAjaran = $now->month >= 7
|
||||
? $now->year . '/' . ($now->year + 1)
|
||||
: ($now->year - 1) . '/' . $now->year;
|
||||
|
||||
// Snapshot badge sebelum submit
|
||||
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)
|
||||
->pluck('id_badge')
|
||||
->toArray();
|
||||
|
||||
DB::transaction(function () use ($siswa, $challenge, $jawabanJson, $totalExp, $semester, $tahunAjaran) {
|
||||
PesertaChallenge::create([
|
||||
'id_challenge' => $challenge->id_challenge,
|
||||
|
|
@ -118,7 +130,6 @@ public function submit(Request $request, $id_challenge)
|
|||
'status' => 'selesai',
|
||||
]);
|
||||
|
||||
// firstOrCreate dengan semester & tahun_ajaran sebagai key
|
||||
$lb = Leaderboard::firstOrCreate(
|
||||
[
|
||||
'id_siswa' => $siswa->id_siswa,
|
||||
|
|
@ -131,7 +142,7 @@ public function submit(Request $request, $id_challenge)
|
|||
|
||||
$lb->increment('total_exp', $totalExp);
|
||||
|
||||
// Recalculate ranking per kelas + semester + tahun ajaran
|
||||
// Update ranking semua siswa di kelas ini
|
||||
Leaderboard::where('id_kelas', $siswa->id_kelas)
|
||||
->where('semester', $semester)
|
||||
->where('tahun_ajaran', $tahunAjaran)
|
||||
|
|
@ -140,6 +151,21 @@ public function submit(Request $request, $id_challenge)
|
|||
->each(fn($row, $i) => $row->update(['ranking' => $i + 1]));
|
||||
});
|
||||
|
||||
// Cek & berikan badge challenge
|
||||
app(BadgeService::class)->checkChallengeBadges($siswa->id_siswa);
|
||||
|
||||
// Deteksi badge baru
|
||||
$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);
|
||||
}
|
||||
|
||||
return redirect()->route('siswa.challenge.hasil', $id_challenge);
|
||||
}
|
||||
|
||||
|
|
@ -164,9 +190,12 @@ public function hasil($id_challenge)
|
|||
$totalSoal = $challenge->soal->count();
|
||||
$persentase = $totalSoal > 0 ? round(($benar / $totalSoal) * 100) : 0;
|
||||
|
||||
$badgeBaru = session('badge_baru', collect());
|
||||
|
||||
return view('siswa.challenge.hasil', compact(
|
||||
'challenge', 'peserta', 'jawabanSiswa',
|
||||
'benar', 'salah', 'totalSoal', 'persentase'
|
||||
'benar', 'salah', 'totalSoal', 'persentase',
|
||||
'badgeBaru'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -25,16 +25,10 @@ public function index()
|
|||
/** @var \App\Models\Siswa $siswa */
|
||||
$siswa = Auth::guard('siswa')->user();
|
||||
|
||||
// =============================================
|
||||
// 1. TUGAS — yang belum dikumpulkan siswa ini
|
||||
// dan deadline belum lewat
|
||||
// =============================================
|
||||
|
||||
// id_tugas yang sudah dikumpulkan siswa ini
|
||||
// Tugas pending (belum dikumpulkan, belum lewat deadline)
|
||||
$sudahDikumpulkan = PengumpulanTugas::where('id_siswa', $siswa->id_siswa)
|
||||
->pluck('id_tugas');
|
||||
|
||||
// Tugas untuk kelas siswa yang belum dikumpulkan
|
||||
$tugasRaw = Tugas::with(['mengajar.mapel'])
|
||||
->whereNotIn('id_tugas', $sudahDikumpulkan)
|
||||
->where('deadline', '>=', Carbon::now())
|
||||
|
|
@ -45,7 +39,6 @@ public function index()
|
|||
->take(5)
|
||||
->get();
|
||||
|
||||
// Kelompokkan per tanggal deadline
|
||||
$tugasList = [];
|
||||
foreach ($tugasRaw as $tugas) {
|
||||
$tgl = Carbon::parse($tugas->deadline)
|
||||
|
|
@ -55,43 +48,35 @@ public function index()
|
|||
$namaMapel = optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-';
|
||||
|
||||
$tugasList[$tgl][] = [
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 2. 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();
|
||||
|
||||
// =============================================
|
||||
// 3. TUGAS SELESAI MINGGU INI (mascot speech bubble)
|
||||
// =============================================
|
||||
// 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();
|
||||
|
||||
// =============================================
|
||||
// 4. LEADERBOARD — top 3 kelas siswa yang login
|
||||
// =============================================
|
||||
// Leaderboard
|
||||
$leaderboardRaw = Leaderboard::with('siswa')
|
||||
->where('id_kelas', $siswa->id_kelas)
|
||||
->orderBy('total_exp', 'desc')
|
||||
|
|
@ -108,8 +93,8 @@ public function index()
|
|||
|
||||
return view('siswa.dashboard', compact(
|
||||
'tugasList',
|
||||
'challengeDone',
|
||||
'challengeTotal',
|
||||
'tugasTotal',
|
||||
'tugasDikumpulkan',
|
||||
'tugasSelesai',
|
||||
'leaderboard',
|
||||
));
|
||||
|
|
|
|||
|
|
@ -4,13 +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 2–3 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();
|
||||
|
||||
|
|
@ -20,40 +109,83 @@ 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);
|
||||
|
||||
return compact('leaderboard', 'myRank', 'semester', 'tahunAjaran');
|
||||
return compact('leaderboard', 'myRank', 'semester', 'tahunAjaran', 'siswa');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$data = $this->getData();
|
||||
unset($data['siswa']);
|
||||
return view('siswa.leaderboard.index', $data);
|
||||
}
|
||||
|
||||
// Endpoint JSON untuk polling real-time
|
||||
/**
|
||||
* Endpoint JSON untuk polling real-time.
|
||||
*/
|
||||
public function json()
|
||||
{
|
||||
$data = $this->getData();
|
||||
$data = $this->getData();
|
||||
$siswa = $data['siswa'];
|
||||
|
||||
// Evaluasi badge leaderboard berdasarkan data terbaru
|
||||
app(BadgeService::class)->checkLeaderboardBadges(
|
||||
$siswa->id_siswa,
|
||||
$siswa->id_kelas
|
||||
);
|
||||
|
||||
$badgeSiswa = SiswaBadge::with('badge')
|
||||
->where('id_siswa', $siswa->id_siswa)
|
||||
->whereHas('badge', fn($q) => $q->whereIn('syarat', ['leaderboard_top5', 'leaderboard_top1']))
|
||||
->get()
|
||||
->map(fn($sb) => [
|
||||
'id_badge' => $sb->id_badge,
|
||||
'nama_badge' => $sb->badge->nama_badge,
|
||||
'deskripsi' => $sb->badge->deskripsi,
|
||||
'icon_url' => asset($sb->badge->icon_badge),
|
||||
]);
|
||||
|
||||
unset($data['siswa']);
|
||||
$data['badgeSiswa'] = $badgeSiswa;
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Http\Controllers\Siswa;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Badge;
|
||||
use App\Models\SiswaBadge;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
|
@ -10,6 +12,27 @@
|
|||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
public function edit()
|
||||
{
|
||||
$siswa = Auth::guard('siswa')->user();
|
||||
|
||||
// Semua badge yang ada di sistem
|
||||
$semuaBadge = Badge::all();
|
||||
|
||||
// ID badge yang dimiliki siswa
|
||||
$idBadgeDimiliki = SiswaBadge::where('id_siswa', $siswa->id_siswa)
|
||||
->pluck('id_badge')
|
||||
->toArray();
|
||||
|
||||
// Gabungkan: tiap badge + flag apakah siswa memilikinya
|
||||
$badges = $semuaBadge->map(function ($badge) use ($idBadgeDimiliki) {
|
||||
$badge->dimiliki = in_array($badge->id_badge, $idBadgeDimiliki);
|
||||
return $badge;
|
||||
});
|
||||
|
||||
return view('siswa.profile.edit', compact('badges'));
|
||||
}
|
||||
|
||||
public function updateAjax(Request $request)
|
||||
{
|
||||
$siswa = Auth::guard('siswa')->user();
|
||||
|
|
@ -38,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)) {
|
||||
|
|
@ -48,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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,15 @@
|
|||
namespace App\Http\Controllers\Siswa;
|
||||
|
||||
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;
|
||||
use App\Models\Mengajar;
|
||||
|
||||
class TugasController extends Controller
|
||||
{
|
||||
|
|
@ -17,14 +20,10 @@ public function __construct()
|
|||
$this->middleware('auth:siswa');
|
||||
}
|
||||
|
||||
/**
|
||||
* Daftar semua tugas untuk kelas siswa
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$siswa = Auth::guard('siswa')->user();
|
||||
|
||||
// Ambil semua tugas untuk kelas siswa
|
||||
$semuaTugas = Tugas::with(['mengajar.mapel', 'mengajar.guru'])
|
||||
->whereHas('mengajar', function ($q) use ($siswa) {
|
||||
$q->where('id_kelas', $siswa->id_kelas);
|
||||
|
|
@ -32,46 +31,47 @@ public function index()
|
|||
->orderBy('deadline', 'asc')
|
||||
->get();
|
||||
|
||||
// Tandai status tiap tugas untuk siswa ini
|
||||
$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; // 'dikumpulkan' atau 'terlambat'
|
||||
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 [
|
||||
'id_tugas' => $tugas->id_tugas,
|
||||
'judul' => $tugas->judul_tugas,
|
||||
'keterangan' => $tugas->keterangan,
|
||||
'deadline' => $deadline,
|
||||
'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-',
|
||||
'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-',
|
||||
'status' => $status,
|
||||
'sudah_kumpul'=> !is_null($pengumpulan),
|
||||
'lampiran' => $pengumpulan?->lampiran_tugas,
|
||||
'exp' => $pengumpulan?->exp ?? 0,
|
||||
'id_tugas' => $tugas->id_tugas,
|
||||
'judul' => $tugas->judul_tugas,
|
||||
'keterangan' => $tugas->keterangan,
|
||||
'deadline' => $deadline,
|
||||
'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-',
|
||||
'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-',
|
||||
'status' => $status,
|
||||
'sudah_kumpul' => $sudahKumpul,
|
||||
'lampiran' => $pengumpulan?->lampiran_tugas,
|
||||
'exp' => $pengumpulan?->exp ?? 0,
|
||||
'file_tugas' => $tugas->lampiran,
|
||||
];
|
||||
});
|
||||
|
||||
// Kelompokkan: belum & pending vs sudah dikumpulkan
|
||||
$tugasBelum = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] !== 'terlambat');
|
||||
$tugasBelum = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] !== 'terlambat');
|
||||
$tugasTerlambat = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] === 'terlambat');
|
||||
$tugasSelesai = $tugasList->filter(fn($t) => $t['sudah_kumpul']);
|
||||
$tugasSelesai = $tugasList->filter(fn($t) => $t['sudah_kumpul']);
|
||||
|
||||
return view('siswa.tugas.index', compact('tugasBelum', 'tugasTerlambat', 'tugasSelesai'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail tugas + form submit
|
||||
*/
|
||||
public function show($id_tugas)
|
||||
{
|
||||
$siswa = Auth::guard('siswa')->user();
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -108,45 +112,94 @@ public function submit(Request $request, $id_tugas)
|
|||
'lampiran_tugas.max' => 'Ukuran file maksimal 5MB.',
|
||||
]);
|
||||
|
||||
// Cek tugas valid untuk kelas siswa
|
||||
$tugas = Tugas::whereHas('mengajar', function ($q) use ($siswa) {
|
||||
$q->where('id_kelas', $siswa->id_kelas);
|
||||
})
|
||||
->where('id_tugas', $id_tugas)
|
||||
->firstOrFail();
|
||||
|
||||
// Cek sudah dikumpulkan belum
|
||||
$sudahAda = PengumpulanTugas::where('id_tugas', $id_tugas)
|
||||
->where('id_siswa', $siswa->id_siswa)
|
||||
->whereNotNull('lampiran_tugas')
|
||||
->exists();
|
||||
|
||||
if ($sudahAda) {
|
||||
return back()->with('error', 'Kamu sudah mengumpulkan tugas ini.');
|
||||
}
|
||||
|
||||
// Upload file
|
||||
$file = $request->file('lampiran_tugas');
|
||||
$filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs('pengumpulan_tugas', $filename, 'public');
|
||||
|
||||
// Tentukan status: terlambat atau dikumpulkan
|
||||
$now = Carbon::now();
|
||||
$deadline = Carbon::parse($tugas->deadline);
|
||||
$status = $now->greaterThan($deadline) ? 'terlambat' : 'dikumpulkan';
|
||||
$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, // exp diberikan guru saat menilai
|
||||
'status' => $status,
|
||||
]);
|
||||
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,
|
||||
]
|
||||
);
|
||||
|
||||
$pesan = $status === 'terlambat'
|
||||
? 'Tugas berhasil dikumpulkan (terlambat).'
|
||||
: 'Tugas berhasil dikumpulkan! 🎉';
|
||||
if (!$terlambat) {
|
||||
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray();
|
||||
app(BadgeService::class)->checkTugasBadges($siswa->id_siswa);
|
||||
$badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray();
|
||||
$idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum);
|
||||
if (!empty($idBadgeBaru)) {
|
||||
session()->flash('badge_baru', Badge::whereIn('id_badge', $idBadgeBaru)->get());
|
||||
}
|
||||
}
|
||||
|
||||
$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! ✅');
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,13 +16,17 @@ class Challenge extends Model
|
|||
protected $primaryKey = 'id_challenge';
|
||||
|
||||
protected $fillable = [
|
||||
'id_admin',
|
||||
'id_guru',
|
||||
'judul_challenge',
|
||||
'deskripsi',
|
||||
'exp',
|
||||
'id_badge',
|
||||
'durasi_pengerjaan',
|
||||
'tenggat_waktu',
|
||||
];
|
||||
protected $casts = [
|
||||
'durasi_pengerjaan' => 'integer',
|
||||
];
|
||||
|
||||
public function kelas()
|
||||
{
|
||||
|
|
@ -37,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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ class Kelas extends Model
|
|||
protected $keyType = 'int';
|
||||
|
||||
protected $fillable = [
|
||||
'id_kelas',
|
||||
'nama_kelas',
|
||||
'tingkat',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ class Tugas extends Model
|
|||
'judul_tugas',
|
||||
'keterangan',
|
||||
'deadline',
|
||||
'lampiran',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
|
|
|||
|
|
@ -3,22 +3,18 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Paginator::defaultView('vendor.pagination.bootstrap-5');
|
||||
app()->usePublicPath(base_path('public'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Badge;
|
||||
use App\Models\SiswaBadge;
|
||||
use App\Models\PesertaChallenge;
|
||||
use App\Models\PengumpulanTugas;
|
||||
use App\Models\Leaderboard;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BadgeService
|
||||
{
|
||||
/**
|
||||
* Syarat (kolom `syarat` di tabel badges) yang dikenali sistem.
|
||||
*
|
||||
* CHALLENGE
|
||||
* challenge_1 → selesaikan 1 challenge
|
||||
* challenge_3 → selesaikan 3 challenge
|
||||
*
|
||||
* TUGAS
|
||||
* 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
|
||||
* leaderboard_top1 → raih peringkat 1 leaderboard kelas aktif
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ENTRY POINTS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Dipanggil setelah siswa submit challenge.
|
||||
*/
|
||||
public function checkChallengeBadges(int $idSiswa): void
|
||||
{
|
||||
$jumlahSelesai = PesertaChallenge::where('id_siswa', $idSiswa)
|
||||
->where('status', 'selesai')
|
||||
->count();
|
||||
|
||||
$this->grantIfEligible($idSiswa, 'challenge_1', $jumlahSelesai >= 1);
|
||||
$this->grantIfEligible($idSiswa, 'challenge_3', $jumlahSelesai >= 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dipanggil setelah siswa submit tugas.
|
||||
* Badge hanya diberikan jika tugas dikumpulkan TEPAT WAKTU
|
||||
* (tanggal_submit ≤ deadline tugas).
|
||||
*/
|
||||
public function checkTugasBadges(int $idSiswa): void
|
||||
{
|
||||
// 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', $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.
|
||||
*/
|
||||
public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void
|
||||
{
|
||||
[$semester, $tahunAjaran] = $this->semesterAktif();
|
||||
|
||||
$lb = Leaderboard::where('id_siswa', $idSiswa)
|
||||
->where('id_kelas', $idKelas)
|
||||
->where('semester', $semester)
|
||||
->where('tahun_ajaran', $tahunAjaran)
|
||||
->first();
|
||||
|
||||
$ranking = $lb?->ranking ?? 0;
|
||||
|
||||
$isTop5 = $ranking >= 1 && $ranking <= 5;
|
||||
$isTop1 = $ranking === 1;
|
||||
|
||||
$this->grantOrRevoke($idSiswa, 'leaderboard_top5', $isTop5);
|
||||
$this->grantOrRevoke($idSiswa, 'leaderboard_top1', $isTop1);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PRIVATE HELPERS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Berikan badge jika eligible. Tidak melakukan apa-apa jika sudah punya.
|
||||
* Digunakan untuk badge challenge & tugas (tidak pernah dicabut).
|
||||
*/
|
||||
private function grantIfEligible(int $idSiswa, string $syarat, bool $eligible): void
|
||||
{
|
||||
if (!$eligible) {
|
||||
return;
|
||||
}
|
||||
|
||||
$badge = Badge::where('syarat', $syarat)->first();
|
||||
if (!$badge) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sudahPunya = SiswaBadge::where('id_siswa', $idSiswa)
|
||||
->where('id_badge', $badge->id_badge)
|
||||
->exists();
|
||||
|
||||
if (!$sudahPunya) {
|
||||
SiswaBadge::create([
|
||||
'id_siswa' => $idSiswa,
|
||||
'id_badge' => $badge->id_badge,
|
||||
'tanggal_diberikan' => Carbon::now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Berikan badge jika eligible, CABUT jika tidak.
|
||||
* Digunakan khusus untuk badge leaderboard (real-time).
|
||||
*/
|
||||
private function grantOrRevoke(int $idSiswa, string $syarat, bool $eligible): void
|
||||
{
|
||||
$badge = Badge::where('syarat', $syarat)->first();
|
||||
if (!$badge) {
|
||||
return;
|
||||
}
|
||||
|
||||
$record = SiswaBadge::where('id_siswa', $idSiswa)
|
||||
->where('id_badge', $badge->id_badge)
|
||||
->first();
|
||||
|
||||
if ($eligible && !$record) {
|
||||
SiswaBadge::create([
|
||||
'id_siswa' => $idSiswa,
|
||||
'id_badge' => $badge->id_badge,
|
||||
'tanggal_diberikan' => Carbon::now(),
|
||||
]);
|
||||
} elseif (!$eligible && $record) {
|
||||
$record->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang.
|
||||
*/
|
||||
private function semesterAktif(): array
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$semester = $now->month >= 7 ? '1' : '2';
|
||||
$tahunAjaran = $now->month >= 7
|
||||
? $now->year . '/' . ($now->year + 1)
|
||||
: ($now->year - 1) . '/' . $now->year;
|
||||
|
||||
return [$semester, $tahunAjaran];
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"doctrine/dbal": "^4.4",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/tinker": "^2.10.1"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,85 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b431c9cde1a46957fa68826ee65622af",
|
||||
"content-hash": "ff0b1f9e9305b6ad7e2e60e462da8ee3",
|
||||
"packages": [
|
||||
{
|
||||
"name": "barryvdh/laravel-dompdf",
|
||||
"version": "v3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-dompdf.git",
|
||||
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc",
|
||||
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dompdf/dompdf": "^3.0",
|
||||
"illuminate/support": "^9|^10|^11|^12|^13.0",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"larastan/larastan": "^2.7|^3.0",
|
||||
"orchestra/testbench": "^7|^8|^9.16|^10|^11.0",
|
||||
"phpro/grumphp": "^2.5",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
|
||||
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
|
||||
},
|
||||
"providers": [
|
||||
"Barryvdh\\DomPDF\\ServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Barryvdh\\DomPDF\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A DOMPDF Wrapper for Laravel",
|
||||
"keywords": [
|
||||
"dompdf",
|
||||
"laravel",
|
||||
"pdf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://fruitcake.nl",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/barryvdh",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-21T08:51:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.14.0",
|
||||
|
|
@ -531,6 +608,161 @@
|
|||
],
|
||||
"time": "2024-02-05T11:56:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
"version": "v3.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/dompdf.git",
|
||||
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
|
||||
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dompdf/php-font-lib": "^1.0.0",
|
||||
"dompdf/php-svg-lib": "^1.0.0",
|
||||
"ext-dom": "*",
|
||||
"ext-mbstring": "*",
|
||||
"masterminds/html5": "^2.0",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"mockery/mockery": "^1.3",
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "Needed to process images",
|
||||
"ext-gmagick": "Improves image processing performance",
|
||||
"ext-imagick": "Improves image processing performance",
|
||||
"ext-zlib": "Needed for pdf stream compression"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dompdf\\": "src/"
|
||||
},
|
||||
"classmap": [
|
||||
"lib/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Dompdf Community",
|
||||
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
|
||||
"homepage": "https://github.com/dompdf/dompdf",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/dompdf/issues",
|
||||
"source": "https://github.com/dompdf/dompdf/tree/v3.1.5"
|
||||
},
|
||||
"time": "2026-03-03T13:54:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/php-font-lib",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-font-lib.git",
|
||||
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
|
||||
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FontLib\\": "src/FontLib"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The FontLib Community",
|
||||
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse, export and make subsets of different types of font files.",
|
||||
"homepage": "https://github.com/dompdf/php-font-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-font-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
|
||||
},
|
||||
"time": "2026-01-20T14:10:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/php-svg-lib",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-svg-lib.git",
|
||||
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
|
||||
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabberworm/php-css-parser": "^8.4 || ^9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Svg\\": "src/Svg"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The SvgLib Community",
|
||||
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse and export to PDF SVG files.",
|
||||
"homepage": "https://github.com/dompdf/php-svg-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-svg-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
|
||||
},
|
||||
"time": "2026-01-02T16:01:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
"version": "v3.4.0",
|
||||
|
|
@ -2163,6 +2395,73 @@
|
|||
],
|
||||
"time": "2024-12-08T08:18:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
"version": "2.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Masterminds/html5-php.git",
|
||||
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
|
||||
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Masterminds\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matt Butcher",
|
||||
"email": "technosophos@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Matt Farina",
|
||||
"email": "matt@mattfarina.com"
|
||||
},
|
||||
{
|
||||
"name": "Asmir Mustafic",
|
||||
"email": "goetas@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "An HTML5 parser and serializer.",
|
||||
"homepage": "http://masterminds.github.io/html5-php",
|
||||
"keywords": [
|
||||
"HTML5",
|
||||
"dom",
|
||||
"html",
|
||||
"parser",
|
||||
"querypath",
|
||||
"serializer",
|
||||
"xml"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Masterminds/html5-php/issues",
|
||||
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
|
||||
},
|
||||
"time": "2025-07-25T09:04:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.9.0",
|
||||
|
|
@ -3479,6 +3778,86 @@
|
|||
},
|
||||
"time": "2025-09-04T20:59:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabberworm/php-css-parser",
|
||||
"version": "v9.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
||||
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
|
||||
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "1.4.0",
|
||||
"phpstan/extension-installer": "1.4.3",
|
||||
"phpstan/phpstan": "1.12.32 || 2.1.32",
|
||||
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
|
||||
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
|
||||
"phpunit/phpunit": "8.5.52",
|
||||
"rawr/phpunit-data-provider": "3.3.1",
|
||||
"rector/rector": "1.2.10 || 2.2.8",
|
||||
"rector/type-perfect": "1.0.0 || 2.1.0",
|
||||
"squizlabs/php_codesniffer": "4.0.1",
|
||||
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "9.4.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Rule/Rule.php",
|
||||
"src/RuleSet/RuleContainer.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Sabberworm\\CSS\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Raphael Schweikert"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake.github@qzdesign.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "Parser for CSS Files written in PHP",
|
||||
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
|
||||
"keywords": [
|
||||
"css",
|
||||
"parser",
|
||||
"stylesheet"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
|
||||
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0"
|
||||
},
|
||||
"time": "2026-03-03T17:31:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.3.0",
|
||||
|
|
@ -5956,6 +6335,149 @@
|
|||
],
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "thecodingmachine/safe",
|
||||
"version": "v3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thecodingmachine/safe.git",
|
||||
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
|
||||
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpunit/phpunit": "^10",
|
||||
"squizlabs/php_codesniffer": "^3.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/special_cases.php",
|
||||
"generated/apache.php",
|
||||
"generated/apcu.php",
|
||||
"generated/array.php",
|
||||
"generated/bzip2.php",
|
||||
"generated/calendar.php",
|
||||
"generated/classobj.php",
|
||||
"generated/com.php",
|
||||
"generated/cubrid.php",
|
||||
"generated/curl.php",
|
||||
"generated/datetime.php",
|
||||
"generated/dir.php",
|
||||
"generated/eio.php",
|
||||
"generated/errorfunc.php",
|
||||
"generated/exec.php",
|
||||
"generated/fileinfo.php",
|
||||
"generated/filesystem.php",
|
||||
"generated/filter.php",
|
||||
"generated/fpm.php",
|
||||
"generated/ftp.php",
|
||||
"generated/funchand.php",
|
||||
"generated/gettext.php",
|
||||
"generated/gmp.php",
|
||||
"generated/gnupg.php",
|
||||
"generated/hash.php",
|
||||
"generated/ibase.php",
|
||||
"generated/ibmDb2.php",
|
||||
"generated/iconv.php",
|
||||
"generated/image.php",
|
||||
"generated/imap.php",
|
||||
"generated/info.php",
|
||||
"generated/inotify.php",
|
||||
"generated/json.php",
|
||||
"generated/ldap.php",
|
||||
"generated/libxml.php",
|
||||
"generated/lzf.php",
|
||||
"generated/mailparse.php",
|
||||
"generated/mbstring.php",
|
||||
"generated/misc.php",
|
||||
"generated/mysql.php",
|
||||
"generated/mysqli.php",
|
||||
"generated/network.php",
|
||||
"generated/oci8.php",
|
||||
"generated/opcache.php",
|
||||
"generated/openssl.php",
|
||||
"generated/outcontrol.php",
|
||||
"generated/pcntl.php",
|
||||
"generated/pcre.php",
|
||||
"generated/pgsql.php",
|
||||
"generated/posix.php",
|
||||
"generated/ps.php",
|
||||
"generated/pspell.php",
|
||||
"generated/readline.php",
|
||||
"generated/rnp.php",
|
||||
"generated/rpminfo.php",
|
||||
"generated/rrd.php",
|
||||
"generated/sem.php",
|
||||
"generated/session.php",
|
||||
"generated/shmop.php",
|
||||
"generated/sockets.php",
|
||||
"generated/sodium.php",
|
||||
"generated/solr.php",
|
||||
"generated/spl.php",
|
||||
"generated/sqlsrv.php",
|
||||
"generated/ssdeep.php",
|
||||
"generated/ssh2.php",
|
||||
"generated/stream.php",
|
||||
"generated/strings.php",
|
||||
"generated/swoole.php",
|
||||
"generated/uodbc.php",
|
||||
"generated/uopz.php",
|
||||
"generated/url.php",
|
||||
"generated/var.php",
|
||||
"generated/xdiff.php",
|
||||
"generated/xml.php",
|
||||
"generated/xmlrpc.php",
|
||||
"generated/yaml.php",
|
||||
"generated/yaz.php",
|
||||
"generated/zip.php",
|
||||
"generated/zlib.php"
|
||||
],
|
||||
"classmap": [
|
||||
"lib/DateTime.php",
|
||||
"lib/DateTimeImmutable.php",
|
||||
"lib/Exceptions/",
|
||||
"generated/Exceptions/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
|
||||
"support": {
|
||||
"issues": "https://github.com/thecodingmachine/safe/issues",
|
||||
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/OskarStark",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/shish",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/silasjoisten",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/staabm",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-04T18:08:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
"version": "v2.3.0",
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
'timezone' => 'Asia/Jakarta',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
|
||||
];
|
||||
|
|
@ -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,18 +591,17 @@ .other-portals {
|
|||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.other-portals a {
|
||||
color: #5b3fc0;
|
||||
color: var(--link-color);
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
padding: 3px 8px;
|
||||
padding: 5px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.other-portals a:hover {
|
||||
background: #ede9fb;
|
||||
background: var(--link-hover-bg);
|
||||
}
|
||||
|
||||
/* ── TOAST ── */
|
||||
|
|
@ -474,7 +619,6 @@ .toast-error {
|
|||
toastIn 0.3s ease both,
|
||||
toastOut 0.4s ease 3.5s forwards;
|
||||
}
|
||||
|
||||
@keyframes toastIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
@ -507,7 +651,6 @@ .back-link {
|
|||
transition: color 0.2s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('tugas', function (Blueprint $table) {
|
||||
$table->string('lampiran')->nullable()->after('deadline');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tugas', function (Blueprint $table) {
|
||||
$table->dropColumn('lampiran');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// database/migrations/xxxx_fix_kelas_autoincrement.php
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Hapus kolom sisa refactor kalau masih ada
|
||||
if (Schema::hasColumn('kelas', 'id_kelas_old')) {
|
||||
Schema::table('kelas', function ($table) {
|
||||
$table->dropColumn('id_kelas_old');
|
||||
});
|
||||
}
|
||||
|
||||
// Reset AUTO_INCREMENT ke angka setelah max id_kelas yang ada
|
||||
DB::statement('ALTER TABLE kelas AUTO_INCREMENT = 1');
|
||||
}
|
||||
|
||||
public function down(): void {}
|
||||
};
|
||||
|
|
@ -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
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('challenges', function (Blueprint $table) {
|
||||
// Simpan dalam menit, nullable agar data lama tidak error
|
||||
$table->unsignedSmallInteger('durasi_pengerjaan')->nullable()->after('tenggat_waktu')
|
||||
->comment('Durasi pengerjaan dalam menit. NULL = tidak ada batas durasi.');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('challenges', function (Blueprint $table) {
|
||||
$table->dropColumn('durasi_pengerjaan');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
|
|
@ -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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 517 B |
|
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 373 B |
|
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 333 B |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 982 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 871 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 847 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 786 B |
|
After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,203 @@
|
|||
@extends('guru.layouts.app')
|
||||
|
||||
@section('title', 'History Materi')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 6px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #2b8ef3;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.back-link:hover { text-decoration: underline; }
|
||||
|
||||
.custom-card {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
border: 2px solid #e5e5e5;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.table-header { background: #a5e6ba; }
|
||||
|
||||
.mapel-badge {
|
||||
display: inline-block;
|
||||
background: #e6f0ff;
|
||||
color: #1d4ed8;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
.kelas-badge {
|
||||
display: inline-block;
|
||||
background: #f0fdf4;
|
||||
color: #166534;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
.file-icon { font-size: 20px; }
|
||||
|
||||
.btn-hapus {
|
||||
background: #fee2e2;
|
||||
color: #ef4444;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 5px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-hapus:hover { background: #fca5a5; }
|
||||
|
||||
.btn-unduh {
|
||||
background: #e6f0ff;
|
||||
color: #2b8ef3;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 5px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-unduh:hover { background: #bfdbfe; color: #1d4ed8; }
|
||||
|
||||
.alert-success-custom {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 50px 20px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
<a href="{{ route('guru.mapel.index') }}" class="back-link">← Kembali ke Mata Pelajaran</a>
|
||||
|
||||
<h3 class="page-title"><img src="{{ asset('images/icon/gurud/file.png') }}" class="topbar-waving" alt="Waving">History Materi</h3>
|
||||
<p style="color:#64748b;font-size:14px;margin-bottom:20px">
|
||||
Semua materi yang pernah Anda upload.
|
||||
</p>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert-success-custom"><img src="{{ asset('images/icon/gurud/v.png') }}" class="topbar-waving" alt="Waving">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="custom-card">
|
||||
@if($materiList->isEmpty())
|
||||
<div class="empty-state">
|
||||
<div style="font-size:48px;margin-bottom:12px">📭</div>
|
||||
<p>Belum ada materi yang diupload.</p>
|
||||
</div>
|
||||
@else
|
||||
<table class="table align-middle">
|
||||
<thead class="table-header text-center">
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Judul Materi</th>
|
||||
<th>Mata Pelajaran</th>
|
||||
<th>Kelas</th>
|
||||
<th>Tanggal Upload</th>
|
||||
<th>File</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($materiList as $i => $materi)
|
||||
<tr>
|
||||
<td class="text-center">{{ $materiList->firstItem() + $i }}</td>
|
||||
<td>
|
||||
<div style="font-weight:600;color:#1e293b">{{ $materi->judul_materi }}</div>
|
||||
@if($materi->deskripsi)
|
||||
<div style="font-size:12px;color:#94a3b8;margin-top:2px">
|
||||
{{ Str::limit($materi->deskripsi, 60) }}
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="mapel-badge">
|
||||
{{ optional(optional($materi->mengajar)->mapel)->nama_mapel ?? '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="kelas-badge">
|
||||
{{ optional(optional($materi->mengajar)->kelas)->tingkat }}
|
||||
{{ optional(optional($materi->mengajar)->kelas)->nama_kelas ?? '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center" style="font-size:13px;color:#64748b">
|
||||
{{ \Carbon\Carbon::parse($materi->tanggal_upload)->format('d M Y, H:i') }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if($materi->lampiran_materi)
|
||||
@php
|
||||
$ext = strtolower(pathinfo($materi->lampiran_materi, PATHINFO_EXTENSION));
|
||||
$icon = match(true) {
|
||||
in_array($ext, ['pdf']) => '📄',
|
||||
in_array($ext, ['doc','docx']) => '📝',
|
||||
in_array($ext, ['ppt','pptx']) => '📊',
|
||||
in_array($ext, ['jpg','jpeg','png']) => '🖼️',
|
||||
default => '📎',
|
||||
};
|
||||
@endphp
|
||||
<a href="{{ asset('storage/' . $materi->lampiran_materi) }}"
|
||||
target="_blank" class="btn-unduh">
|
||||
{{ $icon }} Unduh
|
||||
</a>
|
||||
@else
|
||||
<span style="font-size:12px;color:#94a3b8">Tidak ada file</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<form action="{{ route('guru.materi.destroy', $materi->id_materi) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Yakin hapus materi ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn-hapus">🗑️ Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
{{ $materiList->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 918 B After Width: | Height: | Size: 918 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 297 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |