diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 5167b75..9c88150 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -12,6 +12,7 @@ use App\Models\Antrian; use App\Models\Poli; use App\Models\RiwayatPanggilan; +use App\Services\AudioService; use Barryvdh\DomPDF\Facade\Pdf; @@ -724,4 +725,96 @@ public function cetakAntrian(Antrian $antrian) ], 500); } } + + /** + * Play audio for queue call + */ + public function playQueueCallAudio(Request $request) + { + try { + $request->validate([ + 'poli_name' => 'required|string' + ]); + + $poliName = $request->input('poli_name'); + + // Get audio sequence from AudioService + $audioService = app(AudioService::class); + $result = $audioService->getQueueCallAudio($poliName); + + return response()->json($result); + + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Error playing audio: ' . $e->getMessage() + ], 500); + } + } + + /** + * Panggil antrian selanjutnya berdasarkan poli + */ + public function panggilSelanjutnya(Request $request) + { + try { + $request->validate([ + 'poli_name' => 'required|string' + ]); + + $poliName = $request->input('poli_name'); + + // Cari antrian berikutnya yang status 'menunggu' + $antrianSelanjutnya = Antrian::whereHas('poli', function($query) use ($poliName) { + $query->where('nama_poli', $poliName); + }) + ->where('status', 'menunggu') + ->whereDate('created_at', today()) + ->orderBy('created_at', 'asc') + ->first(); + + if (!$antrianSelanjutnya) { + return response()->json([ + 'success' => false, + 'message' => 'Tidak ada antrian yang menunggu untuk ' . $poliName + ]); + } + + // Update status menjadi 'dipanggil' + $antrianSelanjutnya->update([ + 'status' => 'dipanggil', + 'waktu_panggil' => now() + ]); + + // Catat di riwayat panggilan + \App\Models\RiwayatPanggilan::create([ + 'antrian_id' => $antrianSelanjutnya->id, + 'waktu_panggilan' => now(), + 'admin_id' => auth()->id() + ]); + + // Get audio sequence + $audioService = app(AudioService::class); + $audioResult = $audioService->getQueueCallAudio($poliName); + + return response()->json([ + 'success' => true, + 'message' => 'Antrian ' . $antrianSelanjutnya->no_antrian . ' berhasil dipanggil', + 'antrian' => [ + 'id' => $antrianSelanjutnya->id, + 'no_antrian' => $antrianSelanjutnya->no_antrian, + 'poli_name' => $poliName, + 'user_name' => $antrianSelanjutnya->user->nama, + 'status' => 'dipanggil' + ], + 'audio_sequence' => $audioResult['audio_sequence'] + ]); + + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan: ' . $e->getMessage() + ], 500); + } + } } diff --git a/app/Http/Controllers/AudioController.php b/app/Http/Controllers/AudioController.php new file mode 100644 index 0000000..3cc4fdc --- /dev/null +++ b/app/Http/Controllers/AudioController.php @@ -0,0 +1,67 @@ +audioService = $audioService; + } + + /** + * Show audio management page + */ + public function index() + { + return view('admin.audio.index'); + } + + /** + * Get audio sequence for queue call + */ + public function getQueueCallAudio(Request $request) + { + $request->validate([ + 'poli_name' => 'required|string' + ]); + + $poliName = $request->input('poli_name'); + $result = $this->audioService->getQueueCallAudio($poliName); + + return response()->json($result); + } + + /** + * Get available audio files + */ + public function getAvailableAudioFiles() + { + $files = $this->audioService->getAvailableAudioFiles(); + + return response()->json([ + 'success' => true, + 'files' => $files + ]); + } + + /** + * Test audio playback + */ + public function testAudio(Request $request) + { + $request->validate([ + 'poli_name' => 'required|string' + ]); + + $poliName = $request->input('poli_name'); + $result = $this->audioService->getQueueCallAudio($poliName); + + return response()->json($result); + } +} diff --git a/app/Http/Controllers/DisplayController.php b/app/Http/Controllers/DisplayController.php index 3a7949b..170ed08 100644 --- a/app/Http/Controllers/DisplayController.php +++ b/app/Http/Controllers/DisplayController.php @@ -10,54 +10,73 @@ class DisplayController extends Controller { public function index() { + // Get all available polis + $polis = Poli::all(); + // Current: sedang dipanggil per poli - $poliUmumCurrent = Antrian::where('poli_id', 1) + $poliUmumCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'umum'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliGigiCurrent = Antrian::where('poli_id', 2) + $poliGigiCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'gigi'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliJiwaCurrent = Antrian::where('poli_id', 3) + $poliJiwaCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliTradisionalCurrent = Antrian::where('poli_id', 4) + $poliTradisionalCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'kesehatan tradisional'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); // Next: menunggu per poli (maks 3) - $poliUmumNext = Antrian::where('poli_id', 1) + $poliUmumNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'umum'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliGigiNext = Antrian::where('poli_id', 2) + $poliGigiNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'gigi'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliJiwaNext = Antrian::where('poli_id', 3) + $poliJiwaNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliTradisionalNext = Antrian::where('poli_id', 4) + $poliTradisionalNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'kesehatan tradisional'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') @@ -107,19 +126,25 @@ public function checkNewCalls(Request $request) public function getDisplayData() { // Current: sedang dipanggil per poli - $poliUmumCurrent = Antrian::where('poli_id', 1) + $poliUmumCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'umum'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliGigiCurrent = Antrian::where('poli_id', 2) + $poliGigiCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'gigi'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliJiwaCurrent = Antrian::where('poli_id', 3) + $poliJiwaCurrent = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) ->where('status', 'dipanggil') ->whereDate('created_at', today()) ->orderByDesc('updated_at') @@ -132,21 +157,26 @@ public function getDisplayData() ->first(); // Next: menunggu per poli (maks 3) - $poliUmumNext = Antrian::where('poli_id', 1) + $poliUmumNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'umum'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliGigiNext = Antrian::where('poli_id', 2) + $poliGigiNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'gigi'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') - ->take(3) ->get(); - $poliJiwaNext = Antrian::where('poli_id', 3) + $poliJiwaNext = Antrian::whereHas('poli', function($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') diff --git a/app/Services/AudioService.php b/app/Services/AudioService.php new file mode 100644 index 0000000..b64dba8 --- /dev/null +++ b/app/Services/AudioService.php @@ -0,0 +1,81 @@ +normalizePoliName($poliName); + + // Check if audio file exists for this poli + $poliAudioFile = "assets/music/tts/antrian selanjutnya. poli {$normalizedPoliName}.mp3"; + + if (!file_exists(public_path($poliAudioFile))) { + // Fallback to default audio + $poliAudioFile = "assets/music/tts/antrian selanjutnya. poli umum.mp3"; + } + + return [ + 'success' => true, + 'audio_sequence' => [ + [ + 'type' => 'audio_file', + 'url' => asset('assets/music/announcement.mp3'), + 'duration' => 4000 // 4 seconds + ], + [ + 'type' => 'delay', + 'duration' => 1000 // 1 second delay between announcement and antrian + ], + [ + 'type' => 'audio_file', + 'url' => asset($poliAudioFile), + 'duration' => 4000 // 4 seconds + ] + ] + ]; + } + + /** + * Normalize poli name to match audio file names + */ + private function normalizePoliName($poliName) + { + $poliName = strtolower(trim($poliName)); + + // Map poli names to match audio files + $poliMap = [ + 'umum' => 'umum', + 'gigi' => 'gigi', + 'kesehatan jiwa' => 'jiwa', + 'jiwa' => 'jiwa', + 'kesehatan tradisional' => 'Tradisional', + 'tradisional' => 'Tradisional' + ]; + + return $poliMap[$poliName] ?? 'umum'; + } + + /** + * Get all available audio files + */ + public function getAvailableAudioFiles() + { + $audioDir = public_path('assets/music/tts'); + $files = []; + + if (is_dir($audioDir)) { + $audioFiles = glob($audioDir . '/*.mp3'); + foreach ($audioFiles as $file) { + $files[] = basename($file); + } + } + + return $files; + } +} diff --git a/config/services.php b/config/services.php index adaaab2..3dc958e 100644 --- a/config/services.php +++ b/config/services.php @@ -36,7 +36,7 @@ ], 'google' => [ - 'tts_api_key' => env('GOOGLE_TTS_API_KEY'), + ], ]; diff --git a/public/assets/music/call-to-attention-123107.mp3 b/public/assets/music/announcement.mp3 similarity index 100% rename from public/assets/music/call-to-attention-123107.mp3 rename to public/assets/music/announcement.mp3 diff --git a/public/assets/music/tts/antrian selanjutnya. poli Tradisional.mp3 b/public/assets/music/tts/antrian selanjutnya. poli Tradisional.mp3 new file mode 100644 index 0000000..c92a792 Binary files /dev/null and b/public/assets/music/tts/antrian selanjutnya. poli Tradisional.mp3 differ diff --git a/public/assets/music/tts/antrian selanjutnya. poli gigi.mp3 b/public/assets/music/tts/antrian selanjutnya. poli gigi.mp3 new file mode 100644 index 0000000..ce1d5c0 Binary files /dev/null and b/public/assets/music/tts/antrian selanjutnya. poli gigi.mp3 differ diff --git a/public/assets/music/tts/antrian selanjutnya. poli jiwa.mp3 b/public/assets/music/tts/antrian selanjutnya. poli jiwa.mp3 new file mode 100644 index 0000000..1807387 Binary files /dev/null and b/public/assets/music/tts/antrian selanjutnya. poli jiwa.mp3 differ diff --git a/public/assets/music/tts/antrian selanjutnya. poli umum.mp3 b/public/assets/music/tts/antrian selanjutnya. poli umum.mp3 new file mode 100644 index 0000000..9169e08 Binary files /dev/null and b/public/assets/music/tts/antrian selanjutnya. poli umum.mp3 differ diff --git a/resources/views/admin/audio/index.blade.php b/resources/views/admin/audio/index.blade.php new file mode 100644 index 0000000..e13f8c3 --- /dev/null +++ b/resources/views/admin/audio/index.blade.php @@ -0,0 +1,244 @@ +@extends('layouts.app') + +@section('title', 'Audio Management') + +@section('content') +
+ @include('admin.partials.top-nav') + +
+ @include('admin.partials.sidebar') + + +
+
+ +
+

๐ŸŽต Audio Management

+

Kelola dan test audio untuk panggilan antrian

+
+ + +
+
+

๐Ÿงช Test Audio

+

Test audio untuk setiap poli

+
+
+
+ @csrf +
+ +
+ + +
+ + +
+ +
+
+
+ + + +
+
+ + +
+
+

๐Ÿ“ Available Audio Files

+

Daftar file audio yang tersedia

+
+
+
+
+ + + +

Loading audio files...

+
+
+
+
+
+
+
+
+ +@push('scripts') + +@endpush +@endsection diff --git a/resources/views/admin/partials/sidebar.blade.php b/resources/views/admin/partials/sidebar.blade.php index fa8c7ca..047e302 100644 --- a/resources/views/admin/partials/sidebar.blade.php +++ b/resources/views/admin/partials/sidebar.blade.php @@ -103,6 +103,19 @@ class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.lapor + +
+ + + + + + Audio Management + +
+
{{ $antrian->no_antrian }} + // Audio Player for Queue Calls + class AudioPlayer { + constructor() { + this.audioQueue = []; + this.isPlaying = false; + } + + // Play complete audio sequence + async playAudioSequence(audioSequence) { + if (this.isPlaying) { + console.log('Audio already playing, queueing...'); + this.audioQueue.push(audioSequence); + return; + } + + this.isPlaying = true; + + for (let i = 0; i < audioSequence.length; i++) { + const audioItem = audioSequence[i]; + await this.playAudioItem(audioItem); + + // Wait between audio items (0.5 second gap) + if (i < audioSequence.length - 1) { + await this.delay(500); + } + } + + this.isPlaying = false; + + // Play next in queue if available + if (this.audioQueue.length > 0) { + const nextSequence = this.audioQueue.shift(); + this.playAudioSequence(nextSequence); + } + } + + // Play single audio item + playAudioItem(audioItem) { + return new Promise((resolve) => { + if (audioItem.type === 'audio_file') { + const audio = new Audio(audioItem.url); + + audio.addEventListener('loadeddata', () => { + console.log('Audio loaded, playing...'); + audio.play().catch(error => { + console.error('Audio play error:', error); + resolve(); + }); + }); + + audio.addEventListener('ended', () => { + console.log('Audio ended'); + resolve(); + }); + + audio.addEventListener('error', (error) => { + console.error('Audio playback error:', error); + resolve(); + }); + + // Fallback timeout + setTimeout(() => { + resolve(); + }, audioItem.duration || 5000); + } else if (audioItem.type === 'delay') { + // Handle delay between audio files + console.log(`Waiting ${audioItem.duration}ms delay...`); + setTimeout(() => { + console.log('Delay finished'); + resolve(); + }, audioItem.duration); + } else { + console.warn('Unknown audio type:', audioItem.type); + resolve(); + } + }); + } + + // Utility function for delays + delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + // Play audio for queue call + async playQueueCall(poliName) { + try { + console.log('Playing audio for:', poliName); + + const response = await fetch('{{ route('audio.queue-call') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content + }, + body: JSON.stringify({ + poli_name: poliName + }) + }); + + const data = await response.json(); + + if (data.success && data.audio_sequence) { + console.log('Audio sequence received:', data.audio_sequence); + await this.playAudioSequence(data.audio_sequence); + } else { + console.error('Failed to get audio sequence:', data.message); + } + } catch (error) { + console.error('Error playing audio:', error); + } + } + } + + // Initialize Audio Player + const audioPlayer = new AudioPlayer(); @@ -292,7 +407,8 @@ function checkForNewCalls() { if (data.has_new_call && data.antrian) { console.log('New call detected:', data.antrian); - + // Play audio for the called queue + audioPlayer.playQueueCall(data.antrian.poli_name); // Show notification showNewCallNotification(data.antrian.poli_name, data.antrian.queue_number); diff --git a/resources/views/vendor/pagination/bootstrap-4.blade.php b/resources/views/vendor/pagination/bootstrap-4.blade.php deleted file mode 100644 index 63c6f56..0000000 --- a/resources/views/vendor/pagination/bootstrap-4.blade.php +++ /dev/null @@ -1,46 +0,0 @@ -@if ($paginator->hasPages()) - -@endif diff --git a/resources/views/vendor/pagination/tailwind.blade.php b/resources/views/vendor/pagination/tailwind.blade.php deleted file mode 100644 index e5b90b0..0000000 --- a/resources/views/vendor/pagination/tailwind.blade.php +++ /dev/null @@ -1,130 +0,0 @@ -@if ($paginator->hasPages()) - -@endif diff --git a/routes/web.php b/routes/web.php index c0ef713..4a12f6e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,7 @@ use App\Http\Controllers\DashboardController; use App\Http\Controllers\DisplayController; use App\Http\Controllers\AdminController; +use App\Http\Controllers\AudioController; // Landing Page @@ -64,6 +65,9 @@ Route::post('/admin/selesai-antrian', [AdminController::class, 'selesaiAntrian'])->name('admin.selesai-antrian'); Route::post('/admin/antrian/batal', [AdminController::class, 'batalAntrian'])->name('admin.batal-antrian'); Route::post('/admin/panggil-antrian/{antrian}', [AdminController::class, 'panggilAntrianById'])->name('admin.panggil-antrian-id'); + Route::post('/admin/play-audio', [AdminController::class, 'playQueueCallAudio'])->name('admin.play-audio'); + Route::post('/admin/panggil-selanjutnya', [AdminController::class, 'panggilSelanjutnya'])->name('admin.panggil-selanjutnya'); + Route::get('/admin/audio', [AudioController::class, 'index'])->name('admin.audio.index'); // User Management Routes Route::get('/admin/users', [AdminController::class, 'manageUsers'])->name('admin.users.index'); @@ -89,6 +93,11 @@ +// Audio Routes +Route::post('/audio/queue-call', [AudioController::class, 'getQueueCallAudio'])->name('audio.queue-call'); +Route::get('/audio/files', [AudioController::class, 'getAvailableAudioFiles'])->name('audio.files'); +Route::post('/audio/test', [AudioController::class, 'testAudio'])->name('audio.test'); + // API Routes for display Route::get('/api/check-new-calls', [DisplayController::class, 'checkNewCalls'])->name('api.check-new-calls'); Route::get('/api/display-data', [DisplayController::class, 'getDisplayData'])->name('api.display-data');