mapel guru, challenge admin update

This commit is contained in:
RetasyaSalsabila 2026-02-18 10:07:54 +07:00
parent 2ff49894ef
commit b33a6bac48
12 changed files with 845 additions and 2 deletions

View File

@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Challenge;
use App\Models\Kelas;
use Illuminate\Support\Facades\Auth;
class ChallengeController extends Controller
{
public function index()
{
$challenges = Challenge::latest()->get();
$kelass = Kelas::all();
return view('admin.challenge.index', compact('challenges', 'kelass'));
}
public function create()
{
return view('admin.challenge.create');
}
public function store(Request $request)
{
$request->validate([
'judul_challenge' => 'required',
'exp' => 'required|integer|min:1',
'tenggat_waktu' => 'required|date',
'kelas' => 'required|array'
]);
$challenge = Challenge::create([
'id_admin' => auth('admin')->id(),
'judul_challenge' => $request->judul_challenge,
'deskripsi' => $request->deskripsi,
'exp' => $request->exp,
'tenggat_waktu' => $request->tenggat_waktu,
]);
// attach kelas
$challenge->kelas()->attach($request->kelas);
return redirect()->route('admin.challenge.soal.create', $challenge->id_challenge);
}
public function createSoal($id)
{
$challenge = Challenge::findOrFail($id);
return view('admin.challenge.soal', compact('challenge'));
}
public function storeSoal(Request $request, $id)
{
$challenge = Challenge::findOrFail($id);
$jumlahSoal = count($request->pertanyaan);
$expPerSoal = floor($challenge->exp / $jumlahSoal);
foreach ($request->pertanyaan as $key => $pertanyaan) {
SoalChallenge::create([
'id_challenge' => $id,
'pertanyaan' => $pertanyaan,
'opsi_a' => $request->opsi_a[$key],
'opsi_b' => $request->opsi_b[$key],
'opsi_c' => $request->opsi_c[$key],
'opsi_d' => $request->opsi_d[$key],
'jawaban_benar' => $request->jawaban_benar[$key],
'exp_per_soal' => $expPerSoal,
]);
}
return redirect()->route('admin.challenge.index')
->with('success', 'Challenge & soal berhasil dibuat!');
}
public function edit($id)
{
$challenge = Challenge::findOrFail($id);
return view('admin.challenge.edit', compact('challenge'));
}
public function update(Request $request, $id)
{
$challenge = Challenge::findOrFail($id);
$challenge->update([
'judul_challenge' => $request->judul_challenge,
'deskripsi' => $request->deskripsi,
'exp' => $request->exp,
'tenggat_waktu' => $request->tenggat_waktu,
]);
return redirect()->route('admin.challenge.index')
->with('success', 'Challenge berhasil diupdate!');
}
public function destroy($id)
{
$challenge = Challenge::findOrFail($id);
$challenge->delete();
return redirect()->route('admin.challenge.index')
->with('success', 'Challenge berhasil dihapus!');
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Leaderboard;
class LeaderboardController extends Controller
{
public function index()
{
$leaderboards = Leaderboard::orderBy('ranking')
->orderByDesc('total_exp')
->paginate(10);
return view('admin.leaderboard.index', compact('leaderboards'));
}
}

View File

@ -4,12 +4,18 @@
use App\Http\Controllers\Controller;
use App\Models\Mapel;
use Illuminate\Support\Facades\Auth;
class MapelController extends Controller
{
public function index()
{
$mapels = Mapel::paginate(10);
$guru = Auth::guard('guru')->user();
// Ambil hanya mapel yang dia ajar
$mapels = Mapel::whereHas('mengajars', function ($query) use ($guru) {
$query->where('nip', $guru->nip);
})->paginate(10);
return view('guru.mapel.index', compact('mapels'));
}

View File

@ -21,4 +21,20 @@ class Challenge extends Model
'id_badge',
'tenggat_waktu',
];
public function kelas()
{
return $this->belongsToMany(
Kelas::class,
'challenge_kelas',
'id_challenge',
'id_kelas'
);
}
public function soal()
{
return $this->hasMany(SoalChallenge::class, 'id_challenge');
}
}

View File

@ -0,0 +1,41 @@
<?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
{
Schema::create('soal_challenge', function (Blueprint $table) {
$table->id('id_soal');
$table->unsignedBigInteger('id_challenge');
$table->text('pertanyaan');
$table->string('opsi_a');
$table->string('opsi_b');
$table->string('opsi_c');
$table->string('opsi_d');
$table->enum('jawaban_benar', ['A','B','C','D']);
$table->integer('exp_per_soal')->default(0);
$table->timestamps();
$table->foreign('id_challenge')
->references('id_challenge')
->on('challenges')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('soal_challenge');
}
};

View File

@ -0,0 +1,40 @@
<?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
{
Schema::create('challenge_kelas', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('id_challenge');
$table->unsignedBigInteger('id_kelas');
$table->timestamps();
$table->foreign('id_challenge')
->references('id_challenge')
->on('challenges')
->onDelete('cascade');
$table->foreign('id_kelas')
->references('id_kelas')
->on('kelas')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('challenge_kelas');
}
};

View File

@ -0,0 +1,23 @@
@extends('admin.layouts.app')
@section('content')
<h2>Tambah Challenge</h2>
<form action="{{ route('admin.challenge.store') }}" method="POST">
@csrf
<label>Judul</label><br>
<input type="text" name="judul_challenge"><br><br>
<label>Deskripsi</label><br>
<textarea name="deskripsi"></textarea><br><br>
<label>EXP</label><br>
<input type="number" name="exp"><br><br>
<label>Tenggat Waktu</label><br>
<input type="datetime-local" name="tenggat_waktu"><br><br>
<button type="submit">Simpan</button>
</form>
@endsection

View File

@ -0,0 +1,26 @@
@extends('admin.layouts.app')
@section('content')
<h2>Edit Challenge</h2>
<form action="{{ route('admin.challenge.update', $challenge->id_challenge) }}" method="POST">
@csrf
@method('PUT')
<label>Judul</label><br>
<input type="text" name="judul_challenge" value="{{ $challenge->judul_challenge }}"><br><br>
<label>Deskripsi</label><br>
<textarea name="deskripsi">{{ $challenge->deskripsi }}</textarea><br><br>
<label>EXP</label><br>
<input type="number" name="exp" value="{{ $challenge->exp }}"><br><br>
<label>Tenggat Waktu</label><br>
<input type="datetime-local"
name="tenggat_waktu"
value="{{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->format('Y-m-d\TH:i') }}"><br><br>
<button type="submit">Update</button>
</form>
@endsection

View File

@ -0,0 +1,282 @@
@extends('admin.layouts.app')
@section('title', 'Daftar Challenge')
@section('content')
<style>
.page-title {
font-size: 30px;
font-weight: 800;
margin-bottom: 10px;
margin-top: -20px;
}
.custom-card {
background: white;
border-radius: 20px;
border: 2px solid #e5e5e5;
padding: 25px;
}
.btn-primary-custom {
background: #2b8ef3;
color: white;
border-radius: 10px;
padding: 8px 18px;
border: none;
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 14px;
text-decoration: none;
}
.table-header {
background: #a5e6ba;
}
.search-box {
background: #a5e6ba;
border-radius: 30px;
padding: 6px 15px;
display: flex;
align-items: center;
gap: 8px;
}
.search-box input {
border: none;
outline: none;
background: transparent;
width: 150px;
}
.action-icon {
width: 20px;
cursor: pointer;
margin: 0 5px;
}
.modal-header-pastel {
background: #FFD97D !important;
color: black !important;
border-bottom: none;
padding: 15px 20px;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
}
</style>
<h3 class="page-title">DAFTAR CHALLENGE</h3>
<div class="custom-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<button class="btn-primary-custom" data-bs-toggle="modal" data-bs-target="#modalTambah">
<img src="{{ asset('images/icon/main/add.png') }}" width="18">
Tambah Data
</button>
<form method="GET">
<div class="search-box">
<input type="text" name="search" placeholder="Cari"
value="{{ request('search') }}">
<button style="border:none;background:none">
<img src="{{ asset('images/icon/main/search.png') }}" width="18">
</button>
</div>
</form>
</div>
{{-- Alert --}}
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<table class="table text-center align-middle">
<thead class="table-header">
<tr>
<th>No</th>
<th>Judul Challenge</th>
<th>EXP</th>
<th>Tenggat</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@forelse($challenges as $index => $challenge)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $challenge->judul_challenge }}</td>
<td>{{ $challenge->exp }}</td>
<td>{{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->format('d M Y H:i') }}</td>
<td>
<button onclick="openEditModal(
'{{ $challenge->id_challenge }}',
'{{ $challenge->judul_challenge }}',
'{{ $challenge->deskripsi }}',
'{{ $challenge->exp }}',
'{{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->format('Y-m-d\TH:i') }}'
)" style="border:none;background:none">
<img src="{{ asset('images/icon/main/edit.png') }}" class="action-icon">
</button>
<form action="{{ route('admin.challenge.destroy', $challenge->id_challenge) }}"
method="POST" class="d-inline"
onsubmit="return confirm('Yakin ingin menghapus challenge ini?')">
@csrf
@method('DELETE')
<button type="submit" style="border:none;background:none">
<img src="{{ asset('images/icon/main/del.png') }}" class="action-icon">
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5">Belum ada challenge</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- MODAL TAMBAH --}}
<div class="modal fade" id="modalTambah">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header modal-header-pastel">
<h5 class="modal-title w-100 text-center">Tambah Challenge</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.challenge.store') }}" method="POST">
@csrf
<div class="modal-body">
<div class="mb-3">
<label>Judul Challenge *</label>
<input type="text" name="judul_challenge" class="form-control" required>
</div>
<div class="mb-3">
<label>Deskripsi</label>
<textarea name="deskripsi" class="form-control"></textarea>
</div>
<div class="mb-3">
<label>EXP *</label>
<input type="number" name="exp" class="form-control" required>
</div>
<div class="mb-3">
<label>Tenggat Waktu *</label>
<input type="datetime-local" name="tenggat_waktu" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button class="btn btn-success">Simpan Data</button>
</div>
</form>
</div>
</div>
</div>
{{-- MODAL EDIT --}}
<div class="modal fade" id="modalEdit">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header modal-header-pastel">
<h5 class="modal-title w-100 text-center">Edit Challenge</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="formEdit" method="POST">
@csrf
@method('PUT')
<div class="modal-body">
<div class="mb-3">
<label>Judul Challenge *</label>
<input type="text" id="editJudul" name="judul_challenge" class="form-control" required>
</div>
<div class="mb-3">
<label>Deskripsi</label>
<textarea id="editDeskripsi" name="deskripsi" class="form-control"></textarea>
</div>
<div class="mb-3">
<label>EXP *</label>
<input type="number" id="editExp" name="exp" class="form-control" required>
</div>
<div class="mb-3">
<label>Tenggat Waktu *</label>
<input type="datetime-local" id="editTenggat" name="tenggat_waktu" class="form-control" required>
</div>
<div class="mb-3">
<label>Pilih Kelas *</label>
@foreach($kelass as $kelas)
<div class="form-check">
<input class="form-check-input"
type="checkbox"
name="kelas[]"
value="{{ $kelas->id_kelas }}">
<label class="form-check-label">
{{ $kelas->tingkat }} - {{ $kelas->nama_kelas }}
</label>
</div>
@endforeach
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button class="btn btn-success">Update</button>
</div>
</form>
</div>
</div>
</div>
<script>
function openEditModal(id, judul, deskripsi, exp, tenggat) {
document.getElementById('editJudul').value = judul;
document.getElementById('editDeskripsi').value = deskripsi;
document.getElementById('editExp').value = exp;
document.getElementById('editTenggat').value = tenggat;
document.getElementById('formEdit').action =
"{{ url('admin/challenge') }}/" + id;
new bootstrap.Modal(document.getElementById('modalEdit')).show();
}
</script>
@endsection

