Compare commits

..

6 Commits

Author SHA1 Message Date
marszayn 9a3d8de8a6 update: testing admin scan 2025-08-06 10:31:09 +07:00
marszayn bed92f8ab0 update: admin scan 2025-08-06 10:29:29 +07:00
marszayn a677ad959e update: qr tester admin lagi 2025-08-06 10:18:33 +07:00
marszayn c0e8aeccbc update: qr tester admin 2025-08-06 10:08:18 +07:00
marszayn 5ef8ff38e8 update: tester qr 2025-08-06 09:58:32 +07:00
marszayn 6080584ac5 update: admin scan 2025-08-06 09:48:31 +07:00
7 changed files with 1387 additions and 263 deletions

View File

@ -25,4 +25,11 @@ public class AdminController : Controller
{
return View("~/Views/Admin/Transport/SpjAdmin/History/Index.cshtml");
}
[HttpGet("history/details/{id}")]
public IActionResult Details(int id)
{
ViewData["Id"] = id;
return View("~/Views/Admin/Transport/SpjAdmin/History/Details.cshtml");
}
}

View File

@ -23,8 +23,8 @@
<i class="w-5 h-5 text-orange-600" data-lucide="file-text"></i>
</div>
<div>
<h2 class="text-lg font-bold text-gray-900">Data SPJ</h2>
<p class="text-xs text-gray-500">Surat Perintah Jalan</p>
<h2 class="text-lg font-bold text-gray-900">Muhammad Yusuf</h2>
<p class="text-xs text-gray-500">Data SPJ</p>
</div>
</div>

View File

