This commit is contained in:
ell 2026-04-30 17:40:44 +07:00
parent 03fa985785
commit 50a5d55169
26 changed files with 2492 additions and 823 deletions

View File

@ -84,7 +84,7 @@ public function store(Request $request)
'alamat' => $request->alamat,
'catatan_medis' => $request->catatan_medis,
'jabatan' => $nama_jabatan,
'status' => 'aktif', // Input admin otomatis aktif
'status' => 'Aktif', // Input admin otomatis aktif
]);
DB::commit();
@ -120,7 +120,7 @@ public function update(Request $request, $id)
'no_hp' => 'required',
'role_id' => 'required|exists:role,id',
'kolat_id' => 'required|exists:kolat,id',
'status' => 'required|in:aktif,Non-Aktif',
'status' => 'required|in:Aktif,Non-Aktif',
]);
DB::beginTransaction();

View File

@ -5,15 +5,17 @@
use App\Models\Jadwal;
use App\Models\Kolat;
use App\Models\Anggota;
use App\Models\Role;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
class JadwalController extends Controller
{
// 1. Menampilkan Daftar Jadwal
public function index()
{
$data_jadwal = Jadwal::with(['kolat', 'pelatih'])
// Tambahkan 'presensi.anggota' agar modal konfirmasi bisa nampilin siapa aja yang hadir
$data_jadwal = Jadwal::with(['kolat', 'pelatih', 'presensi.anggota'])
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
@ -21,33 +23,49 @@ public function index()
return view('jadwal.jadwal', compact('data_jadwal'));
}
public function konfirmasiSelesai($id)
{
// 2. Proses Konfirmasi Latihan Selesai (Update Status + Upload Foto Kegiatan)
public function konfirmasiSelesai(Request $request, $id)
{
$jadwal = Jadwal::findOrFail($id);
// Kita paksa ubah status ke 'selesai'
// Validasi input dari modal
$request->validate([
'foto_bukti' => 'required|image|mimes:jpg,jpeg,png|max:2048',
'catatan_latihan' => 'nullable|string'
]);
// Proses upload foto kegiatan pelatih ke folder storage/app/public/kegiatan_pelatih
if ($request->hasFile('foto_bukti')) {
$path = $request->file('foto_bukti')->store('kegiatan_pelatih', 'public');
$jadwal->foto_bukti = $path;
}
// Simpan catatan dan ubah status menjadi selesai
$jadwal->catatan_latihan = $request->catatan_latihan;
$jadwal->status = 'selesai';
$jadwal->save();
return redirect()->route('jadwal.index')
->with('success', 'Latihan ' . $jadwal->judul_kegiatan . ' telah dikonfirmasi selesai!');
}
}
// 3. Menampilkan Form Tambah Jadwal
public function create()
{
$data_kolat = Kolat::all();
$rolePelatih = Role::where('nama_role', 'Pelatih')->first();
// Ambil Anggota yang memiliki role 'Pelatih' saja (filter lewat relasi)
// Ambil Anggota yang memiliki role 'Pelatih' dan statusnya 'Aktif'
$data_pelatih = Anggota::whereHas('role', function($query) {
$query->where('nama_role', 'Pelatih');
})->get();
})
->where('status', 'Aktif')
->orderBy('nama_lengkap', 'asc')
->get();
return view('jadwal.jadwalcreate', compact('data_kolat', 'data_pelatih'));
}
// 4. Proses Simpan Jadwal Baru
public function store(Request $request)
{
$request->validate([
@ -66,14 +84,14 @@ public function store(Request $request)
->with('success', 'Jadwal latihan berhasil ditambahkan!');
}
// 5. Menampilkan Detail Jadwal (Opsional jika pakai modal)
public function show($id)
{
// Ambil satu data dengan relasinya
{
$jadwal = Jadwal::with(['kolat', 'pelatih'])->findOrFail($id);
return view('jadwal.show', compact('jadwal'));
}
}
// 6. Menampilkan Form Edit Jadwal
public function edit($id)
{
$jadwal = Jadwal::findOrFail($id);
@ -87,6 +105,7 @@ public function edit($id)
return view('jadwal.editjadwal', compact('jadwal', 'data_kolat', 'data_pelatih'));
}
// 7. Proses Update Jadwal
public function update(Request $request, $id)
{
$jadwal = Jadwal::findOrFail($id);
@ -107,10 +126,16 @@ public function update(Request $request, $id)
->with('success', 'Jadwal berhasil diperbarui!');
}
// 8. Proses Hapus Jadwal
public function destroy($id)
{
$jadwal = Jadwal::findOrFail($id);
// Cek dan hapus file foto bukti dari storage jika ada, biar hosting nggak penuh
if ($jadwal->foto_bukti) {
Storage::disk('public')->delete($jadwal->foto_bukti);
}
$jadwal->delete();
return redirect()->route('jadwal.index')

View File

@ -2,127 +2,177 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Anggota;
// TAMBAHAN WAJIB: Memanggil Facades dan Model yang digunakan
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use App\Models\Jadwal;
use App\Models\Presensi;
use App\Models\Anggota;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PresensiController extends Controller
{
/**
* AKTOR: PENGURUS/PELATIH
* Menampilkan daftar semua jadwal untuk dipilih mana yang mau dicek presensinya
*/
public function index()
{
// Mengambil semua jadwal secara manual menggunakan Query Builder (JOIN)
$data_jadwal = DB::table('jadwals')
->leftJoin('anggotas', 'jadwals.pelatih_id', '=', 'anggotas.id')
->leftJoin('kolat', 'jadwals.kolat_id', '=', 'kolat.id') // Sesuaikan jika nama tabel di database kamu adalah 'kolat'
->select(
'jadwals.*',
'anggotas.nama_lengkap as nama_pelatih',
'kolat.nama_kolat as nama_kolat'
)
->orderBy('jadwals.tanggal', 'desc')
// Menggunakan Eloquent agar lebih konsisten dengan fungsi lainnya
$data_jadwal = Jadwal::with(['kolat', 'pelatih'])
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
// Pastikan nama foldernya konsisten (admin atau pengurus)
return view('presensi.pengurus.index', compact('data_jadwal'));
}
/**
* AKTOR: PENGURUS/PELATIH
* Menampilkan detail siapa saja yang sudah scan/absen di jadwal tertentu
*/
public function kehadiran($jadwal_id)
{
// Variabel dibuat dengan nama $jadwal (tunggal)
{
$jadwal = Jadwal::with(['kolat', 'pelatih'])->findOrFail($jadwal_id);
$data_presensi = Presensi::with('anggota', 'pelatihVerifikator')
->where('jadwal_id', $jadwal_id)
->get();
$daftar_anggota = Anggota::where('kolat_id', $jadwal->kolat_id)
// 1. Ambil array ID anggota yang SUDAH absen
$id_sudah_absen = $data_presensi->pluck('anggota_id')->toArray();
// 2. Ambil anggota yang se-Kolat, status Aktif, tapi ID-nya BELUM ADA di daftar absen
$anggota_belum_absen = Anggota::where('kolat_id', $jadwal->kolat_id)
->where('status', 'Aktif')
->whereNotIn('id', $id_sudah_absen)
->get();
// Pastikan di sini tulisannya 'jadwal' (sesuai nama variabel di atas)
return view('presensi.pengurus.kehadiran', compact('jadwal', 'data_presensi', 'daftar_anggota'));
}
/**
* 2. Proses Pelatih klik "Konfirmasi Hadir"
*/
public function konfirmasi($presensi_id)
{
$presensi = Presensi::findOrFail($presensi_id);
// Ubah status menjadi terverifikasi dan catat ID Pelatih yang klik
$presensi->update([
'is_verified' => true,
'verified_by' => Auth::user()->id
]);
return redirect()->back()->with('success', 'Kehadiran ' . $presensi->anggota->nama_lengkap . ' berhasil dikonfirmasi!');
// Kirim variabel baru ini ke View
return view('presensi.pengurus.kehadiran', compact('jadwal', 'data_presensi', 'anggota_belum_absen'));
}
/**
* 3. Proses Pelatih input manual dari Modal (Kasus Lupa HP)
* AKTOR: PENGURUS/PELATIH
* Mensahkan (Verify) absensi anggota yang masuk lewat scan QR
*/
public function konfirmasi(Request $request, $presensi_id)
{
$presensi = Presensi::findOrFail($presensi_id);
$request->validate([
'action' => 'required|in:sah,tolak',
'keterangan' => 'nullable|string' // <-- Tambahan validasi keterangan
]);
if ($request->action == 'sah') {
$presensi->update([
'is_verified' => true,
'status' => 'Hadir',
'verified_by' => Auth::user()->anggota->id,
]);
$pesan = 'Kehadiran ' . $presensi->anggota->nama_lengkap . ' berhasil disahkan!';
} else {
$presensi->update([
'is_verified' => true,
'status' => 'Alfa',
'verified_by' => Auth::user()->anggota->id,
'keterangan' => $request->keterangan // <-- Simpan alasan penolakan
]);
$pesan = 'Absensi ' . $presensi->anggota->nama_lengkap . ' ditolak (Alfa).';
}
return redirect()->back()->with('success', $pesan);
}
/**
* AKTOR: PENGURUS/PELATIH
* Input manual jika anggota lupa bawa HP (Otomatis is_verified = true)
*/
public function storeManual(Request $request)
{
// Validasi input dari form modal
$request->validate([
'jadwal_id' => 'required|exists:jadwals,id',
'anggota_id' => 'required|exists:anggotas,id',
'status' => 'required|in:Hadir,Izin,Sakit,Alfa',
'keterangan' => 'nullable|string' // <-- Tambahan validasi keterangan
]);
// Cek apakah anggota tersebut sudah absen sebelumnya agar tidak ada data ganda (double input)
$sudah_absen = Presensi::where('jadwal_id', $request->jadwal_id)
->where('anggota_id', $request->anggota_id)
->first();
if ($sudah_absen) {
return redirect()->back()->with('error', 'Anggota tersebut sudah tercatat di daftar kehadiran!');
return redirect()->back()->with('error', 'Anggota tersebut sudah memiliki catatan kehadiran di jadwal ini!');
}
// Simpan data presensi baru
// Karena diinput langsung oleh pelatih, is_verified otomatis bernilai true
Presensi::create([
'jadwal_id' => $request->jadwal_id,
'anggota_id' => $request->anggota_id,
'status' => 'Hadir',
'waktu_presensi' => now(), // Waktu dicatat saat form disubmit
'is_verified' => true, // Langsung Sah
'verified_by' => Auth::user()->id
'status' => $request->status,
'keterangan' => $request->keterangan, // <-- Simpan keterangan manual
'waktu_presensi' => now(),
'is_verified' => true,
'verified_by' => Auth::user()->anggota->id,
]);
return redirect()->back()->with('success', 'Presensi manual berhasil ditambahkan!');
return redirect()->back()->with('success', 'Presensi manual berhasil disimpan!');
}
/**
* AKTOR: ANGGOTA
* Melihat daftar jadwal latihan hari ini untuk di-scan
*/
/**
* AKTOR: ANGGOTA
* Melihat daftar jadwal latihan hari ini dan riwayat 7 hari terakhir
*/
public function indexAnggota()
{
$now = \Carbon\Carbon::now('Asia/Jakarta');
$hari_ini = $now->format('Y-m-d');
$user = Auth::user();
// 1. Ambil jadwal latihan yang terjadi HARI INI sesuai dengan Kolat si Anggota
// Asumsinya di tabel anggotas (users) ada kolom kolat_id
$jadwal_hari_ini = Jadwal::with('pelatih')
->where('tanggal', $hari_ini)
->where('kolat_id', $user->kolat_id)
->orderBy('jam_mulai', 'asc')
->get();
// 2. Cek apakah anggota ini sudah absen di jadwal tersebut
// Menggunakan pluck() untuk mengambil ID jadwal hari ini saja
$presensi_saya = Presensi::where('anggota_id', $user->id)
->whereIn('jadwal_id', $jadwal_hari_ini->pluck('id'))
->get()
->keyBy('jadwal_id'); // Jadikan ID jadwal sebagai key array agar mudah dicek di View
return view('presensi.anggota.index', compact('jadwal_hari_ini', 'presensi_saya', 'now'));
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'Silakan login terlebih dahulu.');
}
$user = Auth::user();
$now = \Carbon\Carbon::now('Asia/Jakarta');
$hari_ini = $now->format('Y-m-d');
// Ambil tanggal 7 hari yang lalu
$seminggu_lalu = $now->copy()->subDays(7)->format('Y-m-d');
if (!$user->anggota) {
return redirect()->route('presensi.index')->with('error', 'Akun Anda tidak terdaftar sebagai peserta latihan.');
}
// Ambil jadwal dalam rentang 7 hari terakhir hingga hari ini
$jadwal_terbaru = Jadwal::with('pelatih')
->whereBetween('tanggal', [$seminggu_lalu, $hari_ini])
->where('kolat_id', $user->anggota->kolat_id)
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
$presensi_saya = Presensi::where('anggota_id', $user->anggota->id)
->whereIn('jadwal_id', $jadwal_terbaru->pluck('id'))
->get()
->keyBy('jadwal_id');
return view('presensi.anggota.index', compact('jadwal_terbaru', 'presensi_saya', 'now', 'hari_ini'));
}
/**
* AKTOR: ANGGOTA
* Menampilkan halaman kamera scanner
*/
public function scan($jadwal_id)
{
$jadwal = Jadwal::findOrFail($jadwal_id);
return view('presensi.anggota.scan', compact('jadwal'));
}
/**
* AKTOR: ANGGOTA
* Menyimpan data presensi hasil scan (Menunggu Verifikasi Pelatih)
*/
public function storeMandiri(Request $request)
{
$request->validate([
@ -130,26 +180,86 @@ public function storeMandiri(Request $request)
]);
$user = Auth::user();
$profilAnggota = $user->anggota;
// Cek agar tidak bisa klik absen 2 kali (Double submit)
if (!$profilAnggota) {
return redirect()->back()->with('error', 'Data profil tidak lengkap.');
}
$jadwal = Jadwal::findOrFail($request->jadwal_id);
$now = \Carbon\Carbon::now('Asia/Jakarta');
// Validasi Waktu: Gabungkan tanggal dan jam untuk pengecekan presisi
$jam_selesai = $jadwal->jam_selesai
? $jadwal->jam_selesai
: \Carbon\Carbon::parse($jadwal->jam_mulai)->addHours(3)->format('H:i:s');
$batas_waktu = \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jam_selesai, 'Asia/Jakarta');
if ($now->gt($batas_waktu)) {
return redirect()->route('presensi.anggota.index')->with('error', 'Sesi absensi sudah berakhir pada pukul ' . substr($jam_selesai, 0, 5) . ' WIB.');
}
if ($jadwal->status == 'selesai') {
return redirect()->route('presensi.anggota.index')->with('error', 'Sesi latihan sudah ditutup oleh pelatih.');
}
// Cek apakah sudah absen
$sudah_absen = Presensi::where('jadwal_id', $request->jadwal_id)
->where('anggota_id', $user->id)
->where('anggota_id', $profilAnggota->id)
->exists();
if ($sudah_absen) {
return redirect()->back()->with('error', 'Kamu sudah melakukan absensi untuk jadwal ini!');
return redirect()->route('presensi.anggota.index')->with('error', 'Kamu sudah melakukan absensi untuk jadwal ini!');
}
// Simpan data presensi dengan status is_verified = false (Menunggu ACC)
Presensi::create([
'jadwal_id' => $request->jadwal_id,
'anggota_id' => $user->id,
'anggota_id' => $profilAnggota->id,
'status' => 'Hadir',
'waktu_presensi' => now(),
'is_verified' => false, // Belum Sah, butuh ACC Pelatih
'verified_by' => null
'waktu_presensi' => $now,
'is_verified' => false,
'verified_by' => null,
]);
return redirect()->back()->with('success', 'Berhasil! Kehadiranmu sedang menunggu konfirmasi Pelatih.');
return redirect()->route('presensi.anggota.index')->with('success', 'Berhasil scan! Silakan minta Pelatih untuk konfirmasi kehadiran.');
}
/**
* AKTOR: ANGGOTA
* Menampilkan riwayat lengkap presensi dengan filter bulan dan tahun
*/
public function riwayatAnggota(Request $request)
{
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'Silakan login terlebih dahulu.');
}
$user = Auth::user();
if (!$user->anggota) {
return redirect()->route('presensi.index')->with('error', 'Profil anggota tidak ditemukan.');
}
// Tangkap request filter bulan & tahun (Default: Bulan & Tahun saat ini)
$bulan = $request->input('bulan', date('m'));
$tahun = $request->input('tahun', date('Y'));
// Ambil semua jadwal sesuai filter bulan & tahun di Kolat anggota tersebut
$jadwal_riwayat = Jadwal::with('pelatih')
->where('kolat_id', $user->anggota->kolat_id)
->whereMonth('tanggal', $bulan)
->whereYear('tanggal', $tahun)
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
// Ambil data presensi anggota pada jadwal-jadwal tersebut
$presensi_saya = Presensi::where('anggota_id', $user->anggota->id)
->whereIn('jadwal_id', $jadwal_riwayat->pluck('id'))
->get()
->keyBy('jadwal_id');
return view('presensi.anggota.riwayat', compact('jadwal_riwayat', 'presensi_saya', 'bulan', 'tahun'));
}
}

View File

