penialain pelatih.

This commit is contained in:
SitiAfiah 2026-06-07 19:58:21 +07:00
parent 9c68d4b734
commit 6307484ffe
5 changed files with 102 additions and 257 deletions

View File

@ -39,7 +39,7 @@ class MemberDashboardController extends Controller
// return view('anggota.dashboard', compact('anggota', 'jadwal', 'tagihan_spp', 'persentase_hadir'));
// }
public function index()
public function index()
{
// 1. Ambil data profil anggota yang sedang login
$user = Auth::user();
@ -49,21 +49,22 @@ public function index()
return redirect('/')->with('error', 'Data anggota tidak ditemukan.');
}
// 2. Ambil Jadwal Latihan (Hanya yang sesuai dengan Kolat si anggota)
// 2. Ambil Jadwal Latihan
// PERBAIKAN: Gunakan today() atau now()->toDateString() alih-alih now().
// now() membawa jam (misal 19:42), sehingga jadwal hari ini yang jamnya sudah lewat bisa tidak terbaca.
$jadwal = Jadwal::where('kolat_id', $anggota->kolat_id)
->where('tanggal', '>=', now())
->where('tanggal', '>=', today())
->orderBy('tanggal', 'asc')
->take(5)
->get();
// 3. Ambil Tagihan SPP
// PERBAIKAN: Menggunakan relasi spp() yang sudah Anda buat di model Anggota.
// Asumsi di tabel SPP Anda terdapat kolom 'status' (misal: 'Pending' atau 'Belum Lunas')
$tagihan_spp = $anggota->spp()->where('status', 'Pending')->get();
// PERBAIKAN PENTING: Tadi kita sepakat status awalnya adalah 'belum_bayar', bukan 'Pending'
$tagihan_spp = $anggota->spp()
->where('status', 'belum_bayar')
->get();
// 4. Hitung Ringkasan Kehadiran
// Contoh mengambil total kehadiran dari relasi riwayatPresensi()
// Ini asumsikan Anda memiliki kolom 'status_hadir' (misal: 'Hadir') di tabel Presensi
$total_latihan = $anggota->riwayatPresensi()->count();
$total_hadir = $anggota->riwayatPresensi()->where('status', 'Hadir')->count();
@ -74,4 +75,4 @@ public function index()
return view('anggota.dashboard', compact('anggota', 'jadwal', 'tagihan_spp', 'persentase_hadir'));
}
}
};

View File

