TKK_E32230908/app/Http/Controllers/Admin/AnggotaController.php

334 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Anggota;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\DB;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use App\Mail\StatusAnggotaDiperbarui;
use PhpMqtt\Client\MqttClient;
use Barryvdh\DomPDF\Facade\Pdf;
class AnggotaController extends Controller
{
/* ================= MQTT HELPER ================= */
private function mqttPublish($topic, $message)
{
try {
// Menggunakan broker lokal (127.0.0.1) bawaan Laragon / Mosquitto
$mqtt = new MqttClient('127.0.0.1', 1883, 'laravel-admin-' . uniqid());
$mqtt->connect();
$payload = is_array($message) ? json_encode($message) : (string)$message;
$mqtt->publish($topic, $payload, 0);
$mqtt->disconnect();
} catch (\Exception $e) {
\Log::error("MQTT Error: " . $e->getMessage());
}
}
/* ================= MANAJEMEN ANGGOTA ================= */
public function index(Request $request)
{
// Ambil input dengan nama variabel yang jelas agar tidak bentrok dengan scope Eloquent ($query)
$searchKeyword = $request->input('search');
$statusFilter = $request->input('status');
// Bangun query dasar
$query = Anggota::query();
// Kondisi jika ada pencarian (Bungkus dalam sub-query OR agar tidak merusak filter status)
if (!empty($searchKeyword)) {
$query->where(function ($q) use ($searchKeyword) {
$q->where('nama', 'like', "%{$searchKeyword}%")
->orWhere('email', 'like', "%{$searchKeyword}%")
->orWhere('nip_nim', 'like', "%{$searchKeyword}%") // Sinkronisasi pencarian NIP/NIM
->orWhere('rfid_uid', 'like', "%{$searchKeyword}%"); // Sinkronisasi pencarian RFID UID (dari database: rfid_uid)
});
}
// Kondisi jika ada filter status
if (!empty($statusFilter)) {
$query->where('status', $statusFilter);
}
// Eksekusi pagination dengan aman
$members = $query->orderByDesc('created_at')->paginate(10)->withQueryString();
// PROTEKSI EXTRA: Jika hasil kosong, pastikan tetap mengirim object kosong yang aman, bukan null
if (!$members) {
$members = new \Illuminate\Pagination\LengthAwarePaginator([], 0, 10);
}
return view('admin.anggota.index', compact('members'));
}
public function create()
{
return view('admin.anggota.create');
}
public function store(Request $request)
{
// 🌟 SINKRONISASI: Menambahkan nip_nim dan gender ke formulir tambah manual oleh admin
$validatedData = $request->validate([
'nama' => 'required|string|max:100',
'gender' => 'required|in:Laki-laki,Perempuan',
'nip_nim' => 'required|string|max:255|unique:anggota,nip_nim',
'email' => 'required|email|unique:anggota,email',
'alamat' => 'required|string|max:255',
'no_hp' => 'nullable|string|max:20',
]);
$validatedData['status'] = 'disetujui';
$member = Anggota::create($validatedData);
// MQTT: Memicu reload halaman index admin karena ada entri data fisik baru
$this->mqttPublish('pendaftaran/refresh/admin', [
'action' => 'created',
'message' => 'Anggota baru telah ditambahkan oleh Admin'
]);
return redirect()->route('admin.anggota.index')
->with('success', 'Anggota baru berhasil didaftarkan dan disetujui!');
}
public function edit($id)
{
$member = Anggota::findOrFail($id);
return view('admin.anggota.edit', compact('member'));
}
public function update(Request $request, $id)
{
$anggota = Anggota::findOrFail($id);
// 1. Validasi Input Form
$validatedData = $request->validate([
'nama' => 'required|string|max:100',
'gender' => 'required|in:Laki-laki,Perempuan',
'nik_nip' => 'required|string|max:255|unique:anggota,nik_nip,' . $anggota->id,
'email' => 'required|email|unique:anggota,email,' . $anggota->id,
'no_hp' => 'nullable|string|max:20',
'alamat' => 'required|string|max:255', // Alamat lengkap gabungan dari JS Blade
'provinsi' => 'nullable|string',
'kota' => 'nullable|string',
'kecamatan' => 'nullable|string',
'desa' => 'nullable|string',
]);
try {
// 2. Simpan Perubahan Utama
$anggota->nama = $validatedData['nama'];
$anggota->gender = $validatedData['gender'];
$anggota->nik_nip = $validatedData['nik_nip'];
$anggota->email = $validatedData['email'];
$anggota->no_hp = $validatedData['no_hp'];
$anggota->alamat = $validatedData['alamat']; // 🌟 Ini sudah mencakup data wilayah lengkap
// 🌟 FIXED: 4 baris pengisian wilayah dihapus karena kolomnya tidak ada di tabel database Anda
// Data wilayah Anda sudah aman dan rapi di dalam kolom $anggota->alamat
$anggota->save();
// 3. MQTT Terintegrasi
try {
if (method_exists($this, 'mqttPublish')) {
$this->mqttPublish('pendaftaran/refresh/admin', [
'action' => 'updated',
'member_id' => $anggota->id
]);
if ($anggota->user_id) {
$this->mqttPublish('pendaftaran/status/' . $anggota->user_id, [
'status' => $anggota->status,
'message' => 'Data pendaftaran Anda telah diperbarui oleh Admin menjadi: ' . $anggota->status
]);
}
}
} catch (\Exception $mqttError) {
\Log::warning('MQTT Gagal: ' . $mqttError->getMessage());
}
// 4. Kembali Ke Index Sembari Membawa Toast/Alert Sukses
return redirect()->route('admin.anggota.index')
->with('success', 'Data anggota berhasil diperbarui! 🎉');
} catch (\Exception $e) {
return redirect()->back()
->withInput()
->withErrors(['error_server' => 'Terjadi kesalahan pada server: ' . $e->getMessage()]);
}
}
public function destroy($id)
{
$member = Anggota::findOrFail($id);
$savedUserId = $member->user_id;
$member->delete();
// MQTT: Sinyal refresh untuk admin karena ada baris data yang hilang
$this->mqttPublish('pendaftaran/refresh/admin', [
'action' => 'deleted'
]);
// MQTT: Paksa pengguna logout atau refresh jika akunnya dihapus oleh admin
if ($savedUserId) {
$this->mqttPublish('pendaftaran/status/' . $savedUserId, [
'status' => 'deleted',
'message' => 'Data pendaftaran Anda telah dihapus oleh pihak admin.'
]);
}
return redirect()->route('admin.anggota.index')
->with('success', 'Anggota berhasil dihapus!');
}
public function cetak($id)
{
$member = Anggota::findOrFail($id);
$qrCode = base64_encode(QrCode::format('svg')->size(120)->generate($member->nama));
return view('admin.anggota.cetak', compact('member', 'qrCode'));
}
/* ================= INTEGRASI IOT & STATE FLOW ================= */
public function approve($id)
{
$member = Anggota::findOrFail($id);
// 1. Cek kesiapan IoT (Jangan timpa jika sedang ada transaksi berjalan)
$current = DB::table('iot_modes')->where('id', 1)->first();
if ($current && $current->mode !== 'standby') {
return back()->with('warning', 'Sistem sedang sibuk. Pastikan alat dalam posisi standby.');
}
// 2. Update status anggota di database menjadi disetujui
$member->update(['status' => 'disetujui']);
// 3. Kunci State IoT ke mode 'admin' untuk User ID target
DB::table('iot_modes')->where('id', 1)->update([
'mode' => 'admin',
'user_id' => $member->id,
'updated_at' => now()
]);
// 4. Perintah utama ke perangkat keras ESP32
$this->mqttPublish('rfid/mode/set', 'admin');
$this->mqttPublish('rfid/admin/request_scan', [
'status' => 'waiting_scan',
'nama' => $member->nama,
'id' => $member->id
]);
// 5. SINKRONISASI STATE ASINKRON: Kirim aksi 'approved' ke JavaScript panel admin.
$this->mqttPublish('pendaftaran/refresh/admin', [
'action' => 'approved',
'member_id' => $member->id
]);
// 6. Notifikasi real-time ke aplikasi/halaman milik user
if ($member->user_id) {
$this->mqttPublish('pendaftaran/status/' . $member->user_id, [
'status' => 'disetujui',
'message' => "Pendaftaran Anda disetujui! Alat RFID sekarang menunggu scan kartu Anda."
]);
}
// 7. Pengiriman Notifikasi via Email
try {
Mail::to($member->email)->send(new StatusAnggotaDiperbarui($member));
} catch (\Exception $e) {
\Log::error("Email Error: " . $e->getMessage());
}
return back()->with('success', "Anggota {$member->nama} disetujui. Perangkat RFID Nawasena siap menerima pemindaian kartu.");
}
public function reject($id)
{
$member = Anggota::findOrFail($id);
$member->update(['status' => 'ditolak']);
// MQTT: Beritahu halaman admin untuk memperbarui status penolakan
$this->mqttPublish('pendaftaran/refresh/admin', [
'action' => 'rejected',
'member_id' => $member->id
]);
// 🌟 SINKRONISASI: Sinyal 'ditolak' dikirim ke user agar di view user langsung muncul tombol pendaftaran ulang (sinkron dengan UserMemberController)
if ($member->user_id) {
$this->mqttPublish('pendaftaran/status/' . $member->user_id, [
'status' => 'ditolak',
'message' => "Pendaftaran Anda ditolak oleh admin. Silakan periksa kembali data Anda dan lakukan daftar ulang jika diperlukan."
]);
}
try {
Mail::to($member->email)->send(new StatusAnggotaDiperbarui($member));
} catch (\Exception $e) {
\Log::error("Email Error: " . $e->getMessage());
}
return back()->with('warning', "Anggota {$member->nama} telah ditolak.");
}
public function receiveRfidUid(Request $request)
{
$uid = $request->input('uid');
$anggotaId = $request->input('anggota_id');
$member = Anggota::find($anggotaId);
if ($member) {
// 1. Simpan string UID fisik kartu ke data anggota
$member->update(['rfid_uid' => $uid]);
// 2. Kembalikan state mesin IoT ke mode 'standby' agar siap digunakan kembali
DB::table('iot_modes')->where('id', 1)->update([
'mode' => 'standby',
'user_id' => null,
'updated_at' => now()
]);
$this->mqttPublish('pendaftaran/refresh/admin', [
'action' => 'uid_saved',
'member_id' => $member->id,
'rfid_uid' => $uid
]);
// 4. Perintahkan sistem user untuk mengalihkan view ke halaman dashboard utama library
if ($member->user_id) {
$this->mqttPublish('pendaftaran/status/' . $member->user_id, [
'status' => 'uid_ready',
'message' => 'Kartu RFID Anda berhasil diregistrasikan!'
]);
}
return response()->json(['status' => 'success', 'message' => 'UID Berhasil disimpan dan disinkronkan ke Web-Admin via MQTT.']);
}
return response()->json(['status' => 'error', 'message' => 'Data Anggota tidak ditemukan.'], 404);
}
public function print($id)
{
$member = Anggota::findOrFail($id);
// Load view kartu yang sudah rapi
$pdf = Pdf::loadView('admin.anggota.print', compact('member'));
// Atur ukuran kertas kustom atau default (misal A4 atau ukuran kartu)
$pdf->setPaper('a4', 'portrait');
// Menggunakan STREAM agar PDF terbuka langsung di PDF Viewer browser (persis seperti contoh gambar)
return $pdf->stream('Kartu Anggota ' . $member->nama . '.pdf');
}
}