@ -0,0 +1,125 @@
// controller jadwal //
<?php
namespace App\Http\Controllers;
use App\Models\Jadwal;
use App\Models\Kolat;
use App\Models\Anggota;
use App\Models\Role;
use Illuminate\Http\Request;
class JadwalController extends Controller
{
public function index()
{
$data_jadwal = Jadwal::with(['kolat', 'pelatih'])
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
return view('jadwal.jadwal', compact('data_jadwal'));
}
public function konfirmasiSelesai($id)
{
$jadwal = Jadwal::findOrFail($id);
// Kita paksa ubah status ke 'selesai'
$jadwal->status = 'selesai';
$jadwal->save();
return redirect()->route('jadwal.index')
->with('success', 'Latihan ' . $jadwal->judul_kegiatan . ' telah dikonfirmasi selesai!');
}
public function create()
{
$data_kolat = Kolat::all();
$rolePelatih = Role::where('nama_role', 'Pelatih')->first();
// Ambil Anggota yang memiliki role 'Pelatih' saja (filter lewat relasi)
$data_pelatih = Anggota::whereHas('role', function($query) {
$query->where('nama_role', 'Pelatih');
})
->where('status', 'Aktif') // <-- INI BATASAN TAMBAHANNYA
->orderBy('nama_lengkap', 'asc') // (Bonus) Biar namanya urut abjad di form
->get();
return view('jadwal.jadwalcreate', compact('data_kolat', 'data_pelatih'));
}
public function store(Request $request)
{
$request->validate([
'judul_kegiatan' => 'required|string|max:255',
'kolat_id' => 'required|exists:kolat,id',
'pelatih_id' => 'required|exists:anggotas,id',
'jenis' => 'required|in:Rutin,Tambahan,Pengumuman',
'tanggal' => 'required|date',
'jam_mulai' => 'required',
'lokasi' => 'required|string',
]);
Jadwal::create($request->all());
return redirect()->route('jadwal.index')
->with('success', 'Jadwal latihan berhasil ditambahkan!');
}
public function show($id)
{
// Ambil satu data dengan relasinya
$jadwal = Jadwal::with(['kolat', 'pelatih'])->findOrFail($id);
return view('jadwal.show', compact('jadwal'));
}
public function edit($id)
{
$jadwal = Jadwal::findOrFail($id);
$data_kolat = Kolat::all();
// Tetap filter agar hanya muncul daftar pelatih
$data_pelatih = Anggota::whereHas('role', function($query) {
$query->where('nama_role', 'Pelatih');
})->get();
return view('jadwal.editjadwal', compact('jadwal', 'data_kolat', 'data_pelatih'));
}
public function update(Request $request, $id)
{
$jadwal = Jadwal::findOrFail($id);
$request->validate([
'judul_kegiatan' => 'required|string|max:255',
'kolat_id' => 'required|exists:kolat,id',
'pelatih_id' => 'required|exists:anggotas,id',
'jenis' => 'required|in:Rutin,Tambahan,Pengumuman',
'tanggal' => 'required|date',
'jam_mulai' => 'required',
'lokasi' => 'required|string',
]);
$jadwal->update($request->all());
return redirect()->route('jadwal.index')
->with('success', 'Jadwal berhasil diperbarui!');
}
public function destroy($id)
{
$jadwal = Jadwal::findOrFail($id);
$jadwal->delete();
return redirect()->route('jadwal.index')
->with('success', 'Jadwal telah dihapus dari sistem.');
}
}

View File

@ -19,7 +19,9 @@ class Jadwal extends Model
'jam_mulai',
'jam_selesai',
'lokasi',
'deskripsi'
'deskripsi',
'foto_bukti', // Tambahan baru untuk foto kegiatan (Pelatih)
'catatan_latihan',
];
// Relasi: Jadwal ini milik Kolat mana?

View File

@ -29,4 +29,9 @@ public function anggota()
{
return $this->belongsTo(Anggota::class, 'anggota_id');
}
public function pelatihVerifikator()
{
return $this->belongsTo(Anggota::class, 'verified_by');
}
}

View File

@ -54,4 +54,5 @@ public function role()
{
return $this->belongsTo(Role::class, 'role_id');
}
}

View File

@ -9,7 +9,8 @@
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8"
"laravel/tinker": "^2.8",
"simplesoftwareio/simple-qrcode": "^4.2"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

View File

@ -4,8 +4,62 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9c491b8531eec05ba41a11d9276a5749",
"content-hash": "d0e9a868dabf1fd8df971928d0612783",
"packages": [
{
"name": "bacon/bacon-qr-code",
"version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
"shasum": ""
},
"require": {
"dasprid/enum": "^1.0.3",
"ext-iconv": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phly/keep-a-changelog": "^2.1",
"phpunit/phpunit": "^7 | ^8 | ^9",
"spatie/phpunit-snapshot-assertions": "^4.2.9",
"squizlabs/php_codesniffer": "^3.4"
},
"suggest": {
"ext-imagick": "to generate QR code images"
},
"type": "library",
"autoload": {
"psr-4": {
"BaconQrCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "BaconQrCode is a QR code generator for PHP.",
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
},
"time": "2022-12-07T17:46:57+00:00"
},
{
"name": "brick/math",
"version": "0.12.3",
@ -135,6 +189,56 @@
],
"time": "2023-12-11T17:09:12+00:00"
},
{
"name": "dasprid/enum",
"version": "1.0.7",
"source": {
"type": "git",
"url": "https://github.com/DASPRiD/Enum.git",
"reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
"reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
"shasum": ""
},
"require": {
"php": ">=7.1 <9.0"
},
"require-dev": {
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"DASPRiD\\Enum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "PHP 7.1 enum implementation",
"keywords": [
"enum",
"map"
],
"support": {
"issues": "https://github.com/DASPRiD/Enum/issues",
"source": "https://github.com/DASPRiD/Enum/tree/1.0.7"
},
"time": "2025-09-16T12:23:56+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@ -3159,6 +3263,74 @@
},
"time": "2025-12-14T04:43:48+00:00"
},
{
"name": "simplesoftwareio/simple-qrcode",
"version": "4.2.0",
"source": {
"type": "git",
"url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git",
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537",
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537",
"shasum": ""
},
"require": {
"bacon/bacon-qr-code": "^2.0",
"ext-gd": "*",
"php": ">=7.2|^8.0"
},
"require-dev": {
"mockery/mockery": "~1",
"phpunit/phpunit": "~9"
},
"suggest": {
"ext-imagick": "Allows the generation of PNG QrCodes.",
"illuminate/support": "Allows for use within Laravel."
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode"
},
"providers": [
"SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"SimpleSoftwareIO\\QrCode\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Simple Software LLC",
"email": "support@simplesoftware.io"
}
],
"description": "Simple QrCode is a QR code generator made for Laravel.",
"homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode",
"keywords": [
"Simple",
"generator",
"laravel",
"qrcode",
"wrapper"
],
"support": {
"issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues",
"source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0"
},
"time": "2021-02-08T20:43:55+00:00"
},
{
"name": "symfony/console",
"version": "v6.4.32",

View File

@ -183,6 +183,7 @@
'aliases' => Facade::defaultAliases()->merge([
// 'Example' => App\Facades\Example::class,
'QrCode' => SimpleSoftwareIO\QrCode\Facades\QrCode::class,
])->toArray(),
];

View File

@ -14,15 +14,8 @@ public function run(): void
{
$this->call([
RoleSeeder::class,
]);
// \App\Models\User::factory(10)->create();
// \App\Models\User::factory()->create([
// 'name' => 'Test User',
// 'email' => 'test@example.com',
// ]);
$this->call([
RoleSeeder::class,
KolatSeeder::class,
UserSeeder::class,
]);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class KolatSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('kolat')->insert([
'id' => 1, // Kita paksa ID 1 biar nyambung sama UserSeeder tadi
'nama_kolat' => 'Kolat Polije',
'created_at' => now(),
'updated_at' => now(),
]);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Database\Seeders;
use App\Models\Anggota;
use App\Models\Role;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$rolePelatih = Role::where('nama_role', 'Pelatih')->first();
// 2. Buat Akun User untuk Pelatih
$user = User::create([
'role_id' => $rolePelatih->id,
'email' => 'pelatih@tapakmp.com',
'password' => Hash::make('password123'), // Password default
]);
// 3. Buat Profil Anggota untuk User tersebut
Anggota::create([
'user_id' => $user->id,
'role_id' => $rolePelatih->id,
'no_induk' => 'MP-2026-001',
'nama_lengkap' => 'Siti Afiah Dahliawati',
'jenis_kelamin' => 'P',
'tempat_lahir' => 'Jember',
'tgl_lahir' => '2004-10-29',
'no_hp' => '081455057453',
'kolat_id' => 1, // Pastikan sudah ada data Kolat ID 1 atau sesuaikan
'tingkatan' => 'Balik 1',
'tgl_gabung' => now(),
'status' => 'Aktif',
'alamat' => 'Jl. Merpati Putih No. 1, Jember',
'jabatan' => 'Pelatih Kolat',
]);
}
}

View File

