715 lines
33 KiB
Plaintext
715 lines
33 KiB
Plaintext
@{
|
|
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
|
|
ViewData["Title"] = "Scan SPJ";
|
|
}
|
|
|
|
@section Styles {
|
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" />
|
|
}
|
|
|
|
|
|
<div class="max-w-sm mx-auto bg-white min-h-screen">
|
|
<div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
|
|
<div class="flex items-center justify-between">
|
|
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
|
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
|
</a>
|
|
<h1 class="text-lg font-bold">Scan SPJ</h1>
|
|
<div class="w-8"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-4">
|
|
@if (TempData["Success"] != null)
|
|
{
|
|
<div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
|
|
<div class="flex items-center">
|
|
<i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
|
|
<span class="text-green-800">@TempData["Success"]</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@if (TempData["Error"] != null)
|
|
{
|
|
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex items-center">
|
|
<i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
|
|
<span class="text-red-800">@TempData["Error"]</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="scanner-container mb-4" style="height: 300px;">
|
|
<div id="scanner-container" class="w-full h-full relative bg-gray-900 rounded-lg overflow-hidden">
|
|
|
|
<div id="loading-scanner" class="absolute inset-0 bg-gray-900 flex items-center justify-center z-10">
|
|
<div class="text-center text-white">
|
|
<div class="loading-spinner mx-auto mb-2"></div>
|
|
<p class="text-sm">Memuat scanner...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3 mb-4">
|
|
<button id="start-scanner" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner">
|
|
<i class="w-5 h-5 inline mr-2" data-lucide="camera"></i>
|
|
Mulai Scan
|
|
</button>
|
|
|
|
<button id="stop-scanner" class="w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner hidden">
|
|
<i class="w-5 h-5 inline mr-2" data-lucide="camera-off"></i>
|
|
Hentikan Scan
|
|
</button>
|
|
|
|
<div id="permission-info" class="hidden bg-blue-50 border border-blue-200 rounded-lg p-3">
|
|
<div class="flex items-start">
|
|
<i class="w-5 h-5 text-blue-600 mr-2 mt-0.5" data-lucide="info"></i>
|
|
<div class="text-blue-800 text-sm">
|
|
<p class="font-medium mb-1">🎥 Meminta Akses Kamera...</p>
|
|
<p class="mb-2">Browser akan meminta izin akses kamera. Pastikan untuk:</p>
|
|
<ul class="text-xs space-y-1 list-disc list-inside">
|
|
<li>Klik tombol <strong>"Allow"</strong> atau <strong>"Izinkan"</strong></li>
|
|
<li>Jika popup tidak muncul, cek address bar browser</li>
|
|
<li>Pastikan kamera tidak sedang digunakan aplikasi lain</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="permission-denied" class="hidden bg-red-50 border border-red-200 rounded-lg p-3">
|
|
<div class="flex items-start">
|
|
<i class="w-5 h-5 text-red-600 mr-2 mt-0.5" data-lucide="alert-triangle"></i>
|
|
<div class="text-red-800 text-sm">
|
|
<p class="font-medium mb-1">Akses Kamera Ditolak</p>
|
|
<p class="mb-2">Untuk menggunakan scanner, aktifkan akses kamera:</p>
|
|
<ol class="list-decimal list-inside space-y-1 text-xs">
|
|
<li>Klik ikon kunci/kamera di address bar browser</li>
|
|
<li>Pilih "Allow" atau "Izinkan" untuk kamera</li>
|
|
<li>Refresh halaman dan coba lagi</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border-t pt-4">
|
|
<h3 class="text-gray-700 font-medium mb-3">Atau input manual:</h3>
|
|
<form id="manual-form" method="post" action="@Url.Action("ProcessScan", "Scan")">
|
|
@Html.AntiForgeryToken()
|
|
<div class="flex gap-2">
|
|
<input type="text"
|
|
id="manual-barcode"
|
|
name="barcode"
|
|
placeholder="Masukkan kode SPJ manual"
|
|
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
|
|
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors">
|
|
<i class="w-5 h-5" data-lucide="search"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="scan-result" class="hidden mt-4 p-4 bg-green-50 border border-green-200 rounded-lg scan-result-card">
|
|
<div class="flex items-center mb-2">
|
|
<i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
|
|
<span class="text-green-800 font-medium">QR Code terdeteksi!</span>
|
|
</div>
|
|
<p class="text-green-700 mb-3">Kode: <span id="detected-code" class="font-mono font-bold"></span></p>
|
|
<div class="flex gap-2">
|
|
<button id="confirm-scan" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors btn-scanner">
|
|
Konfirmasi
|
|
</button>
|
|
<button id="retry-scan" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors btn-scanner">
|
|
Scan Ulang
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="error-message" class="hidden mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex items-center">
|
|
<i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
|
|
<span class="text-red-800" id="error-text"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
|
|
</div>
|
|
|
|
|
|
<div id="scan-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center">
|
|
<div class="bg-white py-4 rounded-2xl shadow-2xl max-w-sm w-full border border-gray-100">
|
|
<div class="p-8 text-center">
|
|
<div id="modal-icon" class="mx-auto mb-6">
|
|
</div>
|
|
<h3 id="modal-title" class="text-xl font-bold mb-3 text-gray-800"></h3>
|
|
<p id="modal-message" class="text-gray-600 mb-6 leading-relaxed"></p>
|
|
<button id="modal-close" class="bg-orange-500 hover:bg-orange-600 text-white px-8 py-3 rounded-xl transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
|
|
OK
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<register-block dynamic-section="scripts" key="jsScan">
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js" type="text/javascript"></script>
|
|
|
|
<script>
|
|
if (typeof Html5Qrcode === 'undefined') {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://cdn.jsdelivr.net/npm/html5-qrcode@2.3.8/html5-qrcode.min.js';
|
|
script.onerror = () => alert('Scanner library failed to load');
|
|
document.head.appendChild(script);
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
class BarcodeScanner {
|
|
constructor() {
|
|
this.isScanning = false;
|
|
this.detectedCode = null;
|
|
this.html5QrCode = null;
|
|
this.initializeElements();
|
|
this.bindEvents();
|
|
this.checkBrowserSupport();
|
|
}
|
|
|
|
initializeElements() {
|
|
this.startBtn = document.getElementById('start-scanner');
|
|
this.stopBtn = document.getElementById('stop-scanner');
|
|
this.loadingDiv = document.getElementById('loading-scanner');
|
|
this.scanResult = document.getElementById('scan-result');
|
|
this.errorMessage = document.getElementById('error-message');
|
|
this.detectedCodeSpan = document.getElementById('detected-code');
|
|
this.confirmBtn = document.getElementById('confirm-scan');
|
|
this.retryBtn = document.getElementById('retry-scan');
|
|
this.manualForm = document.getElementById('manual-form');
|
|
this.manualInput = document.getElementById('manual-barcode');
|
|
this.permissionInfo = document.getElementById('permission-info');
|
|
this.permissionDenied = document.getElementById('permission-denied');
|
|
}
|
|
|
|
bindEvents() {
|
|
this.startBtn.addEventListener('click', () => this.startScanner());
|
|
this.stopBtn.addEventListener('click', () => this.stopScanner());
|
|
this.confirmBtn.addEventListener('click', () => this.confirmScan());
|
|
this.retryBtn.addEventListener('click', () => this.retryScan());
|
|
this.manualForm.addEventListener('submit', (e) => this.handleManualSubmit(e));
|
|
|
|
$('#modal-close').on('click', () => this.hideModal());
|
|
$('#scan-modal').on('click', (e) => {
|
|
if (e.target.id === 'scan-modal') {
|
|
this.hideModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
checkBrowserSupport() {
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
this.startBtn.disabled = true;
|
|
this.startBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="x-circle"></i>Browser Tidak Didukung';
|
|
this.startBtn.classList.remove('bg-orange-500', 'hover:bg-orange-600');
|
|
this.startBtn.classList.add('bg-gray-400', 'cursor-not-allowed');
|
|
this.showError('Browser Anda tidak mendukung akses kamera. Gunakan browser modern seperti Chrome, Firefox, atau Safari.');
|
|
return;
|
|
}
|
|
|
|
if (typeof Html5Qrcode === 'undefined') {
|
|
this.startBtn.disabled = true;
|
|
this.startBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="x-circle"></i>Library Tidak Dimuat';
|
|
this.startBtn.classList.remove('bg-orange-500', 'hover:bg-orange-600');
|
|
this.startBtn.classList.add('bg-gray-400', 'cursor-not-allowed');
|
|
this.showError('Library scanner tidak dapat dimuat. Periksa koneksi internet dan refresh halaman.');
|
|
return;
|
|
}
|
|
|
|
if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
|
|
this.showError('Scanner barcode memerlukan koneksi HTTPS yang aman. Hubungi administrator sistem.');
|
|
}
|
|
}
|
|
|
|
async startScanner() {
|
|
try {
|
|
this.showLoading();
|
|
this.hideError();
|
|
this.hideResult();
|
|
this.hidePermissionMessages();
|
|
|
|
await this.initializeHtml5QrCode();
|
|
|
|
this.isScanning = true;
|
|
this.startBtn.classList.add('hidden');
|
|
this.stopBtn.classList.remove('hidden');
|
|
this.hideLoading();
|
|
|
|
} catch (error) {
|
|
this.handleScannerError(error);
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async initializeHtml5QrCode() {
|
|
try {
|
|
this.permissionInfo.classList.remove('hidden');
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
this.html5QrCode = new Html5Qrcode("scanner-container");
|
|
|
|
const cameras = await Html5Qrcode.getCameras();
|
|
|
|
if (cameras && cameras.length > 0) {
|
|
let cameraId = cameras[0].id;
|
|
|
|
const backCamera = cameras.find(camera =>
|
|
camera.label.toLowerCase().includes('back') ||
|
|
camera.label.toLowerCase().includes('rear') ||
|
|
camera.label.toLowerCase().includes('environment')
|
|
);
|
|
|
|
if (backCamera) {
|
|
cameraId = backCamera.id;
|
|
}
|
|
|
|
await this.html5QrCode.start(
|
|
cameraId,
|
|
{
|
|
fps: 10,
|
|
qrbox: function(viewfinderWidth, viewfinderHeight) {
|
|
let minEdgePercentage = 0.7;
|
|
let minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
|
|
let qrboxSize = Math.floor(minEdgeSize * minEdgePercentage);
|
|
return {
|
|
width: qrboxSize,
|
|
height: qrboxSize
|
|
};
|
|
},
|
|
aspectRatio: 1.0,
|
|
rememberLastUsedCamera: true
|
|
},
|
|
(decodedText, decodedResult) => {
|
|
this.handleBarcodeDetected(decodedText, decodedResult);
|
|
},
|
|
(errorMessage) => {
|
|
}
|
|
);
|
|
|
|
this.hidePermissionMessages();
|
|
|
|
} else {
|
|
throw new Error('No cameras found on this device');
|
|
}
|
|
|
|
} catch (error) {
|
|
this.hidePermissionMessages();
|
|
|
|
if (error.message.includes('Permission denied') ||
|
|
error.message.includes('NotAllowedError') ||
|
|
error.message.includes('permission') ||
|
|
error.name === 'NotAllowedError') {
|
|
this.permissionDenied.classList.remove('hidden');
|
|
throw new Error('Camera permission denied');
|
|
} else if (error.message.includes('No cameras found')) {
|
|
throw new Error('No camera found on this device');
|
|
} else {
|
|
throw new Error('Unable to access camera: ' + error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
handleScannerError(error) {
|
|
if (error.message.includes('permission denied') || error.message.includes('Camera permission denied')) {
|
|
this.permissionDenied.classList.remove('hidden');
|
|
} else if (error.message.includes('No camera found')) {
|
|
this.showError('Kamera tidak ditemukan pada perangkat ini.');
|
|
} else if (error.message.includes('NotReadableError')) {
|
|
this.showError('Kamera sedang digunakan aplikasi lain. Tutup aplikasi lain dan coba lagi.');
|
|
} else {
|
|
this.showError('Gagal memulai scanner. Pastikan kamera dapat diakses dan coba lagi.');
|
|
}
|
|
}
|
|
|
|
handleBarcodeDetected(decodedText, decodedResult) {
|
|
console.log(`QR Code terdeteksi: "${decodedText}"`);
|
|
console.log(`Panjang kode: ${decodedText.length}`);
|
|
|
|
if (decodedText && decodedText.length >= 5) {
|
|
console.log(`✅ Kode valid, memproses: ${decodedText}`);
|
|
this.flashSuccess();
|
|
this.playSuccessSound();
|
|
this.vibrate();
|
|
|
|
this.detectedCode = decodedText;
|
|
this.stopScanner();
|
|
|
|
this.processScanCode(decodedText);
|
|
} else {
|
|
console.log(`❌ Kode terlalu pendek: ${decodedText}`);
|
|
}
|
|
}
|
|
|
|
async stopScanner() {
|
|
if (this.isScanning && this.html5QrCode) {
|
|
try {
|
|
await this.html5QrCode.stop();
|
|
} catch (error) {
|
|
}
|
|
this.isScanning = false;
|
|
}
|
|
|
|
this.startBtn.classList.remove('hidden');
|
|
this.stopBtn.classList.add('hidden');
|
|
}
|
|
|
|
flashSuccess() {
|
|
const flash = document.createElement('div');
|
|
flash.className = 'absolute inset-0 bg-green-500 opacity-50 rounded-lg';
|
|
flash.style.zIndex = '20';
|
|
document.getElementById('scanner-container').appendChild(flash);
|
|
|
|
setTimeout(() => {
|
|
flash.remove();
|
|
}, 200);
|
|
}
|
|
|
|
vibrate() {
|
|
if ('vibrate' in navigator) {
|
|
navigator.vibrate([200]);
|
|
}
|
|
}
|
|
|
|
confirmScan() {
|
|
if (this.detectedCode) {
|
|
this.processScanCode(this.detectedCode);
|
|
}
|
|
}
|
|
|
|
processScanCode(code) {
|
|
this.showModal('loading', 'Memproses...', 'Sedang memverifikasi kode SPJ...', false);
|
|
|
|
// Testing mode - uncomment kalau testing udah selesai
|
|
this.mockResponse(code);
|
|
return;
|
|
|
|
// ini bagian ajax yang asli
|
|
/*
|
|
$.ajax({
|
|
url: @Url.Action("ProcessScan", "Scan")', // nanti tinggal ganti aja yaa
|
|
type: 'POST',
|
|
data: {
|
|
barcode: code
|
|
},
|
|
success: (response) => {
|
|
if (response.success) {
|
|
this.showModal('success', 'Scan Berhasil!', 'SPJ berhasil ditemukan dan diproses.', true);
|
|
setTimeout(() => {
|
|
this.hideResult();
|
|
this.manualInput.value = '';
|
|
}, 2000);
|
|
} else {
|
|
this.showModal('error', 'SPJ Tidak Ditemukan', response.message || 'Kode SPJ tidak ditemukan dalam database.', true);
|
|
}
|
|
},
|
|
error: (xhr, status, error) => {
|
|
let errorMessage = 'Terjadi kesalahan saat memproses scan.';
|
|
|
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
errorMessage = xhr.responseJSON.message;
|
|
} else if (xhr.status === 404) {
|
|
errorMessage = 'SPJ tidak ditemukan dalam database.';
|
|
} else if (xhr.status === 500) {
|
|
errorMessage = 'Terjadi kesalahan server. Silakan coba lagi.';
|
|
}
|
|
|
|
this.showModal('error', 'Error', errorMessage, true);
|
|
}
|
|
});
|
|
*/
|
|
}
|
|
|
|
// Mock response untuk testing
|
|
mockResponse(code) {
|
|
console.log(`Testing scan untuk kode: ${code}`);
|
|
|
|
setTimeout(() => {
|
|
const validCodes = [
|
|
'SPJ001', 'SPJ002', 'SPJ003', 'SPJ004', 'SPJ005',
|
|
'TEST123', 'TEST456', 'TEST789',
|
|
'12345', '67890', '11111', '22222',
|
|
'ABCDEF', 'GHIJKL', 'MNOPQR'
|
|
];
|
|
|
|
const isValid = validCodes.some(validCode =>
|
|
validCode.toLowerCase() === code.toLowerCase()
|
|
);
|
|
|
|
if (isValid) {
|
|
console.log(`✅ Kode ${code} VALID - menampilkan success`);
|
|
|
|
// Format tanggal Indonesia
|
|
const now = new Date();
|
|
const tanggal = now.toLocaleDateString('id-ID', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
const waktu = now.toLocaleTimeString('id-ID', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
|
|
// Custom modal untuk success dengan HTML yang lebih rapi
|
|
this.showSuccessModal(code, tanggal, waktu);
|
|
|
|
setTimeout(() => {
|
|
this.hideResult();
|
|
this.manualInput.value = '';
|
|
}, 3000);
|
|
} else {
|
|
console.log(`❌ Kode ${code} TIDAK VALID - menampilkan error`);
|
|
this.showErrorModal(code);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
async retryScan() {
|
|
this.hideResult();
|
|
this.hideError();
|
|
this.hidePermissionMessages();
|
|
this.detectedCode = null;
|
|
|
|
if (this.isScanning && this.html5QrCode) {
|
|
await this.stopScanner();
|
|
}
|
|
|
|
setTimeout(() => {
|
|
this.startScanner();
|
|
}, 500);
|
|
}
|
|
|
|
handleManualSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const code = this.manualInput.value.trim();
|
|
if (!code) {
|
|
this.showModal('error', 'Input Kosong', 'Silakan masukkan kode SPJ.', true);
|
|
return;
|
|
}
|
|
|
|
if (code.length < 5) {
|
|
this.showModal('error', 'Kode Tidak Valid', 'Kode SPJ minimal 5 karakter.', true);
|
|
return;
|
|
}
|
|
|
|
this.processScanCode(code);
|
|
}
|
|
|
|
showLoading() {
|
|
this.loadingDiv.classList.remove('hidden');
|
|
}
|
|
|
|
hideLoading() {
|
|
this.loadingDiv.classList.add('hidden');
|
|
}
|
|
|
|
showResult(code) {
|
|
this.detectedCodeSpan.textContent = code;
|
|
this.scanResult.classList.remove('hidden');
|
|
}
|
|
|
|
hideResult() {
|
|
this.scanResult.classList.add('hidden');
|
|
}
|
|
|
|
showError(message) {
|
|
document.getElementById('error-text').textContent = message;
|
|
this.errorMessage.classList.remove('hidden');
|
|
}
|
|
|
|
hideError() {
|
|
this.errorMessage.classList.add('hidden');
|
|
}
|
|
|
|
hidePermissionMessages() {
|
|
this.permissionInfo.classList.add('hidden');
|
|
this.permissionDenied.classList.add('hidden');
|
|
}
|
|
|
|
playSuccessSound() {
|
|
try {
|
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
const oscillator = audioContext.createOscillator();
|
|
const gainNode = audioContext.createGain();
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioContext.destination);
|
|
|
|
oscillator.frequency.value = 800;
|
|
oscillator.type = 'square';
|
|
|
|
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
|
|
|
|
oscillator.start(audioContext.currentTime);
|
|
oscillator.stop(audioContext.currentTime + 0.2);
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
showModal(type, title, message, showCloseButton = true) {
|
|
const iconHtml = {
|
|
'success': '<div class="w-20 h-20 mx-auto bg-green-100 rounded-full flex items-center justify-center shadow-lg"><i class="w-10 h-10 text-green-600" data-lucide="check-circle"></i></div>',
|
|
'error': '<div class="w-20 h-20 mx-auto bg-red-100 rounded-full flex items-center justify-center shadow-lg"><i class="w-10 h-10 text-red-600" data-lucide="x-circle"></i></div>',
|
|
'loading': '<div class="w-20 h-20 mx-auto bg-blue-100 rounded-full flex items-center justify-center shadow-lg"><div class="loading-spinner-modal"></div></div>'
|
|
};
|
|
|
|
$('#modal-icon').html(iconHtml[type] || iconHtml['error']);
|
|
$('#modal-title').text(title);
|
|
$('#modal-message').text(message);
|
|
|
|
if (showCloseButton) {
|
|
$('#modal-close').show();
|
|
} else {
|
|
$('#modal-close').hide();
|
|
}
|
|
|
|
$('#scan-modal').removeClass('hidden');
|
|
|
|
$('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300);
|
|
$('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({
|
|
'transform': 'scale(1)'
|
|
}, 300);
|
|
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
showSuccessModal(code, tanggal, waktu) {
|
|
// Custom success modal dengan UI yang lebih keren
|
|
const successIcon = `
|
|
<div class="w-24 h-24 mx-auto bg-gradient-to-r from-green-400 to-emerald-500 rounded-full flex items-center justify-center shadow-xl mb-4">
|
|
<i class="w-12 h-12 text-white" data-lucide="check-circle"></i>
|
|
</div>
|
|
`;
|
|
|
|
const successContent = `
|
|
<div class="bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl p-4 mb-6 border border-green-200">
|
|
<div class="flex items-center justify-center mb-3">
|
|
<div class="bg-green-100 rounded-lg px-3 py-1">
|
|
<span class="text-green-800 font-mono font-bold text-lg">${code}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3 text-sm">
|
|
<div class="bg-white rounded-lg p-3 text-center border border-green-100">
|
|
<div class="text-green-600 font-medium mb-1">Status</div>
|
|
<div class="flex items-center justify-center">
|
|
<div class="w-2 h-2 bg-green-500 rounded-full mr-2"></div>
|
|
<span class="text-gray-700 font-semibold">Aktif</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg p-3 text-center border border-green-100">
|
|
<div class="text-green-600 font-medium mb-1">Waktu Scan</div>
|
|
<div class="text-gray-700 font-semibold">${waktu}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3 bg-white rounded-lg p-3 border border-green-100">
|
|
<div class="text-green-600 font-medium mb-1 text-center">Tanggal</div>
|
|
<div class="text-gray-700 font-semibold text-center text-sm">${tanggal}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('#modal-icon').html(successIcon);
|
|
$('#modal-title').html('<span class="text-2xl">🎉 Scan Berhasil!</span>');
|
|
$('#modal-message').html(successContent);
|
|
$('#modal-close').html('<i class="w-5 h-5 mr-2" data-lucide="check"></i>Selesai').show();
|
|
|
|
$('#scan-modal').removeClass('hidden');
|
|
|
|
$('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300);
|
|
$('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({
|
|
'transform': 'scale(1)'
|
|
}, 300);
|
|
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
showErrorModal(code) {
|
|
// Custom error modal dengan UI yang lebih keren
|
|
const errorIcon = `
|
|
<div class="w-24 h-24 mx-auto bg-gradient-to-r from-red-400 to-rose-500 rounded-full flex items-center justify-center shadow-xl mb-4">
|
|
<i class="w-12 h-12 text-white" data-lucide="x-circle"></i>
|
|
</div>
|
|
`;
|
|
|
|
const errorContent = `
|
|
<div class="bg-gradient-to-r from-red-50 to-rose-50 rounded-xl p-4 mb-4 border border-red-200">
|
|
<div class="flex items-center justify-center mb-3">
|
|
<div class="bg-red-100 rounded-lg px-3 py-1">
|
|
<span class="text-red-800 font-mono font-bold">${code}</span>
|
|
</div>
|
|
</div>
|
|
<div class="text-center text-red-700">
|
|
<div class="font-semibold mb-1">Kode SPJ tidak ditemukan</div>
|
|
<div class="text-sm opacity-75">Silakan periksa kembali kode yang Anda masukkan</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('#modal-icon').html(errorIcon);
|
|
$('#modal-title').html('<span class="text-xl">❌ SPJ Tidak Ditemukan</span>');
|
|
$('#modal-message').html(errorContent);
|
|
$('#modal-close').html('<i class="w-5 h-5 mr-2" data-lucide="arrow-left"></i>Coba Lagi').show();
|
|
|
|
$('#scan-modal').removeClass('hidden');
|
|
|
|
$('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300);
|
|
$('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({
|
|
'transform': 'scale(1)'
|
|
}, 300);
|
|
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
hideModal() {
|
|
$('#scan-modal').animate({'opacity': '0'}, 200, function() {
|
|
$('#scan-modal').addClass('hidden');
|
|
$('#scan-modal').css('opacity', '');
|
|
$('#scan-modal .bg-white').css('transform', '');
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
$.ajaxSetup({
|
|
beforeSend: function(xhr, settings) {
|
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
|
xhr.setRequestHeader("RequestVerificationToken", $('input[name="__RequestVerificationToken"]').val());
|
|
}
|
|
}
|
|
});
|
|
|
|
function waitForLibrary() {
|
|
if (typeof Html5Qrcode !== 'undefined') {
|
|
new BarcodeScanner();
|
|
} else {
|
|
setTimeout(waitForLibrary, 500);
|
|
}
|
|
}
|
|
|
|
waitForLibrary();
|
|
});
|
|
</script>
|
|
</register-block> |