fix send nama ruang

This commit is contained in:
rendygaafk 2025-05-19 03:16:43 +07:00
parent b7cec503d1
commit 822b613cb1
5 changed files with 95 additions and 54 deletions

View File

@ -46,9 +46,9 @@ public function history()
->when(request('date'), function($query, $date) { ->when(request('date'), function($query, $date) {
return $query->whereDate('sent_at', $date); return $query->whereDate('sent_at', $date);
}) })
->when(request('ruangan'), function($query, $ruanganId) { ->when(request('ruangan'), function($query, $ruanganName) {
return $query->whereHas('ruangans', function($q) use ($ruanganId) { return $query->whereHas('ruangans', function($q) use ($ruanganName) {
$q->where('ruangan.id', $ruanganId); $q->where('nama_ruangan', $ruanganName); // Filter by name
}); });
}) })
->orderBy('sent_at', 'desc') ->orderBy('sent_at', 'desc')
@ -74,6 +74,25 @@ public function store(StoreAnnouncementRequest $request)
DB::beginTransaction(); DB::beginTransaction();
try { try {
// 1. Konversi nama ruangan ke ID
$ruanganNames = $request->ruangans;
$ruanganRecords = Ruangan::whereIn('nama_ruangan', $ruanganNames)->get();
// Validasi semua nama ruangan ditemukan
if ($ruanganRecords->count() !== count($ruanganNames)) {
$missingRooms = array_diff(
$ruanganNames,
$ruanganRecords->pluck('nama_ruangan')->toArray()
);
throw new \Exception(
'Ruangan tidak ditemukan: ' . implode(', ', $missingRooms)
);
}
$ruanganIds = $ruanganRecords->pluck('id')->toArray();
// 2. Persiapkan data pengumuman
$announcementData = [ $announcementData = [
'mode' => $request->mode, 'mode' => $request->mode,
'sent_at' => now(), 'sent_at' => now(),
@ -83,47 +102,57 @@ public function store(StoreAnnouncementRequest $request)
$announcementData['message'] = $request->message; $announcementData['message'] = $request->message;
} }
// 3. Simpan pengumuman dan relasinya
$announcement = Announcement::create($announcementData); $announcement = Announcement::create($announcementData);
$announcement->ruangans()->sync($request->ruangans); $announcement->ruangans()->sync($ruanganIds);
// 4. Kirim perintah ke MQTT
$mqttService = app(MqttService::class); $mqttService = app(MqttService::class);
$success = false;
if ($request->mode === self::MODE_TTS) { if ($request->mode === self::MODE_TTS) {
$success = $mqttService->sendTTSAnnouncement( $success = $mqttService->sendTTSAnnouncement(
$request->ruangans, $ruanganNames, // Kirim nama ruangan (bukan ID)
$request->message $request->message
); );
} else { } else {
$success = $mqttService->sendRelayControl( $success = $mqttService->sendRelayControl(
'activate', // Default action for announcements self::ACTION_ACTIVATE,
$request->ruangans, $ruanganNames, // Kirim nama ruangan (bukan ID)
$request->mode $request->mode
); );
} }
if (!$success) { if (!$success) {
throw new \Exception('Gagal mengirim perintah ke perangkat'); throw new \Exception('Gagal mengirim perintah ke perangkat MQTT');
} }
DB::commit(); DB::commit();
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'message' => 'Pengumuman berhasil dikirim' 'message' => 'Pengumuman berhasil dikirim',
'data' => [
'announcement_id' => $announcement->id,
'ruangan' => $ruanganNames
]
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack(); DB::rollBack();
Log::error('Failed to store announcement: ' . $e->getMessage()); Log::error('Failed to store announcement', [
'error' => $e->getMessage(),
'request' => $request->all()
]);
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'message' => 'Gagal mengirim pengumuman: ' . $e->getMessage() 'message' => 'Gagal mengirim pengumuman: ' . $e->getMessage(),
'error_details' => $e->getMessage()
], 500); ], 500);
} }
} }
public function details($id) public function details($id)
{ {
try { try {
@ -196,7 +225,7 @@ public function controlRelay(Request $request)
{ {
$validated = $request->validate([ $validated = $request->validate([
'ruangans' => 'required|array|min:1', 'ruangans' => 'required|array|min:1',
'ruangans.*' => 'exists:ruangan,id', 'ruangans.*' => 'string|exists:ruangan,nama_ruangan', // Pastikan validasi string
'action' => 'required|in:'.self::ACTION_ACTIVATE.','.self::ACTION_DEACTIVATE, 'action' => 'required|in:'.self::ACTION_ACTIVATE.','.self::ACTION_DEACTIVATE,
'mode' => 'required|in:'.self::MODE_MANUAL.','.self::MODE_TTS 'mode' => 'required|in:'.self::MODE_MANUAL.','.self::MODE_TTS
]); ]);
@ -204,13 +233,15 @@ public function controlRelay(Request $request)
DB::beginTransaction(); DB::beginTransaction();
try { try {
$state = $validated['action'] === self::ACTION_ACTIVATE ? 'on' : 'off'; // Dapatkan ID untuk operasi database
$ruanganIds = $validated['ruangans']; $ruanganIds = Ruangan::whereIn('nama_ruangan', $validated['ruangans'])
->pluck('id')
->toArray();
$mqttService = app(MqttService::class); $mqttService = app(MqttService::class);
$success = $mqttService->sendRelayControl( $success = $mqttService->sendRelayControl(
$validated['action'], $validated['action'],
$ruanganIds, $validated['ruangans'], // Kirim NAMA ruangan ke MQTT
$validated['mode'] $validated['mode']
); );
@ -218,10 +249,11 @@ public function controlRelay(Request $request)
throw new \Exception('Gagal mengirim perintah ke perangkat'); throw new \Exception('Gagal mengirim perintah ke perangkat');
} }
// Update database // Update database menggunakan ID
$state = $validated['action'] === self::ACTION_ACTIVATE ? 'on' : 'off';
Ruangan::whereIn('id', $ruanganIds)->update(['relay_state' => $state]); Ruangan::whereIn('id', $ruanganIds)->update(['relay_state' => $state]);
// Log manual activations as announcements // Log manual activations
if ($validated['action'] === self::ACTION_ACTIVATE && $validated['mode'] === self::MODE_MANUAL) { if ($validated['action'] === self::ACTION_ACTIVATE && $validated['mode'] === self::MODE_MANUAL) {
$announcement = Announcement::create([ $announcement = Announcement::create([
'mode' => self::MODE_MANUAL, 'mode' => self::MODE_MANUAL,
@ -229,14 +261,15 @@ public function controlRelay(Request $request)
'sent_at' => now() 'sent_at' => now()
]); ]);
$announcement->ruangans()->sync($ruanganIds); $announcement->ruangans()->sync($ruanganIds); // Gunakan ID untuk relasi
} }
DB::commit(); DB::commit();
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'message' => 'Relay berhasil dikontrol' 'message' => 'Relay berhasil dikontrol',
'ruangans' => $validated['ruangans'] // Kembalikan nama ruangan
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -253,11 +286,16 @@ public function controlRelay(Request $request)
public function relayStatus() public function relayStatus()
{ {
try { try {
$ruangans = Ruangan::select('id', 'nama_ruangan', 'relay_state')->get(); $ruangans = Ruangan::select('nama_ruangan', 'relay_state')->get(); // Hanya ambil nama dan status
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'data' => $ruangans 'data' => $ruangans->map(function($ruangan) {
return [
'nama_ruangan' => $ruangan->nama_ruangan,
'relay_state' => $ruangan->relay_state
];
})
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -274,7 +312,7 @@ public function announcementStatus(Request $request)
try { try {
$request->validate([ $request->validate([
'ruangans' => 'required|array|min:1', 'ruangans' => 'required|array|min:1',
'ruangans.*' => 'exists:ruangan,id' 'ruangans.*' => 'exists:ruangan,nama_ruangan'
]); ]);
$mqttService = app(MqttService::class); $mqttService = app(MqttService::class);

View File

@ -14,10 +14,10 @@ public function authorize()
public function rules() public function rules()
{ {
return [ return [
'message' => 'required|string|max:500', 'message' => 'required_if:mode,tts|string|max:500',
'mode' => 'required|in:tts,manual', 'mode' => 'required|in:tts,manual',
'ruangans' => 'required|array|min:1', 'ruangans' => 'required|array|min:1',
'ruangans.*' => 'exists:ruangan,id', 'ruangans.*' => 'string|exists:ruangan,nama_ruangan', // Ubah validasi ke nama ruangan
]; ];
} }

View File

@ -199,7 +199,8 @@ class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm fo
<script> <script>
// Show announcement details in modal // Show announcement details in modal
function showAnnouncementDetails(id) { function showAnnouncementDetails(id) {
$.get(`/admin/announcement/${id}/details`, function(data) { $.get(`/admin/announcement/${id}/details`, function(response) {
const data = response.data; // ambil data sebenarnya dari response
Swal.fire({ Swal.fire({
title: 'Detail Pengumuman', title: 'Detail Pengumuman',
html: ` html: `
@ -211,10 +212,6 @@ function showAnnouncementDetails(id) {
${data.mode === 'tts' ? 'Text-to-Speech' : 'Manual'} ${data.mode === 'tts' ? 'Text-to-Speech' : 'Manual'}
</span> </span>
</div> </div>
<div>
<span class="font-medium w-24">Waktu:</span>
<span>${data.formatted_sent_at}</span>
</div>
<div> <div>
<span class="font-medium w-24">Ruangan:</span> <span class="font-medium w-24">Ruangan:</span>
<div class="flex flex-wrap gap-1 mt-1"> <div class="flex flex-wrap gap-1 mt-1">

View File

@ -85,25 +85,24 @@ class="flex items-center px-5 py-2.5 bg-white border border-blue-500 text-blue-6
@foreach($ruangans as $ruangan) @foreach($ruangans as $ruangan)
<div class="relative flex items-start p-3 rounded-lg border border-gray-200 hover:border-blue-300 transition-colors"> <div class="relative flex items-start p-3 rounded-lg border border-gray-200 hover:border-blue-300 transition-colors">
<div class="flex items-center h-5 mt-1"> <div class="flex items-center h-5 mt-1">
<input id="manual-ruang-{{ $ruangan->id }}" name="ruangans[]" <input id="manual-ruang-{{ $ruangan->nama_ruangan }}"
type="checkbox" value="{{ $ruangan->id }}" name="ruangans[]"
type="checkbox"
value="{{ $ruangan->nama_ruangan }}"
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"> class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
</div> </div>
<div class="ml-3 text-sm flex-1"> <div class="ml-3 text-sm flex-1">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label for="manual-ruang-{{ $ruangan->id }}" class="font-medium text-gray-700 flex items-center"> <label for="manual-ruang-{{ $ruangan->nama_ruangan }}" class="font-medium text-gray-700 flex items-center">
<span class="inline-block w-2.5 h-2.5 rounded-full bg-blue-500 mr-2"></span> <span class="inline-block w-2.5 h-2.5 rounded-full bg-blue-500 mr-2"></span>
{{ $ruangan->nama_ruangan }} {{ $ruangan->nama_ruangan }}
</label> </label>
<span id="status-ruang-{{ $ruangan->id }}" class="text-xs px-2 py-0.5 rounded-full <span id="status-ruang-{{ $ruangan->nama_ruangan }}"
{{ $ruangan->relay_state === 'on' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}"> class="text-xs px-2 py-0.5 rounded-full
{{ $ruangan->relay_state === 'on' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
{{ $ruangan->relay_state === 'on' ? 'AKTIF' : 'NONAKTIF' }} {{ $ruangan->relay_state === 'on' ? 'AKTIF' : 'NONAKTIF' }}
</span> </span>
</div> </div>
<!-- <p class="text-xs text-gray-500 mt-1 flex items-center">
<i class="fas fa-map-marker-alt mr-1 text-gray-400"></i>
{{ $ruangan->lokasi }}
</p> -->
</div> </div>
</div> </div>
@endforeach @endforeach
@ -169,19 +168,17 @@ class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:
@foreach($ruangans as $ruangan) @foreach($ruangans as $ruangan)
<div class="relative flex items-start p-3 rounded-lg border border-gray-200 hover:border-green-300 transition-colors"> <div class="relative flex items-start p-3 rounded-lg border border-gray-200 hover:border-green-300 transition-colors">
<div class="flex items-center h-5 mt-1"> <div class="flex items-center h-5 mt-1">
<input id="tts-ruang-{{ $ruangan->id }}" name="ruangans[]" <input id="tts-ruang-{{ $ruangan->nama_ruangan }}"
type="checkbox" value="{{ $ruangan->id }}" name="ruangans[]"
class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded"> type="checkbox"
value="{{ $ruangan->nama_ruangan }}"
class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded">
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="tts-ruang-{{ $ruangan->id }}" class="font-medium text-gray-700 flex items-center"> <label for="tts-ruang-{{ $ruangan->nama_ruangan }}" class="font-medium text-gray-700 flex items-center">
<span class="inline-block w-2.5 h-2.5 rounded-full bg-green-500 mr-2"></span> <span class="inline-block w-2.5 h-2.5 rounded-full bg-green-500 mr-2"></span>
{{ $ruangan->nama_ruangan }} {{ $ruangan->nama_ruangan }}
</label> </label>
<!-- <p class="text-xs text-gray-500 mt-1 flex items-center">
<i class="fas fa-map-marker-alt mr-1 text-gray-400"></i>
{{ $ruangan->lokasi }}
</p> -->
</div> </div>
</div> </div>
@endforeach @endforeach
@ -210,6 +207,11 @@ class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded">
<script> <script>
$(document).ready(function() { $(document).ready(function() {
// Fungsi untuk membuat selector aman dari karakter khusus
function getSafeRoomSelector(roomName) {
return roomName.replace(/[^a-zA-Z0-9-]/g, '-');
}
// Tab switching functionality // Tab switching functionality
function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) { function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
activeTab.classList.remove('hidden'); activeTab.classList.remove('hidden');
@ -282,12 +284,12 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
e.preventDefault(); e.preventDefault();
const form = this; const form = this;
const formData = new FormData(form); const formData = new FormData(form);
const rooms = formData.getAll('ruangans[]'); const roomNames = formData.getAll('ruangans[]');
// Reset error states // Reset error states
$('#ruanganError').addClass('hidden'); $('#ruanganError').addClass('hidden');
if (rooms.length === 0) { if (roomNames.length === 0) {
$('#ruanganError').removeClass('hidden'); $('#ruanganError').removeClass('hidden');
Swal.fire({ Swal.fire({
title: 'Peringatan', title: 'Peringatan',
@ -324,8 +326,9 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
}, },
success: function(response) { success: function(response) {
// Update room status indicators // Update room status indicators
rooms.forEach(roomId => { roomNames.forEach(roomName => {
const statusElement = $(`#status-ruang-${roomId}`); const safeRoomName = getSafeRoomSelector(roomName);
const statusElement = $(`#status-ruang-${safeRoomName}`);
statusElement.text(isRelayActive ? 'AKTIF' : 'NONAKTIF'); statusElement.text(isRelayActive ? 'AKTIF' : 'NONAKTIF');
statusElement.removeClass('bg-gray-100 text-gray-800 bg-green-100 text-green-800') statusElement.removeClass('bg-gray-100 text-gray-800 bg-green-100 text-green-800')
.addClass(isRelayActive ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'); .addClass(isRelayActive ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800');
@ -409,8 +412,8 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
return; return;
} }
const rooms = formData.getAll('ruangans[]'); const roomNames = formData.getAll('ruangans[]');
if (rooms.length === 0) { if (roomNames.length === 0) {
$('#ttsRuanganError').removeClass('hidden'); $('#ttsRuanganError').removeClass('hidden');
Swal.fire({ Swal.fire({
title: 'Peringatan', title: 'Peringatan',
@ -484,7 +487,8 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
function checkRelayStatus() { function checkRelayStatus() {
$.get("{{ url('/api/announcements/relay/status') }}", function(data) { $.get("{{ url('/api/announcements/relay/status') }}", function(data) {
data.forEach(room => { data.forEach(room => {
const statusElement = $(`#status-ruang-${room.id}`); const safeRoomName = getSafeRoomSelector(room.nama_ruangan);
const statusElement = $(`#status-ruang-${safeRoomName}`);
if (statusElement.length) { if (statusElement.length) {
statusElement.text(room.relay_state === 'on' ? 'AKTIF' : 'NONAKTIF'); statusElement.text(room.relay_state === 'on' ? 'AKTIF' : 'NONAKTIF');
statusElement.removeClass('bg-gray-100 text-gray-800 bg-green-100 text-green-800') statusElement.removeClass('bg-gray-100 text-gray-800 bg-green-100 text-green-800')

View File

@ -111,6 +111,8 @@
Route::get('/', 'index')->name('admin.announcement.index'); Route::get('/', 'index')->name('admin.announcement.index');
Route::get('/history', 'history')->name('admin.announcement.history'); Route::get('/history', 'history')->name('admin.announcement.history');
Route::delete('/{id}', 'destroy')->name('announcement.destroy'); Route::delete('/{id}', 'destroy')->name('announcement.destroy');
Route::get('{id}/details', 'details')->name('announcement.details');
}); });
}); });