View File

@ -0,0 +1,174 @@
@extends('admin.layouts.app')
@section('title', 'Tambah Soal Challenge')
@section('content')
<style>
.page-title {
font-size: 28px;
font-weight: 800;
margin-top: -20px;
margin-bottom: 10px;
}
.custom-card {
background: white;
border-radius: 20px;
border: 2px solid #e5e5e5;
padding: 25px;
}
.soal-box {
border: 2px solid #e5e5e5;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
background: #f9fbff;
}
.btn-add {
background: #2b8ef3;
color: white;
border-radius: 10px;
padding: 8px 18px;
border: none;
}
.btn-remove {
background: #ef4444;
color: white;
border: none;
border-radius: 8px;
padding: 5px 12px;
font-size: 12px;
}
</style>
<h3 class="page-title">
TAMBAH SOAL - {{ $challenge->judul_challenge }}
</h3>
<div class="custom-card">
<form action="{{ route('admin.challenge.soal.store', $challenge->id_challenge) }}" method="POST">
@csrf
<div id="soalContainer">
{{-- SOAL PERTAMA (DEFAULT) --}}
<div class="soal-box">
<h6>Soal 1</h6>
<div class="mb-3">
<label>Pertanyaan *</label>
<textarea name="pertanyaan[]" class="form-control" required></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-2">
<input type="text" name="opsi_a[]" class="form-control" placeholder="Opsi A" required>
</div>
<div class="col-md-6 mb-2">
<input type="text" name="opsi_b[]" class="form-control" placeholder="Opsi B" required>
</div>
<div class="col-md-6 mb-2">
<input type="text" name="opsi_c[]" class="form-control" placeholder="Opsi C" required>
</div>
<div class="col-md-6 mb-2">
<input type="text" name="opsi_d[]" class="form-control" placeholder="Opsi D" required>
</div>
</div>
<div class="mb-2">
<label>Jawaban Benar *</label>
<select name="jawaban_benar[]" class="form-control" required>
<option value="">-- Pilih --</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
</div>
</div>
</div>
<button type="button" onclick="tambahSoal()" class="btn-add">
+ Tambah Soal
</button>
<hr>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-success">
Simpan Semua Soal
</button>
</div>
</form>
</div>
<script>
let nomorSoal = 1;
function tambahSoal() {
nomorSoal++;
let container = document.getElementById('soalContainer');
let html = `
<div class="soal-box">
<div class="d-flex justify-content-between">
<h6>Soal ${nomorSoal}</h6>
<button type="button" class="btn-remove" onclick="hapusSoal(this)">
Hapus
</button>
</div>
<div class="mb-3">
<label>Pertanyaan *</label>
<textarea name="pertanyaan[]" class="form-control" required></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-2">
<input type="text" name="opsi_a[]" class="form-control" placeholder="Opsi A" required>
</div>
<div class="col-md-6 mb-2">
<input type="text" name="opsi_b[]" class="form-control" placeholder="Opsi B" required>
</div>
<div class="col-md-6 mb-2">
<input type="text" name="opsi_c[]" class="form-control" placeholder="Opsi C" required>
</div>
<div class="col-md-6 mb-2">
<input type="text" name="opsi_d[]" class="form-control" placeholder="Opsi D" required>
</div>
</div>
<div class="mb-2">
<label>Jawaban Benar *</label>
<select name="jawaban_benar[]" class="form-control" required>
<option value="">-- Pilih --</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
function hapusSoal(button) {
button.closest('.soal-box').remove();
}
</script>
@endsection

View File

@ -0,0 +1,91 @@
@extends('guru.layouts.app')
@section('title', 'Daftar Mata Pelajaran')
@section('content')
<style>
.page-title {
font-size: 30px;
font-weight: 800;
margin-bottom: 10px;
margin-top: -20px;
}
.custom-card {
background: white;
border-radius: 20px;
border: 2px solid #e5e5e5;
padding: 25px;
}
.table-header {
background: #a5e6ba;
}
.action-btn {
padding: 6px 14px;
border-radius: 10px;
border: none;
font-size: 13px;
}
.btn-materi {
background: #2b8ef3;
color: white;
}
.btn-tugas {
background: #f97316;
color: white;
}
</style>
<h3 class="page-title">MATA PELAJARAN YANG ANDA AJAR</h3>
<div class="custom-card">
<table class="table text-center align-middle">
<thead class="table-header">
<tr>
<th>No</th>
<th>ID Mapel</th>
<th>Nama Mata Pelajaran</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@forelse($mapels as $index => $mapel)
<tr>
<td>{{ $mapels->firstItem() + $index }}</td>
<td>{{ $mapel->id_mapel }}</td>
<td>{{ $mapel->nama_mapel }}</td>
<td>
<a href="{{ route('guru.materi.create', $mapel->id_mapel) }}"
class="action-btn btn-materi">
Upload Materi
</a>
<a href="{{ route('guru.tugas.create', $mapel->id_mapel) }}"
class="action-btn btn-tugas">
Buat Tugas
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4">Anda belum mengajar mata pelajaran apapun.</td>
</tr>
@endforelse
</tbody>
</table>
<div class="d-flex justify-content-end">
{{ $mapels->links() }}
</div>
</div>
@endsection

View File

@ -11,8 +11,10 @@
use App\Http\Controllers\Admin\KelasController as AdminKelasController;
use App\Http\Controllers\Admin\SiswaController as AdminSiswaController;
use App\Http\Controllers\Admin\MapelController as AdminMapelController;
use App\Http\Controllers\Admin\ChallengeController as AdminChallengeController;
use App\Http\Controllers\Admin\LeaderboardController as AdminLeaderboardController;
// GURU CONTROLLERS
use App\Http\Controllers\Guru\LoginController as GuruLoginController;
use App\Http\Controllers\Guru\DashboardController as GuruDashboardController;
@ -98,7 +100,15 @@
Route::resource('mapel', AdminMapelController::class);
Route::resource('leaderboard', AdminLeaderboardController::class)
->only(['index']);
Route::resource('challenge', AdminChallengeController::class);
Route::get('challenge/{id}/soal',
[AdminChallengeController::class, 'createSoal']
)->name('challenge.soal.create');
Route::post('challenge/{id}/soal',
[AdminChallengeController::class, 'storeSoal']
)->name('challenge.soal.store');
// LOGOUT ADMIN
Route::post('/logout', [LoginController::class, 'logout'])
@ -125,9 +135,17 @@
Route::get('/mapel', [GuruMapelController::class, 'index'])
->name('mapel.index');
Route::get('/leaderboard', [GuruLeaderboardController::class, 'index'])
Route::get('/leaderboard', [GuruLeaderboardController::class, 'index'])
->name('leaderboard.index');
Route::get('/materi/{id_mapel}/create', function ($id_mapel) {
return "Form Upload Materi untuk Mapel: " . $id_mapel;
})->name('materi.create');
Route::get('/tugas/{id_mapel}/create', function ($id_mapel) {
return "Form Buat Tugas untuk Mapel: " . $id_mapel;
})->name('tugas.create');
// Profil (Edit)
Route::get('/profil', [GuruProfilController::class, 'show'])->name('profil.show');