@ -4,9 +4,10 @@
use App\Models\Anggota;
use App\Models\Penilaian;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PenilaianController extends Controller
{
@ -24,7 +25,25 @@ public function index()
public function create($id)
{
// BYPASS: Hapus pengecekan auth() agar tidak mental ke login
// 1. Tentukan ID Anggota Penilai (Lebih aman pakai operator ?? untuk mencegah error jika relasi anggota kosong)
$anggota_penilai_id = auth()->check() ? (auth()->user()->anggota->id ?? 1) : 1;
// 2. Tentukan rentang waktu bulan berjalan
$awalBulanIni = Carbon::now()->startOfMonth();
$akhirBulanIni = Carbon::now()->endOfMonth();
// 3. PENJAGA AWAL: Cek sebelum halaman form kuis terbuka
$sudahMenilai = Penilaian::where('anggota_id', $anggota_penilai_id)
->where('pelatih_id', $id)
->whereBetween('bulan_evaluasi', [$awalBulanIni, $akhirBulanIni])
->exists();
if ($sudahMenilai) {
// Jika sudah pernah menilai bulan ini, langsung tendang balik ke halaman daftar pelatih
return redirect()->route('penilaian.anggota_index')
->with('error', 'Anda sudah memberikan penilaian untuk pelatih ini pada bulan ini!');
}
$pelatih = Anggota::with('kolat')->findOrFail($id);
return view('penilaian.create', compact('pelatih'));
}
@ -42,10 +61,25 @@ public function store(Request $request)
'kritik_saran' => 'nullable|string'
]);
$anggota_penilai_id = auth()->check() ? (auth()->user()->anggota->id ?? 1) : 1;
$awalBulanIni = Carbon::now()->startOfMonth();
$akhirBulanIni = Carbon::now()->endOfMonth();
// 4. PENJAGA KEDUA: Memastikan keamanan data saat disubmit
$sudahMenilai = Penilaian::where('anggota_id', $anggota_penilai_id)
->where('pelatih_id', $request->pelatih_id)
->whereBetween('bulan_evaluasi', [$awalBulanIni, $akhirBulanIni])
->exists();
if ($sudahMenilai) {
return redirect()->route('penilaian.anggota_index')
->with('error', 'Gagal! Anda sudah menilai pelatih ini bulan ini.');
}
Penilaian::create([
'pelatih_id' => $request->pelatih_id,
'anggota_id' => 1, // TESTING: Isi manual ID Anggota penilai (misal ID 1)
'bulan_evaluasi' => Carbon::now()->startOfMonth(),
'anggota_id' => $anggota_penilai_id,
'bulan_evaluasi' => $awalBulanIni,
'metode_pelatihan' => $request->metode_pelatihan,
'komunikasi' => $request->komunikasi,
'sikap_kepribadian' => $request->sikap_kepribadian,
@ -55,35 +89,36 @@ public function store(Request $request)
'kritik_saran' => $request->kritik_saran,
]);
return redirect()->route('penilaian.index')->with('success', 'Penilaian berhasil disimpan!');
return redirect()->route('penilaian.anggota_index')->with('success', 'Penilaian pelatih berhasil disimpan!');
}
public function show($id)
{
$pelatih = Anggota::with('kolat')->findOrFail($id);
// FUNGSI INI KEMBALI DITAMBAHKAN KARENA SEMPAT TERHAPUS
public function show($id)
{
$pelatih = Anggota::with('kolat')->findOrFail($id);
// Hitung rata-rata tiap kriteria bulan ini
$rekap = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->selectRaw('
AVG(metode_pelatihan) as avg_metode,
AVG(komunikasi) as avg_komunikasi,
AVG(sikap_kepribadian) as avg_sikap,
AVG(kepemimpinan) as avg_kepemimpinan,
AVG(konsistensi_komitmen) as avg_komitmen,
AVG(kedekatan_interpersonal) as avg_interpersonal
')->first();
// Hitung rata-rata tiap kriteria bulan ini
$rekap = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->selectRaw('
AVG(metode_pelatihan) as avg_metode,
AVG(komunikasi) as avg_komunikasi,
AVG(sikap_kepribadian) as avg_sikap,
AVG(kepemimpinan) as avg_kepemimpinan,
AVG(konsistensi_komitmen) as avg_komitmen,
AVG(kedekatan_interpersonal) as avg_interpersonal
')->first();
// Ambil ulasan/kritik saran (Pluck agar anonim)
$ulasan = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->whereNotNull('kritik_saran')
->pluck('kritik_saran');
// Ambil ulasan/kritik saran (Pluck agar anonim)
$ulasan = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->whereNotNull('kritik_saran')
->pluck('kritik_saran');
return view('penilaian.rekap', compact('pelatih', 'rekap', 'ulasan'));
}
return view('penilaian.rekap', compact('pelatih', 'rekap', 'ulasan'));
}
public function exportPdf($id)
public function exportPdf($id)
{
// 1. Ambil data pelatih
$pelatih = Anggota::with('kolat')->findOrFail($id);
@ -124,5 +159,4 @@ public function indexAnggota()
return view('penilaian.anggota_index', compact('data_pelatih'));
}
}

View File

@ -137,7 +137,7 @@ class="rounded-circle shadow-sm border border-3 border-white" width="100" alt="F
<div class="card card-custom h-100 border-0">
<div class="card-header bg-white border-0 pt-4 pb-0 px-4 d-flex align-items-center justify-content-between">
<h5 class="fw-bold text-dark mb-0">Jadwal Latihan Terdekat</h5>
<a href="#" class="btn btn-sm btn-light text-primary fw-semibold rounded-pill px-3">Lihat Semua</a>
<a href="{{ route('presensi.anggota.index') }}" class="btn btn-sm btn-light text-primary fw-semibold rounded-pill px-3">Lihat Semua</a>
</div>
<div class="card-body px-4 pt-3 pb-4 table-responsive">
<table class="table table-modern table-borderless align-middle mb-0">

View File

@ -130,4 +130,29 @@
font-size: 2.5rem;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
@if(session('error'))
Swal.fire({
title: 'Tidak Bisa Menilai!',
text: "{{ session('error') }}",
icon: 'warning',
confirmButtonColor: '#f59e0b',
confirmButtonText: 'Mengerti'
});
@endif
@if(session('success'))
Swal.fire({
title: 'Berhasil!',
text: "{{ session('success') }}",
icon: 'success',
confirmButtonColor: '#10b981',
confirmButtonText: 'OK'
});
@endif
});
</script>
@endsection

View File