@ -1,264 +1,121 @@
@extends('layout.app')
@section('content')
<div class="container-fluid py-4">
<<<<<<< Updated upstream
<!-- Header & Breadcrumb -->
=======
>>>>>>> Stashed changes
<div class="container-fluid py-4">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="page-header mb-4 text-start">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-transparent p-0 mb-2">
<li class="breadcrumb-item"><a href="{{ route('anggota.anggota') }}"
class="text-muted text-decoration-none small">Daftar Anggota</a></li>
<li class="breadcrumb-item"><a href="{{ route('anggota.anggota') }}" class="text-muted text-decoration-none small">Daftar Anggota</a></li>
<li class="breadcrumb-item active text-primary fw-bold small" aria-current="page">Edit Anggota</li>
</ol>
</nav>
<h3 class="fw-bold text-dark">Form Edit Anggota Merpati Putih</h3>
<p class="text-muted small">Perbarui informasi identitas atau legalitas SK anggota di bawah ini.</p>
</div>
<div class="row">
<div class="col-md-12">
<<<<<<< Updated upstream
<!-- Form Start - Pastikan enctype ada untuk upload foto SK -->
=======
>>>>>>> Stashed changes
<form action="{{ route('anggota.update', $anggota->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<<<<<<< Updated upstream
<!-- CARD 1: INFORMASI PRIBADI -->
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-primary mb-0"><i class="icon-user me-2"></i>Informasi Pribadi</h5>
<h5 class="fw-bold text-primary mb-0"><i class="icon-user me-2"></i>Informasi Akun & Pribadi</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-bold">No Induk</label>
<input type="text" name="no_induk" class="form-control bg-light fw-bold text-primary"
value="{{ $anggota->no_induk }}" readonly>
<small class="text-muted" style="font-size: 11px;">* No. induk permanen dan tidak dapat
diubah</small>
<label class="form-label fw-bold">Email Login</label>
<input type="email" name="email" class="form-control" value="{{ old('email', $anggota->user->email) }}" required>
<small class="text-muted" style="font-size: 11px;">* Email digunakan untuk login sistem</small>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Nama Lengkap</label>
<input type="text" name="nama_lengkap" class="form-control bg-light"
value="{{ $anggota->nama_lengkap }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">No. WhatsApp (HP)</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0">+62</span>
<input type="number" name="no_hp" class="form-control"
value="{{ $anggota->no_hp }}" placeholder="81234567xxx" required>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Alamat Lengkap</label>
<input type="text" name="alamat" class="form-control" value="{{ $anggota->alamat }}"
required>
</div>
</div>
</div>
</div>
<!-- CARD 2: INFORMASI ORGANISASI -->
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-success mb-0"><i class="icon-badge me-2"></i>Informasi Organisasi</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label fw-bold">Jabatan Organisasi</label>
<select name="jabatan" id="jabatanSelect" class="form-select border-primary"
onchange="toggleSKFields()" required>
<option value="anggota" {{ $anggota->jabatan == 'anggota' ? 'selected' : '' }}>
Anggota</option>
<option value="pelatih" {{ $anggota->jabatan == 'pelatih' ? 'selected' : '' }}>
Pelatih</option>
<option value="pengurus" {{ $anggota->jabatan == 'pengurus' ? 'selected' : '' }}>
Pengurus</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Tingkatan</label>
<input type="text" name="tingkatan" class="form-control"
value="{{ $anggota->tingkatan }}" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Status Anggota</label>
<select name="status" class="form-select" required>
<option value="Aktif" {{ $anggota->status == 'Aktif' ? 'selected' : '' }}>Aktif
</option>
<option value="Non-Aktif" {{ $anggota->status == 'Non-Aktif' ? 'selected' : '' }}>
Non-Aktif</option>
</select>
</div>
<!-- BOX DATA SK (Otomatis muncul jika pilih Pelatih/Pengurus) -->
<div id="boxSK"
class="col-12 mt-3 p-4 bg-light rounded-4 border border-dashed border-danger"
style="display: none;">
<h6 class="text-danger fw-bold mb-3"><i class="icon-certificate me-2"></i>Data Legalitas
SK (Wajib diisi untuk Pelatih/Pengurus)</h6>
<div class="row g-3">
<div class="col-md-3">
<label class="form-label small fw-bold">Nomor SK</label>
<input type="text" name="no_sk" class="form-control"
value="{{ $anggota->no_sk ?? '' }}" placeholder="Contoh: SK/001/2026">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold">Tanggal SK</label>
<input type="date" name="tgl_sk" class="form-control"
value="{{ $anggota->tgl_sk ?? '' }}">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold">Masa Berlaku</label>
<input type="date" name="masa_berlaku" class="form-control"
value="{{ $anggota->masa_berlaku ?? '' }}">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold">Upload Scan SK</label>
<input type="file" name="foto_sk" class="form-control">
@if ($anggota->foto_sk)
<small class="text-muted mt-1 d-block">File:
{{ $anggota->foto_sk }}</small>
@endif
</div>
</div>
</div>
{{-- <div class="col-md-12 mt-3">
<label class="form-label fw-bold">Asal Kolat</label>
<input type="text" name="asal_kolat" class="form-control" value="{{ $anggota->asal_kolat }}" required>
</div> --}}
<div class="col-md-12 mt-3">
<label class="form-label fw-bold">Asal Kolat</label>
<select name="kolat_id" class="form-select" required>
<option value="" disabled>-- Pilih Kolat --</option>
@foreach ($data_kolat as $kolat)
<option value="{{ $kolat->id }}"
{{ $anggota->kolat_id == $kolat->id ? 'selected' : '' }}>
{{ $kolat->nama_kolat }}
</option>
@endforeach
</select>
</div>
</div>
</div>
<!-- FOOTER CARD UNTUK BUTTON -->
<div class="card-footer bg-light border-0 p-4" style="border-radius: 0 0 20px 20px;">
<div class="d-flex justify-content-end align-items-center gap-3">
<a href="{{ route('anggota.anggota') }}"
class="btn btn-outline-secondary px-4 fw-bold btn-cancel">
<i class="icon-x me-1"></i> Batal
</a>
<button type="submit" class="btn btn-primary px-5 fw-bold btn-save shadow">
<i class="icon-check me-1"></i> Simpan Perubahan
</button>
</div>
</div>
</div>
=======
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-primary mb-0"><i class="icon-user me-2"></i>Informasi Pribadi & Akun</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-bold">No Induk</label>
<input type="text" class="form-control bg-light fw-bold text-primary"
value="{{ $anggota->no_induk }}" readonly>
<small class="text-muted" style="font-size: 11px;">* No. induk permanen</small>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Nama Lengkap</label>
<input type="text" name="nama_lengkap" class="form-control"
value="{{ old('nama_lengkap', $anggota->nama_lengkap) }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Alamat Email (Login)</label>
<input type="email" name="email" class="form-control"
value="{{ old('email', $anggota->user->email ?? '') }}" required>
<small class="text-muted" style="font-size: 11px;">* Email digunakan untuk masuk ke sistem</small>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">No. WhatsApp (HP)</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0">+62</span>
<input type="number" name="no_hp" class="form-control"
value="{{ old('no_hp', $anggota->no_hp) }}" placeholder="81234567xxx" required>
</div>
</div>
<div class="col-md-12">
<label class="form-label fw-bold">Alamat Lengkap</label>
<textarea name="alamat" class="form-control" rows="2" required>{{ old('alamat', $anggota->alamat) }}</textarea>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-success mb-0"><i class="icon-badge me-2"></i>Informasi Organisasi</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label fw-bold">Jabatan / Role</label>
<select name="role_id" id="roleSelect" class="form-select border-primary"
onchange="toggleSKFields()" required>
<option value="" disabled>-- Pilih Jabatan --</option>
@foreach ($data_role as $role)
<option value="{{ $role->id }}"
{{ old('role_id', $anggota->role_id) == $role->id ? 'selected' : '' }}>
<label class="form-label fw-bold">Role / Hak Akses</label>
<select name="role_id" class="form-select" required>
@foreach($data_role as $role)
<option value="{{ $role->id }}" {{ $anggota->role_id == $role->id ? 'selected' : '' }}>
{{ $role->nama_role }}
</option>
@endforeach
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">No Induk</label>
<input type="text" name="no_induk" class="form-control bg-light fw-bold text-primary" value="{{ $anggota->no_induk }}" readonly>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Nama Lengkap</label>
<input type="text" name="nama_lengkap" class="form-control" value="{{ old('nama_lengkap', $anggota->nama_lengkap) }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">No. WhatsApp</label>
<div class="input-group">
<span class="input-group-text bg-light">+62</span>
<input type="number" name="no_hp" class="form-control" value="{{ old('no_hp', $anggota->no_hp) }}" required>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Alamat</label>
<input type="text" name="alamat" class="form-control" value="{{ old('alamat', $anggota->alamat) }}" required>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-success mb-0"><i class="icon-badge me-2"></i>Informasi Organisasi</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label fw-bold">Tingkatan Sabuk</label>
<input type="text" name="tingkatan" class="form-control"
value="{{ old('tingkatan', $anggota->tingkatan) }}" required>
<label class="form-label fw-bold">Jabatan Khusus</label>
<select name="jabatan" id="jabatanSelect" class="form-select border-primary" onchange="toggleSKFields()" required>
<option value="anggota" {{ $anggota->jabatan == 'anggota' ? 'selected' : '' }}>Anggota</option>
<option value="pelatih" {{ $anggota->jabatan == 'pelatih' ? 'selected' : '' }}>Pelatih</option>
<option value="pengurus" {{ $anggota->jabatan == 'pengurus' ? 'selected' : '' }}>Pengurus</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Status Anggota</label>
<label class="form-label fw-bold">Tingkatan</label>
<input type="text" name="tingkatan" class="form-control" value="{{ old('tingkatan', $anggota->tingkatan) }}" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Status</label>
<select name="status" class="form-select" required>
<option value="aktif" {{ old('status', $anggota->status) == 'aktif' ? 'selected' : '' }}>Aktif</option>
<option value="Non-Aktif" {{ old('status', $anggota->status) == 'Non-Aktif' ? 'selected' : '' }}>Non-Aktif</option>
<option value="Aktif" {{ $anggota->status == 'Aktif' ? 'selected' : '' }}>Aktif</option>
<option value="Non-Aktif" {{ $anggota->status == 'Non-Aktif' ? 'selected' : '' }}>Non-Aktif</option>
</select>
</div>
<div id="boxSK"
class="col-12 mt-3 p-4 bg-light rounded-4 border border-dashed border-danger"
style="display: none;">
<h6 class="text-danger fw-bold mb-3"><i class="icon-certificate me-2"></i>Data Legalitas SK</h6>
<div id="boxSK" class="col-12 mt-3 p-4 bg-light rounded-4 border border-dashed border-danger" style="display: none;">
<h6 class="text-danger fw-bold mb-3">Data Legalitas SK</h6>
<div class="row g-3">
<div class="col-md-4">
<div class="col-md-3">
<label class="form-label small fw-bold">Nomor SK</label>
<input type="text" name="no_sk" class="form-control"
value="{{ old('no_sk', $anggota->no_sk) }}" placeholder="Contoh: SK/001/2026">
<input type="text" name="no_sk" class="form-control" value="{{ $anggota->no_sk }}">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">Masa Berlaku SK</label>
<input type="date" name="masa_berlaku" class="form-control"
value="{{ old('masa_berlaku', $anggota->masa_berlaku) }}">
<div class="col-md-3">
<label class="form-label small fw-bold">Tanggal SK</label>
<input type="date" name="tgl_sk" class="form-control" value="{{ $anggota->tgl_sk }}">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">Update File SK (Opsional)</label>
<div class="col-md-3">
<label class="form-label small fw-bold">Masa Berlaku</label>
<input type="date" name="masa_berlaku" class="form-control" value="{{ $anggota->masa_berlaku }}">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold">Upload Scan SK</label>
<input type="file" name="foto_sk" class="form-control">
@if ($anggota->foto_sk)
<small class="text-primary mt-1 d-block"><i class="icon-eye"></i> File saat ini: {{ $anggota->foto_sk }}</small>
@endif
</div>
</div>
</div>
@ -266,10 +123,8 @@ class="col-12 mt-3 p-4 bg-light rounded-4 border border-dashed border-danger"
<div class="col-md-12 mt-3">
<label class="form-label fw-bold">Asal Kolat</label>
<select name="kolat_id" class="form-select" required>
<option value="" disabled>-- Pilih Kolat --</option>
@foreach ($data_kolat as $kolat)
<option value="{{ $kolat->id }}"
{{ old('kolat_id', $anggota->kolat_id) == $kolat->id ? 'selected' : '' }}>
<option value="{{ $kolat->id }}" {{ $anggota->kolat_id == $kolat->id ? 'selected' : '' }}>
{{ $kolat->nama_kolat }}
</option>
@endforeach
@ -279,117 +134,23 @@ class="col-12 mt-3 p-4 bg-light rounded-4 border border-dashed border-danger"
</div>
<div class="card-footer bg-light border-0 p-4" style="border-radius: 0 0 20px 20px;">
<div class="d-flex justify-content-end align-items-center gap-3">
<a href="{{ route('anggota.anggota') }}"
class="btn btn-outline-secondary px-4 fw-bold btn-cancel">
<i class="icon-x me-1"></i> Batal
</a>
<button type="submit" class="btn btn-primary px-5 fw-bold btn-save shadow">
<i class="icon-check me-1"></i> Simpan Perubahan
</button>
<div class="d-flex justify-content-end gap-3">
<a href="{{ route('anggota.anggota') }}" class="btn btn-outline-secondary px-4 fw-bold">Batal</a>
<button type="submit" class="btn btn-primary px-5 fw-bold shadow">Simpan Perubahan</button>
</div>
</div>
</div>
>>>>>>> Stashed changes
</form>
</div>
</div>
</div>
</div>
<<<<<<< Updated upstream
<!-- JAVASCRIPT TOGGLE -->
<script>
<script>
function toggleSKFields() {
const jabatan = document.getElementById('jabatanSelect').value;
const boxSK = document.getElementById('boxSK');
if (jabatan === 'pelatih' || jabatan === 'pengurus') {
boxSK.style.display = 'block';
} else {
boxSK.style.display = 'none';
boxSK.style.display = (jabatan === 'pelatih' || jabatan === 'pengurus') ? 'block' : 'none';
}
}
// Jalankan saat pertama kali halaman dimuat
=======
<script>
function toggleSKFields() {
const roleSelect = document.getElementById('roleSelect');
const boxSK = document.getElementById('boxSK');
if (roleSelect.selectedIndex >= 0) {
const selectedText = roleSelect.options[roleSelect.selectedIndex].text.toLowerCase();
if (selectedText.includes('pelatih') || selectedText.includes('pengurus')) {
boxSK.style.display = 'block';
} else {
boxSK.style.display = 'none';
}
}
}
>>>>>>> Stashed changes
document.addEventListener("DOMContentLoaded", function() {
toggleSKFields();
});
</script>
<style>
<<<<<<< Updated upstream
.form-control,
.form-select {
border-radius: 10px;
padding: 10px 15px;
}
.form-control:focus,
.form-select:focus {
=======
.form-control, .form-select {
border-radius: 10px;
padding: 10px 15px;
border: 1px solid #dce1e7;
}
.form-control:focus, .form-select:focus {
>>>>>>> Stashed changes
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.1);
}
.btn-cancel {
border-radius: 12px;
<<<<<<< Updated upstream
transition: all 0.3s ease;
border: 2px solid #6c757d;
}
.btn-cancel:hover {
background-color: #6c757d;
color: white;
transform: translateY(-2px);
}
.btn-save {
border-radius: 12px;
background: (45deg, #0d6efd);
border: none;
transition: all 0.3s ease;
}
.btn-save:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(13, 110, 253, 0.4) !important;
=======
border: 2px solid #6c757d;
}
.btn-save {
border-radius: 12px;
background: linear-gradient(45deg, #0d6efd, #004dc0);
border: none;
color: white;
>>>>>>> Stashed changes
}
</style>
document.addEventListener("DOMContentLoaded", toggleSKFields);
</script>
@endsection

View File

@ -0,0 +1,547 @@
// index jadwal.blade.php
@extends('layout.app')
@section('content')
<div class="container-fluid py-4">
<div class="page-header mb-4 text-start">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-transparent p-0 mb-2">
<li class="breadcrumb-item"><a href="#" class="text-muted text-decoration-none small">Home</a></li>
<li class="breadcrumb-item active text-primary fw-bold small" aria-current="page">Jadwal Latihan</li>
</ol>
</nav>
<h3 class="fw-bold text-dark">Manajemen Latihan TapakMP</h3>
</div>
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm mb-4" role="alert" style="border-radius: 15px;">
<strong>Berhasil!</strong> {{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="text-start">
<h5 class="card-title mb-0 fw-bold text-dark">Agenda PPS Merpati Putih</h5>
<p class="text-muted small mb-0">Cabang Jember - Kelola jadwal rutin dan pengumuman latihan.</p>
</div>
<a href="{{ route('jadwal.create') }}" class="btn btn-primary px-4 py-2 shadow-sm"
style="border-radius: 12px;">
<i class="icon-plus me-1"></i> Buat Jadwal
</a>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle main-table">
<thead>
<tr>
<th width="50" class="text-center">No</th>
<th>Kegiatan & Pelatih</th>
<th class="text-center">Status</th>
<th>Waktu & Lokasi</th>
<th class="text-center">Tipe</th>
<th class="text-center" width="220">Aksi</th>
</tr>
</thead>
<tbody>
@forelse ($data_jadwal as $no => $item)
@php
// Set timezone ke Jakarta agar sinkron dengan waktu Indonesia
$now = \Carbon\Carbon::now('Asia/Jakarta');
$start = \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_mulai, 'Asia/Jakarta');
$end = $item->jam_selesai
? \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_selesai, 'Asia/Jakarta')
: $start->copy()->addHours(2);
$dbStatus = trim(strtolower($item->status));
// Logika Penentuan Label Status
if ($dbStatus == 'selesai') {
$status_label = 'Selesai';
$status_color = 'success';
} elseif ($now->greaterThanOrEqualTo($start) && $now->lessThanOrEqualTo($end)) {
$status_label = 'Berlangsung';
$status_color = 'primary';
} elseif ($now->lt($start)) {
$status_label = 'Mendatang';
$status_color = 'secondary';
} else {
$status_label = 'Menunggu Konfirmasi';
$status_color = 'warning';
}
@endphp
<tr>
<td class="text-muted small text-center">{{ $no + 1 }}</td>
<td>
<div class="fw-bold text-dark text-start ps-2">
{{ $item->judul_kegiatan }}
@if($dbStatus == 'selesai')
<i class="bi bi-check-circle-fill text-success ms-1"></i>
@endif
</div>
<small class="text-muted text-start ps-2 d-block">
<i class="icon-user small"></i> {{ $item->pelatih->nama_lengkap ?? 'Belum Ditentukan' }}
</small>
</td>
<td class="text-center">
<span class="badge bg-{{ $status_color }} px-3 py-2" style="border-radius: 8px; font-size: 11px;">
{{ $status_label }}
</span>
</td>
<td class="text-start ps-4">
<div class="small fw-bold text-dark">
<i class="icon-calendar me-1"></i> {{ \Carbon\Carbon::parse($item->tanggal)->format('d M Y') }}
</div>
<div class="small text-muted">
<i class="icon-clock me-1"></i> {{ substr($item->jam_mulai, 0, 5) }} - {{ $item->jam_selesai ? substr($item->jam_selesai, 0, 5) : 'Selesai' }}
</div>
<div class="small text-muted text-truncate" style="max-width: 150px;">
<i class="icon-map-pin me-1 text-danger"></i> {{ $item->lokasi }}
</div>
</td>
<td class="text-center">
@php
$bg_type = 'bg-secondary-soft text-secondary';
if($item->jenis == 'Rutin') $bg_type = 'bg-success-soft text-success border-success';
elseif($item->jenis == 'Tambahan') $bg_type = 'bg-info-soft text-info border-info';
elseif($item->jenis == 'Pengumuman') $bg_type = 'bg-danger-soft text-danger border-danger';
@endphp
<span class="badge rounded-pill {{ $bg_type }} px-3 border" style="font-size: 10px;">
{{ $item->jenis }}
</span>
</td>
<td class="text-center">
<div class="d-flex justify-content-center gap-2">
{{-- TOMBOL KONFIRMASI: Muncul jika sudah masuk jamnya, bukan pengumuman, dan belum selesai --}}
@if($dbStatus != 'selesai' && $item->jenis != 'Pengumuman' && $now->greaterThanOrEqualTo($start))
<form action="{{ route('jadwal.konfirmasi', $item->id) }}" method="POST" id="form-selesai-{{ $item->id }}" class="d-inline">
@csrf
<button type="button" class="btn btn-sm btn-success fw-bold px-2 shadow-xs"
style="border-radius: 10px;"
onclick="konfirmasiLatihan('{{ $item->id }}', '{{ $item->judul_kegiatan }}')">
<i class="bi bi-check2-square"></i>
</button>
</form>
@endif
<button class="btn btn-sm btn-action-detail fw-bold px-2 shadow-xs"
data-bs-toggle="modal"
data-bs-target="#modalDetailJadwal{{ $item->id }}">
<i class="icon-eye"></i>
</button>
<a href="{{ route('jadwal.edit', $item->id) }}"
class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
<i class="icon-pencil"></i>
</a>
</div>
</td>
</tr>
{{-- MODAL DETAIL JADWAL --}}
<div class="modal fade" id="modalDetailJadwal{{ $item->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="border-radius: 20px; border: none; overflow: hidden;">
<div class="modal-header bg-primary text-white border-0 py-3 px-4">
<h5 class="modal-title fw-bold">
<i class="icon-calendar me-2"></i>Detail Agenda Latihan
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="row">
<div class="col-md-4 text-center border-end">
<div class="mb-3">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center shadow-sm"
style="width: 100px; height: 100px; border: 4px solid #f8f9fa;">
<i class="icon-clipboard text-primary" style="font-size: 50px;"></i>
</div>
</div>
<h5 class="fw-bold mb-1 text-dark">{{ $item->judul_kegiatan }}</h5>
<p class="text-muted small mb-3">Status: <span class="text-{{ $status_color }} fw-bold">{{ strtoupper($status_label) }}</span></p>
</div>
<div class="col-md-8 ps-md-4 text-start">
<div class="row g-3">
<div class="col-6">
<small class="text-muted d-block small">Pelatih</small>
<span class="fw-bold text-dark">{{ $item->pelatih->nama_lengkap ?? '-' }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block small">Kolat</small>
<span class="fw-bold text-dark">{{ $item->kolat->nama_kolat ?? '-' }}</span>
</div>
<div class="col-12 mt-3">
<h6 class="fw-bold text-primary border-bottom pb-1 small">Deskripsi Kegiatan</h6>
<p class="small text-muted mb-0" style="white-space: pre-line;">{{ $item->deskripsi ?? 'Tidak ada deskripsi.' }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer border-0 px-4 pb-4">
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal" style="border-radius: 10px;">Tutup</button>
</div>
</div>
</div>
</div>
@empty
<tr>
<td colspan="6" class="text-center py-5 text-muted small">Belum ada jadwal latihan atau pengumuman.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.main-table thead th { background-color: #f8faff; padding: 15px 10px; font-size: 0.7rem; color: #8c98a5; letter-spacing: 1px; text-transform: uppercase; border-bottom: 2px solid #edf2f9; }
.main-table tbody td { padding: 15px 10px; border-bottom: 1px solid #f1f4f8; color: #495057; font-size: 0.85rem; }
.bg-success-soft { background-color: #e6fffa; color: #38b2ac; }
.bg-info-soft { background-color: #e0f2fe; color: #0ea5e9; }
.bg-danger-soft { background-color: #fff5f5; color: #e53e3e; }
.bg-secondary-soft { background-color: #f3f4f6; color: #6b7280; }
.btn-action-detail { background-color: #f0f7ff; color: #0d6efd; border: 1px solid #d0e4ff; border-radius: 10px; transition: 0.3s; }
.btn-action-edit { background-color: #fff8e6; color: #f59e0b; border: 1px solid #ffecb3; border-radius: 10px; transition: 0.3s; }
.btn-action-edit:hover { background-color: #f59e0b !important; color: white !important; transform: translateY(-2px); }
.btn-action-detail:hover { background-color: #0d6efd !important; color: white !important; transform: translateY(-2px); }
.shadow-xs { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); }
</style>
<script>
function konfirmasiLatihan(id, judul) {
Swal.fire({
title: 'Konfirmasi Selesai',
text: "Apakah benar latihan '" + judul + "' telah dilaksanakan?",
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#198754', // Warna hijau sukses
cancelButtonColor: '#6c757d',
confirmButtonText: 'Ya, Sudah Selesai!',
cancelButtonText: 'Batal',
border: 'none',
borderRadius: '20px'
}).then((result) => {
if (result.isConfirmed) {
// Submit form jika user klik OK
document.getElementById('form-selesai-' + id).submit();
}
})
}
</script>
@endsection
{{-- edit --}}
@extends('layout.app')
@section('content')
<div class="container-fluid py-4">
<div class="page-header mb-4 text-start">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-transparent p-0 mb-2">
<li class="breadcrumb-item"><a href="{{ route('jadwal.index') }}"
class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
<li class="breadcrumb-item active text-primary fw-bold small" aria-current="page">Edit Jadwal</li>
</ol>
</nav>
<h3 class="fw-bold text-dark">Edit Agenda Latihan</h3>
<p class="text-muted small">Perbarui detail waktu, lokasi, atau pelatih yang bertugas untuk agenda ini.</p>
</div>
<div class="row">
<div class="col-md-12">
<form action="{{ route('jadwal.update', $jadwal->id) }}" method="POST">
@csrf
@method('PUT')
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-primary mb-0"><i class="icon-calendar me-2"></i>Informasi Agenda</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label fw-bold">Judul Kegiatan / Materi Latihan</label>
<input type="text" name="judul_kegiatan" class="form-control"
value="{{ old('judul_kegiatan', $jadwal->judul_kegiatan) }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Kelompok Latihan (Kolat)</label>
<select name="kolat_id" class="form-select" required>
@foreach($data_kolat as $kolat)
<option value="{{ $kolat->id }}" {{ $jadwal->kolat_id == $kolat->id ? 'selected' : '' }}>
{{ $kolat->nama_kolat }}
</option>
@endforeach
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Pelatih Bertugas</label>
<select name="pelatih_id" class="form-select" required>
@foreach($data_pelatih as $pelatih)
<option value="{{ $pelatih->id }}" {{ $jadwal->pelatih_id == $pelatih->id ? 'selected' : '' }}>
{{ $pelatih->nama_lengkap }}
</option>
@endforeach
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Tanggal</label>
<input type="date" name="tanggal" class="form-control"
value="{{ old('tanggal', $jadwal->tanggal) }}" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Jam Mulai</label>
<input type="time" name="jam_mulai" class="form-control"
value="{{ old('jam_mulai', substr($jadwal->jam_mulai, 0, 5)) }}" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Jam Selesai (Opsional)</label>
<input type="time" name="jam_selesai" class="form-control"
value="{{ old('jam_selesai', $jadwal->jam_selesai ? substr($jadwal->jam_selesai, 0, 5) : '') }}">
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Lokasi / Tempat Latihan</label>
<input type="text" name="lokasi" class="form-control"
value="{{ old('lokasi', $jadwal->lokasi) }}" placeholder="Contoh: GOR Perjuangan" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Tipe Agenda</label>
<select name="jenis" class="form-select" required>
<option value="Rutin" {{ $jadwal->jenis == 'Rutin' ? 'selected' : '' }}>Rutin</option>
<option value="Tambahan" {{ $jadwal->jenis == 'Tambahan' ? 'selected' : '' }}>Tambahan</option>
<option value="Pengumuman" {{ $jadwal->jenis == 'Pengumuman' ? 'selected' : '' }}>Pengumuman</option>
</select>
</div>
<div class="col-md-12">
<label class="form-label fw-bold">Deskripsi / Catatan Tambahan</label>
<textarea name="deskripsi" class="form-control" rows="3">{{ old('deskripsi', $jadwal->deskripsi) }}</textarea>
</div>
</div>
</div>
<div class="card-footer bg-light border-0 p-4" style="border-radius: 0 0 20px 20px;">
<div class="d-flex justify-content-between align-items-center">
<div class="small text-muted">
Status saat ini: <span class="badge bg-{{ $jadwal->status == 'selesai' ? 'success' : 'secondary' }}">{{ strtoupper($jadwal->status) }}</span>
</div>
<div class="d-flex gap-3">
<a href="{{ route('jadwal.index') }}" class="btn btn-outline-secondary px-4 fw-bold btn-cancel">
Batal
</a>
<button type="submit" class="btn btn-primary px-5 fw-bold btn-save shadow">
Simpan Perubahan
</button>
</div>
</div>
</div>
</div>
</form>
<div class="text-center mt-2">
<form action="{{ route('jadwal.destroy', $jadwal->id) }}" method="POST" onsubmit="return confirm('Hapus jadwal ini secara permanen?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-link text-danger text-decoration-none small">
<i class="icon-trash"></i> Hapus Agenda Ini
</button>
</form>
</div>
</div>
</div>
</div>
<style>
.form-control, .form-select { border-radius: 10px; padding: 10px 15px; }
.btn-cancel { border-radius: 12px; transition: 0.3s; border: 2px solid #6c757d; }
.btn-save { border-radius: 12px; background: linear-gradient(45deg, #0d6efd, #004fb1); border: none; transition: 0.3s; }
.btn-save:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(13, 110, 253, 0.4) !important; }
</style>
@endsection
{{-- create --}}
@extends('layout.app')
@section('content')
<div class="container-fluid py-4">
<div class="page-header mb-4 text-start">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-transparent p-0 mb-2">
<li class="breadcrumb-item"><a href="{{ route('jadwal.index') }}" class="text-muted text-decoration-none small">Jadwal Latihan</a></li>
<li class="breadcrumb-item active text-primary fw-bold small" aria-current="page">Buat Jadwal Baru</li>
</ol>
</nav>
<h3 class="fw-bold text-dark">Tambah Jadwal Latihan</h3>
</div>
<div class="row">
<div class="col-lg-8 col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4">
<form action="{{ route('jadwal.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-md-12 mb-3">
<label class="form-label fw-bold text-dark">Judul Kegiatan / Agenda</label>
<input type="text" name="judul_kegiatan" class="form-control @error('judul_kegiatan') is-invalid @enderror"
placeholder="Contoh: Latihan Rutin Sabuk Putih" value="{{ old('judul_kegiatan') }}" required>
@error('judul_kegiatan')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold text-dark">Kelompok Latihan (Kolat)</label>
<select name="kolat_id" class="form-select @error('kolat_id') is-invalid @enderror" required>
<option value="" selected disabled>Pilih Kolat</option>
@foreach($data_kolat as $kolat)
<option value="{{ $kolat->id }}" {{ old('kolat_id') == $kolat->id ? 'selected' : '' }}>
{{ $kolat->nama_kolat }}
</option>
@endforeach
</select>
@error('kolat_id')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold text-dark">Pelatih Bertugas</label>
<select name="pelatih_id" class="form-select @error('pelatih_id') is-invalid @enderror" required>
<option value="" selected disabled>Pilih Pelatih</option>
@foreach($data_pelatih as $pelatih)
<option value="{{ $pelatih->id }}" {{ old('pelatih_id') == $pelatih->id ? 'selected' : '' }}>
{{ $pelatih->nama_lengkap }} ({{ $pelatih->no_induk }})
</option>
@endforeach
</select>
@error('pelatih_id')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold text-dark">Jenis Kegiatan</label>
<select name="jenis" class="form-select @error('jenis') is-invalid @enderror" required>
<option value="Rutin" {{ old('jenis') == 'Rutin' ? 'selected' : '' }}>Rutin</option>
<option value="Tambahan" {{ old('jenis') == 'Tambahan' ? 'selected' : '' }}>Tambahan</option>
<option value="Pengumuman" {{ old('jenis') == 'Pengumuman' ? 'selected' : '' }}>Pengumuman</option>
</select>
@error('jenis')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold text-dark">Tanggal</label>
<input type="date" name="tanggal" class="form-control @error('tanggal') is-invalid @enderror"
value="{{ old('tanggal') }}" required>
@error('tanggal')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold text-dark">Jam Mulai</label>
<input type="time" name="jam_mulai" class="form-control @error('jam_mulai') is-invalid @enderror"
value="{{ old('jam_mulai') }}" required>
@error('jam_mulai')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold text-dark">Jam Selesai (Opsional)</label>
<input type="time" name="jam_selesai" class="form-control @error('jam_selesai') is-invalid @enderror"
value="{{ old('jam_selesai') }}">
@error('jam_selesai')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-12 mb-3">
<label class="form-label fw-bold text-dark">Lokasi Latihan</label>
<input type="text" name="lokasi" class="form-control @error('lokasi') is-invalid @enderror"
placeholder="Contoh: Gedung Serbaguna Lt. 2" value="{{ old('lokasi') }}" required>
@error('lokasi')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-12 mb-4">
<label class="form-label fw-bold text-dark">Deskripsi / Catatan (Opsional)</label>
<textarea name="deskripsi" class="form-control @error('deskripsi') is-invalid @enderror"
rows="3" placeholder="Masukkan detail tambahan jika ada...">{{ old('deskripsi') }}</textarea>
@error('deskripsi')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="d-flex justify-content-end gap-2 border-top pt-4">
<a href="{{ route('jadwal.index') }}" class="btn btn-light px-4 py-2 fw-bold" style="border-radius: 12px;">Batal</a>
<button type="submit" class="btn btn-primary px-4 py-2 fw-bold shadow-sm" style="border-radius: 12px;">
<i class="icon-save me-1"></i> Simpan Jadwal
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4 col-12 mt-4 mt-lg-0">
<div class="card border-0 bg-primary text-white" style="border-radius: 20px;">
<div class="card-body p-4">
<h5 class="fw-bold mb-3"><i class="icon-info me-2"></i> Tips Pengisian</h5>
<ul class="small opacity-75 ps-3">
<li class="mb-2">Pastikan memilih <strong>Pelatih</strong> yang sedang aktif.</li>
<li class="mb-2">Pilih jenis <strong>Pengumuman</strong> jika hanya ingin memberikan informasi tanpa perlu presensi.</li>
<li class="mb-2">Gunakan lokasi yang spesifik agar anggota tidak kesulitan mencari tempat.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<style>
.form-control, .form-select {
border: 1px solid #edf2f9;
padding: 0.6rem 1rem;
border-radius: 12px;
background-color: #f8faff;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.05);
background-color: #fff;
}
.form-label {
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
</style>
@endsection

View File

@ -22,21 +22,26 @@ class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px;">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold text-primary mb-0"><i class="icon-calendar me-2"></i>Informasi Agenda</h5>
<h5 class="fw-bold text-primary mb-0"><i class="bi bi-calendar-check me-2"></i>Informasi Agenda
</h5>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label fw-bold">Judul Kegiatan / Materi Latihan</label>
<label class="form-label fw-bold">Judul Kegiatan / Materi Latihan <span
class="text-danger">*</span></label>
<input type="text" name="judul_kegiatan" class="form-control"
value="{{ old('judul_kegiatan', $jadwal->judul_kegiatan) }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Kelompok Latihan (Kolat)</label>
<label class="form-label fw-bold">Kelompok Latihan (Kolat) <span
class="text-danger">*</span></label>
<select name="kolat_id" class="form-select" required>
@foreach($data_kolat as $kolat)
<option value="{{ $kolat->id }}" {{ $jadwal->kolat_id == $kolat->id ? 'selected' : '' }}>
<option value="" disabled>-- Pilih Kolat --</option>
@foreach ($data_kolat as $kolat)
<option value="{{ $kolat->id }}"
{{ $jadwal->kolat_id == $kolat->id ? 'selected' : '' }}>
{{ $kolat->nama_kolat }}
</option>
@endforeach
@ -44,10 +49,13 @@ class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Pelatih Bertugas</label>
<label class="form-label fw-bold">Pelatih Bertugas <span
class="text-danger">*</span></label>
<select name="pelatih_id" class="form-select" required>
@foreach($data_pelatih as $pelatih)
<option value="{{ $pelatih->id }}" {{ $jadwal->pelatih_id == $pelatih->id ? 'selected' : '' }}>
<option value="" disabled>-- Pilih Pelatih --</option>
@foreach ($data_pelatih as $pelatih)
<option value="{{ $pelatih->id }}"
{{ $jadwal->pelatih_id == $pelatih->id ? 'selected' : '' }}>
{{ $pelatih->nama_lengkap }}
</option>
@endforeach
@ -55,12 +63,12 @@ class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Tanggal</label>
<label class="form-label fw-bold">Tanggal <span class="text-danger">*</span></label>
<input type="date" name="tanggal" class="form-control"
value="{{ old('tanggal', $jadwal->tanggal) }}" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Jam Mulai</label>
<label class="form-label fw-bold">Jam Mulai <span class="text-danger">*</span></label>
<input type="time" name="jam_mulai" class="form-control"
value="{{ old('jam_mulai', substr($jadwal->jam_mulai, 0, 5)) }}" required>
</div>
@ -71,23 +79,29 @@ class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Lokasi / Tempat Latihan</label>
<label class="form-label fw-bold">Lokasi / Tempat Latihan <span
class="text-danger">*</span></label>
<input type="text" name="lokasi" class="form-control"
value="{{ old('lokasi', $jadwal->lokasi) }}" placeholder="Contoh: GOR Perjuangan" required>
value="{{ old('lokasi', $jadwal->lokasi) }}" placeholder="Contoh: GOR Perjuangan"
required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Tipe Agenda</label>
<label class="form-label fw-bold">Tipe Agenda <span class="text-danger">*</span></label>
<select name="jenis" class="form-select" required>
<option value="Rutin" {{ $jadwal->jenis == 'Rutin' ? 'selected' : '' }}>Rutin</option>
<option value="Tambahan" {{ $jadwal->jenis == 'Tambahan' ? 'selected' : '' }}>Tambahan</option>
<option value="Pengumuman" {{ $jadwal->jenis == 'Pengumuman' ? 'selected' : '' }}>Pengumuman</option>
<option value="Rutin" {{ $jadwal->jenis == 'Rutin' ? 'selected' : '' }}>Rutin
</option>
<option value="Tambahan" {{ $jadwal->jenis == 'Tambahan' ? 'selected' : '' }}>
Tambahan</option>
<option value="Pengumuman" {{ $jadwal->jenis == 'Pengumuman' ? 'selected' : '' }}>
Pengumuman</option>
</select>
</div>
<div class="col-md-12">
<label class="form-label fw-bold">Deskripsi / Catatan Tambahan</label>
<textarea name="deskripsi" class="form-control" rows="3">{{ old('deskripsi', $jadwal->deskripsi) }}</textarea>
<textarea name="deskripsi" class="form-control" rows="3"
placeholder="Tuliskan peralatan yang harus dibawa, atau informasi penting lainnya...">{{ old('deskripsi', $jadwal->deskripsi) }}</textarea>
</div>
</div>
</div>
@ -95,10 +109,18 @@ class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
<div class="card-footer bg-light border-0 p-4" style="border-radius: 0 0 20px 20px;">
<div class="d-flex justify-content-between align-items-center">
<div class="small text-muted">
Status saat ini: <span class="badge bg-{{ $jadwal->status == 'selesai' ? 'success' : 'secondary' }}">{{ strtoupper($jadwal->status) }}</span>
Status saat ini:
@php
$badgeColor = 'secondary';
if ($jadwal->status == 'selesai') {
$badgeColor = 'success';
}
@endphp
<span class="badge bg-{{ $badgeColor }}">{{ strtoupper($jadwal->status) }}</span>
</div>
<div class="d-flex gap-3">
<a href="{{ route('jadwal.index') }}" class="btn btn-outline-secondary px-4 fw-bold btn-cancel">
<a href="{{ route('jadwal.index') }}"
class="btn btn-outline-secondary px-4 fw-bold btn-cancel">
Batal
</a>
<button type="submit" class="btn btn-primary px-5 fw-bold btn-save shadow">
@ -109,24 +131,45 @@ class="text-muted text-decoration-none small">Daftar Jadwal</a></li>
</div>
</div>
</form>
<div class="text-center mt-2">
<form action="{{ route('jadwal.destroy', $jadwal->id) }}" method="POST" onsubmit="return confirm('Hapus jadwal ini secara permanen?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-link text-danger text-decoration-none small">
<i class="icon-trash"></i> Hapus Agenda Ini
</button>
</form>
</div>
</div>
</div>
</div>
<style>
.form-control, .form-select { border-radius: 10px; padding: 10px 15px; }
.btn-cancel { border-radius: 12px; transition: 0.3s; border: 2px solid #6c757d; }
.btn-save { border-radius: 12px; background: linear-gradient(45deg, #0d6efd, #004fb1); border: none; transition: 0.3s; }
.btn-save:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(13, 110, 253, 0.4) !important; }
.form-control,
.form-select {
border-radius: 10px;
padding: 10px 15px;
border: 1px solid #dee2e6;
}
.form-control:focus,
.form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
}
.btn-cancel {
border-radius: 12px;
transition: 0.3s;
border: 2px solid #6c757d;
}
.btn-cancel:hover {
background-color: #6c757d;
color: white;
}
.btn-save {
border-radius: 12px;
background: linear-gradient(45deg, #0d6efd, #004fb1);
border: none;
transition: 0.3s;
}
.btn-save:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(13, 110, 253, 0.4) !important;
}
</style>
@endsection

View File

@ -29,12 +29,12 @@
<h5 class="card-title mb-0 fw-bold text-dark">Agenda PPS Merpati Putih</h5>
<p class="text-muted small mb-0">Cabang Jember - Kelola jadwal rutin dan pengumuman latihan.</p>
</div>
<a href="{{ route('jadwal.create') }}" class="btn btn-primary px-4 py-2 shadow-sm"
style="border-radius: 12px;">
<i class="icon-plus me-1"></i> Buat Jadwal
<a href="{{ route('jadwal.create') }}" class="btn btn-primary px-4 py-2 shadow-sm" style="border-radius: 12px;">
<i class="bi bi-plus-lg me-1"></i> Buat Jadwal
</a>
</div>
{{-- TABEL DATA JADWAL --}}
<div class="table-responsive">
<table class="table table-hover align-middle main-table">
<thead>
@ -50,7 +50,6 @@
<tbody>
@forelse ($data_jadwal as $no => $item)
@php
// Set timezone ke Jakarta agar sinkron dengan waktu Indonesia
$now = \Carbon\Carbon::now('Asia/Jakarta');
$start = \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_mulai, 'Asia/Jakarta');
$end = $item->jam_selesai
@ -59,7 +58,6 @@
$dbStatus = trim(strtolower($item->status));
// Logika Penentuan Label Status
if ($dbStatus == 'selesai') {
$status_label = 'Selesai';
$status_color = 'success';
@ -84,7 +82,7 @@
@endif
</div>
<small class="text-muted text-start ps-2 d-block">
<i class="icon-user small"></i> {{ $item->pelatih->nama_lengkap ?? 'Belum Ditentukan' }}
<i class="bi bi-person-fill small"></i> {{ $item->pelatih->nama_lengkap ?? 'Belum Ditentukan' }}
</small>
</td>
<td class="text-center">
@ -94,13 +92,13 @@
</td>
<td class="text-start ps-4">
<div class="small fw-bold text-dark">
<i class="icon-calendar me-1"></i> {{ \Carbon\Carbon::parse($item->tanggal)->format('d M Y') }}
<i class="bi bi-calendar-event me-1"></i> {{ \Carbon\Carbon::parse($item->tanggal)->format('d M Y') }}
</div>
<div class="small text-muted">
<i class="icon-clock me-1"></i> {{ substr($item->jam_mulai, 0, 5) }} - {{ $item->jam_selesai ? substr($item->jam_selesai, 0, 5) : 'Selesai' }}
<i class="bi bi-clock me-1"></i> {{ substr($item->jam_mulai, 0, 5) }} - {{ $item->jam_selesai ? substr($item->jam_selesai, 0, 5) : 'Selesai' }}
</div>
<div class="small text-muted text-truncate" style="max-width: 150px;">
<i class="icon-map-pin me-1 text-danger"></i> {{ $item->lokasi }}
<i class="bi bi-geo-alt-fill me-1 text-danger"></i> {{ $item->lokasi }}
</div>
</td>
<td class="text-center">
@ -117,31 +115,148 @@
<td class="text-center">
<div class="d-flex justify-content-center gap-2">
{{-- TOMBOL KONFIRMASI: Muncul jika sudah masuk jamnya, bukan pengumuman, dan belum selesai --}}
@if($dbStatus != 'selesai' && $item->jenis != 'Pengumuman' && $now->greaterThanOrEqualTo($start))
<form action="{{ route('jadwal.konfirmasi', $item->id) }}" method="POST" id="form-selesai-{{ $item->id }}" class="d-inline">
@csrf
<button type="button" class="btn btn-sm btn-success fw-bold px-2 shadow-xs"
style="border-radius: 10px;"
onclick="konfirmasiLatihan('{{ $item->id }}', '{{ $item->judul_kegiatan }}')">
<button class="btn btn-sm btn-success fw-bold px-2 shadow-xs" style="border-radius: 10px;" data-bs-toggle="modal" data-bs-target="#modalKonfirmasi{{ $item->id }}">
<i class="bi bi-check2-square"></i>
</button>
</form>
@endif
<button class="btn btn-sm btn-action-detail fw-bold px-2 shadow-xs"
data-bs-toggle="modal"
data-bs-target="#modalDetailJadwal{{ $item->id }}">
<i class="icon-eye"></i>
<button class="btn btn-sm btn-action-detail fw-bold px-2 shadow-xs" data-bs-toggle="modal" data-bs-target="#modalDetailJadwal{{ $item->id }}">
<i class="bi bi-eye"></i>
</button>
<a href="{{ route('jadwal.edit', $item->id) }}"
class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
<i class="icon-pencil"></i>
<a href="{{ route('jadwal.edit', $item->id) }}" class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
<i class="bi bi-pencil"></i>
</a>
<form action="{{ route('jadwal.destroy', $item->id) }}" method="POST" id="form-hapus-{{ $item->id }}" class="d-inline">
@csrf
@method('DELETE')
<button type="button" class="btn btn-sm btn-action-delete fw-bold px-2 shadow-xs" onclick="hapusJadwal('{{ $item->id }}', '{{ addslashes($item->judul_kegiatan) }}')">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-5 text-muted small">Belum ada jadwal latihan atau pengumuman.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- KUMPULAN MODAL DITEMPATKAN DI LUAR TABLE --}}
@foreach ($data_jadwal as $item)
@php
// Perlu inisialisasi status lagi untuk tampilan dalam Modal
$now = \Carbon\Carbon::now('Asia/Jakarta');
$start = \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_mulai, 'Asia/Jakarta');
$end = $item->jam_selesai
? \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_selesai, 'Asia/Jakarta')
: $start->copy()->addHours(2);
$dbStatus = trim(strtolower($item->status));
if ($dbStatus == 'selesai') {
$status_label = 'Selesai';
$status_color = 'success';
} elseif ($now->greaterThanOrEqualTo($start) && $now->lessThanOrEqualTo($end)) {
$status_label = 'Berlangsung';
$status_color = 'primary';
} elseif ($now->lt($start)) {
$status_label = 'Mendatang';
$status_color = 'secondary';
} else {
$status_label = 'Menunggu Konfirmasi';
$status_color = 'warning';
}
@endphp
{{-- MODAL KONFIRMASI SELESAI --}}
<div class="modal fade" id="modalKonfirmasi{{ $item->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content" style="border-radius: 20px; border: none;">
<div class="modal-header bg-success text-white border-0 py-3 px-4">
<h5 class="modal-title fw-bold">
<i class="bi bi-check-circle me-2"></i>Konfirmasi Latihan Selesai
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('jadwal.konfirmasi', $item->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body p-4 text-start">
<div class="mb-4">
<h6 class="fw-bold"><i class="bi bi-people-fill me-2"></i>Rekapitulasi Kehadiran</h6>
<div class="table-responsive border rounded" style="max-height: 250px; overflow-y: auto;">
<table class="table table-sm table-hover mb-0">
<thead class="table-light sticky-top">
<tr>
<th class="ps-3 py-2 text-muted small">No</th>
<th class="py-2 text-muted small">Nama Anggota</th>
<th class="py-2 text-center text-muted small">Status</th>
</tr>
</thead>
<tbody>
@php
$semua_absen = $item->presensi ? $item->presensi->where('is_verified', true) : collect([]);
$total_hadir = $semua_absen->where('status', 'Hadir')->count();
@endphp
@forelse($semua_absen as $idx => $absen)
<tr>
<td class="ps-3 py-2 small text-muted">{{ $idx + 1 }}</td>
<td class="py-2 small fw-bold text-dark">{{ $absen->anggota->nama_lengkap ?? 'Tidak diketahui' }}</td>
<td class="py-2 text-center">
@php
$bgBadge = 'bg-secondary';
if($absen->status == 'Hadir') $bgBadge = 'bg-success';
elseif($absen->status == 'Izin') $bgBadge = 'bg-info';
elseif($absen->status == 'Sakit') $bgBadge = 'bg-warning text-dark';
elseif($absen->status == 'Alfa') $bgBadge = 'bg-danger';
@endphp
<span class="badge {{ $bgBadge }}">{{ $absen->status }}</span>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="text-center py-4 small text-muted">Belum ada data presensi yang diverifikasi.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-2 small text-muted">
<strong>Total Hadir:</strong> {{ $total_hadir }} Orang
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Upload Foto Kegiatan (Wajib) <span class="text-danger">*</span></label>
<input type="file" name="foto_bukti" class="form-control" accept="image/*" required style="border-radius: 10px;">
<small class="text-muted">Upload foto bersama sebagai bukti latihan telah dilaksanakan.</small>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Catatan Latihan (Opsional)</label>
<textarea name="catatan_latihan" class="form-control" rows="3" placeholder="Tulis evaluasi atau materi yang diajarkan hari ini..." style="border-radius: 10px;"></textarea>
</div>
</div>
<div class="modal-footer border-0 px-4 pb-4">
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal" style="border-radius: 10px;">Batal</button>
<button type="submit" class="btn btn-success px-4 shadow-sm" style="border-radius: 10px;">Sahkan Latihan</button>
</div>
</form>
</div>
</div>
</div>
{{-- MODAL DETAIL JADWAL --}}
<div class="modal fade" id="modalDetailJadwal{{ $item->id }}" tabindex="-1" aria-hidden="true">
@ -149,7 +264,7 @@ class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
<div class="modal-content" style="border-radius: 20px; border: none; overflow: hidden;">
<div class="modal-header bg-primary text-white border-0 py-3 px-4">
<h5 class="modal-title fw-bold">
<i class="icon-calendar me-2"></i>Detail Agenda Latihan
<i class="bi bi-calendar-event me-2"></i>Detail Agenda Latihan
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
@ -157,9 +272,8 @@ class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
<div class="row">
<div class="col-md-4 text-center border-end">
<div class="mb-3">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center shadow-sm"
style="width: 100px; height: 100px; border: 4px solid #f8f9fa;">
<i class="icon-clipboard text-primary" style="font-size: 50px;"></i>
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center shadow-sm" style="width: 100px; height: 100px; border: 4px solid #f8f9fa;">
<i class="bi bi-clipboard-data text-primary" style="font-size: 50px;"></i>
</div>
</div>
<h5 class="fw-bold mb-1 text-dark">{{ $item->judul_kegiatan }}</h5>
@ -176,10 +290,35 @@ class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
<small class="text-muted d-block small">Kolat</small>
<span class="fw-bold text-dark">{{ $item->kolat->nama_kolat ?? '-' }}</span>
</div>
<div class="col-12 mt-2">
<div class="p-3 bg-light rounded" style="border-left: 4px solid #0d6efd;">
<small class="text-muted d-block fw-bold mb-1">Waktu Pelaksanaan</small>
<div class="d-flex align-items-center gap-3">
<div>
<i class="bi bi-calendar3 text-primary me-1"></i>
<span class="fw-bold text-dark">{{ \Carbon\Carbon::parse($item->tanggal)->isoFormat('dddd, D MMMM Y') }}</span>
</div>
<div class="border-start ps-3">
<i class="bi bi-clock-history text-primary me-1"></i>
<span class="fw-bold text-dark">{{ substr($item->jam_mulai, 0, 5) }} - {{ $item->jam_selesai ? substr($item->jam_selesai, 0, 5) : 'Selesai' }} WIB</span>
</div>
</div>
</div>
</div>
<div class="col-12 mt-3">
<h6 class="fw-bold text-primary border-bottom pb-1 small">Deskripsi Kegiatan</h6>
<p class="small text-muted mb-0" style="white-space: pre-line;">{{ $item->deskripsi ?? 'Tidak ada deskripsi.' }}</p>
</div>
@if($item->foto_bukti)
<div class="col-12 mt-3">
<h6 class="fw-bold text-success border-bottom pb-1 small mt-2">Bukti Kegiatan & Catatan</h6>
<p class="small text-muted mb-2">"{{ $item->catatan_latihan ?? 'Tidak ada catatan khusus.' }}"</p>
<img src="{{ asset('storage/' . $item->foto_bukti) }}" class="img-fluid rounded shadow-sm" style="max-height: 200px; object-fit: cover;">
</div>
@endif
</div>
</div>
</div>
@ -190,20 +329,7 @@ class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
</div>
</div>
</div>
@empty
<tr>
<td colspan="6" class="text-center py-5 text-muted small">Belum ada jadwal latihan atau pengumuman.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
@endforeach
<style>
.main-table thead th { background-color: #f8faff; padding: 15px 10px; font-size: 0.7rem; color: #8c98a5; letter-spacing: 1px; text-transform: uppercase; border-bottom: 2px solid #edf2f9; }
@ -214,30 +340,35 @@ class="btn btn-sm btn-action-edit fw-bold px-2 shadow-xs">
.bg-secondary-soft { background-color: #f3f4f6; color: #6b7280; }
.btn-action-detail { background-color: #f0f7ff; color: #0d6efd; border: 1px solid #d0e4ff; border-radius: 10px; transition: 0.3s; }
.btn-action-edit { background-color: #fff8e6; color: #f59e0b; border: 1px solid #ffecb3; border-radius: 10px; transition: 0.3s; }
.btn-action-delete { background-color: #fff5f5; color: #dc3545; border: 1px solid #f8d7da; border-radius: 10px; transition: 0.3s; }
.btn-action-edit:hover { background-color: #f59e0b !important; color: white !important; transform: translateY(-2px); }
.btn-action-detail:hover { background-color: #0d6efd !important; color: white !important; transform: translateY(-2px); }
.btn-action-delete:hover { background-color: #dc3545 !important; color: white !important; transform: translateY(-2px); }
.shadow-xs { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); }
.table-responsive::-webkit-scrollbar { width: 6px; }
.table-responsive::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 10px; }
</style>
<script>
function konfirmasiLatihan(id, judul) {
function hapusJadwal(id, judul) {
Swal.fire({
title: 'Konfirmasi Selesai',
text: "Apakah benar latihan '" + judul + "' telah dilaksanakan?",
icon: 'question',
title: 'Hapus Jadwal?',
text: "Jadwal '" + judul + "' akan dihapus permanen beserta semua data presensinya!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#198754', // Warna hijau sukses
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Ya, Sudah Selesai!',
confirmButtonText: 'Ya, Hapus!',
cancelButtonText: 'Batal',
border: 'none',
borderRadius: '20px'
}).then((result) => {
if (result.isConfirmed) {
// Submit form jika user klik OK
document.getElementById('form-selesai-' + id).submit();
document.getElementById('form-hapus-' + id).submit();
}
})
}
</script>
</script>
@endsection

View File

@ -212,10 +212,15 @@
</li>
<li>
<a class="dropdown-item d-flex align-items-center" href="#">
<a class="dropdown-item d-flex align-items-center" href="{{ route('logout') }}"
onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
<i class="bi bi-box-arrow-right"></i>
<span>Sign Out</span>
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</li>
</ul><!-- End Profile Dropdown Items -->

View File

@ -1,6 +1,12 @@
@php
// Ambil nama role user yang login saat ini
$userRole = auth()->user()->role->nama_role ?? 'Anggota';
@endphp
<aside id="sidebar" class="sidebar">
<ul class="sidebar-nav" id="sidebar-nav">
{{-- MENU DASHBOARD (Semua Role Bisa Akses) --}}
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('dashboard') ? '' : 'collapsed' }}" href="{{ route('dashboard') }}">
<i class="bi bi-grid"></i>
@ -8,6 +14,8 @@
</a>
</li>
{{-- MENU MANAJEMEN ANGGOTA (Hanya Pelatih & Pengurus) --}}
@if($userRole != 'Anggota')
<li class="nav-item">
<a class="nav-link {{ request()->routeIs(['pelatih.*', 'anggota.*', 'kolat.*']) ? '' : 'collapsed' }}"
data-bs-target="#anggota-nav" data-bs-toggle="collapse" href="#" aria-expanded="{{ request()->routeIs(['pelatih.*', 'anggota.*', 'kolat.*']) ? 'true' : 'false' }}">
@ -31,15 +39,28 @@
</li>
</ul>
</li>
@endif
{{-- MENU MANAJEMEN LATIHAN (Semua Bisa Akses, Tapi Isinya Beda) --}}
<li class="nav-item">
<a class="nav-link {{ request()->routeIs(['presensi.*', 'jadwal.*']) ? '' : 'collapsed' }}"
data-bs-target="#latihan-nav" data-bs-toggle="collapse" href="#" aria-expanded="{{ request()->routeIs(['presensi.*', 'jadwal.*']) ? 'true' : 'false' }}">
<i class="bi bi-journal-check"></i><span>Manajemen Latihan</span><i class="bi bi-chevron-down ms-auto"></i>
</a>
<ul id="latihan-nav" class="nav-content collapse {{ request()->routeIs(['presensi.*', 'jadwal.*']) ? 'show' : '' }}" data-bs-parent="#sidebar-nav">
{{-- Jika Login Sebagai ANGGOTA --}}
@if($userRole == 'Anggota')
<li>
<a href="{{ route('presensi.index') }}" class="{{ request()->routeIs('presensi.*') ? 'active' : '' }}">
<a href="{{ route('presensi.anggota.index') }}" class="{{ request()->routeIs('presensi.anggota.*') ? 'active' : '' }}">
<i class="bi bi-circle"></i><span>Kehadiran Latihan</span>
</a>
</li>
{{-- Jika Login Sebagai PELATIH / PENGURUS --}}
@else
<li>
<a href="{{ route('presensi.index') }}" class="{{ request()->routeIs('presensi.index', 'presensi.kehadiran') ? 'active' : '' }}">
<i class="bi bi-circle"></i><span>Presensi</span>
</a>
</li>
@ -49,19 +70,25 @@
</a>
</li>
<li><a href="#"><i class="bi bi-circle"></i><span>Penilaian Pelatih</span></a></li>
@endif
</ul>
</li>
{{-- MENU TRANSAKSI (Semua Bisa Akses, Tapi Anggota Cuma Lihat SPP) --}}
<li class="nav-item">
<a class="nav-link collapsed" data-bs-target="#transaksi-nav" data-bs-toggle="collapse" href="#">
<i class="bi bi-wallet2"></i><span>Transaksi</span><i class="bi bi-chevron-down ms-auto"></i>
</a>
<ul id="transaksi-nav" class="nav-content collapse" data-bs-parent="#sidebar-nav">
@if($userRole != 'Anggota')
<li><a href="#"><i class="bi bi-circle"></i><span>Kas Cabang</span></a></li>
@endif
<li><a href="#"><i class="bi bi-circle"></i><span>SPP Anggota</span></a></li>
</ul>
</li>
{{-- MENU LAPORAN (Hanya Pelatih & Pengurus) --}}
@if($userRole != 'Anggota')
<li class="nav-item">
<a class="nav-link collapsed" data-bs-target="#laporan-nav" data-bs-toggle="collapse" href="#">
<i class="bi bi-file-earmark-bar-graph"></i><span>Laporan</span><i class="bi bi-chevron-down ms-auto"></i>
@ -73,7 +100,9 @@
<li><a href="#"><i class="bi bi-circle"></i><span>Laporan Penilaian</span></a></li>
</ul>
</li>
@endif
{{-- MENU AKUN (Semua Bisa Akses) --}}
<li class="nav-heading">Akun</li>
<li class="nav-item">
<a class="nav-link collapsed" href="#"><i class="bi bi-person"></i><span>Profile</span></a>
@ -87,15 +116,10 @@
</ul>
</aside>
{{-- SCRIPT PENTING: Supaya menu yang otomatis terbuka BISA DITUTUP KEMBALI --}}
<script>
document.addEventListener("DOMContentLoaded", function() {
// Cari semua dropdown yang punya class 'show' (dibuka otomatis oleh Laravel)
var bootstrapCollapseMenus = document.querySelectorAll('.nav-content.collapse.show');
bootstrapCollapseMenus.forEach(function (menuElement) {
// Kita kasih tahu Bootstrap secara manual: "Woi, ini menu udah kebuka!"
// Dengan inisialisasi ini, klik berikutnya akan menjalankan fungsi 'hide'
new bootstrap.Collapse(menuElement, {
toggle: false
});

View File

@ -2,11 +2,18 @@
@section('content')
<div class="container-fluid py-4">
<div class="page-header mb-4 text-start">
<h3 class="fw-bold text-dark">Kehadiran Latihan</h3>
<p class="text-muted small">Silakan lakukan absensi mandiri untuk jadwal hari ini.</p>
<div class="d-flex justify-content-between align-items-end mb-4 text-start">
<div>
<h3 class="fw-bold text-dark mb-1">Kehadiran Latihan</h3>
<p class="text-muted small mb-0">Jadwal hari ini & riwayat 7 hari terakhir.</p>
</div>
{{-- Tombol menuju route riwayat yang akan kita buat nanti --}}
<a href="{{ route('presensi.anggota.riwayat') }}" class="btn btn-outline-primary shadow-sm fw-bold" style="border-radius: 12px;">
<i class="bi bi-clock-history me-1"></i> Riwayat Lengkap
</a>
</div>
{{-- Notifikasi --}}
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm rounded-3 mb-4">
<i class="bi bi-check-circle-fill me-2"></i> {{ session('success') }}
@ -21,57 +28,138 @@
</div>
@endif
<div class="row">
@forelse($jadwal_hari_ini as $jadwal)
{{-- PISAHKAN JADWAL HARI INI DAN SEBELUMNYA --}}
@php
$jadwalHariIni = $jadwal_terbaru->where('tanggal', $hari_ini);
$jadwalSebelumnya = $jadwal_terbaru->where('tanggal', '!=', $hari_ini);
@endphp
{{-- SEGMEN 1: JADWAL HARI INI --}}
<h5 class="fw-bold text-dark mb-3"><i class="bi bi-calendar-event-fill text-primary me-2"></i>Jadwal Hari Ini</h5>
<div class="row mb-5">
@forelse($jadwalHariIni as $jadwal)
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4">
<div class="card border-0 shadow-sm h-100" style="border-radius: 20px; transition: 0.3s; border-top: 5px solid #0d6efd !important;">
<div class="card-body p-4 d-flex flex-column">
<div class="d-flex justify-content-between align-items-start mb-3">
<span class="badge bg-primary-soft text-primary px-3 py-2 rounded-pill shadow-xs">
{{ $jadwal->jenis }}
</span>
<span class="small fw-bold text-muted">{{ \Carbon\Carbon::parse($jadwal->tanggal)->format('d M Y') }}</span>
<span class="small fw-bold text-dark text-end">Hari Ini</span>
</div>
<h5 class="fw-bold text-dark mb-1">{{ $jadwal->judul_kegiatan }}</h5>
<p class="small text-muted mb-3"><i class="bi bi-person-fill me-1"></i> Pelatih: {{ $jadwal->pelatih->nama_lengkap ?? '-' }}</p>
<div class="bg-light p-3 rounded-3 mb-4">
<div class="small fw-bold text-dark mb-1"><i class="bi bi-clock me-2 text-primary"></i>{{ substr($jadwal->jam_mulai, 0, 5) }} - Selesai</div>
<div class="small text-muted"><i class="bi bi-geo-alt-fill me-2 text-danger"></i>{{ $jadwal->lokasi }}</div>
<div class="small fw-bold text-dark mb-1">
<i class="bi bi-clock me-2 text-primary"></i>
{{ substr($jadwal->jam_mulai, 0, 5) }} - {{ $jadwal->jam_selesai ? substr($jadwal->jam_selesai, 0, 5) : 'Selesai' }} WIB
</div>
<div class="small text-muted text-truncate">
<i class="bi bi-geo-alt-fill me-2 text-danger"></i>{{ $jadwal->lokasi }}
</div>
</div>
{{-- LOGIKA TOMBOL ABSEN --}}
<div class="mt-auto">
@if(isset($presensi_saya[$jadwal->id]))
{{-- Jika sudah absen --}}
{{-- Sudah Absen Hari Ini --}}
@if($presensi_saya[$jadwal->id]->is_verified)
<button class="btn btn-success w-100 fw-bold disabled py-2" style="border-radius: 12px;">
<i class="bi bi-check2-all me-1"></i> Kehadiran Disahkan
</button>
<div class="text-center p-2 rounded-3 bg-success-soft text-success border border-success fw-bold">
<i class="bi bi-patch-check-fill me-1"></i> Kehadiran Disahkan
</div>
@else
<button class="btn btn-warning w-100 fw-bold disabled py-2" style="border-radius: 12px; color: #854d0e; background-color: #fef08a; border:none;">
<div class="text-center p-2 rounded-3 bg-warning-soft text-warning border border-warning fw-bold">
<i class="bi bi-hourglass-split me-1"></i> Menunggu Konfirmasi
</button>
</div>
@endif
@else
{{-- Jika belum absen, tampilkan tombol submit --}}
<form action="{{ route('anggota.presensi.store') }}" method="POST">
@csrf
<input type="hidden" name="jadwal_id" value="{{ $jadwal->id }}">
<button type="submit" class="btn btn-primary w-100 fw-bold py-2 shadow-sm" style="border-radius: 12px;" onclick="return confirm('Apakah kamu yakin ingin absen untuk jadwal ini?')">
<i class="bi bi-hand-index-thumb me-1"></i> Absen Sekarang
</button>
</form>
@endif
{{-- Belum Absen Hari Ini --}}
@php
$end = $jadwal->jam_selesai
? \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jadwal->jam_selesai, 'Asia/Jakarta')
: \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jadwal->jam_mulai, 'Asia/Jakarta')->addHours(3);
@endphp
@if($now->gt($end))
<button class="btn btn-secondary w-100 fw-bold py-2 disabled" style="border-radius: 12px; opacity: 0.6;">
<i class="bi bi-lock-fill me-1"></i> Sesi Absen Berakhir
</button>
@elseif($jadwal->status == 'selesai')
<button class="btn btn-secondary w-100 fw-bold py-2 disabled" style="border-radius: 12px; opacity: 0.6;">
<i class="bi bi-x-circle me-1"></i> Latihan Ditutup Pelatih
</button>
@else
<a href="{{ route('presensi.anggota.scan', $jadwal->id) }}" class="btn btn-primary w-100 fw-bold py-2 shadow-sm btn-scan" style="border-radius: 12px;">
<i class="bi bi-qr-code-scan me-2"></i> Scan Barcode Sekarang
</a>
@endif
@endif
</div>
</div>
</div>
</div>
@empty
<div class="col-12 text-center py-5">
<img src="https://cdn-icons-png.flaticon.com/512/7486/7486747.png" width="120" class="mb-3 opacity-50" alt="No Schedule">
<h5 class="text-muted fw-bold">Tidak ada jadwal latihan hari ini.</h5>
<p class="text-muted small">Selamat istirahat atau berlatih mandiri di rumah!</p>
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px; background-color: #f8f9fa;">
<div class="card-body text-center py-5">
<i class="bi bi-calendar-x text-muted mb-3 d-block" style="font-size: 3rem; opacity: 0.5;"></i>
<h6 class="fw-bold text-secondary mb-0">Tidak ada jadwal latihan untuk Anda hari ini.</h6>
</div>
</div>
</div>
@endforelse
</div>
{{-- SEGMEN 2: RIWAYAT 7 HARI TERAKHIR --}}
<h5 class="fw-bold text-dark mb-3"><i class="bi bi-clock-history text-secondary me-2"></i>Riwayat Minggu Ini</h5>
<div class="row">
@forelse($jadwalSebelumnya as $jadwal)
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 shadow-sm h-100" style="border-radius: 20px; background-color: #fafbfc;">
<div class="card-body p-4 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-bold text-dark mb-0 text-truncate">{{ $jadwal->judul_kegiatan }}</h6>
<span class="small text-muted" style="font-size: 0.75rem;">{{ \Carbon\Carbon::parse($jadwal->tanggal)->isoFormat('D MMM YYYY') }}</span>
</div>
<div class="mt-auto">
@if(isset($presensi_saya[$jadwal->id]))
@php
$presensi = $presensi_saya[$jadwal->id];
$statusColor = match($presensi->status) {
'Hadir' => 'success',
'Izin' => 'info',
'Sakit' => 'warning',
'Alfa' => 'danger',
default => 'secondary'
};
@endphp
@if($presensi->is_verified)
<div class="d-flex align-items-center justify-content-between p-2 rounded-3 bg-{{ $statusColor }}-soft text-{{ $statusColor }} fw-bold" style="font-size: 0.85rem;">
<span><i class="bi bi-check-circle-fill me-1"></i> Disahkan</span>
<span>{{ $presensi->status }}</span>
</div>
@else
<div class="d-flex align-items-center justify-content-between p-2 rounded-3 bg-warning-soft text-warning fw-bold" style="font-size: 0.85rem;">
<span><i class="bi bi-hourglass-split me-1"></i> Menunggu</span>
<span>Hadir</span>
</div>
@endif
@else
<div class="d-flex align-items-center justify-content-between p-2 rounded-3 bg-danger-soft text-danger fw-bold" style="font-size: 0.85rem;">
<span><i class="bi bi-x-circle-fill me-1"></i> Tidak Absen</span>
<span>Alfa</span>
</div>
@endif
</div>
</div>
</div>
</div>
@empty
<div class="col-12 text-center py-4">
<p class="text-muted small">Belum ada riwayat latihan dalam 7 hari terakhir.</p>
</div>
@endforelse
</div>
@ -79,5 +167,12 @@
<style>
.bg-primary-soft { background-color: #e0f2fe; color: #0ea5e9; }
.bg-success-soft { background-color: #d1e7dd; color: #0f5132; }
.bg-warning-soft { background-color: #fff3cd; color: #664d03; }
.bg-info-soft { background-color: #cff4fc; color: #055160; }
.bg-danger-soft { background-color: #f8d7da; color: #842029; }
.btn-scan { background: linear-gradient(45deg, #0d6efd, #004fb1); border: none; transition: 0.3s; }
.btn-scan:hover { transform: translateY(-3px); box-shadow: 0 8px 15px rgba(13, 110, 253, 0.3) !important; }
.card:hover { transform: translateY(-3px); }
</style>
@endsection

View File

@ -0,0 +1,179 @@
@extends('layout.app')
@section('content')
<div class="container-fluid py-4">
{{-- HEADER & BREADCRUMB --}}
<div class="page-header mb-4 text-start">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-transparent p-0 mb-2">
<li class="breadcrumb-item"><a href="#" class="text-muted text-decoration-none small">Home</a></li>
<li class="breadcrumb-item"><a href="{{ route('presensi.anggota.index') }}" class="text-muted text-decoration-none small">Kehadiran</a></li>
<li class="breadcrumb-item active text-primary fw-bold small" aria-current="page">Riwayat Lengkap</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center">
<div>
<h3 class="fw-bold text-dark mb-1">Riwayat Kehadiran</h3>
<p class="text-muted small mb-0">Rekap seluruh partisipasi latihan Anda.</p>
</div>
<a href="{{ route('presensi.anggota.index') }}" class="btn btn-light border shadow-sm fw-bold" style="border-radius: 12px;">
<i class="bi bi-arrow-left me-1"></i> Kembali
</a>
</div>
</div>
{{-- FILTER BULAN & TAHUN --}}
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4 text-start">
<form action="{{ route('presensi.anggota.riwayat') }}" method="GET" class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label fw-bold small text-muted">Pilih Bulan</label>
<select name="bulan" class="form-select border-2 shadow-none" style="border-radius: 10px;">
@php
$namaBulan = ['01'=>'Januari', '02'=>'Februari', '03'=>'Maret', '04'=>'April', '05'=>'Mei', '06'=>'Juni', '07'=>'Juli', '08'=>'Agustus', '09'=>'September', '10'=>'Oktober', '11'=>'November', '12'=>'Desember'];
@endphp
@foreach($namaBulan as $angka => $nama)
<option value="{{ $angka }}" {{ $bulan == $angka ? 'selected' : '' }}>{{ $nama }}</option>
@endforeach
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold small text-muted">Pilih Tahun</label>
<select name="tahun" class="form-select border-2 shadow-none" style="border-radius: 10px;">
@for($i = date('Y'); $i >= 2024; $i--)
<option value="{{ $i }}" {{ $tahun == $i ? 'selected' : '' }}>{{ $i }}</option>
@endfor
</select>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary w-100 fw-bold shadow-sm" style="border-radius: 10px;">
<i class="bi bi-funnel-fill me-1"></i> Terapkan Filter
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{{-- TABEL RIWAYAT --}}
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4 text-start">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold text-dark mb-0">Data Bulan {{ $namaBulan[$bulan] }} {{ $tahun }}</h5>
<span class="badge bg-primary-soft text-primary px-3 py-2 rounded-pill">Total: {{ $jadwal_riwayat->count() }} Pertemuan</span>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle main-table">
<thead>
<tr>
<th width="5%" class="text-center">No</th>
<th width="20%">Tanggal Latihan</th>
<th width="30%">Materi / Kegiatan</th>
<th width="15%" class="text-center">Status</th>
<th width="15%" class="text-center">Verifikasi</th>
<th width="15%">Keterangan</th>
</tr>
</thead>
<tbody>
@forelse($jadwal_riwayat as $no => $jadwal)
@php
// Pengecekan apakah jadwal sudah lewat waktunya
$end = $jadwal->jam_selesai
? \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jadwal->jam_selesai, 'Asia/Jakarta')
: \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jadwal->jam_mulai, 'Asia/Jakarta')->addHours(3);
$sudah_lewat = \Carbon\Carbon::now('Asia/Jakarta')->gt($end);
@endphp
<tr>
<td class="text-muted small text-center">{{ $no + 1 }}</td>
<td>
<div class="fw-bold text-dark">{{ \Carbon\Carbon::parse($jadwal->tanggal)->isoFormat('D MMM YYYY') }}</div>
<small class="text-muted"><i class="bi bi-clock me-1"></i>{{ substr($jadwal->jam_mulai, 0, 5) }} WIB</small>
</td>
<td>
<div class="fw-bold text-dark text-truncate" style="max-width: 250px;">{{ $jadwal->judul_kegiatan }}</div>
<span class="badge bg-light text-secondary border mt-1" style="font-size: 0.65rem;">{{ $jadwal->jenis }}</span>
</td>
<td class="text-center">
@if(isset($presensi_saya[$jadwal->id]))
@php
$presensi = $presensi_saya[$jadwal->id];
$statusColor = match($presensi->status) {
'Hadir' => 'success',
'Izin' => 'info',
'Sakit' => 'warning',
'Alfa' => 'danger',
default => 'secondary'
};
@endphp
<span class="badge bg-{{ $statusColor }}-soft text-{{ $statusColor }} px-3 py-2 rounded-pill fw-bold">
{{ $presensi->status }}
</span>
@else
@if($sudah_lewat || $jadwal->status == 'selesai')
<span class="badge bg-danger-soft text-danger px-3 py-2 rounded-pill fw-bold">Alfa</span>
@else
<span class="badge bg-light text-muted px-3 py-2 rounded-pill border">Belum Mulai</span>
@endif
@endif
</td>
<td class="text-center">
@if(isset($presensi_saya[$jadwal->id]))
@if($presensi_saya[$jadwal->id]->is_verified)
<span class="text-success small fw-bold"><i class="bi bi-check-circle-fill me-1"></i>Sah</span>
@else
<span class="text-warning small fw-bold"><i class="bi bi-hourglass-split me-1"></i>Menunggu</span>
@endif
@else
<span class="text-muted small">-</span>
@endif
</td>
<td>
@if(isset($presensi_saya[$jadwal->id]) && $presensi_saya[$jadwal->id]->keterangan)
<span class="small text-muted" style="font-size: 0.75rem;">
<i class="bi bi-chat-left-text me-1"></i>{{ $presensi_saya[$jadwal->id]->keterangan }}
</span>
@else
<span class="text-muted small">-</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-5">
<div class="mb-3">
<i class="bi bi-folder-x text-muted" style="font-size: 3rem; opacity: 0.5;"></i>
</div>
<h6 class="fw-bold text-secondary mb-1">Tidak ada data di bulan ini</h6>
<p class="text-muted small">Silakan pilih bulan atau tahun lain pada filter di atas.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.main-table thead th { background-color: #f8faff; padding: 16px 12px; font-size: 0.75rem; color: #6c757d; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; border-bottom: 2px solid #edf2f9; }
.main-table tbody td { padding: 16px 12px; border-bottom: 1px solid #f1f4f8; vertical-align: middle; }
.bg-primary-soft { background-color: #e0f2fe; color: #0ea5e9; }
.bg-success-soft { background-color: #d1e7dd; color: #0f5132; }
.bg-warning-soft { background-color: #fff3cd; color: #664d03; }
.bg-info-soft { background-color: #cff4fc; color: #055160; }
.bg-danger-soft { background-color: #f8d7da; color: #842029; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); }
</style>
@endsection

View File

@ -0,0 +1,128 @@
@extends('layout.app')
@section('content')
<div class="container-fluid py-4">
<div class="page-header mb-4 text-start">
<a href="{{ route('presensi.anggota.index') }}" class="text-decoration-none text-muted small mb-2 d-inline-block">
<i class="bi bi-arrow-left me-1"></i> Kembali
</a>
<h3 class="fw-bold text-dark">Scan Barcode Kehadiran</h3>
<p class="text-muted small">Arahkan kamera Anda ke QR Code yang ditampilkan oleh Pelatih.</p>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card border-0 shadow-sm overflow-hidden" style="border-radius: 25px;">
{{-- Area Kamera --}}
<div id="reader" style="width: 100%; border: none;"></div>
<div class="card-body p-4 text-center">
<div id="result-info">
<div class="spinner-grow text-primary spinner-grow-sm me-2" role="status"></div>
<span class="text-muted small">Mencari kamera...</span>
</div>
<div class="mt-4 p-3 bg-light rounded-3 text-start">
<h6 class="fw-bold text-dark small mb-1"><i class="bi bi-info-circle me-2 text-primary"></i>Informasi Jadwal:</h6>
<p class="mb-0 small text-muted">{{ $jadwal->judul_kegiatan }}</p>
<p class="mb-0 small text-muted"><i class="bi bi-geo-alt small"></i> {{ $jadwal->lokasi }}</p>
</div>
</div>
</div>
{{-- Form Tersembunyi untuk Submit Data --}}
<form id="form-scan" action="{{ route('presensi.storeMandiri') }}" method="POST" style="display: none;">
@csrf
<input type="hidden" name="jadwal_id" id="jadwal_id_input">
</form>
</div>
</div>
</div>
{{-- Script QR Scanner --}}
<script src="https://unpkg.com/html5-qrcode"></script>
<script>
function onScanSuccess(decodedText, decodedResult) {
// decodedText biasanya berisi URL, misal: http://tapakmp.test/presensi/scan-absen/12
// Kita hanya butuh ID Jadwal-nya.
// Logika sederhana: Kita bandingkan apakah URL yang discan mengandung ID jadwal yang benar
const expectedUrl = "{{ route('presensi.anggota.scan', $jadwal->id) }}";
if (decodedText === expectedUrl) {
// Berhenti scan agar tidak submit berkali-kali
html5QrcodeScanner.clear();
// Masukkan ID ke form dan submit
document.getElementById('jadwal_id_input').value = "{{ $jadwal->id }}";
Swal.fire({
title: 'Berhasil Mendeteksi!',
text: 'Sedang memproses absensi Anda...',
icon: 'success',
showConfirmButton: false,
timer: 2000
});
document.getElementById('form-scan').submit();
} else {
// Jika barcode yang discan salah/milik jadwal lain
Swal.fire({
title: 'Barcode Tidak Valid',
text: 'Pastikan Anda men-scan barcode pelatih untuk latihan ini.',
icon: 'error',
confirmButtonColor: '#0d6efd'
});
}
}
function onScanFailure(error) {
// Kegagalan scan (biasanya karena kamera tidak fokus atau barcode belum terlihat)
// Kita biarkan saja agar dia terus mencoba scan otomatis
}
let html5QrcodeScanner = new Html5QrcodeScanner(
"reader",
{
fps: 10,
qrbox: {width: 250, height: 250},
aspectRatio: 1.0
},
/* verbose= */ false
);
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
// Update status UI
document.addEventListener("DOMContentLoaded", function() {
setTimeout(() => {
document.getElementById('result-info').innerHTML =
'<span class="badge bg-success-soft text-success rounded-pill px-3 py-2">' +
'<i class="bi bi-camera-video me-2"></i>Kamera Aktif</span>';
}, 2000);
});
</script>
<style>
/* Styling Scanner agar terlihat seperti aplikasi modern */
#reader {
background-color: #000;
}
#reader__dashboard_section_csr button {
background-color: #0d6efd !important;
color: white !important;
border: none !important;
padding: 8px 20px !important;
border-radius: 10px !important;
font-weight: bold !important;
margin: 10px 0 !important;
}
#reader__status_span {
display: none;
}
video {
border-radius: 0 0 0 0 !important;
}
.bg-success-soft { background-color: #f0fdf4; color: #16a34a; }
</style>
@endsection

View File

@ -12,8 +12,9 @@
<h3 class="fw-bold text-dark">Rekap Presensi Latihan</h3>
</div>
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm mb-4" role="alert" style="border-radius: 15px;">
@if (session('success'))
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm mb-4" role="alert"
style="border-radius: 15px;">
<strong>Berhasil!</strong> {{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@ -27,7 +28,8 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="text-start">
<h5 class="card-title mb-0 fw-bold text-dark">Pilih Jadwal Latihan</h5>
<p class="text-muted small mb-0">Klik tombol cek kehadiran untuk memverifikasi absen anggota dan upload bukti latihan.</p>
<p class="text-muted small mb-0">Klik tombol cek kehadiran untuk memverifikasi absen anggota
dan mengelola data manual.</p>
</div>
</div>
@ -48,9 +50,15 @@
@php
// Set timezone ke Jakarta agar sinkron dengan waktu Indonesia
$now = \Carbon\Carbon::now('Asia/Jakarta');
$start = \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_mulai, 'Asia/Jakarta');
$start = \Carbon\Carbon::parse(
$item->tanggal . ' ' . $item->jam_mulai,
'Asia/Jakarta',
);
$end = $item->jam_selesai
? \Carbon\Carbon::parse($item->tanggal . ' ' . $item->jam_selesai, 'Asia/Jakarta')
? \Carbon\Carbon::parse(
$item->tanggal . ' ' . $item->jam_selesai,
'Asia/Jakarta',
)
: $start->copy()->addHours(2);
$dbStatus = trim(strtolower($item->status));
@ -59,7 +67,10 @@
if ($dbStatus == 'selesai') {
$status_label = 'Selesai';
$status_color = 'success';
} elseif ($now->greaterThanOrEqualTo($start) && $now->lessThanOrEqualTo($end)) {
} elseif (
$now->greaterThanOrEqualTo($start) &&
$now->lessThanOrEqualTo($end)
) {
$status_label = 'Berlangsung';
$status_color = 'primary';
} elseif ($now->lt($start)) {
@ -75,39 +86,48 @@
<td>
<div class="fw-bold text-dark text-start ps-2">
{{ $item->judul_kegiatan }}
@if($dbStatus == 'selesai')
@if ($dbStatus == 'selesai')
<i class="bi bi-check-circle-fill text-success ms-1"></i>
@endif
</div>
<small class="text-muted text-start ps-2 d-block">
{{-- Pemanggilan dari Query Builder Manual --}}
<i class="icon-user small"></i> {{ $item->nama_pelatih ?? 'Belum Ditentukan' }}
<i class="bi bi-person-fill small"></i>
{{ $item->nama_pelatih ?? 'Belum Ditentukan' }}
</small>
</td>
<td class="text-center">
<span class="badge bg-{{ $status_color }} px-3 py-2" style="border-radius: 8px; font-size: 11px;">
<span class="badge bg-{{ $status_color }} px-3 py-2"
style="border-radius: 8px; font-size: 11px;">
{{ $status_label }}
</span>
</td>
<td class="text-start ps-4">
<div class="small fw-bold text-dark">
<i class="icon-calendar me-1"></i> {{ \Carbon\Carbon::parse($item->tanggal)->format('d M Y') }}
<i class="bi bi-calendar-event me-1"></i>
{{ \Carbon\Carbon::parse($item->tanggal)->format('d M Y') }}
</div>
<div class="small text-muted">
<i class="icon-clock me-1"></i> {{ substr($item->jam_mulai, 0, 5) }} - {{ $item->jam_selesai ? substr($item->jam_selesai, 0, 5) : 'Selesai' }}
<i class="bi bi-clock me-1"></i> {{ substr($item->jam_mulai, 0, 5) }} -
{{ $item->jam_selesai ? substr($item->jam_selesai, 0, 5) : 'Selesai' }}
</div>
<div class="small text-muted text-truncate" style="max-width: 150px;">
<i class="icon-map-pin me-1 text-danger"></i> {{ $item->lokasi }}
<i class="bi bi-geo-alt-fill me-1 text-danger"></i> {{ $item->lokasi }}
</div>
</td>
<td class="text-center">
@php
$bg_type = 'bg-secondary-soft text-secondary';
if($item->jenis == 'Rutin') $bg_type = 'bg-success-soft text-success border-success';
elseif($item->jenis == 'Tambahan') $bg_type = 'bg-info-soft text-info border-info';
elseif($item->jenis == 'Pengumuman') $bg_type = 'bg-danger-soft text-danger border-danger';
if ($item->jenis == 'Rutin') {
$bg_type = 'bg-success-soft text-success border-success';
} elseif ($item->jenis == 'Tambahan') {
$bg_type = 'bg-info-soft text-info border-info';
} elseif ($item->jenis == 'Pengumuman') {
$bg_type = 'bg-danger-soft text-danger border-danger';
}
@endphp
<span class="badge rounded-pill {{ $bg_type }} px-3 border" style="font-size: 10px;">
<span class="badge rounded-pill {{ $bg_type }} px-3 border"
style="font-size: 10px;">
{{ $item->jenis }}
</span>
</td>
@ -122,7 +142,8 @@ class="btn btn-sm btn-info fw-bold px-3 shadow-xs text-white"
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-5 text-muted small">Belum ada jadwal latihan tersedia.</td>
<td colspan="6" class="text-center py-5 text-muted small">Belum ada jadwal
latihan tersedia.</td>
</tr>
@endforelse
</tbody>
@ -136,14 +157,56 @@ class="btn btn-sm btn-info fw-bold px-3 shadow-xs text-white"
</div>
<style>
.main-table thead th { background-color: #f8faff; padding: 15px 10px; font-size: 0.7rem; color: #8c98a5; letter-spacing: 1px; text-transform: uppercase; border-bottom: 2px solid #edf2f9; }
.main-table tbody td { padding: 15px 10px; border-bottom: 1px solid #f1f4f8; color: #495057; font-size: 0.85rem; }
.bg-success-soft { background-color: #e6fffa; color: #38b2ac; }
.bg-info-soft { background-color: #e0f2fe; color: #0ea5e9; }
.bg-danger-soft { background-color: #fff5f5; color: #e53e3e; }
.bg-secondary-soft { background-color: #f3f4f6; color: #6b7280; }
.shadow-xs { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); }
.btn-info { background-color: #0ea5e9; border: none; transition: 0.3s; }
.btn-info:hover { background-color: #0284c7; transform: translateY(-2px); }
.main-table thead th {
background-color: #f8faff;
padding: 15px 10px;
font-size: 0.7rem;
color: #8c98a5;
letter-spacing: 1px;
text-transform: uppercase;
border-bottom: 2px solid #edf2f9;
}
.main-table tbody td {
padding: 15px 10px;
border-bottom: 1px solid #f1f4f8;
color: #495057;
font-size: 0.85rem;
}
.bg-success-soft {
background-color: #e6fffa;
color: #38b2ac;
}
.bg-info-soft {
background-color: #e0f2fe;
color: #0ea5e9;
}
.bg-danger-soft {
background-color: #fff5f5;
color: #e53e3e;
}
.bg-secondary-soft {
background-color: #f3f4f6;
color: #6b7280;
}
.shadow-xs {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.btn-info {
background-color: #0ea5e9;
border: none;
transition: 0.3s;
}
.btn-info:hover {
background-color: #0284c7;
transform: translateY(-2px);
}
</style>
@endsection

View File

@ -2,6 +2,7 @@
@section('content')
<div class="container-fluid py-4">
{{-- HEADER BREADCRUMB --}}
<div class="page-header mb-4 text-start">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-transparent p-0 mb-2">
@ -13,40 +14,58 @@
<h3 class="fw-bold text-dark">Verifikasi Kehadiran Anggota</h3>
</div>
{{-- Menampilkan Pesan Sukses --}}
@if(session('success'))
{{-- NOTIFIKASI --}}
@if (session('success'))
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm mb-4" role="alert" style="border-radius: 15px;">
<strong>Berhasil!</strong> {{ session('success') }}
<i class="bi bi-check-circle-fill me-2"></i><strong>Berhasil!</strong> {{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
{{-- Menampilkan Pesan Error (Misal: Input manual ganda) --}}
@if(session('error'))
@if (session('error'))
<div class="alert alert-danger alert-dismissible fade show border-0 shadow-sm mb-4" role="alert" style="border-radius: 15px;">
<strong>Gagal!</strong> {{ session('error') }}
<i class="bi bi-exclamation-triangle-fill me-2"></i><strong>Gagal!</strong> {{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
@php
$now = \Carbon\Carbon::now('Asia/Jakarta');
$start = \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jadwal->jam_mulai, 'Asia/Jakarta');
$end = $jadwal->jam_selesai
? \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jadwal->jam_selesai, 'Asia/Jakarta')
: $start->copy()->addHours(3);
$is_active = $now->between($start, $end);
@endphp
{{-- KARTU INFORMASI JADWAL --}}
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px; background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);">
<div class="card-body p-4 text-white">
<div class="row align-items-center">
<div class="col-md-8">
<h5 class="fw-bold mb-1">{{ $jadwal->judul_kegiatan }}</h5>
<div class="col-md-7 text-start">
<span class="badge bg-white text-primary px-3 py-2 rounded-pill shadow-sm mb-3">
<i class="bi bi-shield-fill-check me-1"></i> Kolat: {{ $jadwal->kolat->nama_kolat ?? '-' }}
</span>
<h4 class="fw-bold mb-2">{{ $jadwal->judul_kegiatan }}</h4>
<p class="mb-0 small" style="opacity: 0.9;">
<i class="icon-map-pin me-1"></i> {{ $jadwal->lokasi }} |
<i class="icon-calendar me-1"></i> {{ \Carbon\Carbon::parse($jadwal->tanggal)->format('d M Y') }} |
<i class="icon-clock me-1"></i> {{ substr($jadwal->jam_mulai, 0, 5) }} - {{ $jadwal->jam_selesai ? substr($jadwal->jam_selesai, 0, 5) : 'Selesai' }}
<i class="bi bi-geo-alt-fill me-1"></i> {{ $jadwal->lokasi }} &nbsp;|&nbsp;
<i class="bi bi-calendar-event me-1"></i> {{ \Carbon\Carbon::parse($jadwal->tanggal)->translatedFormat('d F Y') }} &nbsp;|&nbsp;
<i class="bi bi-clock me-1"></i> {{ substr($jadwal->jam_mulai, 0, 5) }} - {{ $jadwal->jam_selesai ? substr($jadwal->jam_selesai, 0, 5) : 'Selesai' }} WIB
</p>
</div>
<div class="col-md-4 text-md-end mt-3 mt-md-0">
<span class="badge bg-white text-primary px-3 py-2 rounded-pill shadow-sm">
Kolat: {{ $jadwal->kolat->nama_kolat ?? '-' }}
</span>
<div class="col-md-5 text-md-end mt-4 mt-md-0">
@if ($is_active && $jadwal->status != 'selesai')
<button class="btn btn-light text-primary fw-bold px-4 py-3 shadow" style="border-radius: 15px;" data-bs-toggle="modal" data-bs-target="#modalQRCode">
<i class="bi bi-qr-code-scan me-2" style="font-size: 1.2rem;"></i> Tampilkan Barcode
</button>
@else
<button class="btn btn-light text-muted fw-bold px-4 py-3 shadow disabled" style="border-radius: 15px; opacity: 0.7;">
<i class="bi bi-lock-fill me-2"></i> Waktu Absen Berakhir
</button>
@endif
</div>
</div>
</div>
@ -54,33 +73,31 @@
</div>
</div>
{{-- TABEL PRESENSI --}}
<div class="row">
{{-- TABEL DAFTAR ABSENSI MASUK --}}
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="text-start">
<h5 class="card-title mb-0 fw-bold text-dark">Daftar Absensi Masuk</h5>
<p class="text-muted small mb-0">Klik konfirmasi untuk mensahkan kehadiran anggota.</p>
<h5 class="card-title mb-1 fw-bold text-dark">Daftar Absensi Masuk</h5>
<p class="text-muted small mb-0">Verifikasi kehadiran anggota yang sudah melakukan absensi.</p>
</div>
{{-- Tombol Buka Modal Tambah Manual --}}
<button class="btn btn-primary px-4 py-2 shadow-sm" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#modalTambahManual">
<i class="bi bi-plus-lg me-1"></i> Tambah Manual
<button class="btn btn-primary px-4 py-2 shadow-sm fw-bold" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#modalTambahManual">
<i class="bi bi-person-plus-fill me-1"></i> Tambah Manual
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle main-table">
<table class="table table-hover align-middle main-table text-start">
<thead>
<tr>
<th width="60" class="text-center">No</th>
<th>Identitas Anggota</th>
<th class="text-center">Waktu Absen</th>
<th class="text-center">Status Verifikasi</th>
<th class="text-center" width="180">Aksi</th>
<th width="5%" class="text-center">No</th>
<th width="30%">Identitas Anggota</th>
<th width="15%" class="text-center">Waktu Absen</th>
<th width="15%" class="text-center">Status Kehadiran</th>
<th width="15%" class="text-center">Verifikasi</th>
<th width="20%" class="text-center">Aksi</th>
</tr>
</thead>
<tbody>
@ -88,106 +105,259 @@
<tr>
<td class="text-muted small text-center">{{ $no + 1 }}</td>
<td>
<div class="fw-bold text-dark text-start ps-2">{{ $item->anggota->nama_lengkap }}</div>
<small class="text-muted text-start ps-2 d-block">ID: {{ $item->anggota->no_induk }}</small>
<div class="fw-bold text-dark" style="font-size: 0.95rem;">{{ $item->anggota->nama_lengkap }}</div>
<small class="text-muted"><i class="bi bi-person-badge me-1"></i>{{ $item->anggota->no_induk }}</small>
</td>
<td class="text-center">
<span class="badge bg-light text-dark border px-3" style="border-radius: 8px;">
<i class="icon-clock me-1 text-primary"></i> {{ \Carbon\Carbon::parse($item->waktu_presensi)->format('H:i') }} WIB
<span class="badge bg-light text-dark border px-3 py-2" style="border-radius: 8px;">
<i class="bi bi-clock me-1"></i>{{ \Carbon\Carbon::parse($item->waktu_presensi)->format('H:i') }} WIB
</span>
</td>
<td class="text-center">
@if($item->is_verified)
<span class="badge bg-success-soft text-success border-success px-3 border rounded-pill">
<i class="bi bi-check-circle-fill me-1"></i> Sah
@php
$statusColor = match($item->status) {
'Hadir' => 'success',
'Izin' => 'info',
'Sakit' => 'warning',
'Alfa' => 'danger',
default => 'secondary'
};
@endphp
<span class="badge bg-{{ $statusColor }}-soft text-{{ $statusColor }} px-3 py-2 rounded-pill fw-bold">
{{ $item->status }}
</span>
<div class="small text-muted mt-1" style="font-size: 10px;">
Oleh: {{ $item->pelatihVerifikator->nama_lengkap ?? 'Pelatih' }}
@if($item->keterangan)
<div class="small text-muted mt-2" style="font-size: 0.75rem;">
<i class="bi bi-chat-left-text text-{{ $statusColor }} me-1"></i> {{ $item->keterangan }}
</div>
@else
<span class="badge bg-warning text-dark px-3 rounded-pill shadow-xs">
<i class="bi bi-hourglass-split me-1"></i> Menunggu
</span>
@endif
</td>
<td class="text-center">
@if(!$item->is_verified)
{{-- Tombol Konfirmasi Hadir --}}
<form action="{{ route('presensi.konfirmasi', $item->id) }}" method="POST" class="d-inline">
@if ($item->is_verified)
<span class="text-success small fw-bold"><i class="bi bi-check-circle-fill me-1"></i>Sah</span>
<div class="small text-muted mt-1" style="font-size: 0.7rem;">
Oleh: {{ $item->pelatihVerifikator->nama_lengkap ?? 'Sistem' }}
</div>
@else
<span class="text-warning small fw-bold"><i class="bi bi-hourglass-split me-1"></i>Menunggu</span>
@endif
</td>
<td class="text-center">
@if (!$item->is_verified)
<div class="d-flex justify-content-center gap-2">
<form action="{{ route('presensi.konfirmasi', $item->id) }}" method="POST">
@csrf
<button type="submit" class="btn btn-sm btn-action-edit fw-bold px-3 shadow-xs" onclick="return confirm('Konfirmasi kehadiran anggota ini?')">
<i class="bi bi-check2-square me-1"></i> Konfirmasi
<input type="hidden" name="action" value="sah">
<button type="submit" class="btn btn-sm btn-success fw-bold px-3 shadow-sm" style="border-radius: 8px;">
<i class="bi bi-check-lg"></i> Sah
</button>
</form>
@else
<button class="btn btn-sm btn-light fw-bold px-3 text-muted border disabled" style="border-radius: 10px;">
Selesai
<button type="button" class="btn btn-sm btn-danger fw-bold px-3 shadow-sm" style="border-radius: 8px;" data-bs-toggle="modal" data-bs-target="#modalTolak{{ $item->id }}">
<i class="bi bi-x-lg"></i> Tolak
</button>
</div>
{{-- Modal Khusus Penolakan --}}
<div class="modal fade text-start" id="modalTolak{{ $item->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 20px; border: none;">
<div class="modal-header bg-danger text-white border-0 py-3 px-4">
<h5 class="modal-title fw-bold"><i class="bi bi-exclamation-octagon me-2"></i>Tolak Absensi</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('presensi.konfirmasi', $item->id) }}" method="POST">
@csrf
<input type="hidden" name="action" value="tolak">
<div class="modal-body p-4">
<p class="text-muted small mb-3">Anda akan menolak kehadiran <strong>{{ $item->anggota->nama_lengkap }}</strong>. Status akan diubah menjadi Alfa.</p>
<div class="mb-3">
<label class="form-label fw-bold small text-dark">Alasan Penolakan <span class="text-danger">*</span></label>
<textarea name="keterangan" class="form-control" rows="3" placeholder="Misal: Scan dari luar area latihan / Titip absen" required style="border-radius: 10px;"></textarea>
</div>
</div>
<div class="modal-footer bg-light border-0 px-4 py-3">
<button type="button" class="btn btn-secondary px-4 fw-bold" data-bs-dismiss="modal" style="border-radius: 10px;">Batal</button>
<button type="submit" class="btn btn-danger px-4 fw-bold shadow-sm" style="border-radius: 10px;"><i class="bi bi-x-circle me-1"></i> Tolak Kehadiran</button>
</div>
</form>
</div>
</div>
</div>
@else
<span class="badge bg-light text-muted border px-3 py-2" style="border-radius: 8px;">
<i class="bi bi-lock-fill me-1"></i> Selesai
</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-5 text-muted small">Belum ada anggota yang melakukan absensi masuk.</td>
<td colspan="6" class="text-center py-5">
<img src="https://cdn-icons-png.flaticon.com/512/7486/7486747.png" width="80" class="mb-3 opacity-50" alt="No Data">
<h6 class="text-muted fw-bold mb-1">Belum ada absensi masuk</h6>
<p class="text-muted small">Anggota yang men-scan barcode akan muncul di sini.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{{-- TABEL DAFTAR ANGGOTA BELUM ABSEN --}}
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm" style="border-radius: 20px;">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="text-start">
<h5 class="card-title mb-1 fw-bold text-dark">
Belum Melakukan Presensi
@if(isset($anggota_belum_absen) && $anggota_belum_absen->count() > 0)
<span class="badge bg-danger ms-2 rounded-pill">{{ $anggota_belum_absen->count() }} Orang</span>
@endif
</h5>
<p class="text-muted small mb-0">Daftar anggota aktif di kolat ini yang belum tercatat kehadirannya.</p>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle text-start main-table">
<thead style="background-color: #fff5f5; border-bottom: 2px solid #ffe3e3;">
<tr>
<th width="5%" class="text-center" style="color: #dc3545;">No</th>
<th width="45%" style="color: #dc3545;">Identitas Anggota</th>
<th width="30%" style="color: #dc3545;">No. HP (WhatsApp)</th>
<th width="20%" class="text-center" style="color: #dc3545;">Aksi Cepat</th>
</tr>
</thead>
<tbody>
@if(isset($anggota_belum_absen))
@forelse ($anggota_belum_absen as $no => $belum)
<tr>
<td class="text-muted small text-center">{{ $no + 1 }}</td>
<td>
<div class="fw-bold text-dark">{{ $belum->nama_lengkap }}</div>
<small class="text-muted"><i class="bi bi-person-badge me-1"></i>{{ $belum->no_induk }}</small>
</td>
<td>
@if($belum->no_hp)
<a href="https://wa.me/{{ preg_replace('/^0/', '62', $belum->no_hp) }}" target="_blank" class="text-decoration-none text-success small fw-bold btn btn-sm btn-light border" style="border-radius: 8px;">
<i class="bi bi-whatsapp me-1"></i> Hubungi
</a>
@else
<span class="text-muted small">-</span>
@endif
</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-primary fw-bold px-3 shadow-sm" style="border-radius: 8px;" data-bs-toggle="modal" data-bs-target="#modalTambahManual" onclick="document.getElementById('pilih_anggota').value = '{{ $belum->id }}'">
<i class="bi bi-pencil-square me-1"></i> Input
</button>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center py-5">
<div class="text-success fw-bold"><i class="bi bi-check-circle-fill me-2 fs-4"></i><br>Mantap! Semua anggota aktif sudah tercatat kehadirannya.</div>
</td>
</tr>
@endforelse
@endif
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- MODAL TAMBAH MANUAL (KASUS LUPA HP) --}}
{{-- MODAL QR CODE --}}
<div class="modal fade" id="modalQRCode" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content text-center p-4" style="border-radius: 20px; border: none;">
<div class="modal-header border-0 pb-0 justify-content-center position-relative">
<h5 class="modal-title fw-bold text-dark">Scan Barcode Kehadiran</h5>
<button type="button" class="btn-close position-absolute end-0 me-3" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="small text-muted mb-4">Barcode ini hanya berlaku selama jadwal latihan berlangsung.</p>
<div class="bg-white p-3 d-inline-block rounded-4 shadow-sm border">
{{-- MENGGUNAKAN ROUTE HELPER AGAR SINKRON DENGAN SCANNER --}}
{!! QrCode::size(250)->gradient(2, 132, 199, 0, 79, 177, 'diagonal')->margin(1)->generate(route('presensi.anggota.scan', $jadwal->id)) !!}
</div>
<div class="mt-4">
<h6 class="fw-bold mb-0">{{ $jadwal->judul_kegiatan }}</h6>
<span class="badge bg-danger mt-2 px-3 py-2 rounded-pill">Batas Waktu: {{ $jadwal->jam_selesai ?: 'Selesai' }} WIB</span>
</div>
</div>
</div>
</div>
</div>
{{-- MODAL TAMBAH MANUAL --}}
<div class="modal fade" id="modalTambahManual" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 20px; border: none; overflow: hidden;">
<div class="modal-header bg-primary text-white border-0 py-3 px-4">
<h5 class="modal-title fw-bold">
<i class="bi bi-person-plus-fill me-2"></i>Input Presensi Manual
</h5>
<h5 class="modal-title fw-bold"><i class="bi bi-person-plus-fill me-2"></i>Input Manual Kehadiran</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('presensi.storeManual') }}" method="POST">
@csrf
{{-- Hidden input untuk ID Jadwal --}}
<input type="hidden" name="jadwal_id" value="{{ $jadwal->id }}">
<div class="modal-body p-4 text-start">
<div class="modal-body p-4">
<div class="alert alert-info border-0 shadow-sm small" style="border-radius: 10px;">
<i class="bi bi-info-circle-fill me-2"></i> Gunakan form ini untuk anggota yang lupa membawa perangkat atau tidak bisa absen mandiri.
</div>
<div class="mb-3 text-start">
<label class="form-label fw-bold text-dark small">Pilih Nama Anggota</label>
<select name="anggota_id" class="form-select shadow-none" style="border-radius: 10px; border-color: #d0e4ff;" required>
<option value="">-- Silakan Pilih Anggota --</option>
@foreach($daftar_anggota as $anggota)
<option value="{{ $anggota->id }}">{{ $anggota->no_induk }} - {{ $anggota->nama_lengkap }}</option>
<div class="mb-3">
<label class="form-label fw-bold text-dark small">Nama Anggota <span class="text-danger">*</span></label>
<select name="anggota_id" id="pilih_anggota" class="form-select border-2 shadow-none" style="border-radius: 10px;" required>
<option value="" disabled selected>-- Pilih Anggota (Kolat {{ $jadwal->kolat->nama_kolat }}) --</option>
@if(isset($anggota_belum_absen))
@foreach ($anggota_belum_absen as $anggota)
<option value="{{ $anggota->id }}">{{ $anggota->nama_lengkap }} ({{ $anggota->no_induk }})</option>
@endforeach
@endif
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold text-dark small">Status Kehadiran <span class="text-danger">*</span></label>
<select name="status" class="form-select border-2 shadow-none" style="border-radius: 10px;" required>
<option value="Hadir">Hadir</option>
<option value="Izin">Izin</option>
<option value="Sakit">Sakit</option>
<option value="Alfa">Alfa</option>
</select>
</div>
<div class="modal-footer border-0 px-4 pb-4 d-flex justify-content-between">
<button type="button" class="btn btn-light px-4 shadow-sm" data-bs-dismiss="modal" style="border-radius: 10px;">Batal</button>
<button type="submit" class="btn btn-success px-4 shadow-sm" style="border-radius: 10px;">
<i class="bi bi-check-lg me-1"></i> Simpan & Sahkan
</button>
<div class="mb-2">
<label class="form-label fw-bold text-dark small">Keterangan <span class="text-muted fw-normal">(Opsional)</span></label>
<textarea name="keterangan" class="form-control border-2 shadow-none" rows="2" placeholder="Tuliskan alasan izin/sakit atau catatan lain..." style="border-radius: 10px;"></textarea>
</div>
</div>
<div class="modal-footer bg-light border-0 px-4 py-3">
<button type="button" class="btn btn-secondary px-4 fw-bold" data-bs-dismiss="modal" style="border-radius: 10px;">Batal</button>
<button type="submit" class="btn btn-primary px-4 fw-bold shadow-sm" style="border-radius: 10px;"><i class="bi bi-save me-1"></i> Simpan Data</button>
</div>
</form>
</div>
</div>
</div>
{{-- KUMPULAN CSS --}}
<style>
.main-table thead th { background-color: #f8faff; padding: 18px 10px; font-size: 0.75rem; color: #8c98a5; letter-spacing: 1px; text-transform: uppercase; border-bottom: 2px solid #edf2f9; }
.main-table tbody td { padding: 20px 10px; border-bottom: 1px solid #f1f4f8; color: #495057; font-size: 0.9rem; }
.bg-success-soft { background-color: #e6fffa; color: #38b2ac; }
.btn-action-edit { background-color: #f0f7ff; color: #0d6efd; border: 1px solid #d0e4ff; border-radius: 10px; transition: 0.3s; }
.btn-action-edit:hover { background-color: #0d6efd !important; color: white !important; transform: translateY(-2px); }
.main-table thead th { background-color: #f8faff; padding: 16px 12px; font-size: 0.75rem; color: #6c757d; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; border-bottom: 2px solid #edf2f9; }
.main-table tbody td { padding: 16px 12px; border-bottom: 1px solid #f1f4f8; vertical-align: middle; }
.bg-success-soft { background-color: #d1e7dd; color: #0f5132; }
.bg-info-soft { background-color: #cff4fc; color: #055160; }
.bg-warning-soft { background-color: #fff3cd; color: #664d03; }
.bg-danger-soft { background-color: #f8d7da; color: #842029; }
.shadow-xs { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); }
.form-select:focus, .form-control:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); }
</style>
@endsection

View File

@ -9,7 +9,6 @@
use App\Http\Controllers\PresensiController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
@ -51,17 +50,15 @@
// Route dashboard atau menu lainnya masukkan di sini
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
});
//anggota
Route::get('/anggota', [AnggotaController::class, 'anggota'])->name('anggota.anggota');
Route::get('/anggota/tambah', [AnggotaController::class, 'create'])->name('anggota.create');
Route::post('/anggota/simpan', [AnggotaController::class, 'store'])->name('anggota.store');
Route::get('/anggota/{id}/edit', [AnggotaController::class, 'edit'])->name('anggota.edit');
Route::put('/anggota/{id}', [AnggotaController::class, 'update'])->name('anggota.update');
// anggota
Route::get('/anggota', [AnggotaController::class, 'anggota'])->name('anggota.anggota');
Route::get('/anggota/tambah', [AnggotaController::class, 'create'])->name('anggota.create');
Route::post('/anggota/simpan', [AnggotaController::class, 'store'])->name('anggota.store');
Route::get('/anggota/{id}/edit', [AnggotaController::class, 'edit'])->name('anggota.edit');
Route::put('/anggota/{id}', [AnggotaController::class, 'update'])->name('anggota.update');
//kolat
// kolat
Route::get('/kolat', [KolatController::class, 'kolat'])->name('kolat.index');
Route::get('/kolat/tambah', [KolatController::class, 'create'])->name('kolat.create');
Route::post('/kolat/simpan', [KolatController::class, 'store'])->name('kolat.store');
@ -69,8 +66,8 @@
Route::put('/kolat/{id}', [KolatController::class, 'update'])->name('kolat.update');
Route::delete('/kolat/{id}', [KolatController::class, 'destroy'])->name('kolat.destroy');
//pelatih
//prefix diguakan untuk mengelompokkan route yang berhubungan dengan pelatih, sehingga URL nya menjadi /manajemen-pelatih/...
// pelatih
// prefix diguakan untuk mengelompokkan route yang berhubungan dengan pelatih, sehingga URL nya menjadi /manajemen-pelatih/...
Route::prefix('manajemen-pelatih')->group(function () {
Route::get('/', [PelatihController::class, 'index'])->name('pelatih.index');
Route::get('/upgrade', [PelatihController::class, 'create'])->name('pelatih.upgrade');
@ -79,8 +76,8 @@
Route::put('/{id}', [PelatihController::class, 'update'])->name('pelatih.update');
});
//jadwal
//prefix diguakan untuk mengelompokkan route yang berhubungan dengan jadwal
// jadwal
// prefix diguakan untuk mengelompokkan route yang berhubungan dengan jadwal
Route::prefix('jadwal')->group(function () {
Route::get('/', [JadwalController::class, 'index'])->name('jadwal.index');
Route::post('/jadwal/{id}/konfirmasi', [JadwalController::class, 'konfirmasiSelesai'])->name('jadwal.konfirmasi');
@ -92,14 +89,35 @@
Route::delete('/{id}/delete', [JadwalController::class, 'destroy'])->name('jadwal.destroy');
});
// --- ROUTE PRESENSI (ABSENSI) ---
Route::prefix('presensi')->name('presensi.')->group(function () {
// Rute untuk Presensi
Route::prefix('presensi')->name('presensi.')->group(function () {
// --- AKTOR: PENGURUS / PELATIH ---
// Halaman utama rekap jadwal (untuk pilih jadwal mana yang mau dicek)
Route::get('/', [PresensiController::class, 'index'])->name('index');
// Halaman daftar kehadiran (View Admin/Pelatih)
// Halaman daftar kehadiran anggota pada jadwal tertentu (tampil QR Code & List)
Route::get('/kehadiran/{jadwal_id}', [PresensiController::class, 'kehadiran'])->name('kehadiran');
// konfirmasi pelatih
// Proses konfirmasi/verifikasi pelatih klik tombol "Sah"
Route::post('/konfirmasi/{presensi_id}', [PresensiController::class, 'konfirmasi'])->name('konfirmasi');
// Proses tambah presensi manual (Kasus Lupa HP)
Route::post('/tambah-manual', [PresensiController::class, 'storeManual'])->name('storeManual');
});
// --- AKTOR: ANGGOTA ---
// Halaman daftar jadwal hari ini di sisi anggota (Tampil tombol "Scan Absen")
Route::get('/anggota', [PresensiController::class, 'indexAnggota'])->name('anggota.index');
Route::get('/riwayat', [PresensiController::class, 'riwayatAnggota'])->name('anggota.riwayat');
// Halaman Kamera Scanner (Tempat anggota men-scan QR Code pelatih)
Route::get('/scan/{jadwal_id}', [PresensiController::class, 'scan'])->name('anggota.scan');
// Proses penyimpanan absen mandiri setelah sukses scan
Route::post('/store-mandiri', [PresensiController::class, 'storeMandiri'])->name('storeMandiri');
});
});