TKK_E3220375/resources/views/admin/announcement/index.blade.php

537 lines
26 KiB
PHP

@extends('layouts.dashboard')
@section('title', 'Pengumuman Sekolah - Smart School')
@section('content')
<div class="container mx-auto px-4 py-8">
<!-- Header Section -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
<div>
<div class="flex items-center mb-2">
<h1 class="text-3xl font-bold text-gray-800">Sistem Pengumuman Sekolah</h1>
<span class="ml-3 px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 flex items-center">
<span class="w-2 h-2 rounded-full bg-blue-500 mr-2"></span>
LIVE CONTROL
</span>
</div>
<p class="text-gray-600">Kelola pengumuman manual dan TTS untuk seluruh ruangan sekolah</p>
<div class="flex items-center mt-4 space-x-4">
<div class="flex items-center">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full {{ $mqttStatus ? 'bg-green-400' : 'bg-red-400' }} opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 {{ $mqttStatus ? 'bg-green-500' : 'bg-red-500' }}"></span>
</span>
<span class="text-sm font-medium {{ $mqttStatus ? 'text-green-700' : 'text-red-700' }}">
MQTT: {{ $mqttStatus ? 'Connected' : 'Disconnected' }}
</span>
</div>
<div class="flex items-center text-sm text-gray-600">
<i class="fas fa-door-open mr-2 text-blue-500"></i>
<span class="font-medium">{{ $ruangans->count() }}</span> Ruangan Terdaftar
</div>
</div>
</div>
<a href="{{ route('admin.announcement.history') }}"
class="flex items-center px-5 py-2.5 bg-white border border-blue-500 text-blue-600 rounded-lg hover:bg-blue-50 transition-all duration-200 shadow-sm hover:shadow-md">
<i class="fas fa-history mr-2"></i> Riwayat Pengumuman
</a>
</div>
<!-- Main Card -->
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
<!-- Tab Navigation -->
<div class="border-b border-gray-200 bg-gradient-to-r from-blue-50 to-gray-50">
<nav class="flex">
<button id="manualTabBtn" class="px-6 py-4 font-medium text-sm border-b-2 border-blue-600 text-blue-600 focus:outline-none transition-colors flex items-center">
<i class="fas fa-microphone-alt mr-2"></i> Mode Manual
</button>
<button id="ttsTabBtn" class="px-6 py-4 font-medium text-sm border-b-2 border-transparent text-gray-500 hover:text-gray-700 focus:outline-none transition-colors flex items-center">
<i class="fas fa-robot mr-2"></i> Text-to-Speech
</button>
</nav>
</div>
<!-- Tab Content -->
<div class="p-6">
<!-- Manual Mode Tab -->
<div id="manualTab" class="tab-content">
<form id="manualForm" action="{{ route('admin.announcement.control-relay') }}" method="POST">
@csrf
<input type="hidden" name="mode" value="manual">
<input type="hidden" id="actionType" name="action" value="activate">
<div class="mb-6">
<div class="flex items-center mb-3">
<div class="p-2 rounded-lg bg-blue-100 text-blue-800 mr-3">
<i class="fas fa-sliders-h"></i>
</div>
<div>
<h3 class="text-xl font-semibold text-gray-800">Kontrol Relay Manual</h3>
<p class="text-gray-600 text-sm">Aktifkan atau nonaktifkan relay untuk ruangan tertentu</p>
</div>
</div>
</div>
<div class="mb-6">
<div class="flex items-center justify-between mb-3">
<label class="block text-sm font-medium text-gray-700">Pilih Ruangan <span class="text-red-500">*</span></label>
<button type="button" id="selectAllManual" class="text-xs font-medium text-blue-600 hover:text-blue-800 transition-colors flex items-center">
<i class="fas fa-check-circle mr-1"></i> Pilih Semua
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@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 }}"
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">
<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' }}">
{{ $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
</div>
<div id="ruanganError" class="mt-2 text-sm text-red-600 hidden flex items-center">
<i class="fas fa-exclamation-circle mr-1"></i> Pilih minimal satu ruangan
</div>
</div>
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-100">
<button type="button" id="resetManual" class="inline-flex items-center px-4 py-2.5 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
<i class="fas fa-redo mr-2"></i> Reset
</button>
<button type="submit" id="toggleRelayBtn" class="inline-flex items-center px-6 py-2.5 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
<i class="fas fa-broadcast-tower mr-2"></i> Aktifkan Relay
</button>
</div>
</form>
</div>
<!-- TTS Mode Tab -->
<div id="ttsTab" class="tab-content hidden">
<form id="ttsForm" action="{{ route('admin.announcement.store') }}" method="POST">
@csrf
<input type="hidden" name="mode" value="tts">
<div class="mb-6">
<div class="flex items-center mb-3">
<div class="p-2 rounded-lg bg-green-100 text-green-800 mr-3">
<i class="fas fa-robot"></i>
</div>
<div>
<h3 class="text-xl font-semibold text-gray-800">Pengumuman Suara (TTS)</h3>
<p class="text-gray-600 text-sm">Masukkan teks yang akan diubah menjadi suara dan pilih ruangan tujuan</p>
</div>
</div>
</div>
<div class="mb-6">
<label for="message" class="block text-sm font-medium text-gray-700 mb-2">Isi Pengumuman <span class="text-red-500">*</span></label>
<div class="relative">
<textarea id="message" name="message" rows="5"
class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm placeholder-gray-400"
placeholder="Contoh: Selamat pagi siswa-siswi, harap berkumpul di lapangan upacara untuk mengikuti kegiatan hari ini">{{ old('message') }}</textarea>
<div class="absolute bottom-3 right-3 bg-white px-2 py-1 rounded text-xs text-gray-500 border border-gray-200">
<span id="charCount">0</span>/500
</div>
</div>
<div id="messageError" class="mt-1 text-sm text-red-600 hidden flex items-center">
<i class="fas fa-exclamation-circle mr-1"></i> Isi pengumuman diperlukan
</div>
</div>
<div class="mb-6">
<div class="flex items-center justify-between mb-4">
<label class="block text-sm font-medium text-gray-700">Pilih Ruangan <span class="text-red-500">*</span></label>
<button type="button" id="selectAllTTS" class="text-sm font-medium text-blue-600 hover:text-blue-800 transition-colors flex items-center">
<i class="fas fa-check-circle mr-1"></i> Pilih Semua
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@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">
</div>
<div class="ml-3 text-sm">
<label for="tts-ruang-{{ $ruangan->id }}" 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
</div>
<div id="ttsRuanganError" class="mt-2 text-sm text-red-600 hidden flex items-center">
<i class="fas fa-exclamation-circle mr-1"></i> Pilih minimal satu ruangan
</div>
</div>
<div class="flex justify-end space-x-4 pt-4 border-t border-gray-100">
<button type="button" id="resetTTS" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2.5 px-6 rounded-lg text-sm font-medium transition-all duration-200 shadow-sm flex items-center">
<i class="fas fa-redo mr-2"></i> Reset Form
</button>
<button type="submit" class="bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white py-2.5 px-6 rounded-lg text-sm font-medium transition-all duration-200 shadow-md hover:shadow-lg flex items-center">
<i class="fas fa-play mr-2"></i> Kirim Pengumuman
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Tab switching functionality
function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
activeTab.classList.remove('hidden');
inactiveTab.classList.add('hidden');
activeBtn.classList.add('border-blue-600', 'text-blue-600');
activeBtn.classList.remove('border-transparent', 'text-gray-500');
inactiveBtn.classList.remove('border-blue-600', 'text-blue-600');
inactiveBtn.classList.add('border-transparent', 'text-gray-500');
}
$('#manualTabBtn').click(function() {
switchTab(document.getElementById('manualTab'), document.getElementById('ttsTab'),
document.getElementById('manualTabBtn'), document.getElementById('ttsTabBtn'));
});
$('#ttsTabBtn').click(function() {
switchTab(document.getElementById('ttsTab'), document.getElementById('manualTab'),
document.getElementById('ttsTabBtn'), document.getElementById('manualTabBtn'));
});
// Character counter for TTS message
$('#message').on('input', function() {
const maxLength = 500;
const currentLength = $(this).val().length;
$('#charCount').text(currentLength);
if (currentLength > maxLength) {
$(this).addClass('border-red-300');
$('#charCount').addClass('text-red-600');
} else {
$(this).removeClass('border-red-300');
$('#charCount').removeClass('text-red-600');
}
});
// Select all checkboxes
$('#selectAllManual').click(function() {
const allChecked = $('#manualTab input[type="checkbox"]').length === $('#manualTab input[type="checkbox"]:checked').length;
$('#manualTab input[type="checkbox"]').prop('checked', !allChecked);
// Update button text
$(this).html(`<i class="fas ${!allChecked ? 'fa-check-circle' : 'fa-times-circle'} mr-1"></i> ${!allChecked ? 'Pilih Semua' : 'Batal Pilih'}`);
});
$('#selectAllTTS').click(function() {
const allChecked = $('#ttsTab input[type="checkbox"]').length === $('#ttsTab input[type="checkbox"]:checked').length;
$('#ttsTab input[type="checkbox"]').prop('checked', !allChecked);
// Update button text
$(this).html(`<i class="fas ${!allChecked ? 'fa-check-circle' : 'fa-times-circle'} mr-1"></i> ${!allChecked ? 'Pilih Semua' : 'Batal Pilih'}`);
});
// Reset forms
$('#resetManual').click(function() {
$('#manualForm')[0].reset();
$('#ruanganError').addClass('hidden');
$('#selectAllManual').html('<i class="fas fa-check-circle mr-1"></i> Pilih Semua');
});
$('#resetTTS').click(function() {
$('#ttsForm')[0].reset();
$('#charCount').text('0');
$('#messageError').addClass('hidden');
$('#ttsRuanganError').addClass('hidden');
$('#selectAllTTS').html('<i class="fas fa-check-circle mr-1"></i> Pilih Semua');
});
// Form validation and submission
$('#manualForm').on('submit', function(e) {
e.preventDefault();
const form = this;
const formData = new FormData(form);
const rooms = formData.getAll('ruangans[]');
// Reset error states
$('#ruanganError').addClass('hidden');
if (rooms.length === 0) {
$('#ruanganError').removeClass('hidden');
Swal.fire({
title: 'Peringatan',
text: 'Pilih minimal satu ruangan untuk mengontrol relay',
icon: 'warning',
confirmButtonText: 'Mengerti',
customClass: {
confirmButton: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'
}
});
return;
}
// Toggle relay state
isRelayActive = !isRelayActive;
formData.set('action', isRelayActive ? 'activate' : 'deactivate');
updateButtonState();
// Show loading state
const submitBtn = $('#toggleRelayBtn');
const originalBtnText = submitBtn.html();
submitBtn.html('<i class="fas fa-circle-notch fa-spin mr-2"></i> Memproses...');
submitBtn.prop('disabled', true);
// Submit data
$.ajax({
url: $(form).attr('action'),
method: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
// Update room status indicators
rooms.forEach(roomId => {
const statusElement = $(`#status-ruang-${roomId}`);
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');
});
// Show success notification
const action = isRelayActive ? 'diaktifkan' : 'dinonaktifkan';
Swal.fire({
title: 'Berhasil!',
text: `Relay berhasil ${action}`,
icon: 'success',
timer: 2000,
timerProgressBar: true,
showConfirmButton: false,
position: 'top-end',
toast: true,
background: '#f0fdf4',
iconColor: '#10b981'
});
},
error: function(xhr) {
// Revert state on error
isRelayActive = !isRelayActive;
updateButtonState();
let errorMessage = 'Terjadi kesalahan saat mengontrol relay';
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
}
Swal.fire({
title: 'Gagal!',
text: errorMessage,
icon: 'error',
confirmButtonText: 'Tutup',
customClass: {
confirmButton: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'
}
});
},
complete: function() {
submitBtn.html(originalBtnText);
submitBtn.prop('disabled', false);
}
});
});
$('#ttsForm').on('submit', function(e) {
e.preventDefault();
const form = this;
const formData = new FormData(form);
// Reset error states
$('#messageError').addClass('hidden');
$('#ttsRuanganError').addClass('hidden');
// Client-side validation
if (!formData.get('message')) {
$('#messageError').removeClass('hidden');
Swal.fire({
title: 'Peringatan',
text: 'Isi pengumuman diperlukan untuk mode TTS',
icon: 'warning',
confirmButtonText: 'Mengerti',
customClass: {
confirmButton: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'
}
});
return;
}
if ($('#message').val().length > 500) {
Swal.fire({
title: 'Peringatan',
text: 'Isi pengumuman melebihi 500 karakter',
icon: 'warning',
confirmButtonText: 'Mengerti',
customClass: {
confirmButton: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'
}
});
return;
}
const rooms = formData.getAll('ruangans[]');
if (rooms.length === 0) {
$('#ttsRuanganError').removeClass('hidden');
Swal.fire({
title: 'Peringatan',
text: 'Pilih minimal satu ruangan',
icon: 'warning',
confirmButtonText: 'Mengerti',
customClass: {
confirmButton: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'
}
});
return;
}
// Show loading state
const submitBtn = $(form).find('button[type="submit"]');
const originalBtnText = submitBtn.html();
submitBtn.html('<i class="fas fa-circle-notch fa-spin mr-2"></i> Mengirim...');
submitBtn.prop('disabled', true);
$.ajax({
url: $(form).attr('action'),
method: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
if (response.success) {
Swal.fire({
title: 'Berhasil!',
text: 'Pengumuman TTS berhasil dikirim',
icon: 'success',
timer: 2000,
timerProgressBar: true,
showConfirmButton: false,
position: 'top-end',
toast: true,
background: '#f0fdf4',
iconColor: '#10b981',
willClose: () => {
window.location.href = "{{ route('admin.announcement.history') }}";
}
});
}
},
error: function(xhr) {
let errorMessage = 'Pengumuman TTS gagal dikirim';
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
}
Swal.fire({
title: 'Gagal!',
text: errorMessage,
icon: 'error',
confirmButtonText: 'Tutup',
customClass: {
confirmButton: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'
}
});
},
complete: function() {
submitBtn.html(originalBtnText);
submitBtn.prop('disabled', false);
}
});
});
// Check relay status periodically
function checkRelayStatus() {
$.get("{{ route('admin.announcement.relay-status') }}", function(data) {
data.forEach(room => {
const statusElement = $(`#status-ruang-${room.id}`);
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')
.addClass(room.relay_state === 'on' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800');
}
});
});
}
// Check MQTT connection status
function checkMqttStatus() {
$.get("{{ route('admin.check.mqtt') }}", function(data) {
const statusElement = $('.mqtt-status-indicator');
const textElement = $('.mqtt-status-text');
if (data.connected) {
statusElement.removeClass('bg-red-500').addClass('bg-green-500');
textElement.removeClass('text-red-600').addClass('text-green-600');
textElement.text('MQTT: Connected');
} else {
statusElement.removeClass('bg-green-500').addClass('bg-red-500');
textElement.removeClass('text-green-600').addClass('text-red-600');
textElement.text('MQTT: Disconnected');
}
});
}
// Update button state based on relay status
function updateButtonState() {
const btn = $('#toggleRelayBtn');
if (isRelayActive) {
btn.html('<i class="fas fa-broadcast-tower mr-2"></i> Matikan Relay');
btn.removeClass('from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800')
.addClass('from-red-600 to-red-700 hover:from-red-700 hover:to-red-800');
} else {
btn.html('<i class="fas fa-broadcast-tower mr-2"></i> Aktifkan Relay');
btn.removeClass('from-red-600 to-red-700 hover:from-red-700 hover:to-red-800')
.addClass('from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800');
}
}
// Initialize
let isRelayActive = false;
updateButtonState();
setInterval(checkRelayStatus, 10000);
checkRelayStatus();
setInterval(checkMqttStatus, 30000);
});
</script>
@endsection