update: guid tester

main
marszayn 2025-08-11 16:21:30 +07:00
parent 46229fccbb
commit d8684446e6
4 changed files with 140 additions and 88 deletions

View File

@ -7,6 +7,8 @@ namespace eSPJ.Controllers.SpjAdminController;
[Route("admin")] [Route("admin")]
public class SpjAdminController : Controller public class SpjAdminController : Controller
{ {
private static readonly Guid DummySpjGuid = new Guid("9f5b8f3a-1c2d-4a5b-9a7c-1234567890ab");
private const string DummySpjNumber = "SPJ/08-2025/PKM/000519";
[HttpGet("")] [HttpGet("")]
public IActionResult Index() public IActionResult Index()
@ -32,4 +34,58 @@ public class SpjAdminController : Controller
ViewData["Id"] = id; ViewData["Id"] = id;
return View("~/Views/Admin/Transport/SpjAdmin/History/Details.cshtml"); return View("~/Views/Admin/Transport/SpjAdmin/History/Details.cshtml");
} }
[ValidateAntiForgeryToken]
[HttpPost("scan/process/{id:guid}", Name = "Admin_Scan_Process")]
public IActionResult Process(Guid id)
{
// Dummy rule: hanya GUID dummy yang dianggap valid
if (id == DummySpjGuid)
{
return Json(new { success = true, data = new { id, status = "Valid" } });
}
return Json(new { success = false, message = "SPJ tidak ditemukan." });
}
// Resolve kode SPJ (contoh: "SPJ/08-2025/PKM/000519") menjadi GUID.
// Catatan: Saat ini dummy (development): setiap format SPJ yang valid bakal ke GUID baru.
// Integrasi produksi: ganti dengan query DB untuk mencari SPJ berdasarkan nomor, lalu kembalikan Id GUID yang sebenarnya.
[ValidateAntiForgeryToken]
[HttpPost("scan/resolve", Name = "Admin_Scan_Resolve")]
public IActionResult Resolve([FromForm] string code)
{
if (string.IsNullOrWhiteSpace(code))
{
return Json(new { success = false, message = "Kode kosong." });
}
code = code.Trim();
// Jika sudah GUID, langsung kembalikan (tetap izinkan test langsung GUID dummy)
if (Guid.TryParse(code, out var guid))
{
return Json(new { success = true, id = guid });
}
// Pola SPJ
var isSpj = System.Text.RegularExpressions.Regex.IsMatch(
code,
@"^SPJ/\d{2}-\d{4}/[A-Z]+/\d{6}$",
System.Text.RegularExpressions.RegexOptions.IgnoreCase
);
if (!isSpj)
{
return Json(new { success = false, message = "Format kode tidak dikenali." });
}
// Dummy mapping: jika persis sesuai nomor di atas, kembalikan GUID tetap; selain itu anggap tidak ditemukan
if (string.Equals(code, DummySpjNumber, StringComparison.OrdinalIgnoreCase))
{
return Json(new { success = true, id = DummySpjGuid });
}
return Json(new { success = false, message = "SPJ tidak ditemukan." });
}
} }

View File

