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

View File

@ -14,10 +14,10 @@ public function authorize()
public function rules()
{
return [
'message' => 'required|string|max:500',
'message' => 'required_if:mode,tts|string|max:500',
'mode' => 'required|in:tts,manual',
'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>
// Show announcement details in modal
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({
title: 'Detail Pengumuman',
html: `
@ -211,10 +212,6 @@ function showAnnouncementDetails(id) {
${data.mode === 'tts' ? 'Text-to-Speech' : 'Manual'}
</span>
</div>
<div>
<span class="font-medium w-24">Waktu:</span>
<span>${data.formatted_sent_at}</span>
</div>
<div>
<span class="font-medium w-24">Ruangan:</span>
<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)
<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">
<input id="manual-ruang-{{ $ruangan->id }}" name="ruangans[]"
type="checkbox" value="{{ $ruangan->id }}"
<input id="manual-ruang-{{ $ruangan->nama_ruangan }}"
name="ruangans[]"
type="checkbox"
value="{{ $ruangan->nama_ruangan }}"
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
</div>
<div class="ml-3 text-sm flex-1">
<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>
{{ $ruangan->nama_ruangan }}
</label>
<span id="status-ruang-{{ $ruangan->id }}" 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' }}">
<span id="status-ruang-{{ $ruangan->nama_ruangan }}"
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' }}
</span>
</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>
@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)
<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">
<input id="tts-ruang-{{ $ruangan->id }}" name="ruangans[]"
type="checkbox" value="{{ $ruangan->id }}"
class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded">
<input id="tts-ruang-{{ $ruangan->nama_ruangan }}"
name="ruangans[]"
type="checkbox"
value="{{ $ruangan->nama_ruangan }}"
class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded">
</div>
<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>
{{ $ruangan->nama_ruangan }}
</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>
@endforeach
@ -210,6 +207,11 @@ class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded">
<script>
$(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
function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
activeTab.classList.remove('hidden');
@ -282,12 +284,12 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
e.preventDefault();
const form = this;
const formData = new FormData(form);
const rooms = formData.getAll('ruangans[]');
const roomNames = formData.getAll('ruangans[]');
// Reset error states
$('#ruanganError').addClass('hidden');
if (rooms.length === 0) {
if (roomNames.length === 0) {
$('#ruanganError').removeClass('hidden');
Swal.fire({
title: 'Peringatan',
@ -324,8 +326,9 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
},
success: function(response) {
// Update room status indicators
rooms.forEach(roomId => {
const statusElement = $(`#status-ruang-${roomId}`);
roomNames.forEach(roomName => {
const safeRoomName = getSafeRoomSelector(roomName);
const statusElement = $(`#status-ruang-${safeRoomName}`);
statusElement.text(isRelayActive ? 'AKTIF' : 'NONAKTIF');
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');
@ -409,8 +412,8 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
return;
}
const rooms = formData.getAll('ruangans[]');
if (rooms.length === 0) {
const roomNames = formData.getAll('ruangans[]');
if (roomNames.length === 0) {
$('#ttsRuanganError').removeClass('hidden');
Swal.fire({
title: 'Peringatan',
@ -484,7 +487,8 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
function checkRelayStatus() {
$.get("{{ url('/api/announcements/relay/status') }}", function(data) {
data.forEach(room => {
const statusElement = $(`#status-ruang-${room.id}`);
const safeRoomName = getSafeRoomSelector(room.nama_ruangan);
const statusElement = $(`#status-ruang-${safeRoomName}`);
if (statusElement.length) {
statusElement.text(room.relay_state === 'on' ? 'AKTIF' : 'NONAKTIF');
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('/history', 'history')->name('admin.announcement.history');
Route::delete('/{id}', 'destroy')->name('announcement.destroy');
Route::get('{id}/details', 'details')->name('announcement.details');
});
});