@ -1,218 +1,3 @@
{{-- @extends('layout.app')
@section('content')
<div class="container-fluid py-4">
<!-- Menggunakan col-12 agar card membentang penuh (Full Width) -->
<div class="row">
<div class="col-12">
<!-- Tombol Kembali -->
<a href="{{ route('penilaian.anggota_index') }}" class="text-decoration-none text-muted mb-3 d-inline-block">
<i class="bi bi-arrow-left me-1"></i> Kembali ke Daftar Pelatih
</a>
<div class="card border-0 shadow-sm form-penilaian-card">
<!-- Header Card Friendly -->
<div class="card-header text-center border-0 pt-5 pb-4 bg-white" style="border-radius: 20px 20px 0 0;">
<div class="pelatih-avatar mb-3 mx-auto">
@if($pelatih->foto_profil)
<img src="{{ asset('storage/' . $pelatih->foto_profil) }}" alt="Foto">
@else
<i class="bi bi-person-fill"></i>
@endif
</div>
<h4 class="fw-bold mb-1">Nilai Kak <span style="color: var(--accent-color);">{{ $pelatih->nama_lengkap }}</span></h4>
<span class="badge bg-light text-dark px-3 py-2 rounded-pill mt-2">
<i class="bi bi-geo-alt-fill text-danger me-1"></i> Kolat: {{ $pelatih->kolat->nama_kolat ?? '-' }}
</span>
<p class="text-muted small mt-3 px-3 mx-auto" style="max-width: 600px;">
Yuk, berikan penilaian jujurmu untuk evaluasi bulan {{ now()->translatedFormat('F Y') }}. Tenang saja, namamu akan dirahasiakan! 🤫
</p>
</div>
<div class="card-body p-4 p-lg-5 pt-2">
<form action="{{ route('penilaian.store') }}" method="POST">
@csrf
<input type="hidden" name="pelatih_id" value="{{ $pelatih->id }}">
@php
$kriterias = [
'metode_pelatihan' => 'Cara Menyampaikan Materi',
'komunikasi' => 'Cara Berkomunikasi dengan Anggota',
'sikap_kepribadian' => 'Sikap & Kedisiplinan Pelatih',
'kepemimpinan' => 'Jiwa Kepemimpinan di Lapangan',
'konsistensi_komitmen' => 'Kehadiran & Semangat Melatih',
'kedekatan_interpersonal' => 'Kedekatan & Kepedulian'
];
// Emoji untuk masing-masing nilai 1-5
$emojis = [
1 => '😞',
2 => '😐',
3 => '🙂',
4 => '😄',
5 => '🤩'
];
@endphp
<!-- Dibikin 2 Kolom untuk Layar Besar agar tidak terlalu kosong saat Full Width -->
<div class="row g-4">
@foreach($kriterias as $key => $label)
<div class="col-md-6">
<div class="kriteria-box h-100 p-4 rounded-4 bg-light">
<label class="fw-bold d-block mb-4 text-dark text-center fs-6">{{ $label }}</label>
<div class="d-flex justify-content-between gap-2 gap-md-3">
@foreach(range(1, 5) as $score)
<div class="rating-wrapper flex-fill">
<!-- Input Radio disembunyikan -->
<input class="rating-input" type="radio" name="{{ $key }}" id="{{ $key.$score }}" value="{{ $score }}" required>
<!-- Label dibuat seperti tombol kotak -->
<label class="rating-label w-100" for="{{ $key.$score }}">
<span class="emoji-rate">{{ $emojis[$score] }}</span>
<span class="score-text">{{ $score }}</span>
</label>
</div>
@endforeach
</div>
<!-- Keterangan text di bawah angka (Hanya tampil 1 dan 5) -->
<div class="d-flex justify-content-between mt-3 px-2 text-muted fw-medium" style="font-size: 0.75rem;">
<span>Kurang</span>
<span>Sangat Baik</span>
</div>
</div>
</div>
@endforeach
</div>
<div class="mb-4 mt-5">
<label class="fw-bold mb-3 text-dark fs-5">Pesan, Kritik & Saran 📝</label>
<textarea name="kritik_saran" class="form-control bg-light border-0" rows="5" placeholder="Tulis masukan, pujian, atau saranmu di sini (boleh dikosongi)..." style="border-radius: 15px; padding: 20px; resize: none;"></textarea>
</div>
<div class="d-grid mt-5">
<button type="submit" class="btn btn-submit-nilai py-3 fw-bold fs-5">
<i class="bi bi-send-fill me-2"></i> Kirim Penilaian Rahasia
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
/* Styling Card Utama */
.form-penilaian-card {
border-radius: 20px;
overflow: hidden;
}
/* Foto Profil Kecil di Atas */
.pelatih-avatar {
width: 100px;
height: 100px;
background: #f8f9fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 3.5rem;
color: var(--dark-blue, #06163a);
border: 5px solid #fff;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.pelatih-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
/* Menyembunyikan radio button asli */
.rating-input {
display: none;
}
/* Kotak Pilihan Angka & Emoji */
.rating-label {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 15px 5px;
background: #ffffff;
border: 2px solid #e9ecef;
border-radius: 15px;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
color: #6c757d;
}
.emoji-rate {
font-size: 1.8rem;
margin-bottom: 5px;
filter: grayscale(100%); /* Bikin abu-abu dulu kalau belum dipilih */
opacity: 0.6;
transition: all 0.2s ease;
}
.score-text {
font-weight: 700;
font-size: 1.1rem;
}
/* Efek Saat Di-Hover (Sentuh) */
.rating-label:hover {
background: #f1f5f9;
border-color: #cbd5e1;
}
.rating-label:hover .emoji-rate {
filter: grayscale(0%);
opacity: 1;
transform: scale(1.1);
}
/* Efek Saat Di-Klik (Dipilih) */
.rating-input:checked + .rating-label {
background: var(--accent-color, #feb900);
border-color: var(--accent-color, #feb900);
color: var(--dark-blue, #06163a);
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(254, 185, 0, 0.3);
}
.rating-input:checked + .rating-label .emoji-rate {
filter: grayscale(0%);
opacity: 1;
transform: scale(1.2);
}
/* Input Textarea Focus */
textarea.form-control:focus {
background: #ffffff !important;
border: 2px solid var(--accent-color, #feb900) !important;
box-shadow: 0 0 0 0.25rem rgba(254, 185, 0, 0.1) !important;
}
/* Tombol Submit */
.btn-submit-nilai {
background: var(--dark-blue, #06163a);
color: #fff;
border-radius: 15px;
border: none;
transition: all 0.3s;
}
.btn-submit-nilai:hover {
background: var(--accent-color, #feb900);
color: var(--dark-blue, #06163a);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(254, 185, 0, 0.4);
}
</style>
@endsection --}}
@extends('layout.app')
@section('content')
@ -252,8 +37,8 @@
</div>
@endif
<div class="text-start">
<div class="fw-bold text-dark lh-1" style="font-size: 0.9rem;">Kak {{ $pelatih->nama_lengkap }}</div>
<div class="text-muted" style="font-size: 0.75rem;">Kolat {{ $pelatih->kolat->nama_kolat ?? '-' }}</div>
<div class="fw-bold text-dark lh-1" style="font-size: 0.9rem;"> {{ $pelatih->nama_lengkap }}</div>
<div class="text-muted" style="font-size: 0.75rem;">{{ $pelatih->kolat->nama_kolat ?? '-' }}</div>
</div>
</div>
@ -263,7 +48,7 @@
@php
$kriterias = [
'metode_pelatihan' => 'Bagaimana cara Kakak Pelatih menyampaikan materi hari ini?',
'metode_pelatihan' => 'Bagaimana cara Pelatih menyampaikan materi hari ini?',
'komunikasi' => 'Seberapa asik dan jelas komunikasinya dengan anggota?',
'sikap_kepribadian' => 'Bagaimana sikap dan kedisiplinan pelatih di lapangan?',
'kepemimpinan' => 'Seberapa baik jiwa kepemimpinan pelatih saat memandu latihan?',
@ -315,7 +100,7 @@
<div class="mb-4">
<span class="emoji-rate d-inline-block mb-3" style="font-size: 4rem; filter: none; opacity: 1;">📝</span>
<h3 class="fw-bolder text-dark mb-2">Satu langkah lagi!</h3>
<p class="text-muted">Ada pesan, kritik, saran, atau pujian untuk Kak {{ $pelatih->nama_lengkap }}? (Boleh dikosongkan kok)</p>
<p class="text-muted">Ada pesan, kritik, saran, atau pujian untuk {{ $pelatih->nama_lengkap }}? (Boleh dikosongkan kok)</p>
</div>
<textarea name="kritik_saran" class="form-control bg-light border-0 mb-4 shadow-inner" rows="5" placeholder="Ketik pesan rahasiamu di sini..." style="border-radius: 20px; padding: 25px; resize: none; font-size: 1.1rem;"></textarea>