@ -6,10 +6,10 @@
<div class="max-w-sm mx-auto bg-gray-50 min-h-screen">
<div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-4 py-4 sticky top-0 z-10 shadow-lg">
<div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">
<a href="@Url.Action("Index", "Admin")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">
<i class="w-5 h-5" data-lucide="chevron-left"></i>
</a>
<h1 class="text-lg font-bold">Riwayat Perjalanan</h1>
<h1 class="text-lg font-bold">Riwayat SPJ</h1>
<div class="w-9"></div>
</div>
</div>
@ -19,6 +19,7 @@
{
new {
Id = 1,
Nama = "Yusuf",
NoSpj = "SPJ/07-2025/PKM/000478",
Plat = "B 5678 ABC",
Kode = "JRC 007",
@ -29,6 +30,7 @@
},
new {
Id = 2,
Nama = "Ahmad Rizki",
NoSpj = "SPJ/07-2025/PKM/000476",
Plat = "B 9632 TOR",
Kode = "JRC 005",
@ -39,6 +41,7 @@
},
new {
Id = 3,
Nama = "Siti Aminah",
NoSpj = "SPJ/07-2025/PKM/000477",
Plat = "B 1234 XYZ",
Kode = "JRC 006",
@ -49,6 +52,7 @@
},
new {
Id = 4,
Nama = "Budi Santoso",
NoSpj = "SPJ/07-2025/PKM/000479",
Plat = "B 9876 DEF",
Kode = "JRC 008",
@ -59,6 +63,7 @@
},
new {
Id = 5,
Nama = "Dewi Lestari",
NoSpj = "SPJ/07-2025/PKM/000480",
Plat = "B 4321 GHI",
Kode = "JRC 009",
@ -73,15 +78,15 @@
<div class="px-4 py-4 space-y-3">
@foreach (var spj in spjList)
{
<a href="@Url.Action("Details", "History", new { id = spj.Id })" class="block">
<a href="@Url.Action("Details", "Admin", new { id = spj.Id })" class="block">
<div class="bg-white rounded-2xl p-4 shadow-sm border border-gray-100 hover:shadow-lg hover:border-orange-200 transition-all duration-300 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-orange-400 to-orange-500"></div>
<div class="flex items-start justify-between mb-3">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<div class="w-2 h-2 bg-orange-400 rounded-full"></div>
<span class="text-xs font-medium text-gray-500 uppercase tracking-wider">No. SPJ</span>
<i class="w-4 h-4 text-green-600" data-lucide="user"></i>
<span class="text-xs font-medium text-gray-500 uppercase tracking-wider">@spj.Nama</span>
</div>
<div class="font-bold text-gray-900 text-sm">@spj.NoSpj</div>
</div>

View File

@ -1,220 +1,183 @@
@{
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
ViewData["Title"] = "Home Page";
ViewData["Title"] = "Admin Dashboard";
}
<div class="container max-w-sm mx-auto bg-white min-h-screen">
<div class="container max-w-sm mx-auto bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen relative overflow-hidden">
<div class="absolute top-0 max-w-sm container mx-auto bg-orange-500 text-white rounded-br-[125px] h-[250px] flex flex-row justify-between items-start px-6 py-6 shadow-lg z-20">
<div class="flex flex-col">
<h1 class="text-md font-bold leading-tight text-white">Bonny Agung Putra</h1>
<p class="text-xs opacity-90 font-medium text-orange-100">Driver UPST</span></p>
<div class="mt-5 flex items-center gap-2">
<i class="w-4 h-4 text-white" data-lucide="map-pin"></i>
<span class="text-sm opacity-90">Lokasi Anda:</span>
</div>
<p id="userLocation" class="font-semibold text-xs tracking-wide cursor-pointer underline text-white hover:text-orange-200 transition">
Mendeteksi lokasi...
</p>
</div>
<div class="flex items-center justify-center">
<div class="relative flex flex-col items-center justify-center">
<div class="w-12 h-12 rounded-full border-3 border-white overflow-hidden shadow-md flex items-center justify-center cursor-pointer group" id="profileMenuButton">
<i class="w-8 h-8 text-white" data-lucide="user"></i>
</div>
<div id="profileMenuDropdown" class="absolute top-12 right-0 mt-2 w-32 bg-white rounded shadow-lg py-2 z-50 hidden">
<form method="post" asp-controller="Auth" asp-action="Logout">
<button type="submit" class="hover:cursor-pointer flex items-center gap-2 w-full text-left px-4 py-2 text-sm font-semibold text-red-600 hover:bg-red-50 transition rounded-md">
<i class="w-4 h-4" data-lucide="log-out"></i>
Logout
</button>
</form>
</div>
</div>
</div>
</div>
<div class="bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 mx-4 px-6 py-12 mt-40 rounded-3xl relative overflow-hidden shadow-2xl z-21 border border-slate-200">
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
<div class="absolute top-4 right-8 w-6 h-6 bg-orange-300 rounded-full opacity-60 animate-bounce" style="animation-delay: 0.5s;"></div>
<div class="absolute top-12 left-12 w-4 h-4 bg-blue-400 rounded-full opacity-40 animate-pulse" style="animation-delay: 1s;"></div>
<div class="absolute bottom-8 left-8 w-5 h-5 bg-indigo-300 rounded-full opacity-50 animate-bounce" style="animation-delay: 1.5s;"></div>
<div class="absolute bottom-16 right-16 w-3 h-3 bg-slate-400 rounded-full opacity-30 animate-pulse" style="animation-delay: 2s;"></div>
</div>
<div class="relative z-10">
<div class="bg-gradient-to-br from-orange-500 via-orange-600 to-red-500 rounded-b-[2rem] shadow-2xl p-6 mb-6">
<div class="relative z-10 text-center">
<div class="w-24 h-24 mx-auto mb-6 relative">
<div class="w-full h-full bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center shadow-xl animate-pulse">
<div class="w-20 h-20 bg-white rounded-full flex items-center justify-center">
<i class="w-10 h-10 text-orange-500" data-lucide="clipboard-list"></i>
</div>
</div>
<div class="absolute inset-0 border-4 border-orange-300 rounded-full animate-ping opacity-30"></div>
</div>
<div class="relative z-10 pt-8 pb-4">
<div class="space-y-4">
<h2 class="text-xl font-bold bg-gradient-to-r from-slate-700 to-slate-900 bg-clip-text text-transparent">
Belum Ada SPJ
</h2>
<p class="text-sm text-slate-600 leading-relaxed px-4 mb-2">
Anda belum memiliki <span class="font-semibold text-orange-600">Surat Perintah Jalan</span> yang aktif saat ini.
</p>
</div>
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20 shadow-lg">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="relative">
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-white to-orange-100 p-1 shadow-lg">
<div class="w-full h-full rounded-xl bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center shadow-inner">
<i class="w-8 h-8 text-white" data-lucide="user"></i>
</div>
</div>
<div class="absolute -bottom-1 -right-1 w-5 h-5 bg-green-400 rounded-full border-2 border-white shadow-sm">
<div class="w-full h-full bg-green-500 rounded-full animate-pulse"></div>
</div>
</div>
<div class="bg-white/70 backdrop-blur-sm border border-white/30 rounded-2xl p-5 mb-6 text-left">
<div class="space-y-2 text-xs text-slate-600">
<div class="flex items-start gap-2">
<div class="w-1 h-1 bg-orange-400 rounded-full mt-1.5 flex-shrink-0"></div>
<p>SPJ akan diterbitkan oleh admin sesuai jadwal kerja</p>
<div class="flex flex-col">
<h1 class="text-xl font-bold text-white leading-tight">Yusuf</h1>
<div class="flex items-center space-x-2">
<span class="bg-orange-700/30 text-orange-100 px-3 py-1 rounded-full text-sm font-base backdrop-blur-sm border border-orange-400/20">
Administrator
</span>
</div>
</div>
</div>
<div class="flex items-start gap-2">
<div class="w-1 h-1 bg-orange-400 rounded-full mt-1.5 flex-shrink-0"></div>
<p>Periksa koneksi internet dan aktifkan lokasi GPS</p>
<div class="relative">
<button id="profileMenuButton" class="w-12 h-12 rounded-xl bg-white/20 backdrop-blur-sm border border-white/30 flex items-center justify-center hover:bg-white/30 transition-all duration-300 group shadow-lg hover:shadow-xl hover:scale-105">
<i class="w-5 h-5 text-white group-hover:rotate-180 transition-transform duration-300" data-lucide="settings"></i>
</button>
<div id="profileMenuDropdown" class="absolute top-14 right-0 w-48 bg-white rounded-xl shadow-2xl py-2 z-50 hidden border border-gray-100 overflow-hidden">
<div class="px-4 py-3 border-b border-gray-100">
<p class="text-sm font-semibold text-gray-900">Yusuf</p>
<p class="text-xs text-gray-500">Administrator</p>
</div>
<div class="py-1">
<div class="border-t border-gray-100 mt-1"></div>
<form method="post" asp-controller="Auth" asp-action="Logout">
<button type="submit" class="flex items-center gap-3 w-full text-left px-4 py-2 text-sm font-semibold text-red-600 hover:bg-red-50 transition-colors">
<i class="w-4 h-4 text-red-500" data-lucide="log-out"></i>
<span>Logout</span>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<button id="refreshButton" class="bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-6 py-3 rounded-2xl text-sm font-bold shadow-lg hover:shadow-xl transition-all duration-300 flex items-center gap-2 mx-auto transform hover:scale-105">
<i class="w-4 h-4" data-lucide="refresh-cw" id="refreshIcon"></i>
<span id="refreshText">Refresh Halaman</span>
</button>
<div class="mt-4 text-center">
<h2 class="text-white/90 text-lg font-semibold">Selamat Datang!</h2>
<p class="text-orange-100/70 text-sm">Siap kelola sistem eSPJ dengan efisien</p>
</div>
</div>
</div>
<div class="px-6 mb-6">
<div class="grid grid-cols-2 gap-4">
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-xs font-medium">Total Driver</p>
<p class="text-2xl font-bold text-gray-900">10</p>
</div>
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<i class="w-5 h-5 text-green-600" data-lucide="users"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-xs font-medium">Total SPJ</p>
<p class="text-2xl font-bold text-gray-900">15</p>
</div>
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<i class="w-5 h-5 text-blue-600" data-lucide="map"></i>
</div>
</div>
</div>
</div>
</div>
<partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
</div>
<partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
</div>
<register-block dynamic-section="scripts" key="jsHomeKosong">
<register-block dynamic-section="scripts" key="jsHomeAdmin">
<style>
@@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
@@keyframes shimmer {
0% { background-position: -200px 0; }
100% { background-position: calc(200px + 100%) 0; }
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-10px) rotate(2deg); }
}
.float-animation {
animation: float 3s ease-in-out infinite;
}
.shimmer {
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
background-size: 200px 100%;
animation: shimmer 2s infinite;
}
.refresh-spin {
animation: spin 1s linear infinite;
}
@@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
.float-animation-delayed {
animation: float 4s ease-in-out infinite;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
const userLocationEl = document.getElementById("userLocation");
function reverseGeocode(lat, lng) {
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
.then(res => res.json())
.then(data => {
const address = data.display_name || `${lat}, ${lng}`;
userLocationEl.textContent = address;
localStorage.setItem("user_latitude", lat);
localStorage.setItem("user_longitude", lng);
localStorage.setItem("user_address", address);
})
.catch(() => {
userLocationEl.textContent = `${lat}, ${lng}`;
});
}
function getLocationUpdate() {
if ("geolocation" in navigator) {
userLocationEl.textContent = "Mendeteksi lokasi baru...";
navigator.geolocation.getCurrentPosition(
function (position) {
const lat = position.coords.latitude.toFixed(6);
const lng = position.coords.longitude.toFixed(6);
reverseGeocode(lat, lng);
},
function () {
userLocationEl.textContent = "Lokasi tidak diizinkan";
}
);
} else {
userLocationEl.textContent = "Browser tidak mendukung lokasi";
}
}
const savedAddress = localStorage.getItem("user_address");
if (savedAddress) {
userLocationEl.textContent = savedAddress;
} else {
getLocationUpdate();
}
userLocationEl.addEventListener("click", function () {
getLocationUpdate();
});
const refreshBtn = document.getElementById('refreshButton');
const refreshIcon = document.getElementById('refreshIcon');
const refreshText = document.getElementById('refreshText');
if (refreshBtn) {
refreshBtn.addEventListener("click", function() {
refreshIcon.style.animation = 'spin 1s linear infinite';
refreshText.textContent = 'Memuat...';
refreshBtn.disabled = true;
refreshBtn.style.opacity = '0.8';
setTimeout(() => {
location.reload();
}, 1000);
});
}
const mainIcon = document.querySelector('[data-lucide="clipboard-list"]');
if (mainIcon) {
mainIcon.closest('.w-24').classList.add('float-animation');
}
const mainCard = document.querySelector('.bg-gradient-to-br');
if (mainCard) {
mainCard.style.opacity = '0';
mainCard.style.transform = 'translateY(20px)';
mainCard.style.transition = 'all 0.6s ease-out';
setTimeout(() => {
mainCard.style.opacity = '1';
mainCard.style.transform = 'translateY(0)';
}, 100);
}
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const btn = document.getElementById("profileMenuButton");
const dropdown = document.getElementById("profileMenuDropdown");
btn.addEventListener("click", function (e) {
e.stopPropagation();
dropdown.classList.toggle("hidden");
});
if (btn && dropdown) {
btn.addEventListener("click", function (e) {
e.stopPropagation();
e.preventDefault();
if (dropdown.classList.contains("hidden")) {
dropdown.classList.remove("hidden");
dropdown.style.opacity = "0";
dropdown.style.transform = "translateY(-10px) scale(0.95)";
setTimeout(() => {
dropdown.style.transition = "all 0.2s ease-out";
dropdown.style.opacity = "1";
dropdown.style.transform = "translateY(0) scale(1)";
}, 10);
} else {
dropdown.style.transition = "all 0.15s ease-in";
dropdown.style.opacity = "0";
dropdown.style.transform = "translateY(-10px) scale(0.95)";
setTimeout(() => {
dropdown.classList.add("hidden");
}, 150);
}
});
document.addEventListener("click", function () {
dropdown.classList.add("hidden");
document.addEventListener("click", function (e) {
if (!btn.contains(e.target) && !dropdown.contains(e.target)) {
dropdown.style.transition = "all 0.15s ease-in";
dropdown.style.opacity = "0";
dropdown.style.transform = "translateY(-10px) scale(0.95)";
setTimeout(() => {
dropdown.classList.add("hidden");
}, 150);
}
});
dropdown.addEventListener("click", function (e) {
e.stopPropagation();
});
}
const statCards = document.querySelectorAll('.grid > div');
statCards.forEach((card, index) => {
card.style.opacity = "0";
card.style.transform = "translateY(20px)";
setTimeout(() => {
card.style.transition = "all 0.3s ease-out";
card.style.opacity = "1";
card.style.transform = "translateY(0)";
}, 300 + (index * 100));
});
});
</script>

View File

@ -93,26 +93,12 @@
</div>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 text-sm">
<div class="flex items-start">
<i class="w-4 h-4 text-gray-600 mr-2 mt-0.5" data-lucide="lightbulb"></i>
<div class="text-gray-700">
<p class="font-medium mb-1">Tips Scanning:</p>
<ul class="text-xs space-y-1">
<li>• Pastikan QR code dalam pencahayaan yang cukup</li>
<li>• Jaga jarak 15-30cm dari kamera</li>
<li>• Arahkan kamera secara tegak lurus ke QR code</li>
<li>• Pastikan QR code tidak buram atau rusak</li>
<li>• <strong>Klik "Izinkan/Allow" saat browser meminta akses kamera</strong></li>
</ul>
</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"
@ -152,7 +138,23 @@
<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>
@ -196,6 +198,13 @@
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() {
@ -324,15 +333,21 @@
}
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.showResult(decodedText);
this.stopScanner();
this.playSuccessSound();
this.stopScanner();
this.vibrate();
this.processScanCode(decodedText);
} else {
console.log(`❌ Kode terlalu pendek: ${decodedText}`);
}
}
@ -368,11 +383,98 @@
confirmScan() {
if (this.detectedCode) {
this.manualInput.value = this.detectedCode;
this.manualForm.submit();
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'
});
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();
@ -389,18 +491,20 @@
}
handleManualSubmit(e) {
e.preventDefault();
const code = this.manualInput.value.trim();
if (!code) {
e.preventDefault();
this.showError('Silakan masukkan kode SPJ.');
this.showModal('error', 'Input Kosong', 'Silakan masukkan kode SPJ.', true);
return;
}
if (code.length < 5) {
e.preventDefault();
this.showError('Kode SPJ minimal 5 karakter.');
this.showModal('error', 'Kode Tidak Valid', 'Kode SPJ minimal 5 karakter.', true);
return;
}
this.processScanCode(code);
}
showLoading() {
@ -454,9 +558,185 @@
} 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"><svg class="w-10 h-10 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg></div>',
'error': '<div class="w-20 h-20 mx-auto bg-red-100 rounded-full flex items-center justify-center shadow-lg"><svg class="w-10 h-10 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg></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 (!showCloseButton && type === 'loading') {
setTimeout(() => {
this.hideModal();
}, 3000);
}
}
showSuccessModal(code, tanggal, waktu) {
const successIcon = `
<div class="w-28 h-28 mx-auto bg-gradient-to-r from-green-400 to-emerald-500 rounded-full flex items-center justify-center shadow-xl mb-6 animate-pulse">
<svg class="w-16 h-16 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
</svg>
</div>
`;
const successContent = `
<div class="bg-gradient-to-br from-green-50 via-emerald-50 to-teal-50 rounded-2xl p-6 border border-green-200 shadow-inner">
<div class="flex items-center justify-center mb-4">
<div class="bg-gradient-to-r from-green-500 to-emerald-600 text-white rounded-xl px-4 py-2 shadow-lg">
<span class="font-mono font-bold text-xl tracking-wider">${code}</span>
</div>
</div>
<div class="space-y-4">
<div class="bg-white rounded-xl p-4 shadow-sm border border-green-100">
<div class="flex items-center justify-center">
<div class="flex items-center space-x-3">
<div class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-gray-700 font-semibold text-lg">Status Aktif</span>
<div class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="bg-white rounded-xl p-4 text-center shadow-sm border border-green-100">
<div class="text-green-600 font-medium mb-2 text-sm">⏰ Waktu Scan</div>
<div class="text-gray-800 font-bold text-lg">${waktu}</div>
</div>
<div class="bg-white rounded-xl p-4 text-center shadow-sm border border-green-100">
<div class="text-green-600 font-medium mb-2 text-sm">📅 Tanggal</div>
<div class="text-gray-800 font-bold text-sm leading-tight">${tanggal}</div>
</div>
</div>
</div>
</div>
`;
$('#modal-icon').html(successIcon);
$('#modal-title').html('<span class="text-2xl font-bold bg-gradient-to-r from-green-600 to-emerald-600 bg-clip-text text-transparent">🎉 Scan Berhasil!</span>');
$('#modal-message').html(successContent);
$('#modal-close').hide(); // Sembunyikan tombol
$('#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);
setTimeout(() => {
this.hideModal();
}, 2000);
}
showErrorModal(code) {
const errorIcon = `
<div class="w-28 h-28 mx-auto bg-gradient-to-r from-red-400 to-rose-500 rounded-full flex items-center justify-center shadow-xl mb-6 animate-pulse">
<svg class="w-16 h-16 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
`;
const errorContent = `
<div class="bg-gradient-to-br from-red-50 via-rose-50 to-pink-50 rounded-2xl p-6 border border-red-200 shadow-inner">
<div class="flex items-center justify-center mb-4">
<div class="bg-gradient-to-r from-red-500 to-rose-600 text-white rounded-xl px-4 py-2 shadow-lg">
<span class="font-mono font-bold text-xl tracking-wider">${code}</span>
</div>
</div>
<div class="space-y-4">
<div class="bg-white rounded-xl p-4 shadow-sm border border-red-100">
<div class="text-center">
<div class="flex items-center justify-center mb-2">
<svg class="w-6 h-6 text-red-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
</svg>
<span class="text-red-700 font-bold text-lg">SPJ Tidak Ditemukan</span>
</div>
<div class="text-red-600 text-sm opacity-90 leading-relaxed">
Kode yang Anda masukkan tidak terdaftar dalam sistem.<br>
Silakan periksa kembali atau hubungi administrator.
</div>
</div>
</div>
<div class="bg-yellow-50 rounded-xl p-4 border border-yellow-200">
<div class="flex items-start">
<svg class="w-5 h-5 text-yellow-600 mr-2 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<div class="text-yellow-800 text-sm">
<div class="font-semibold mb-1">💡 Tips:</div>
<div class="text-xs leading-relaxed">
• Pastikan QR Code dalam kondisi baik<br>
• Coba scan ulang dengan pencahayaan yang cukup<br>
• Gunakan input manual jika diperlukan
</div>
</div>
</div>
</div>
</div>
</div>
`;
$('#modal-icon').html(errorIcon);
$('#modal-title').html('<span class="text-xl font-bold bg-gradient-to-r from-red-600 to-rose-600 bg-clip-text text-transparent">❌ Scan Gagal</span>');
$('#modal-message').html(errorContent);
$('#modal-close').hide(); // Sembunyikan tombol
$('#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);
setTimeout(() => {
this.hideModal();
}, 2000);
}
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();

View File

@ -1,3 +1,94 @@
/* Modal animations and enhancements */
#scan-modal {
backdrop-filter: blur(8px);
transition: all 0.3s ease;
}
#scan-modal .bg-white {
transform-origin: center center;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
/* Enhanced modal auto-hide animation */
.modal-auto-hide {
animation: modalAutoHide 0.4s ease-in-out;
}
@keyframes modalAutoHide {
0% {
opacity: 1;
transform: scale(1);
}
80% {
opacity: 1;
transform: scale(1.02);
}
100% {
opacity: 0;
transform: scale(0.95);
}
}
/* Success modal specific animations */
.success-gradient-bg {
background: linear-gradient(135deg, #10b981, #059669, #047857);
animation: gradientShift 2s ease-in-out;
}
@keyframes gradientShift {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* Error modal specific animations */
.error-gradient-bg {
background: linear-gradient(135deg, #ef4444, #dc2626, #b91c1c);
animation: gradientShift 2s ease-in-out;
}
/* Enhanced pulse animation for icons */
.icon-pulse {
animation: iconPulse 1.5s ease-in-out infinite;
}
@keyframes iconPulse {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
/* Card shadow effects */
.card-shadow-success {
box-shadow: 0 10px 25px rgba(16, 185, 129, 0.2);
}
.card-shadow-error {
box-shadow: 0 10px 25px rgba(239, 68, 68, 0.2);
}
/* Responsive modal improvements */
@media (max-width: 640px) {
#scan-modal .bg-white {
margin: 16px;
max-width: calc(100vw - 32px);
}
#scan-modal .bg-white .space-y-4 > div {
margin-bottom: 12px;
}
}
.scanner-container {
position: relative;
background: #1a1a1a;
@ -86,6 +177,15 @@
animation: spin 1s linear infinite;
}
.loading-spinner-modal {
border: 3px solid rgba(59, 130, 246, 0.3);
border-top: 3px solid #3b82f6;
border-radius: 50%;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);

File diff suppressed because it is too large Load Diff