@ -105,7 +105,7 @@
<div class="border-t pt-4"> <div class="border-t pt-4">
<h3 class="text-gray-700 font-medium mb-3">Atau input manual:</h3> <h3 class="text-gray-700 font-medium mb-3">Atau input manual:</h3>
<form id="manual-form" method="post" action="@Url.Action("ProcessScan", "Scan")"> <form id="manual-form" method="post" action="#" novalidate>
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<div class="flex gap-2"> <div class="flex gap-2">
<input type="text" <input type="text"
@ -427,94 +427,87 @@
} }
} }
processScanCode(code) { isGuid(value) {
const re = /^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/;
return re.test((value || '').trim());
}
isSpjCode(value) {
const re = /^SPJ\/\d{2}-\d{4}\/[A-Z]+\/\d{6}$/i;
return re.test((value || '').trim());
}
processUrlFor(id) {
const basePath = '@Url.Content("~/admin/scan/process/")';
return basePath + encodeURIComponent(id);
}
postGuid(id) {
const url = this.processUrlFor(id);
return $.ajax({
url: url,
type: 'POST',
headers: { 'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val() }
});
}
resolveSpj(code) {
const url = '@Url.Content("~/admin/scan/resolve")';
return $.ajax({
url: url,
type: 'POST',
headers: { 'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val() },
data: { code }
});
}
async processScanCode(code) {
this.showModal('loading', 'Memproses...', 'Sedang memverifikasi kode SPJ...', false); this.showModal('loading', 'Memproses...', 'Sedang memverifikasi kode SPJ...', false);
// Testing mode - uncomment kalau testing udah selesai try {
this.mockResponse(code); let guid = null;
return; const trimmed = (code || '').trim();
// ini bagian ajax yang asli if (this.isGuid(trimmed)) {
/* guid = trimmed;
$.ajax({ } else if (this.isSpjCode(trimmed)) {
url: @Url.Action("ProcessScan", "Scan")', // nanti tinggal ganti aja yaa // minta backend resolve SPJ ke GUID
type: 'POST', const rs = await this.resolveSpj(trimmed);
data: { if (!rs || rs.success !== true || !rs.id) {
barcode: code throw new Error((rs && rs.message) ? rs.message : 'Gagal resolve SPJ ke GUID.');
}, }
success: (response) => { guid = rs.id;
if (response.success) {
this.showModal('success', 'Scan Berhasil!', 'SPJ berhasil ditemukan dan diproses.', true);
setTimeout(() => {
this.hideResult();
this.manualInput.value = '';
}, 2000);
} else { } else {
this.showModal('error', 'SPJ Tidak Ditemukan', response.message || 'Kode SPJ tidak ditemukan dalam database.', true); throw new Error('Format kode tidak dikenali. Masukkan GUID atau nomor SPJ yang valid.');
}
},
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); // POST GUID ke process endpoint
} const resp = await this.postGuid(guid);
}); console.debug('process response', resp);
*/ if (resp && resp.success) {
}
// 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 now = new Date();
const tanggal = now.toLocaleDateString('id-ID', { const tanggal = now.toLocaleDateString('id-ID', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
weekday: 'long', const waktu = now.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
year: 'numeric', this.showSuccessModal(trimmed, tanggal, waktu);
month: 'long',
day: 'numeric'
});
const waktu = now.toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit'
});
this.showSuccessModal(code, tanggal, waktu);
// Scanner tetap aktif, tidak perlu reset tombol
setTimeout(() => {
this.hideResult();
this.manualInput.value = '';
}, 3000);
} else { } else {
console.log(`❌ Kode ${code} TIDAK VALID - menampilkan error`); this.showModal('error', 'SPJ Tidak Ditemukan', (resp && resp.message) ? resp.message : 'Kode SPJ tidak ditemukan dalam database.', true);
this.showErrorModal(code);
} }
}, 1000); } catch (xhrOrErr) {
let message = '';
if (xhrOrErr && xhrOrErr.responseJSON) {
message = xhrOrErr.responseJSON.message || 'Terjadi kesalahan saat memproses.';
} else if (xhrOrErr && xhrOrErr.responseText) {
message = xhrOrErr.responseText;
} else if (xhrOrErr instanceof Error) {
message = xhrOrErr.message;
} else {
message = 'Terjadi kesalahan saat memproses scan.';
} }
console.error('processScanCode error', xhrOrErr);
this.showModal('error', 'Error', message, true);
}
}
async retryScan() { async retryScan() {
this.hideResult(); this.hideResult();

View File

@ -32,10 +32,12 @@
<div class="upload-area border-2 border-dashed border-gray-300 rounded-xl p-6 text-center hover:border-orange-400 transition-colors cursor-pointer" id="upload-area"> <div class="upload-area border-2 border-dashed border-gray-300 rounded-xl p-6 text-center hover:border-orange-400 transition-colors cursor-pointer" id="upload-area">
<input type="file" name="FotoKondisiKendaraan" accept="image/jpeg,image/png" class="hidden" id="foto-upload"> <input type="file" name="FotoKondisiKendaraan" accept="image/jpeg,image/png" class="hidden" id="foto-upload">
<label for="foto-upload" class="cursor-pointer w-full block"> <label for="foto-upload" class="cursor-pointer w-full block">
<div class="relative w-full h-32 bg-gray-100 rounded-xl flex items-center justify-center mx-auto mb-3 overflow-hidden preview-container" id="preview-container"> <div class="relative w-full h-60 bg-center bg-gray-100 rounded-xl flex items-center justify-center mx-auto mb-3 overflow-hidden preview-container" id="preview-container">
<div id="default-state"> <div id="default-state">
<div class="upload-icon-container"> <div class="upload-icon-container">
<i class="w-8 h-8 text-orange-600" data-lucide="upload-cloud" id="preview-icon"></i> <img src="~/driver/images/trukk.jpg" alt="contoh gambar">
<p class="absolute inset-0 flex items-center justify-center text-white">Contoh Foto</p>
@* <i class="w-8 h-8 text-orange-600" data-lucide="upload-cloud" id="preview-icon"></i> *@
</div> </div>
</div> </div>
<img id="preview-image" src="#" alt="Preview" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;" /> <img id="preview-image" src="#" alt="Preview" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;" />
@ -73,6 +75,7 @@
<div class="location-badge rounded-2xl p-4"> <div class="location-badge rounded-2xl p-4">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<div class="w-8 h-8 bg-gradient-to-br from-blue-100 to-blue-200 rounded-full flex items-center justify-center"> <div class="w-8 h-8 bg-gradient-to-br from-blue-100 to-blue-200 rounded-full flex items-center justify-center">
<i class="w-4 h-4 text-blue-600" data-lucide="map-pin"></i> <i class="w-4 h-4 text-blue-600" data-lucide="map-pin"></i>
</div> </div>
<span class="text-sm font-medium text-gray-700">Lokasi Anda:</span> <span class="text-sm font-medium text-gray-700">Lokasi Anda:</span>

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB