update: css dan regex struk

main
marszayn 2025-08-08 16:35:52 +07:00
parent 512eb82743
commit e9a82f8665
17 changed files with 1679 additions and 309 deletions

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "Detail Perjalanan - DLH"; ViewData["Title"] = "Detail Perjalanan - DLH";
} }
<div class="max-w-sm mx-auto bg-gray-50 min-h-screen"> <div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
<!-- Header --> <!-- Header -->
<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="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"> <div class="flex items-center justify-between">

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "History - DLH"; ViewData["Title"] = "History - DLH";
} }
<div class="max-w-sm mx-auto bg-gray-50 min-h-screen"> <div class="w-full lg: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="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"> <div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Admin")" 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">

View File

@ -3,8 +3,7 @@
ViewData["Title"] = "Admin Dashboard"; ViewData["Title"] = "Admin Dashboard";
} }
<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="w-full lg:max-w-sm mx-auto bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen relative overflow-hidden">
<div class="relative z-10"> <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="bg-gradient-to-br from-orange-500 via-orange-600 to-red-500 rounded-b-[2rem] shadow-2xl p-6 mb-6">

View File

@ -8,7 +8,7 @@
} }
<div class="max-w-sm mx-auto bg-white min-h-screen"> <div class="w-full lg: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="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors"> <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
@ -49,6 +49,14 @@
<p class="text-sm">Memuat scanner...</p> <p class="text-sm">Memuat scanner...</p>
</div> </div>
</div> </div>
<!-- Status indicator ketika scanning aktif -->
<div id="scanning-indicator" class="absolute top-2 left-2 bg-green-500 text-white px-3 py-1 rounded-full text-xs font-medium z-20 hidden animate-pulse">
<div class="flex items-center">
<div class="w-2 h-2 bg-white rounded-full mr-2 animate-ping"></div>
Scanner Aktif
</div>
</div>
</div> </div>
</div> </div>
@ -58,7 +66,7 @@
Mulai Scan Mulai Scan
</button> </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"> <button id="stop-scanner" class="w-full bg-red-500 hover:bg-red-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> <i class="w-5 h-5 inline mr-2" data-lucide="camera-off"></i>
Hentikan Scan Hentikan Scan
</button> </button>
@ -139,7 +147,7 @@
</div> </div>
<div id="scan-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center"> <div id="scan-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden 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="bg-white py-4 rounded-2xl shadow-2xl max-w-sm w-full border border-gray-100">
<div class="p-8 text-center"> <div class="p-8 text-center">
<div id="modal-icon" class="mx-auto mb-6"> <div id="modal-icon" class="mx-auto mb-6">
@ -190,6 +198,7 @@
this.manualInput = document.getElementById('manual-barcode'); this.manualInput = document.getElementById('manual-barcode');
this.permissionInfo = document.getElementById('permission-info'); this.permissionInfo = document.getElementById('permission-info');
this.permissionDenied = document.getElementById('permission-denied'); this.permissionDenied = document.getElementById('permission-denied');
this.scanningIndicator = document.getElementById('scanning-indicator');
} }
bindEvents() { bindEvents() {
@ -243,6 +252,7 @@
this.isScanning = true; this.isScanning = true;
this.startBtn.classList.add('hidden'); this.startBtn.classList.add('hidden');
this.stopBtn.classList.remove('hidden'); this.stopBtn.classList.remove('hidden');
this.scanningIndicator.classList.remove('hidden');
this.hideLoading(); this.hideLoading();
} catch (error) { } catch (error) {
@ -343,7 +353,7 @@
this.vibrate(); this.vibrate();
this.detectedCode = decodedText; this.detectedCode = decodedText;
this.stopScanner(); // Jangan stop scanner, biarkan tetap aktif untuk scan berikutnya
this.processScanCode(decodedText); this.processScanCode(decodedText);
} else { } else {
@ -362,6 +372,7 @@
this.startBtn.classList.remove('hidden'); this.startBtn.classList.remove('hidden');
this.stopBtn.classList.add('hidden'); this.stopBtn.classList.add('hidden');
this.scanningIndicator.classList.add('hidden');
} }
flashSuccess() { flashSuccess() {
@ -464,6 +475,7 @@
this.showSuccessModal(code, tanggal, waktu); this.showSuccessModal(code, tanggal, waktu);
// Scanner tetap aktif, tidak perlu reset tombol
setTimeout(() => { setTimeout(() => {
this.hideResult(); this.hideResult();
this.manualInput.value = ''; this.manualInput.value = '';
@ -481,10 +493,13 @@
this.hidePermissionMessages(); this.hidePermissionMessages();
this.detectedCode = null; this.detectedCode = null;
if (this.isScanning && this.html5QrCode) { // Jika scanner sudah aktif, tidak perlu restart
await this.stopScanner(); if (this.isScanning) {
console.log("Scanner sudah aktif, tidak perlu restart");
return;
} }
// Hanya restart jika scanner tidak aktif
setTimeout(() => { setTimeout(() => {
this.startScanner(); this.startScanner();
}, 500); }, 500);
@ -577,6 +592,7 @@
} }
$('#scan-modal').removeClass('hidden'); $('#scan-modal').removeClass('hidden');
$('#scan-modal').addClass('flex');
$('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300); $('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300);
$('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({ $('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({
@ -629,6 +645,7 @@
$('#modal-close').hide(); // Sembunyikan tombol $('#modal-close').hide(); // Sembunyikan tombol
$('#scan-modal').removeClass('hidden'); $('#scan-modal').removeClass('hidden');
$('#scan-modal').addClass('flex');
$('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300); $('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300);
$('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({ $('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({
@ -637,6 +654,7 @@
setTimeout(() => { setTimeout(() => {
this.hideModal(); this.hideModal();
// Scanner tetap aktif setelah modal ditutup
}, 2000); }, 2000);
} }
@ -678,6 +696,7 @@
$('#modal-close').hide(); // Sembunyikan tombol $('#modal-close').hide(); // Sembunyikan tombol
$('#scan-modal').removeClass('hidden'); $('#scan-modal').removeClass('hidden');
$('#scan-modal').addClass('flex');
$('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300); $('#scan-modal').css('opacity', '0').animate({'opacity': '1'}, 300);
$('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({ $('#scan-modal .bg-white').css('transform', 'scale(0.8)').animate({
@ -686,12 +705,14 @@
setTimeout(() => { setTimeout(() => {
this.hideModal(); this.hideModal();
// Scanner tetap aktif setelah modal error ditutup
}, 2000); }, 2000);
} }
hideModal() { hideModal() {
$('#scan-modal').animate({'opacity': '0'}, 200, function() { $('#scan-modal').animate({'opacity': '0'}, 200, function() {
$('#scan-modal').addClass('hidden'); $('#scan-modal').addClass('hidden');
$('#scan-modal').removeClass('flex');
$('#scan-modal').css('opacity', ''); $('#scan-modal').css('opacity', '');
$('#scan-modal .bg-white').css('transform', ''); $('#scan-modal .bg-white').css('transform', '');
}); });

View File

@ -1,4 +1,4 @@
<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full max-w-sm z-99"> <div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full lg:max-w-sm z-99">
<div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden"> <div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden">
<div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10"> <div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10">
<div class="w-full h-full bg-transparent relative"> <div class="w-full h-full bg-transparent relative">

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "Detail Batal Penjemputan"; ViewData["Title"] = "Detail Batal Penjemputan";
} }
<div class="max-w-sm mx-auto bg-gray-50 min-h-screen"> <div class="w-full lg: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="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"> <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", "Home")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "Detail Penjemputan"; ViewData["Title"] = "Detail Penjemputan";
} }
<div class="max-w-sm mx-auto min-h-screen"> <div class="w-full lg:max-w-sm mx-auto min-h-screen">
<div class="bg-orange-500 text-white px-4 py-6 rounded-b-3xl relative pb-12"> <div class="bg-orange-500 text-white px-4 py-6 rounded-b-3xl relative pb-12">
<div class="flex items-center justify-center relative"> <div class="flex items-center justify-center relative">
<a href="@Url.Action("Index", "Home")" class="absolute left-0 p-1 hover:bg-white/10 rounded-full transition-colors"> <a href="@Url.Action("Index", "Home")" class="absolute left-0 p-1 hover:bg-white/10 rounded-full transition-colors">
@ -97,7 +97,22 @@ document.addEventListener('DOMContentLoaded', function() {
const inputAlamat = document.getElementById('input-alamat-jalan'); const inputAlamat = document.getElementById('input-alamat-jalan');
function reverseGeocode(lat, lng) { function reverseGeocode(lat, lng) {
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`) const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const fetchOptions = {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'eSPJ-App/1.0'
}
};
if (isFirefox) {
fetchOptions.cache = 'force-cache';
fetchOptions.keepalive = true;
}
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`, fetchOptions)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
const address = data.display_name || `${lat}, ${lng}`; const address = data.display_name || `${lat}, ${lng}`;

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "Detail Perjalanan - DLH"; ViewData["Title"] = "Detail Perjalanan - DLH";
} }
<div class="max-w-sm mx-auto bg-gray-50 min-h-screen"> <div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
<!-- Header --> <!-- Header -->
<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="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"> <div class="flex items-center justify-between">

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "History - DLH"; ViewData["Title"] = "History - DLH";
} }
<div class="max-w-sm mx-auto bg-gray-50 min-h-screen"> <div class="w-full lg: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="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"> <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", "Home")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">

View File

@ -1,155 +0,0 @@
@{
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
ViewData["Title"] = "History - DLH";
}
<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">
<i class="w-5 h-5" data-lucide="chevron-left"></i>
</a>
<h1 class="text-lg font-bold">Riwayat Perjalanan</h1>
<div class="w-9"></div>
</div>
</div>
@{
var spjList = new[]
{
new {
Id = 1,
NoSpj = "SPJ/07-2025/PKM/000478",
Plat = "B 5678 ABC",
Kode = "JRC 007",
Tujuan = "Bantar Gebang",
Status = "In Progress",
Tanggal = "28 Jul 2025",
Waktu = "16:45"
},
new {
Id = 2,
NoSpj = "SPJ/07-2025/PKM/000476",
Plat = "B 9632 TOR",
Kode = "JRC 005",
Tujuan = "RDF Rorotan",
Status = "Completed",
Tanggal = "27 Jul 2025",
Waktu = "14:30"
},
new {
Id = 3,
NoSpj = "SPJ/07-2025/PKM/000477",
Plat = "B 1234 XYZ",
Kode = "JRC 006",
Tujuan = "RDF Pesanggarahan",
Status = "Completed",
Tanggal = "26 Jul 2025",
Waktu = "09:15"
},
new {
Id = 4,
NoSpj = "SPJ/07-2025/PKM/000479",
Plat = "B 9876 DEF",
Kode = "JRC 008",
Tujuan = "RDF Sunter",
Status = "Completed",
Tanggal = "25 Jul 2025",
Waktu = "11:20"
},
new {
Id = 5,
NoSpj = "SPJ/07-2025/PKM/000480",
Plat = "B 4321 GHI",
Kode = "JRC 009",
Tujuan = "Bantar Gebang",
Status = "Completed",
Tanggal = "24 Jul 2025",
Waktu = "08:45"
}
};
}
<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">
<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>
</div>
<div class="font-bold text-gray-900 text-sm">@spj.NoSpj</div>
</div>
<div class="flex flex-col items-end gap-1">
@if (spj.Status == "Completed")
{
<span class="bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
Selesai
</span>
}
else
{
<span class="bg-blue-100 text-blue-700 px-2 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
<div class="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
Berlangsung
</span>
}
</div>
</div>
<div class="bg-gray-50 rounded-xl p-3 mb-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
<i class="w-5 h-5 text-orange-600" data-lucide="car"></i>
</div>
<div>
<div class="font-bold text-gray-900 text-sm">@spj.Plat</div>
<div class="text-xs text-gray-500">@spj.Kode</div>
</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-500">@spj.Tanggal</div>
<div class="text-xs font-medium text-gray-700">@spj.Waktu</div>
</div>
</div>
</div>
<div class="flex items-center gap-3 pt-2 border-t border-gray-100">
<div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0">
<i class="w-4 h-4 text-green-600" data-lucide="map-pin"></i>
</div>
<div class="flex-1 min-w-0">
<div class="text-xs text-gray-500 mb-1">Tujuan</div>
<div class="font-semibold text-gray-900 text-sm">@spj.Tujuan</div>
</div>
<div class="p-2 hover:bg-gray-100 rounded-full transition-colors">
<i class="w-4 h-4 text-gray-400" data-lucide="chevron-right"></i>
</div>
</div>
</div>
</a>
}
</div>
<!-- Bottom Navigation -->
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
<!-- Kalau butuh tampilan kosong (jika tidak ada data) -->
@* <div class="flex flex-col items-center justify-center py-16 px-4">
<div class="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<i class="w-12 h-12 text-gray-400" data-lucide="clock"></i>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Belum Ada Riwayat</h3>
<p class="text-gray-500 text-center text-sm">Riwayat perjalanan Anda akan muncul di sini setelah melakukan perjalanan pertama.</p>
</div> *@
</div>

View File

@ -3,10 +3,10 @@
ViewData["Title"] = "Home Page"; ViewData["Title"] = "Home Page";
} }
<div class="container max-w-sm mx-auto bg-white min-h-screen"> <div class="w-full lg:max-w-sm mx-auto bg-white min-h-screen">
<!-- Header --> <!-- Header -->
<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="absolute top-0 w-full lg:max-w-sm 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"> <div class="flex flex-col">
<h1 class="text-md font-bold leading-tight text-white">Bonny Agung Putra</h1> <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> <p class="text-xs opacity-90 font-medium text-orange-100">Driver UPST</span></p>

