flow admin-guru
This commit is contained in:
parent
e0db94bf1a
commit
ce2ca560f9
|
|
@ -4,121 +4,153 @@
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Guru;
|
||||
use App\Models\Mapel;
|
||||
use App\Models\Kelas;
|
||||
use App\Models\Mengajar;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class GuruController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Guru::query();
|
||||
{
|
||||
$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%");
|
||||
});
|
||||
}
|
||||
|
||||
// SEARCH
|
||||
if ($request->has('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('nama', 'like', "%$search%")
|
||||
->orWhere('nip', 'like', "%$search%");
|
||||
$perPage = $request->get('perPage', 10);
|
||||
$gurus = $query->paginate($perPage)->appends($request->all());
|
||||
$mapels = Mapel::all();
|
||||
$kelas = Kelas::all();
|
||||
|
||||
return view('admin.guru.index', compact('gurus', 'mapels', 'kelas'));
|
||||
}
|
||||
|
||||
// SHOW PER PAGE
|
||||
$perPage = $request->get('perPage', 10);
|
||||
|
||||
$gurus = $query->paginate($perPage)->appends($request->all());
|
||||
|
||||
return view('admin.guru.index', compact('gurus'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.guru.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* Tambah guru + bisa pilih banyak pasangan (mapel, kelas)
|
||||
* Input: mapel[] dan kelas[] dengan index yang bersesuaian
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'nip' => 'required|string|max:30|unique:gurus,nip',
|
||||
'nama' => 'required|string|max:100',
|
||||
'password' => 'required|string|min:6',
|
||||
$request->validate([
|
||||
'nip' => 'required|string|max:30|unique:gurus,nip',
|
||||
'nama' => 'required|string|max:100',
|
||||
'password' => 'required|string|min:6',
|
||||
'id_mapel' => 'required|array|min:1',
|
||||
'id_mapel.*' => 'required|exists:mapels,id_mapel',
|
||||
'id_kelas' => 'required|array|min:1',
|
||||
'id_kelas.*' => 'required|exists:kelas,id_kelas',
|
||||
], [
|
||||
'nip.required' => 'NIP wajib diisi',
|
||||
'nip.unique' => 'NIP sudah terdaftar',
|
||||
'nama.required' => 'Nama wajib diisi',
|
||||
'password.required' => 'Password wajib diisi',
|
||||
'password.min' => 'Password minimal 6 karakter',
|
||||
'id_mapel.required' => 'Pilih minimal 1 mata pelajaran.',
|
||||
'id_kelas.required' => 'Pilih minimal 1 kelas.',
|
||||
]);
|
||||
|
||||
Guru::create([
|
||||
'nip' => $validated['nip'],
|
||||
'nama' => $validated['nama'],
|
||||
'password' => Hash::make($validated['password']),
|
||||
$guru = Guru::create([
|
||||
'nip' => $request->nip,
|
||||
'nama' => $request->nama,
|
||||
'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,
|
||||
'id_mapel' => $idMapel,
|
||||
'id_kelas' => $idKelas,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.guru.index')
|
||||
->with('success', 'Data guru berhasil ditambahkan!');
|
||||
->with('success', 'Data guru berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $nip)
|
||||
public function show(string $id)
|
||||
{
|
||||
$guru = Guru::findOrFail($nip);
|
||||
$guru = Guru::findOrFail($id);
|
||||
return view('admin.guru.show', compact('guru'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $nip)
|
||||
public function edit(string $id)
|
||||
{
|
||||
$guru = Guru::findOrFail($nip);
|
||||
$guru = Guru::findOrFail($id);
|
||||
return view('admin.guru.edit', compact('guru'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* Update guru — hapus semua mengajar lama, insert ulang
|
||||
*/
|
||||
public function update(Request $request, string $nip)
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$guru = Guru::findOrFail($nip);
|
||||
$guru = Guru::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nama' => 'required|string|max:100',
|
||||
'password' => 'nullable|string|min:6',
|
||||
], [
|
||||
'nama.required' => 'Nama wajib diisi',
|
||||
'password.min' => 'Password minimal 6 karakter',
|
||||
$request->validate([
|
||||
'nip' => 'required|string|max:30|unique:gurus,nip,' . $guru->id_guru . ',id_guru',
|
||||
'nama' => 'required|string|max:100',
|
||||
'password' => 'nullable|string|min:6',
|
||||
'id_mapel' => 'required|array|min:1',
|
||||
'id_mapel.*' => 'required|exists:mapels,id_mapel',
|
||||
'id_kelas' => 'required|array|min:1',
|
||||
'id_kelas.*' => 'required|exists:kelas,id_kelas',
|
||||
]);
|
||||
|
||||
$guru->nama = $validated['nama'];
|
||||
|
||||
// Update password hanya jika diisi
|
||||
$guru->nip = $request->nip;
|
||||
$guru->nama = $request->nama;
|
||||
if ($request->filled('password')) {
|
||||
$guru->password = Hash::make($validated['password']);
|
||||
$guru->password = Hash::make($request->password);
|
||||
}
|
||||
|
||||
$guru->save();
|
||||
|
||||
// Hapus semua data mengajar lama, insert ulang
|
||||
Mengajar::where('id_guru', $guru->id_guru)->delete();
|
||||
|
||||
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,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.guru.index')
|
||||
->with('success', 'Data guru berhasil diupdate!');
|
||||
->with('success', 'Data guru berhasil diupdate.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $nip)
|
||||
public function destroy(string $id)
|
||||
{
|
||||
$guru = Guru::findOrFail($nip);
|
||||
$guru = Guru::findOrFail($id);
|
||||
$guru->delete();
|
||||
|
||||
return redirect()->route('admin.guru.index')
|
||||
->with('success', 'Data guru berhasil dihapus!');
|
||||
->with('success', 'Data guru berhasil dihapus.');
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Ambil kelas yang memiliki mapel tertentu (lewat tabel mengajars)
|
||||
* Dipanggil via AJAX saat admin pilih mapel di modal
|
||||
*/
|
||||
public function getKelasByMapel(Request $request)
|
||||
{
|
||||
$idMapel = $request->id_mapel;
|
||||
|
||||
// Cari kelas yang sudah punya mapel ini di tabel mengajars
|
||||
$kelasList = Kelas::whereHas('mengajars', function ($q) use ($idMapel) {
|
||||
$q->where('id_mapel', $idMapel);
|
||||
})
|
||||
->get(['id_kelas', 'nama_kelas', 'tingkat']);
|
||||
|
||||
return response()->json($kelasList);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Mengajar;
|
||||
use App\Models\Guru;
|
||||
use App\Models\Mapel;
|
||||
use App\Models\Kelas;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MengajarController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$mengajars = Mengajar::with('guru','mapel','kelas')->get();
|
||||
$gurus = Guru::all();
|
||||
$mapels = Mapel::all();
|
||||
$kelas = Kelas::all();
|
||||
|
||||
return view('admin.mengajar.index', compact(
|
||||
'mengajars','gurus','mapels','kelas'
|
||||
));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id_guru' => 'required',
|
||||
'id_mapel' => 'required',
|
||||
'id_kelas' => 'required',
|
||||
]);
|
||||
|
||||
Mengajar::create([
|
||||
'id_guru' => $request->id_guru,
|
||||
'id_mapel' => $request->id_mapel,
|
||||
'id_kelas' => $request->id_kelas,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success','Data mengajar berhasil ditambahkan');
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$mengajar = Mengajar::findOrFail($id);
|
||||
|
||||
$mengajar->update([
|
||||
'id_guru' => $request->id_guru,
|
||||
'id_mapel' => $request->id_mapel,
|
||||
'id_kelas' => $request->id_kelas,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success','Data mengajar berhasil diupdate');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
Mengajar::findOrFail($id)->delete();
|
||||
|
||||
return redirect()->back()->with('success','Data mengajar berhasil dihapus');
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Mengajar;
|
||||
use App\Models\Guru;
|
||||
use App\Models\Kelas;
|
||||
use App\Models\Siswa;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
|
@ -14,28 +12,23 @@ class DashboardController extends Controller
|
|||
public function index()
|
||||
{
|
||||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
// Cek table mengajars ada data atau enggak
|
||||
|
||||
try {
|
||||
// Hitung total kelas yang diajar
|
||||
$totalKelas = Mengajar::where('nip', $guru->nip)
|
||||
$totalKelas = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->distinct('id_kelas')
|
||||
->count('id_kelas');
|
||||
|
||||
// Hitung total mapel yang diajar
|
||||
$totalMapel = Mengajar::where('nip', $guru->nip)
|
||||
|
||||
$totalMapel = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->distinct('id_mapel')
|
||||
->count('id_mapel');
|
||||
|
||||
// Hitung total siswa yang diajar (lewat kelas)
|
||||
$kelasIds = Mengajar::where('nip', $guru->nip)
|
||||
|
||||
$kelasIds = Mengajar::where('id_guru', $guru->id_guru)
|
||||
->pluck('id_kelas')
|
||||
->unique();
|
||||
|
||||
|
||||
$totalSiswa = Siswa::whereIn('id_kelas', $kelasIds)->count();
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Kalau error (table kosong atau relasi belum ada), set default 0
|
||||
$totalKelas = 0;
|
||||
$totalMapel = 0;
|
||||
$totalSiswa = 0;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Mapel;
|
||||
use App\Models\Materi;
|
||||
use App\Models\Tugas;
|
||||
use App\Models\Mengajar;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class MapelController extends Controller
|
||||
|
|
@ -12,11 +16,85 @@ public function index()
|
|||
{
|
||||
$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);
|
||||
// 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
|
||||
|
||||
return view('guru.mapel.index', compact('mapels'));
|
||||
return view('guru.mapel.index', compact('mengajars'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpan materi baru
|
||||
*/
|
||||
public function storeMateri(Request $request)
|
||||
{
|
||||
$guru = Auth::guard('guru')->user();
|
||||
|
||||
$request->validate([
|
||||
'id_mengajar' => 'required|exists:mengajars,id_mengajar',
|
||||
'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.',
|
||||
]);
|
||||
|
||||
// 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');
|
||||
$filename = 'materi_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs('materi', $filename, 'public');
|
||||
}
|
||||
|
||||
Materi::create([
|
||||
'id_mengajar' => $request->id_mengajar,
|
||||
'judul_materi' => $request->judul_materi,
|
||||
'deskripsi' => $request->deskripsi,
|
||||
'lampiran_materi' => $path,
|
||||
]);
|
||||
|
||||
return redirect()->route('guru.mapel.index')
|
||||
->with('success', 'Materi berhasil diupload!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpan tugas baru
|
||||
*/
|
||||
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',
|
||||
], [
|
||||
'deadline.after' => 'Deadline harus lebih dari waktu sekarang.',
|
||||
]);
|
||||
|
||||
// Pastikan mengajar ini milik guru yang login
|
||||
$mengajar = Mengajar::where('id_mengajar', $request->id_mengajar)
|
||||
->where('id_guru', $guru->id_guru)
|
||||
->firstOrFail();
|
||||
|
||||
Tugas::create([
|
||||
'id_mengajar' => $request->id_mengajar,
|
||||
'judul_tugas' => $request->judul_tugas,
|
||||
'keterangan' => $request->keterangan,
|
||||
'deadline' => $request->deadline,
|
||||
]);
|
||||
|
||||
return redirect()->route('guru.mapel.index')
|
||||
->with('success', 'Tugas berhasil dibuat!');
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,6 @@ class Guru extends Authenticatable
|
|||
// Relasi ke Mengajar
|
||||
public function mengajars()
|
||||
{
|
||||
return $this->hasMany(Mengajar::class, 'nip', 'nip');
|
||||
return $this->hasMany(Mengajar::class, 'id_guru', 'id_guru');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,10 @@
|
|||
gap: 8px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background: #a5e6ba;
|
||||
}
|
||||
.table-header { background: #a5e6ba; }
|
||||
|
||||
.search-box {
|
||||
background: #a5e6ba;
|
||||
|
|
@ -52,11 +51,7 @@
|
|||
width: 150px;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.action-icon { width: 20px; cursor: pointer; margin: 0 5px; }
|
||||
|
||||
.per-page-select {
|
||||
border-radius: 10px;
|
||||
|
|
@ -64,8 +59,6 @@
|
|||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* ===== STYLE BARU UNTUK MODAL ===== */
|
||||
|
||||
.modal-header-pastel {
|
||||
background: #FFD97D !important;
|
||||
color: black !important;
|
||||
|
|
@ -73,57 +66,101 @@
|
|||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 15px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.modal-body label {
|
||||
font-weight: bold;
|
||||
.modal-body label { font-weight: bold; }
|
||||
|
||||
/* ===== PASANGAN MAPEL - KELAS ===== */
|
||||
.mengajar-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
background: #f8fafc;
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.modal {
|
||||
left: auto !important;
|
||||
right: 0;
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
.mengajar-row select {
|
||||
flex: 1;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #cbd5e1;
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
left: auto !important;
|
||||
right: 0;
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
.btn-hapus-row {
|
||||
background: #fee2e2;
|
||||
color: #ef4444;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
.btn-hapus-row:hover { background: #fca5a5; }
|
||||
|
||||
.modal-content {
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
}
|
||||
.btn-tambah-row {
|
||||
background: #e6f0ff;
|
||||
color: #2b8ef3;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 7px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-tambah-row:hover { background: #bfdbfe; }
|
||||
|
||||
.row-label {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.alert-success-custom {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h3 class="page-title">DAFTAR GURU</h3>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert-success-custom">✅ {{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="custom-card">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<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>
|
||||
|
||||
<button class="btn-primary-custom">
|
||||
<img src="{{ asset('images/icon/main/download.png') }}" width="18">
|
||||
Download PDF
|
||||
</button>
|
||||
|
||||
<button class="btn-primary-custom">
|
||||
<img src="{{ asset('images/icon/main/download.png') }}" width="18">
|
||||
Download Excel
|
||||
|
|
@ -138,21 +175,17 @@
|
|||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<form method="GET" class="mb-2">
|
||||
<span>Tampilkan</span>
|
||||
|
||||
<select name="perPage" onchange="this.form.submit()" class="per-page-select">
|
||||
<option value="10" {{ request('perPage') == 10 ? 'selected' : '' }}>10</option>
|
||||
<option value="25" {{ request('perPage') == 25 ? 'selected' : '' }}>25</option>
|
||||
<option value="50" {{ request('perPage') == 50 ? 'selected' : '' }}>50</option>
|
||||
<option value="10" {{ request('perPage') == 10 ? 'selected' : '' }}>10</option>
|
||||
<option value="25" {{ request('perPage') == 25 ? 'selected' : '' }}>25</option>
|
||||
<option value="50" {{ request('perPage') == 50 ? 'selected' : '' }}>50</option>
|
||||
<option value="100" {{ request('perPage') == 100 ? 'selected' : '' }}>100</option>
|
||||
</select>
|
||||
|
||||
<span>data</span>
|
||||
|
||||
<input type="hidden" name="search" value="{{ request('search') }}">
|
||||
</form>
|
||||
|
||||
|
|
@ -162,28 +195,52 @@
|
|||
<th>No</th>
|
||||
<th>Nama Lengkap</th>
|
||||
<th>NIP</th>
|
||||
<th>Mapel</th>
|
||||
<th>Kelas</th>
|
||||
<th>Password</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@forelse($gurus as $index => $guru)
|
||||
<tr>
|
||||
<td>{{ $gurus->firstItem() + $index }}</td>
|
||||
<td>{{ $guru->nama }}</td>
|
||||
<td>{{ $guru->nip }}</td>
|
||||
|
||||
<td>
|
||||
@forelse($guru->mengajars as $m)
|
||||
<div>{{ optional($m->mapel)->nama_mapel ?? '-' }}</div>
|
||||
@empty
|
||||
<span class="text-muted">-</span>
|
||||
@endforelse
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@forelse($guru->mengajars as $m)
|
||||
<div>{{ optional($m->kelas)->tingkat }} {{ optional($m->kelas)->nama_kelas }}</div>
|
||||
@empty
|
||||
<span class="text-muted">-</span>
|
||||
@endforelse
|
||||
</td>
|
||||
|
||||
<td>********</td>
|
||||
|
||||
<td>
|
||||
<button onclick="openEditModal('{{ $guru->nip }}', '{{ $guru->nama }}')"
|
||||
style="border:none;background:none">
|
||||
{{-- TOMBOL EDIT --}}
|
||||
<button onclick="openEditModal(
|
||||
'{{ $guru->id_guru }}',
|
||||
'{{ $guru->nip }}',
|
||||
'{{ addslashes($guru->nama) }}',
|
||||
{{ $guru->mengajars->map(fn($m) => ['id_mapel' => $m->id_mapel, 'id_kelas' => $m->id_kelas])->toJson() }}
|
||||
)" style="border:none;background:none">
|
||||
<img src="{{ asset('images/icon/main/edit.png') }}" class="action-icon">
|
||||
</button>
|
||||
|
||||
<form action="{{ route('admin.guru.destroy', $guru->id_guru) }}"
|
||||
{{-- TOMBOL HAPUS --}}
|
||||
<form action="{{ route('admin.guru.destroy', $guru->id_guru) }}"
|
||||
method="POST" class="d-inline"
|
||||
onsubmit="return confirm('Yakin ingin menghapus data?')">
|
||||
onsubmit="return confirm('Yakin hapus guru ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" style="border:none;background:none">
|
||||
|
|
@ -192,10 +249,9 @@
|
|||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5">Belum ada data guru</td>
|
||||
<td colspan="7" class="text-muted">Belum ada data guru.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
|
|
@ -204,10 +260,12 @@
|
|||
<div class="d-flex justify-content-end">
|
||||
{{ $gurus->links() }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- MODAL TAMBAH DATA --}}
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- MODAL TAMBAH DATA --}}
|
||||
{{-- ============================================================ --}}
|
||||
<div class="modal fade" id="modalTambah" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
|
@ -219,7 +277,6 @@
|
|||
|
||||
<form action="{{ route('admin.guru.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="mb-3">
|
||||
|
|
@ -235,23 +292,55 @@
|
|||
<div class="mb-3">
|
||||
<label>Password <span class="text-danger">*</span></label>
|
||||
<input type="password" name="password" class="form-control" placeholder="Minimal 6 karakter" required>
|
||||
<small class="text-muted">Password akan di-hash otomatis</small>
|
||||
<small class="text-muted">Password akan di-hash otomatis.</small>
|
||||
</div>
|
||||
|
||||
{{-- PASANGAN MAPEL + KELAS --}}
|
||||
<div class="mb-1">
|
||||
<label>Mata Pelajaran & Kelas yang Diajar <span class="text-danger">*</span></label>
|
||||
<small class="text-muted d-block mb-2">Tambahkan baris untuk setiap kombinasi mapel & kelas.</small>
|
||||
</div>
|
||||
|
||||
<div id="tambahRows">
|
||||
{{-- Baris pertama default --}}
|
||||
<div class="mengajar-row">
|
||||
<span class="row-label">Mapel</span>
|
||||
<select name="id_mapel[]" required>
|
||||
<option value="">-- Pilih Mapel --</option>
|
||||
@foreach($mapels as $mapel)
|
||||
<option value="{{ $mapel->id_mapel }}">{{ $mapel->nama_mapel }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="row-label">Kelas</span>
|
||||
<select name="id_kelas[]" required>
|
||||
<option value="">-- Pilih Kelas --</option>
|
||||
@foreach($kelas as $k)
|
||||
<option value="{{ $k->id_kelas }}">{{ $k->tingkat }} {{ $k->nama_kelas }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="button" class="btn-hapus-row" onclick="hapusRow(this)" title="Hapus baris">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-tambah-row" onclick="tambahRow('tambahRows')">
|
||||
+ Tambah Mapel/Kelas
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-success">Simpan Data</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- MODAL EDIT DATA --}}
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- MODAL EDIT DATA --}}
|
||||
{{-- ============================================================ --}}
|
||||
<div class="modal fade" id="modalEdit" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
|
@ -268,8 +357,8 @@
|
|||
<div class="modal-body">
|
||||
|
||||
<div class="mb-3">
|
||||
<label>NIP</label>
|
||||
<input type="text" id="editNip" class="form-control" disabled>
|
||||
<label>NIP <span class="text-danger">*</span></label>
|
||||
<input type="text" name="nip" id="editNip" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
|
@ -278,32 +367,173 @@
|
|||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Password Baru (Opsional)</label>
|
||||
<input type="password" name="password" class="form-control" placeholder="Isi jika ingin mengganti password">
|
||||
<label>Password Baru <small class="text-muted fw-normal">(Kosongkan jika tidak ingin mengubah)</small></label>
|
||||
<input type="password" name="password" class="form-control" placeholder="Minimal 6 karakter">
|
||||
</div>
|
||||
|
||||
{{-- PASANGAN MAPEL + KELAS --}}
|
||||
<div class="mb-1">
|
||||
<label>Mata Pelajaran & Kelas yang Diajar <span class="text-danger">*</span></label>
|
||||
<small class="text-muted d-block mb-2">Tambahkan baris untuk setiap kombinasi mapel & kelas.</small>
|
||||
</div>
|
||||
|
||||
<div id="editRows">
|
||||
{{-- Diisi oleh JavaScript --}}
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-tambah-row" onclick="tambahRow('editRows')">
|
||||
+ Tambah Mapel/Kelas
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-save-pastel">Update Data</button>
|
||||
<button type="submit" class="btn btn-warning">Update Data</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openEditModal(nip, nama) {
|
||||
document.getElementById('editNip').value = nip;
|
||||
document.getElementById('editNama').value = nama;
|
||||
|
||||
document.getElementById('formEdit').action = "{{ url('admin/guru') }}/" + nip;
|
||||
{{-- ============================================================ --}}
|
||||
{{-- TEMPLATE ROW — kelas auto-fill via AJAX saat mapel dipilih --}}
|
||||
{{-- ============================================================ --}}
|
||||
<template id="rowTemplate">
|
||||
<div class="mengajar-row">
|
||||
<span class="row-label">Mapel</span>
|
||||
<select name="id_mapel[]" class="select-mapel" onchange="onMapelChange(this)" required>
|
||||
<option value="">-- Pilih Mapel --</option>
|
||||
@foreach($mapels as $mapel)
|
||||
<option value="{{ $mapel->id_mapel }}">{{ $mapel->nama_mapel }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="row-label">Kelas</span>
|
||||
<select name="id_kelas[]" class="select-kelas" required disabled>
|
||||
<option value="">-- Pilih mapel dulu --</option>
|
||||
</select>
|
||||
<button type="button" class="btn-hapus-row" onclick="hapusRow(this)" title="Hapus baris">✕</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
const KELAS_BY_MAPEL_URL = "{{ route('admin.guru.kelasByMapel') }}";
|
||||
|
||||
// ===== SAAT MAPEL DIPILIH → fetch kelas otomatis =====
|
||||
async function onMapelChange(selectMapel) {
|
||||
const idMapel = selectMapel.value;
|
||||
const row = selectMapel.closest('.mengajar-row');
|
||||
const selectKelas = row.querySelector('.select-kelas');
|
||||
|
||||
// Reset kelas
|
||||
selectKelas.innerHTML = '<option value="">-- Memuat kelas... --</option>';
|
||||
selectKelas.disabled = true;
|
||||
|
||||
if (!idMapel) {
|
||||
selectKelas.innerHTML = '<option value="">-- Pilih mapel dulu --</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${KELAS_BY_MAPEL_URL}?id_mapel=${idMapel}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
selectKelas.innerHTML = '<option value="">Tidak ada kelas untuk mapel ini</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
selectKelas.innerHTML = '<option value="">-- Pilih Kelas --</option>';
|
||||
data.forEach(k => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = k.id_kelas;
|
||||
opt.textContent = k.tingkat + ' ' + k.nama_kelas;
|
||||
selectKelas.appendChild(opt);
|
||||
});
|
||||
|
||||
// Jika hanya 1 kelas, langsung pilih otomatis
|
||||
if (data.length === 1) {
|
||||
selectKelas.value = data[0].id_kelas;
|
||||
}
|
||||
|
||||
selectKelas.disabled = false;
|
||||
|
||||
} catch (e) {
|
||||
selectKelas.innerHTML = '<option value="">Gagal memuat kelas</option>';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== TAMBAH BARIS BARU =====
|
||||
async function tambahRow(containerId, selectedMapel = null, selectedKelas = null) {
|
||||
const template = document.getElementById('rowTemplate');
|
||||
const clone = template.content.cloneNode(true);
|
||||
const container = document.getElementById(containerId);
|
||||
|
||||
container.appendChild(clone);
|
||||
|
||||
const rows = container.querySelectorAll('.mengajar-row');
|
||||
const last = rows[rows.length - 1];
|
||||
const selectMapel = last.querySelector('.select-mapel');
|
||||
const selectKelas = last.querySelector('.select-kelas');
|
||||
|
||||
if (selectedMapel) {
|
||||
selectMapel.value = selectedMapel;
|
||||
|
||||
// Fetch kelas untuk mapel yang sudah dipilih
|
||||
try {
|
||||
const res = await fetch(`${KELAS_BY_MAPEL_URL}?id_mapel=${selectedMapel}`);
|
||||
const data = await res.json();
|
||||
|
||||
selectKelas.innerHTML = '<option value="">-- Pilih Kelas --</option>';
|
||||
data.forEach(k => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = k.id_kelas;
|
||||
opt.textContent = k.tingkat + ' ' + k.nama_kelas;
|
||||
selectKelas.appendChild(opt);
|
||||
});
|
||||
|
||||
if (selectedKelas) selectKelas.value = selectedKelas;
|
||||
selectKelas.disabled = false;
|
||||
|
||||
} catch (e) {
|
||||
selectKelas.innerHTML = '<option value="">Gagal memuat kelas</option>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== HAPUS BARIS =====
|
||||
function hapusRow(btn) {
|
||||
const container = btn.closest('[id$="Rows"]');
|
||||
const rows = container.querySelectorAll('.mengajar-row');
|
||||
if (rows.length <= 1) {
|
||||
alert('Minimal harus ada 1 mata pelajaran.');
|
||||
return;
|
||||
}
|
||||
btn.closest('.mengajar-row').remove();
|
||||
}
|
||||
|
||||
// ===== BUKA MODAL EDIT =====
|
||||
async function openEditModal(idGuru, nip, nama, mengajars) {
|
||||
document.getElementById('formEdit').action = "{{ url('admin/guru') }}/" + idGuru;
|
||||
document.getElementById('editNip').value = nip;
|
||||
document.getElementById('editNama').value = nama;
|
||||
|
||||
const editRows = document.getElementById('editRows');
|
||||
editRows.innerHTML = '';
|
||||
|
||||
if (mengajars && mengajars.length > 0) {
|
||||
for (const m of mengajars) {
|
||||
await tambahRow('editRows', m.id_mapel, m.id_kelas);
|
||||
}
|
||||
} else {
|
||||
tambahRow('editRows');
|
||||
}
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalEdit')).show();
|
||||
}
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
@endsection
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@yield('title', 'Panel Guru')</title>
|
||||
|
||||
<!-- Fonts & Bootstrap -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
|
|
@ -16,12 +15,8 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.wrapper { display: flex; min-height: 100vh; }
|
||||
|
||||
/* SIDEBAR */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background: #ffffff;
|
||||
|
|
@ -31,14 +26,8 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sidebar-logo img {
|
||||
width: 90px;
|
||||
}
|
||||
.sidebar-logo { text-align: center; margin-bottom: 40px; }
|
||||
.sidebar-logo img { width: 90px; }
|
||||
|
||||
.sidebar-link {
|
||||
display: flex;
|
||||
|
|
@ -53,35 +42,14 @@
|
|||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-link:hover {
|
||||
background: #e6f0ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
.sidebar-link:hover { background: #e6f0ff; color: #1d4ed8; }
|
||||
.sidebar-link.active { background: #e6f0ff; color: #1d4ed8; }
|
||||
|
||||
.sidebar-link.active {
|
||||
background: #e6f0ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
.sidebar-icon { width: 20px; height: 20px; flex-shrink: 0; }
|
||||
.sidebar-logout { margin-top: auto; }
|
||||
|
||||
.sidebar-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.main { flex: 1; background: #f5f9ff; display: flex; flex-direction: column; }
|
||||
|
||||
.sidebar-logout {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* MAIN */
|
||||
.main {
|
||||
flex: 1;
|
||||
background: #f5f9ff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* TOPBAR */
|
||||
.topbar {
|
||||
background: #2b8ef3;
|
||||
margin: 20px;
|
||||
|
|
@ -101,37 +69,21 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
.topbar-waving {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
.topbar-waving { width: 26px; height: 26px; }
|
||||
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.topbar-right { display: flex; align-items: center; gap: 16px; }
|
||||
.topbar-icon { width: 24px; height: 24px; cursor: pointer; }
|
||||
|
||||
.topbar-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* CONTENT */
|
||||
.content {
|
||||
padding: 20px 30px;
|
||||
flex: 1;
|
||||
}
|
||||
.content { padding: 20px 30px; flex: 1; }
|
||||
</style>
|
||||
|
||||
@stack('styles')
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<aside class="sidebar">
|
||||
|
||||
<div class="sidebar-logo">
|
||||
<img src="{{ asset('images/logo/logosmk.png') }}" alt="Logo">
|
||||
</div>
|
||||
|
|
@ -174,48 +126,30 @@ class="sidebar-link {{ request()->routeIs('guru.leaderboard.*') ? 'active' : ''
|
|||
|
||||
<form action="{{ route('guru.logout') }}" method="POST" class="sidebar-logout">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-danger w-100">
|
||||
Logout
|
||||
</button>
|
||||
<button type="submit" class="btn btn-danger w-100">Logout</button>
|
||||
</form>
|
||||
|
||||
</aside>
|
||||
|
||||
<!-- MAIN -->
|
||||
<div class="main">
|
||||
|
||||
<!-- TOPBAR -->
|
||||
<header class="topbar">
|
||||
|
||||
<div class="topbar-left">
|
||||
<img src="{{ asset('images/icon/main/hi.png') }}"
|
||||
class="topbar-waving"
|
||||
alt="Waving">
|
||||
|
||||
<img src="{{ asset('images/icon/main/hi.png') }}" class="topbar-waving" alt="Waving">
|
||||
Selamat datang, {{ Auth::guard('guru')->user()->nama ?? 'Guru' }}
|
||||
</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<img src="{{ asset('images/icon/sidebar/notif.png') }}"
|
||||
class="topbar-icon"
|
||||
alt="Notification">
|
||||
|
||||
<img src="{{ asset('images/icon/sidebar/profil.png') }}"
|
||||
class="topbar-icon"
|
||||
alt="Profile">
|
||||
<img src="{{ asset('images/icon/sidebar/notif.png') }}" class="topbar-icon" alt="Notification">
|
||||
<img src="{{ asset('images/icon/sidebar/profil.png') }}" class="topbar-icon" alt="Profile">
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<main class="content">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
@section('title', 'Daftar Mata Pelajaran')
|
||||
|
||||
@section('content')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.page-title {
|
||||
font-size: 30px;
|
||||
|
|
@ -28,6 +27,9 @@
|
|||
border-radius: 10px;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-materi {
|
||||
|
|
@ -35,57 +37,372 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.btn-materi:hover { background: #1a7ae0; color: white; }
|
||||
|
||||
.btn-tugas {
|
||||
background: #f97316;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-tugas:hover { background: #ea6c0a; color: white; }
|
||||
|
||||
.kelas-badge {
|
||||
display: inline-block;
|
||||
background: #e6f0ff;
|
||||
color: #1d4ed8;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: 99px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* MODAL */
|
||||
.modal-header-blue {
|
||||
background: #2b8ef3;
|
||||
color: white;
|
||||
border-bottom: none;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.modal-header-orange {
|
||||
background: #f97316;
|
||||
color: white;
|
||||
border-bottom: none;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.modal-header-blue .btn-close,
|
||||
.modal-header-orange .btn-close {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.modal-body label { font-weight: 600; font-size: 14px; }
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed #cbd5e1;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.upload-area:hover { border-color: #2b8ef3; background: #f0f7ff; }
|
||||
|
||||
.upload-area input[type="file"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #f0f7ff;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.file-preview.show { display: flex; }
|
||||
|
||||
.alert-success-custom {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.alert-error-custom {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
<h3 class="page-title">MATA PELAJARAN YANG ANDA AJAR</h3>
|
||||
|
||||
<div class="custom-card">
|
||||
@if(session('success'))
|
||||
<div class="alert-success-custom">✅ {{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert-error-custom">❌ {{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<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>Kelas yang Diajar</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@forelse($mapels as $index => $mapel)
|
||||
@forelse($mengajars as $idMapel => $rows)
|
||||
@php
|
||||
$mapel = $rows->first()->mapel;
|
||||
$kelasList = $rows->map(fn($m) => $m->kelas)->filter();
|
||||
// Ambil id_mengajar pertama sebagai default (bisa dipilih per kelas di modal)
|
||||
$mengajarOptions = $rows->map(fn($m) => [
|
||||
'id_mengajar' => $m->id_mengajar,
|
||||
'kelas' => optional($m->kelas)->tingkat . ' ' . optional($m->kelas)->nama_kelas,
|
||||
]);
|
||||
@endphp
|
||||
<tr>
|
||||
<td>{{ $mapels->firstItem() + $index }}</td>
|
||||
<td>{{ $mapel->id_mapel }}</td>
|
||||
<td>{{ $mapel->nama_mapel }}</td>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td>{{ optional($mapel)->nama_mapel ?? '-' }}</td>
|
||||
|
||||
{{-- KOLOM KELAS --}}
|
||||
<td>
|
||||
<a href="{{ route('guru.materi.create', $mapel->id_mapel) }}"
|
||||
class="action-btn btn-materi">
|
||||
Upload Materi
|
||||
</a>
|
||||
@foreach($kelasList as $kelas)
|
||||
<span class="kelas-badge">
|
||||
{{ $kelas->tingkat }} {{ $kelas->nama_kelas }}
|
||||
</span>
|
||||
@endforeach
|
||||
</td>
|
||||
|
||||
<a href="{{ route('guru.tugas.create', $mapel->id_mapel) }}"
|
||||
class="action-btn btn-tugas">
|
||||
Buat Tugas
|
||||
</a>
|
||||
{{-- AKSI --}}
|
||||
<td>
|
||||
<button class="action-btn btn-materi"
|
||||
onclick="openMateriModal(
|
||||
'{{ addslashes(optional($mapel)->nama_mapel) }}',
|
||||
{{ $mengajarOptions->toJson() }}
|
||||
)">
|
||||
📄 Upload Materi
|
||||
</button>
|
||||
|
||||
<button class="action-btn btn-tugas mt-1"
|
||||
onclick="openTugasModal(
|
||||
'{{ addslashes(optional($mapel)->nama_mapel) }}',
|
||||
{{ $mengajarOptions->toJson() }}
|
||||
)">
|
||||
📋 Buat Tugas
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4">Anda belum mengajar mata pelajaran apapun.</td>
|
||||
<td colspan="4" class="text-muted py-4">
|
||||
Anda belum mengajar mata pelajaran apapun.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
{{ $mapels->links() }}
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- MODAL UPLOAD MATERI --}}
|
||||
{{-- ============================================================ --}}
|
||||
<div class="modal fade" id="modalMateri" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header modal-header-blue">
|
||||
<h5 class="modal-title">📄 Upload Materi — <span id="materiMapelLabel"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('guru.materi.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
|
||||
{{-- Pilih Kelas (jika guru ajar mapel ini di lebih dari 1 kelas) --}}
|
||||
<div class="mb-3">
|
||||
<label>Kelas Tujuan <span class="text-danger">*</span></label>
|
||||
<select name="id_mengajar" id="materiMengajar" class="form-control" required>
|
||||
<option value="">-- Pilih Kelas --</option>
|
||||
</select>
|
||||
<small class="text-muted">Materi akan dikirim ke kelas yang dipilih.</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Judul Materi <span class="text-danger">*</span></label>
|
||||
<input type="text" name="judul_materi" class="form-control"
|
||||
placeholder="Contoh: Pertemuan 1 - Pengantar Algoritma" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Deskripsi</label>
|
||||
<textarea name="deskripsi" class="form-control" rows="3"
|
||||
placeholder="Deskripsi singkat materi (opsional)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>File Materi <small class="text-muted fw-normal">(PDF, DOC, PPT, JPG — maks 10MB)</small></label>
|
||||
<div class="upload-area" id="materiUploadArea">
|
||||
<input type="file" name="lampiran_materi" id="materiFile"
|
||||
accept=".pdf,.doc,.docx,.ppt,.pptx,.jpg,.jpeg,.png"
|
||||
onchange="previewFile(this, 'materiPreview', 'materiFileName')">
|
||||
<div style="font-size:32px">☁️</div>
|
||||
<p style="margin:6px 0 0;font-size:14px;color:#64748b">
|
||||
<strong>Klik</strong> atau drag file ke sini
|
||||
</p>
|
||||
</div>
|
||||
<div class="file-preview" id="materiPreview">
|
||||
📎 <span id="materiFileName">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary">Upload Materi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- MODAL BUAT TUGAS --}}
|
||||
{{-- ============================================================ --}}
|
||||
<div class="modal fade" id="modalTugas" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header modal-header-orange">
|
||||
<h5 class="modal-title">📋 Buat Tugas — <span id="tugasMapelLabel"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('guru.tugas.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
|
||||
{{-- Pilih Kelas --}}
|
||||
<div class="mb-3">
|
||||
<label>Kelas Tujuan <span class="text-danger">*</span></label>
|
||||
<select name="id_mengajar" id="tugasMengajar" class="form-control" required>
|
||||
<option value="">-- Pilih Kelas --</option>
|
||||
</select>
|
||||
<small class="text-muted">Tugas akan dikirim ke kelas yang dipilih.</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Judul Tugas <span class="text-danger">*</span></label>
|
||||
<input type="text" name="judul_tugas" class="form-control"
|
||||
placeholder="Contoh: Latihan Soal Bab 3" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Keterangan / Instruksi</label>
|
||||
<textarea name="keterangan" class="form-control" rows="4"
|
||||
placeholder="Instruksi pengerjaan tugas (opsional)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Deadline <span class="text-danger">*</span></label>
|
||||
<input type="datetime-local" name="deadline" class="form-control" required
|
||||
min="{{ now()->format('Y-m-d\TH:i') }}">
|
||||
<small class="text-muted">Pastikan deadline lebih dari waktu sekarang.</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn" style="background:#f97316;color:white">Buat Tugas</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// ===== BUKA MODAL MATERI =====
|
||||
function openMateriModal(namaMapel, mengajars) {
|
||||
document.getElementById('materiMapelLabel').textContent = namaMapel;
|
||||
|
||||
const select = document.getElementById('materiMengajar');
|
||||
select.innerHTML = '<option value="">-- Pilih Kelas --</option>';
|
||||
|
||||
mengajars.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.id_mengajar;
|
||||
opt.textContent = m.kelas;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
// Jika hanya 1 kelas, langsung pilih otomatis
|
||||
if (mengajars.length === 1) select.value = mengajars[0].id_mengajar;
|
||||
|
||||
// Reset form
|
||||
document.querySelector('#modalMateri input[name="judul_materi"]').value = '';
|
||||
document.querySelector('#modalMateri textarea[name="deskripsi"]').value = '';
|
||||
document.getElementById('materiPreview').classList.remove('show');
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalMateri')).show();
|
||||
}
|
||||
|
||||
// ===== BUKA MODAL TUGAS =====
|
||||
function openTugasModal(namaMapel, mengajars) {
|
||||
document.getElementById('tugasMapelLabel').textContent = namaMapel;
|
||||
|
||||
const select = document.getElementById('tugasMengajar');
|
||||
select.innerHTML = '<option value="">-- Pilih Kelas --</option>';
|
||||
|
||||
mengajars.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.id_mengajar;
|
||||
opt.textContent = m.kelas;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
if (mengajars.length === 1) select.value = mengajars[0].id_mengajar;
|
||||
|
||||
// Reset form
|
||||
document.querySelector('#modalTugas input[name="judul_tugas"]').value = '';
|
||||
document.querySelector('#modalTugas textarea[name="keterangan"]').value = '';
|
||||
document.querySelector('#modalTugas input[name="deadline"]').value = '';
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalTugas')).show();
|
||||
}
|
||||
|
||||
// ===== PREVIEW FILE =====
|
||||
function previewFile(input, previewId, nameId) {
|
||||
const preview = document.getElementById(previewId);
|
||||
const nameEl = document.getElementById(nameId);
|
||||
if (input.files && input.files[0]) {
|
||||
nameEl.textContent = input.files[0].name +
|
||||
' (' + (input.files[0].size / 1024 / 1024).toFixed(2) + ' MB)';
|
||||
preview.classList.add('show');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -89,7 +89,6 @@
|
|||
|
||||
.sidebar-logout {
|
||||
margin-top: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-logout button {
|
||||
|
|
@ -100,7 +99,6 @@
|
|||
font-weight: 600;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ===== TOGGLE ARROW BUTTON ===== */
|
||||
|
|
@ -241,18 +239,13 @@ class="sidebar-link {{ request()->routeIs('siswa.leaderboard*') ? 'active' : ''
|
|||
<img src="{{ asset('images/icon/sidebar/lb.png') }}" class="sidebar-icon" alt="">
|
||||
<span>Leaderboard</span>
|
||||
</a>
|
||||
|
||||
<a href="#"
|
||||
class="sidebar-link {{ request()->routeIs('siswa.profil*') ? 'active' : '' }}">
|
||||
<img src="{{ asset('images/icon/sidebar/profil.png') }}" class="sidebar-icon" alt="">
|
||||
<span>Profil</span>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
<form action="{{ route('siswa.logout') }}" method="POST" class="sidebar-logout">
|
||||
@csrf
|
||||
<button type="submit">Logout</button>
|
||||
<button type="submit" class="btn btn-danger w-100">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
|
|
|
|||
|
|
@ -87,10 +87,11 @@
|
|||
|
||||
Route::get('/profil', function () {
|
||||
return view('admin.profil');
|
||||
})->name('profil');
|
||||
|
||||
})->name('profil');
|
||||
|
||||
// CRUD AREA
|
||||
Route::get('/guru/kelas-by-mapel', [AdminGuruController::class, 'getKelasByMapel'])
|
||||
->name('guru.kelasByMapel');
|
||||
Route::resource('guru', AdminGuruController::class);
|
||||
Route::resource('siswa', AdminSiswaController::class);
|
||||
Route::resource('kelas', AdminKelasController::class);
|
||||
|
|
@ -98,6 +99,7 @@
|
|||
Route::resource('leaderboard', AdminLeaderboardController::class)
|
||||
->only(['index']);
|
||||
Route::resource('challenge', AdminChallengeController::class);
|
||||
|
||||
|
||||
// LOGOUT ADMIN
|
||||
Route::post('/logout', [LoginController::class, 'logout'])
|
||||
|
|
@ -123,6 +125,8 @@
|
|||
|
||||
Route::get('/mapel', [GuruMapelController::class, 'index'])
|
||||
->name('mapel.index');
|
||||
Route::post('/materi/store', [GuruMapelController::class, 'storeMateri'])->name('materi.store');
|
||||
Route::post('/tugas/store', [GuruMapelController::class, 'storeTugas'])->name('tugas.store');
|
||||
|
||||
Route::get('/leaderboard', [GuruLeaderboardController::class, 'index'])
|
||||
->name('leaderboard.index');
|
||||
|
|
|
|||
Loading…
Reference in New Issue