334 lines
12 KiB
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');
|
|
}
|
|
} |