View File

@ -82,7 +82,7 @@
</div> </div>
</div> </div>
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_NavigationAdmin.cshtml" /> <partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full max-w-sm z-99"> <div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full lg:max-w-sm z-99">
<div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden"> <div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden">
<div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10"> <div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10">
<div class="w-full h-full bg-transparent relative"> <div class="w-full h-full bg-transparent relative">

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "Submit Foto Muatan"; ViewData["Title"] = "Submit Foto Muatan";
} }
<div class="max-w-sm mx-auto bg-white min-h-screen"> <div class="w-full lg:max-w-sm mx-auto bg-white min-h-screen">
<div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-3 py-4 rounded-b-2xl relative pb-12"> <div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-3 py-4 rounded-b-2xl relative pb-12">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors"> <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" /> <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="w-full lg: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="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors"> <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
@ -253,7 +253,7 @@
</div> </div>
<register-block dynamic-section="scripts" key="jsSubmitStruk"> <register-block dynamic-section="scripts" key="jsSubmitStruk">
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.1.1/dist/tesseract.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tesseract.js@5.1.1/dist/tesseract.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const nomorStrukInput = document.getElementById('NomorStruk'); const nomorStrukInput = document.getElementById('NomorStruk');
@ -576,37 +576,40 @@
let weightOut = ''; let weightOut = '';
let weightNett = ''; let weightNett = '';
// Enhanced receipt number detection - prioritize month prefix patterns
const receiptPatterns = [ const receiptPatterns = [
/(\d{2})\s+(\d{7,})/gi, /(\d{2})_(\d{6,})/gi, // "08_7999566" - underscore pattern (highest priority)
/(\d{2})_(\d{7,})/gi, /(\d{2})\s+(\d{6,})/gi, // "08 7999566" - space pattern
/(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{7,})/gi, /(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{6,})/gi, // Context-based pattern
/(?:^|\s)(\d{7,10})(?:\s|$)/g, /(?:^|\s)(\d{6,10})(?:\s|$)/g, // Standalone number pattern
]; ];
for (const line of lines) { for (const line of lines) {
console.log(`Processing line for receipt: "${line}"`); console.log(`Processing line for receipt: "${line}"`);
const monthNumberMatch = line.match(/(\d{2})\s+(\d{7,})/); // Pattern 1: Month_Number format (08_7999566) - HIGHEST PRIORITY
if (monthNumberMatch && monthNumberMatch[2]) { const monthUnderscoreMatch = line.match(/(\d{2})_(\d{6,})/);
receiptNumber = monthNumberMatch[2]; // Take the number after space/underscore
console.log(`Found receipt number: ${receiptNumber} using month-number pattern`);
break;
}
const monthUnderscoreMatch = line.match(/(\d{2})_(\d{7,})/);
if (monthUnderscoreMatch && monthUnderscoreMatch[2]) { if (monthUnderscoreMatch && monthUnderscoreMatch[2]) {
receiptNumber = monthUnderscoreMatch[2]; // Take the number after underscore receiptNumber = monthUnderscoreMatch[2]; // Take ONLY the number after underscore
console.log(`Found receipt number: ${receiptNumber} using month-underscore pattern`); console.log(`Found receipt number: ${receiptNumber} using month-underscore pattern (removed prefix: ${monthUnderscoreMatch[1]}_)`);
break; break;
} }
// Other patterns // Pattern 2: Month Number format (08 7999566)
for (const pattern of receiptPatterns.slice(2)) { // Skip first 2 patterns already handled above const monthSpaceMatch = line.match(/(\d{2})\s+(\d{6,})/);
if (monthSpaceMatch && monthSpaceMatch[2]) {
receiptNumber = monthSpaceMatch[2]; // Take ONLY the number after space
console.log(`Found receipt number: ${receiptNumber} using month-space pattern (removed prefix: ${monthSpaceMatch[1]} )`);
break;
}
// Pattern 3: Context-based patterns (fallback)
for (const pattern of receiptPatterns.slice(2)) {
const matches = [...line.matchAll(pattern)]; const matches = [...line.matchAll(pattern)];
for (const match of matches) { for (const match of matches) {
if (match[1] && match[1].length >= 7) { if (match[1] && match[1].length >= 6) {
receiptNumber = match[1]; receiptNumber = match[1];
console.log(`Found receipt number: ${receiptNumber} using pattern: ${pattern}`); console.log(`Found receipt number: ${receiptNumber} using context pattern: ${pattern}`);
break; break;
} }
} }
@ -668,106 +671,54 @@
} }
if (!truckNumber) { if (!truckNumber) {
console.log('No truck number found in context lines, trying all lines...'); // Ambil semua karakter setelah label "No Truk" tanpa filter khusus
for (const line of lines) { for (const line of lines) {
console.log(`Scanning line for truck pattern: "${line}"`); if (line.toLowerCase().includes('no truk')) {
// Contoh: "No Truk : B 9501 TOQ"
if (line.match(/[A-Z]\s*\d+\s*[A-Z]{2,3}/i)) { const match = line.match(/no\s*truk\s*:?\s*(.+)/i);
console.log('Potential truck pattern found:', line); if (match && match[1]) {
truckNumber = match[1].trim();
for (const pattern of truckPatterns) { console.log(`Found truck number: ${truckNumber} (ambil semua karakter setelah label No Truk)`);
const match = line.match(pattern);
if (match) {
let foundTruck = '';
if (match.length === 4 && match[1] && match[2] && match[3]) {
foundTruck = `${match[1]} ${match[2]} ${match[3]}`;
} else if (match[1]) {
foundTruck = match[1].trim();
}
if (foundTruck) {
foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3');
foundTruck = foundTruck.replace(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
truckNumber = foundTruck;
console.log(`Found truck number: "${truckNumber}" using general pattern`);
break; break;
} }
} }
} }
} // Jika tidak ditemukan, fallback ke pattern lama (awalan B)
if (truckNumber) break; if (!truckNumber) {
} for (const line of lines) {
} const match = line.match(/\bB\s*\d{3,5}\s*[A-Z]{2,4}\b/i);
} if (match && match[0]) {
truckNumber = match[0].trim();
if (!truckNumber) { console.log(`Fallback truck number: ${truckNumber}`);
console.log('Still no truck number, trying loose patterns...'); break;
for (const line of lines) { }
const looseMatch = line.match(/([A-Z]{1,2})\s*(\d{3,4})\s*([A-Z]{2,3})/i); }
if (looseMatch && looseMatch[1] && looseMatch[2] && looseMatch[3]) { }
const candidate = `${looseMatch[1].toUpperCase()} ${looseMatch[2]} ${looseMatch[3].toUpperCase()}`;
console.log(`Found potential truck number with loose pattern: "${candidate}"`);
if (looseMatch[1].length <= 2 &&
looseMatch[2].length >= 3 && looseMatch[2].length <= 4 &&
looseMatch[3].length >= 2 && looseMatch[3].length <= 3) {
truckNumber = candidate;
console.log(`Accepted truck number: "${truckNumber}"`);
break;
}
}
}
}
console.log('Truck detection results:', { truckNumber });
console.log('Truck detection result:', truckNumber); console.log('Truck detection result:', truckNumber);
const assignmentPatterns = [ // Assignment detection - ambil semua karakter setelah label "Penugasan"
/(?:penugasan|assignment)[\s.:]*([A-Z\s]+?)(?:\n|$)/gi,
/(JAKARTA\s+\w+)/gi, // Specific pattern for Jakarta areas
/(BANDUNG|SURABAYA|MEDAN|SEMARANG|PALEMBANG|MAKASSAR)[\s\w]*/gi,
];
for (const line of lines) { for (const line of lines) {
console.log(`Processing line for assignment: "${line}"`); console.log(`Processing line for assignment: "${line}"`);
if (line.toLowerCase().includes('penugasan')) { if (line.toLowerCase().includes('penugasan')) {
console.log('Found penugasan line:', line); console.log('Found penugasan line:', line);
const assignmentMatch = line.match(/penugasan\s*:\s*(.+)/i); // Contoh: "Penugasan : JAKARTA TIMUR" atau "Penugasan: UPST DLH"
if (assignmentMatch && assignmentMatch[1]) { const match = line.match(/penugasan\s*:?\s*(.+)/i);
assignment = assignmentMatch[1].trim();
console.log(`Found assignment: ${assignment}`);
break;
}
}
if (line.toUpperCase().includes('JAKARTA') && line.toUpperCase().includes('BARAT')) {
assignment = 'JAKARTA BARAT';
console.log(`Found assignment: ${assignment} from Jakarta Barat line`);
break;
}
for (const pattern of assignmentPatterns) {
const match = line.match(pattern);
if (match && match[1]) { if (match && match[1]) {
assignment = match[1].trim(); assignment = match[1].trim();
console.log(`Found assignment: ${assignment} using pattern: ${pattern}`); console.log(`Found assignment: ${assignment} (ambil semua karakter setelah label Penugasan)`);
break; break;
} }
} }
if (assignment) break;
} }
console.log('Assignment detection result:', assignment); console.log('Assignment detection result:', assignment);
// Enhanced time patterns // Enhanced time patterns - prioritize YYYY-MM-DD HH:MM:SS format
const timePatterns = [ const timePatterns = [
/(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/gi, // 2025-08-02 12:35:34 (highest priority)
/(\d{1,2}\s+\w{3}\s+\d{4},\s*\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025, 08:13:51 /(\d{1,2}\s+\w{3}\s+\d{4},\s*\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025, 08:13:51
/(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025 08:13:51 /(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025 08:13:51
/(\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04/08/2025 08:13:51 /(\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04/08/2025 08:13:51
@ -780,12 +731,21 @@
// Entry time - look for "Masuk :" pattern // Entry time - look for "Masuk :" pattern
if (line.toLowerCase().includes('masuk') && line.includes(':')) { if (line.toLowerCase().includes('masuk') && line.includes(':')) {
console.log('Found masuk line:', line); console.log('Found masuk line:', line);
for (const pattern of timePatterns) {
const match = line.match(pattern); // First try to find exact YYYY-MM-DD HH:MM:SS format after "Masuk :"
if (match && match[1]) { const masukMatch = line.match(/masuk\s*:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i);
entryTime = match[1].trim(); if (masukMatch && masukMatch[1]) {
console.log(`Found entry time: ${entryTime}`); entryTime = masukMatch[1].trim();
break; console.log(`Found entry time via masuk pattern: ${entryTime} (format: YYYY-MM-DD HH:MM:SS)`);
} else {
// Fallback to general patterns
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
entryTime = match[1].trim();
console.log(`Found entry time: ${entryTime}`);
break;
}
} }
} }
// Also try to extract after "Masuk : " // Also try to extract after "Masuk : "
@ -799,19 +759,22 @@
// Exit time - look for "Keluar :" pattern // Exit time - look for "Keluar :" pattern
if (line.toLowerCase().includes('keluar') && line.includes(':')) { if (line.toLowerCase().includes('keluar') && line.includes(':')) {
console.log('Found keluar line:', line); console.log('Found keluar line:', line);
for (const pattern of timePatterns) {
const match = line.match(pattern); // First try to find exact YYYY-MM-DD HH:MM:SS format after "Keluar :"
if (match && match[1]) { const keluarMatch = line.match(/keluar\s*:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i);
exitTime = match[1].trim(); if (keluarMatch && keluarMatch[1]) {
console.log(`Found exit time: ${exitTime}`);
break;
}
}
// Also try to extract after "Keluar : "
const keluarMatch = line.match(/keluar\s*:\s*(.+)/i);
if (keluarMatch && keluarMatch[1] && !exitTime) {
exitTime = keluarMatch[1].trim(); exitTime = keluarMatch[1].trim();
console.log(`Found exit time via keluar pattern: ${exitTime}`); console.log(`Found exit time via keluar pattern: ${exitTime} (format: YYYY-MM-DD HH:MM:SS)`);
} else {
// Fallback to general patterns
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
exitTime = match[1].trim();
console.log(`Found exit time: ${exitTime}`);
break;
}
}
} }
} }
} }
@ -841,16 +804,16 @@
if (specificMatch) { if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/); const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) { if (numbers && numbers[1]) {
weightIn = numbers[1]; weightIn = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight in via specific pattern: ${weightIn} kg`); console.log(`Found weight in via specific pattern: ${weightIn} (removed kg)`);
} }
} else { } else {
// Try general patterns // Try general patterns
for (const pattern of weightPatterns) { for (const pattern of weightPatterns) {
const match = line.match(pattern); const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 1000) { if (match && match[1] && parseInt(match[1]) > 1000) {
weightIn = match[1]; weightIn = match[1]; // Hanya angka
console.log(`Found weight in via general pattern: ${weightIn} kg`); console.log(`Found weight in via general pattern: ${weightIn} (removed kg)`);
break; break;
} }
} }
@ -866,16 +829,16 @@
if (specificMatch) { if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/); const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) { if (numbers && numbers[1]) {
weightOut = numbers[1]; weightOut = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight out via specific pattern: ${weightOut} kg`); console.log(`Found weight out via specific pattern: ${weightOut} (removed kg)`);
} }
} else { } else {
// Try general patterns // Try general patterns
for (const pattern of weightPatterns) { for (const pattern of weightPatterns) {
const match = line.match(pattern); const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 1000) { if (match && match[1] && parseInt(match[1]) > 1000) {
weightOut = match[1]; weightOut = match[1]; // Hanya angka
console.log(`Found weight out via general pattern: ${weightOut} kg`); console.log(`Found weight out via general pattern: ${weightOut} (removed kg)`);
break; break;
} }
} }
@ -891,16 +854,16 @@
if (specificMatch) { if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/); const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) { if (numbers && numbers[1]) {
weightNett = numbers[1]; weightNett = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight nett via specific pattern: ${weightNett} kg`); console.log(`Found weight nett via specific pattern: ${weightNett} (removed kg)`);
} }
} else { } else {
// Try general patterns // Try general patterns
for (const pattern of weightPatterns) { for (const pattern of weightPatterns) {
const match = line.match(pattern); const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 100) { if (match && match[1] && parseInt(match[1]) > 100) {
weightNett = match[1]; weightNett = match[1]; // Hanya angka
console.log(`Found weight nett via general pattern: ${weightNett} kg`); console.log(`Found weight nett via general pattern: ${weightNett} (removed kg)`);
break; break;
} }
} }
@ -908,6 +871,24 @@
} }
} }
// Validasi Berat Nett = Berat Masuk - Berat Keluar
if (weightIn && weightOut && weightNett) {
const calculatedNett = parseInt(weightIn) - parseInt(weightOut);
const detectedNett = parseInt(weightNett);
console.log(`Weight validation: ${weightIn} - ${weightOut} = ${calculatedNett}, detected: ${detectedNett}`);
if (calculatedNett !== detectedNett) {
console.warn(`WARNING: Berat Nett tidak sesuai! Perhitungan: ${calculatedNett}, Terdeteksi: ${detectedNett}`);
// Gunakan hasil perhitungan yang benar
weightNett = calculatedNett.toString();
console.log(`Using calculated weight nett: ${weightNett}`);
// Alert akan ditampilkan di frontend saat apply data
this.weightValidationError = `Berat Nett tidak sesuai! Perhitungan: ${calculatedNett} kg, Terdeteksi: ${detectedNett} kg. Menggunakan hasil perhitungan.`;
}
}
console.log('Weight detection results:', { weightIn, weightOut, weightNett }); console.log('Weight detection results:', { weightIn, weightOut, weightNett });
console.log('Final Detected Data before assignment:', { console.log('Final Detected Data before assignment:', {
@ -960,9 +941,15 @@
} }
applyDetectedDataDirectly() { applyDetectedDataDirectly() {
console.log('=== APPLYING DATA DIRECTLY TO FORM ==='); console.log(`=== APPLYING DATA DIRECTLY TO FORM ===`);
console.log('Data to apply:', this.detectedData); console.log('Data to apply:', this.detectedData);
// Tampilkan alert jika ada error validasi berat nett
if (this.weightValidationError) {
alert(this.weightValidationError);
this.weightValidationError = null; // Reset error
}
// Get input elements by ID directly // Get input elements by ID directly
const nomorStrukInput = document.getElementById('NomorStruk'); const nomorStrukInput = document.getElementById('NomorStruk');
const nomorPolisiInput = document.getElementById('NomorPolisi'); const nomorPolisiInput = document.getElementById('NomorPolisi');

View File

@ -87,6 +87,7 @@
--color-white: #fff; --color-white: #fff;
--spacing: 0.25rem; --spacing: 0.25rem;
--container-sm: 24rem; --container-sm: 24rem;
--container-md: 28rem;
--text-xs: 0.75rem; --text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75); --text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem; --text-sm: 0.875rem;
@ -113,7 +114,6 @@
--radius-xl: 0.75rem; --radius-xl: 0.75rem;
--radius-2xl: 1rem; --radius-2xl: 1rem;
--radius-3xl: 1.5rem; --radius-3xl: 1.5rem;
--radius-4xl: 2rem;
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15); --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
--ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1);
@ -1213,9 +1213,6 @@
.rounded-3xl { .rounded-3xl {
border-radius: var(--radius-3xl); border-radius: var(--radius-3xl);
} }
.rounded-4xl {
border-radius: var(--radius-4xl);
}
.rounded-full { .rounded-full {
border-radius: calc(infinity * 1px); border-radius: calc(infinity * 1px);
} }
@ -2639,6 +2636,21 @@
outline-style: none; outline-style: none;
} }
} }
.md\:max-w-md {
@media (width >= 48rem) {
max-width: var(--container-md);
}
}
.md\:max-w-sm {
@media (width >= 48rem) {
max-width: var(--container-sm);
}
}
.lg\:max-w-sm {
@media (width >= 64rem) {
max-width: var(--container-sm);
}
}
} }
@property --tw-translate-x { @property --tw-translate-x {
syntax: "*"; syntax: "*";