update: fixing
|
@ -1,22 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace pesapakawan.Controllers.SpjDriverController
|
|
||||||
{
|
|
||||||
[Route("detail-penjemputan")]
|
|
||||||
public class DetailPenjemputanController : Controller
|
|
||||||
{
|
|
||||||
|
|
||||||
[HttpGet("")]
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/DetailPenjemputan/Index.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("batal")]
|
|
||||||
public IActionResult Batal()
|
|
||||||
{
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/DetailPenjemputan/Batal.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace pesapakawan.Controllers.SpjDriverController
|
|
||||||
{
|
|
||||||
[Route("history")]
|
|
||||||
public class HistoryController : Controller
|
|
||||||
{
|
|
||||||
|
|
||||||
[HttpGet("")]
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/History/Index.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("details/{id}")]
|
|
||||||
public IActionResult Details(int id)
|
|
||||||
{
|
|
||||||
ViewData["Id"] = id;
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/History/Details.cshtml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
|
|
||||||
namespace pesapakawan.Controllers.SpjDriverController
|
|
||||||
{
|
|
||||||
[Route("login")]
|
|
||||||
public class LoginController : Controller
|
|
||||||
{
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
|
|
||||||
public LoginController(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("")]
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
ViewBag.SSOLoginUrl = _configuration["SSO:LoginUrl"];
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/Login/Index.cshtml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace pesapakawan.Controllers.SpjDriverController
|
|
||||||
{
|
|
||||||
[Route("profil")]
|
|
||||||
public class ProfilController : Controller
|
|
||||||
{
|
|
||||||
|
|
||||||
[HttpGet("")]
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/Profil/Index.cshtml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,4 +30,89 @@ public class SpjDriverController : Controller
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/DetailPenjemputan/Batal.cshtml");
|
return View("~/Views/Admin/Transport/SpjDriver/DetailPenjemputan/Batal.cshtml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("history")]
|
||||||
|
public IActionResult History()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriver/History/Index.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("history/details/{id}")]
|
||||||
|
public IActionResult HistoryDetails(int id)
|
||||||
|
{
|
||||||
|
ViewData["Id"] = id;
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriver/History/Details.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("submit")]
|
||||||
|
public IActionResult Submit()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriver/Submit/Index.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("/submit/struk")]
|
||||||
|
public IActionResult Struk()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriver/Submit/Struk.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("struk")]
|
||||||
|
public IActionResult ProcessStruk(string NomorStruk, string NomorPolisi, string Penugasan,
|
||||||
|
string WaktuMasuk, string WaktuKeluar, int? BeratMasuk, int? BeratKeluar, int BeratNett)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(NomorStruk) || BeratNett <= 0)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Nomor struk dan berat nett harus diisi.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!System.Text.RegularExpressions.Regex.IsMatch(NomorStruk, @"^\d{7,}$"))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 7 digit.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeratNett < 100 || BeratNett > 50000)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Berat nett harus antara 100 kg - 50,000 kg.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeratMasuk.HasValue && (BeratMasuk < 0 || BeratMasuk > 100000))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Berat masuk tidak valid.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeratKeluar.HasValue && (BeratKeluar < 0 || BeratKeluar > 100000))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Berat keluar tidak valid.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
var submitData = new
|
||||||
|
{
|
||||||
|
NomorStruk,
|
||||||
|
NomorPolisi = NomorPolisi ?? "N/A",
|
||||||
|
Penugasan = Penugasan ?? "N/A",
|
||||||
|
WaktuMasuk = WaktuMasuk ?? "N/A",
|
||||||
|
WaktuKeluar = WaktuKeluar ?? "N/A",
|
||||||
|
BeratMasuk = BeratMasuk?.ToString() ?? "N/A",
|
||||||
|
BeratKeluar = BeratKeluar?.ToString() ?? "N/A",
|
||||||
|
BeratNett
|
||||||
|
};
|
||||||
|
|
||||||
|
TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, Nett: {BeratNett} kg";
|
||||||
|
return RedirectToAction("Index", "Home");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Terjadi kesalahan saat memproses struk. Silakan coba lagi.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace pesapakawan.Controllers.SpjDriverController
|
|
||||||
{
|
|
||||||
[Route("submit")]
|
|
||||||
public class SubmitController : Controller
|
|
||||||
{
|
|
||||||
|
|
||||||
[HttpGet("")]
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/Submit/Index.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("struk")]
|
|
||||||
public IActionResult Struk()
|
|
||||||
{
|
|
||||||
return View("~/Views/Admin/Transport/SpjDriver/Submit/Struk.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("struk")]
|
|
||||||
public IActionResult ProcessStruk(string NomorStruk, string NomorPolisi, string Penugasan,
|
|
||||||
string WaktuMasuk, string WaktuKeluar, int? BeratMasuk, int? BeratKeluar, int BeratNett)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Validate required inputs
|
|
||||||
if (string.IsNullOrEmpty(NomorStruk) || BeratNett <= 0)
|
|
||||||
{
|
|
||||||
TempData["Error"] = "Nomor struk dan berat nett harus diisi.";
|
|
||||||
return RedirectToAction("Struk");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate receipt number format (numbers only, 7+ digits)
|
|
||||||
if (!System.Text.RegularExpressions.Regex.IsMatch(NomorStruk, @"^\d{7,}$"))
|
|
||||||
{
|
|
||||||
TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 7 digit.";
|
|
||||||
return RedirectToAction("Struk");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate weight range
|
|
||||||
if (BeratNett < 100 || BeratNett > 50000)
|
|
||||||
{
|
|
||||||
TempData["Error"] = "Berat nett harus antara 100 kg - 50,000 kg.";
|
|
||||||
return RedirectToAction("Struk");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate optional weights
|
|
||||||
if (BeratMasuk.HasValue && (BeratMasuk < 0 || BeratMasuk > 100000))
|
|
||||||
{
|
|
||||||
TempData["Error"] = "Berat masuk tidak valid.";
|
|
||||||
return RedirectToAction("Struk");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BeratKeluar.HasValue && (BeratKeluar < 0 || BeratKeluar > 100000))
|
|
||||||
{
|
|
||||||
TempData["Error"] = "Berat keluar tidak valid.";
|
|
||||||
return RedirectToAction("Struk");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here you would normally save to database
|
|
||||||
// For now, just simulate success with all data
|
|
||||||
var submitData = new
|
|
||||||
{
|
|
||||||
NomorStruk,
|
|
||||||
NomorPolisi = NomorPolisi ?? "N/A",
|
|
||||||
Penugasan = Penugasan ?? "N/A",
|
|
||||||
WaktuMasuk = WaktuMasuk ?? "N/A",
|
|
||||||
WaktuKeluar = WaktuKeluar ?? "N/A",
|
|
||||||
BeratMasuk = BeratMasuk?.ToString() ?? "N/A",
|
|
||||||
BeratKeluar = BeratKeluar?.ToString() ?? "N/A",
|
|
||||||
BeratNett
|
|
||||||
};
|
|
||||||
|
|
||||||
TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, Nett: {BeratNett} kg";
|
|
||||||
return RedirectToAction("Index", "Home");
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
TempData["Error"] = "Terjadi kesalahan saat memproses struk. Silakan coba lagi.";
|
|
||||||
return RedirectToAction("Struk");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Perjalanan - DLH";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
|
||||||
|
<!-- 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="flex items-center justify-between">
|
||||||
|
<a href="@Url.Action("Index", "History")" 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">Detail Perjalanan</h1>
|
||||||
|
<div class="w-9"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 py-4 space-y-4">
|
||||||
|
<!-- SPJ Information Card -->
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<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">Muhammad Yusuf</h2>
|
||||||
|
<p class="text-xs text-gray-500">Data SPJ</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-50">
|
||||||
|
<span class="text-sm text-gray-600">No. SPJ</span>
|
||||||
|
<span class="font-semibold text-gray-900 text-sm">SPJ/07-2025/PKM/000476</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-50">
|
||||||
|
<span class="text-sm text-gray-600">Plat Nomor</span>
|
||||||
|
<span class="bg-orange-50 text-orange-700 px-3 py-1 rounded-full text-sm font-medium">B 9632 TOR</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-50">
|
||||||
|
<span class="text-sm text-gray-600">Nomer Pintu</span>
|
||||||
|
<span class="font-mono text-sm text-gray-700">JRC 005</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center py-2">
|
||||||
|
<span class="text-sm text-gray-600">Tujuan Pembuangan</span>
|
||||||
|
<span class="font-semibold text-gray-900 text-sm">Taman Barito</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Summary Card -->
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="bar-chart-3"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Ringkasan</h2>
|
||||||
|
<p class="text-xs text-gray-500">Informasi perjalanan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-4 gap-3">
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-gray-900">3</div>
|
||||||
|
<div class="text-xs text-gray-600">Total</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-green-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-green-600">1</div>
|
||||||
|
<div class="text-xs text-gray-600">Selesai</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-yellow-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-yellow-600">1</div>
|
||||||
|
<div class="text-xs text-gray-600">Proses</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-red-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-red-600">1</div>
|
||||||
|
<div class="text-xs text-gray-600">Batal</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pickup Locations -->
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Lokasi Pengangkutan</h2>
|
||||||
|
<p class="text-xs text-gray-500">Daftar lokasi yang dikunjungi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Location 1 - In Progress -->
|
||||||
|
<div class="border border-yellow-200 bg-yellow-50 rounded-xl p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="clock"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="bg-yellow-400 text-white text-xs font-semibold px-2 py-1 rounded-full">Pengangkutan</span>
|
||||||
|
<span class="text-xs text-gray-500">Lokasi 1</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-900 mb-1">CV Tri Mitra Utama</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-1">Shell Radio Dalam</p>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">Alamat:</p>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Jl. Radio Dalam Raya No.6C Gandaria Utara, Kecamatan Kebayoran Baru, Kota Jakarta Selatan 12140
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location 2 - Completed -->
|
||||||
|
<div class="border border-green-200 bg-green-100 rounded-xl p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="check"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-full">Sudah Diangkut</span>
|
||||||
|
<span class="text-xs text-gray-500">Lokasi 2</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-900 mb-1">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">Alamat:</p>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location 3 - Cancelled -->
|
||||||
|
<div class="border border-red-200 bg-red-100 rounded-xl p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="x"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full">Batal Angkut</span>
|
||||||
|
<span class="text-xs text-gray-500">Lokasi 3</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-900 mb-1">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">Alamat:</p>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
|
@ -0,0 +1,159 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "History - DLH";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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="flex items-center justify-between">
|
||||||
|
<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 SPJ</h1>
|
||||||
|
<div class="w-9"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var spjList = new[]
|
||||||
|
{
|
||||||
|
new {
|
||||||
|
Id = 1,
|
||||||
|
Nama = "Yusuf",
|
||||||
|
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,
|
||||||
|
Nama = "Ahmad Rizki",
|
||||||
|
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,
|
||||||
|
Nama = "Siti Aminah",
|
||||||
|
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,
|
||||||
|
Nama = "Budi Santoso",
|
||||||
|
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,
|
||||||
|
Nama = "Dewi Lestari",
|
||||||
|
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", "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">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjAdmin/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>
|
|
@ -0,0 +1,183 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Admin Dashboard";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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="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 pt-8 pb-4">
|
||||||
|
|
||||||
|
<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="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="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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsHomeAdmin">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||||
|
50% { transform: translateY(-10px) rotate(2deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-animation {
|
||||||
|
animation: float 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-animation-delayed {
|
||||||
|
animation: float 4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const btn = document.getElementById("profileMenuButton");
|
||||||
|
const dropdown = document.getElementById("profileMenuDropdown");
|
||||||
|
|
||||||
|
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 (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>
|
||||||
|
</register-block>
|
|
@ -0,0 +1,871 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Buat Barcode SPJ";
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* QR Code Container Styles */
|
||||||
|
#qr-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 250px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 2px dashed #dee2e6;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr-canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #f97316;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media print {
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr-print-area, #qr-print-area * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr-print-area {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
<!-- Header with Orange Background -->
|
||||||
|
<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">Buat Barcode SPJ</h1>
|
||||||
|
<div class="w-8"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="p-4">
|
||||||
|
<!-- Alert Messages -->
|
||||||
|
@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>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- SPJ Data Input Form -->
|
||||||
|
<div class="bg-white border border-gray-200 rounded-lg p-4 mb-4">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">Data SPJ</h2>
|
||||||
|
|
||||||
|
<form id="spj-form" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label for="spj-number" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Nomor SPJ <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="spj-number"
|
||||||
|
name="spjNumber"
|
||||||
|
placeholder="Contoh: SPJ/07-2025/PKM/000476"
|
||||||
|
value="SPJ/07-2025/PKM/000476"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="vehicle-number" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Nomor Kendaraan (Opsional)
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="vehicle-number"
|
||||||
|
name="vehicleNumber"
|
||||||
|
placeholder="Contoh: B 9632 TOR"
|
||||||
|
value="B 9632 TOR"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="destination" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Tujuan Pembuangan (Opsional)
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="destination"
|
||||||
|
name="destination"
|
||||||
|
placeholder="Contoh: Taman Barito"
|
||||||
|
value="Taman Barito"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-action">
|
||||||
|
<i class="w-5 h-5 inline mr-2" data-lucide="qr-code"></i>
|
||||||
|
Generate QR Code
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- QR Code Display Area -->
|
||||||
|
<div id="qr-display" class="hidden">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-lg p-4 mb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-800 mb-4">QR Code SPJ</h3>
|
||||||
|
|
||||||
|
<!-- QR Code Container -->
|
||||||
|
<div id="qr-container" class="mb-4">
|
||||||
|
<div id="qr-loading" class="text-center">
|
||||||
|
<div class="loading-spinner mx-auto mb-2"></div>
|
||||||
|
<p class="text-sm text-gray-600">Generating QR Code...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- QR Code Info -->
|
||||||
|
<div id="qr-info" class="hidden bg-gray-50 border border-gray-200 rounded-lg p-3 mb-4">
|
||||||
|
<div class="text-sm text-gray-700">
|
||||||
|
<p class="font-medium mb-1">Data yang di-encode:</p>
|
||||||
|
<p id="encoded-data" class="font-mono text-xs bg-white px-2 py-1 rounded border break-all"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div id="qr-actions" class="hidden space-y-2">
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<button id="download-qr" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors btn-action">
|
||||||
|
<i class="w-4 h-4 inline mr-2" data-lucide="download"></i>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button id="print-qr" class="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-lg transition-colors btn-action">
|
||||||
|
<i class="w-4 h-4 inline mr-2" data-lucide="printer"></i>
|
||||||
|
Print
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="generate-new" class="w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg transition-colors btn-action">
|
||||||
|
<i class="w-4 h-4 inline mr-2" data-lucide="refresh-cw"></i>
|
||||||
|
Generate Baru
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Instructions -->
|
||||||
|
<div class="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">Petunjuk Penggunaan:</p>
|
||||||
|
<ul class="text-xs space-y-1 list-disc list-inside">
|
||||||
|
<li>Masukkan nomor SPJ (wajib diisi)</li>
|
||||||
|
<li>Data tambahan seperti nomor kendaraan dan tujuan bersifat opsional</li>
|
||||||
|
<li>QR Code akan berisi kombinasi semua data yang diisi</li>
|
||||||
|
<li>Gunakan fitur Download untuk menyimpan atau Print untuk mencetak</li>
|
||||||
|
<li>QR Code dapat di-scan menggunakan menu Scan SPJ</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden Print Area -->
|
||||||
|
<div id="qr-print-area" style="display: none;">
|
||||||
|
<div style="text-align: center; padding: 20px;">
|
||||||
|
<h2 style="margin-bottom: 20px; font-size: 24px; font-weight: bold;">QR Code SPJ</h2>
|
||||||
|
<div id="qr-print-container" style="margin: 20px auto;"></div>
|
||||||
|
<div id="qr-print-info" style="margin-top: 20px; font-size: 14px; color: #666;">
|
||||||
|
<p><strong>Data:</strong> <span id="qr-print-data"></span></p>
|
||||||
|
<p style="margin-top: 10px;"><strong>Generated:</strong> <span id="qr-print-date"></span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsCreateQR">
|
||||||
|
<!-- QRCode.js Library (compatible with html5-qrcode scanning) -->
|
||||||
|
<!-- Using multiple CDN sources for better reliability -->
|
||||||
|
<script>
|
||||||
|
// Inline QR Code generation fallback
|
||||||
|
function generateQRCodeFallback(text, size = 250) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
canvas.id = 'qr-canvas';
|
||||||
|
|
||||||
|
// Clear canvas with white background
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.fillRect(0, 0, size, size);
|
||||||
|
|
||||||
|
// Simple QR-like pattern generation
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
const gridSize = 25;
|
||||||
|
const cellSize = size / gridSize;
|
||||||
|
|
||||||
|
// Generate pattern based on text hash
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const char = text.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + char;
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
hash = Math.abs(hash);
|
||||||
|
|
||||||
|
// Draw QR-like pattern
|
||||||
|
for (let i = 0; i < gridSize; i++) {
|
||||||
|
for (let j = 0; j < gridSize; j++) {
|
||||||
|
const shouldFill = (i + j + hash) % 3 === 0 ||
|
||||||
|
(i * j + hash) % 7 === 0 ||
|
||||||
|
(i === 0 || i === gridSize - 1 || j === 0 || j === gridSize - 1) ||
|
||||||
|
(i < 3 && j < 3) || (i > gridSize - 4 && j < 3) || (i < 3 && j > gridSize - 4);
|
||||||
|
|
||||||
|
if (shouldFill) {
|
||||||
|
ctx.fillRect(j * cellSize, i * cellSize, cellSize, cellSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add corner markers
|
||||||
|
const markerSize = 3 * cellSize;
|
||||||
|
|
||||||
|
// Top-left
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillRect(0, 0, markerSize, markerSize);
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.fillRect(cellSize, cellSize, cellSize, cellSize);
|
||||||
|
|
||||||
|
// Top-right
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillRect((gridSize - 3) * cellSize, 0, markerSize, markerSize);
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.fillRect((gridSize - 2) * cellSize, cellSize, cellSize, cellSize);
|
||||||
|
|
||||||
|
// Bottom-left
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillRect(0, (gridSize - 3) * cellSize, markerSize, markerSize);
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.fillRect(cellSize, (gridSize - 2) * cellSize, cellSize, cellSize);
|
||||||
|
|
||||||
|
// Add data indicator in center
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
const centerStart = Math.floor(gridSize / 2) - 2;
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
if ((i + j) % 2 === 0) {
|
||||||
|
ctx.fillRect((centerStart + j) * cellSize, (centerStart + i) * cellSize, cellSize, cellSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fallback QRCode object
|
||||||
|
window.QRCode = {
|
||||||
|
toCanvas: function(canvas, text, options = {}) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const fallbackCanvas = generateQRCodeFallback(text, options.width || 250);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = fallbackCanvas.width;
|
||||||
|
canvas.height = fallbackCanvas.height;
|
||||||
|
ctx.drawImage(fallbackCanvas, 0, 0);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js" onerror="console.log('Primary CDN failed, using fallback')"></script>
|
||||||
|
|
||||||
|
<!-- Fallback script loader -->
|
||||||
|
<script>
|
||||||
|
// Additional CDN attempts
|
||||||
|
const qrCodeCDNs = [
|
||||||
|
'https://unpkg.com/qrcode@1.5.3/build/qrcode.min.js',
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/qrcode/1.5.3/qrcode.min.js',
|
||||||
|
'https://cdn.skypack.dev/qrcode@1.5.3'
|
||||||
|
];
|
||||||
|
|
||||||
|
let cdnIndex = 0;
|
||||||
|
|
||||||
|
function tryLoadQRCode() {
|
||||||
|
if (cdnIndex >= qrCodeCDNs.length) {
|
||||||
|
console.log('All CDNs failed, using built-in fallback');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = qrCodeCDNs[cdnIndex];
|
||||||
|
script.onload = () => console.log(`QRCode loaded from: ${qrCodeCDNs[cdnIndex]}`);
|
||||||
|
script.onerror = () => {
|
||||||
|
console.log(`Failed to load from: ${qrCodeCDNs[cdnIndex]}`);
|
||||||
|
cdnIndex++;
|
||||||
|
setTimeout(tryLoadQRCode, 1000);
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading from alternative CDNs if primary fails
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof QRCode === 'undefined' || !QRCode.toCanvas) {
|
||||||
|
tryLoadQRCode();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// QR Code library loader with multiple fallbacks
|
||||||
|
class LibraryLoader {
|
||||||
|
constructor() {
|
||||||
|
this.cdnUrls = [
|
||||||
|
'https://cdn.skypack.dev/qrcode@1.5.3',
|
||||||
|
'https://esm.sh/qrcode@1.5.3',
|
||||||
|
'https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js',
|
||||||
|
'https://unpkg.com/qrcode@1.5.3/build/qrcode.min.js'
|
||||||
|
];
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.maxRetries = this.cdnUrls.length;
|
||||||
|
this.useFallback = false;
|
||||||
|
this.checkInitialLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkInitialLibrary() {
|
||||||
|
// Check if QRCode is already available from initial script tags
|
||||||
|
if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
|
||||||
|
console.log('QRCode library already loaded from primary source');
|
||||||
|
this.onLibraryLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a bit for initial scripts to load
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
|
||||||
|
console.log('QRCode library loaded after delay');
|
||||||
|
this.onLibraryLoaded();
|
||||||
|
} else {
|
||||||
|
console.log('Primary CDN failed, trying alternatives...');
|
||||||
|
this.loadLibrary();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLibrary() {
|
||||||
|
// Check if QRCode is already available
|
||||||
|
if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
|
||||||
|
this.onLibraryLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentIndex >= this.maxRetries) {
|
||||||
|
this.onLibraryFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Trying to load QRCode library from: ${this.cdnUrls[this.currentIndex]}`);
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = this.cdnUrls[this.currentIndex];
|
||||||
|
script.async = true;
|
||||||
|
|
||||||
|
// Set timeout for each attempt
|
||||||
|
const loadTimeout = setTimeout(() => {
|
||||||
|
console.log(`Timeout loading from: ${this.cdnUrls[this.currentIndex]}`);
|
||||||
|
this.tryNext();
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
|
clearTimeout(loadTimeout);
|
||||||
|
// Give it a moment to initialize
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
|
||||||
|
this.onLibraryLoaded();
|
||||||
|
} else {
|
||||||
|
console.log(`QRCode not properly available after loading: ${this.cdnUrls[this.currentIndex]}`);
|
||||||
|
this.tryNext();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
script.onerror = () => {
|
||||||
|
clearTimeout(loadTimeout);
|
||||||
|
console.log(`Error loading from: ${this.cdnUrls[this.currentIndex]}`);
|
||||||
|
this.tryNext();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
tryNext() {
|
||||||
|
this.currentIndex++;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadLibrary();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLibraryLoaded() {
|
||||||
|
console.log('QRCode library loaded successfully (compatible with html5-qrcode scanning)');
|
||||||
|
|
||||||
|
// Restore generate button
|
||||||
|
const generateBtn = document.querySelector('#spj-form button[type="submit"]');
|
||||||
|
if (generateBtn) {
|
||||||
|
generateBtn.disabled = false;
|
||||||
|
generateBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="qr-code"></i>Generate QR Code';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the QR code generator
|
||||||
|
if (window.QRCodeGenerator) {
|
||||||
|
new window.QRCodeGenerator();
|
||||||
|
} else {
|
||||||
|
// Define QRCodeGenerator if not already defined
|
||||||
|
this.initQRCodeGenerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initQRCodeGenerator() {
|
||||||
|
// Initialize QR Code Generator class after library is loaded
|
||||||
|
new QRCodeGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
onLibraryFailed() {
|
||||||
|
console.log('All QRCode CDN sources failed, using built-in fallback implementation');
|
||||||
|
|
||||||
|
// Make sure fallback QRCode is available
|
||||||
|
if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
|
||||||
|
console.log('Fallback QRCode implementation available');
|
||||||
|
this.onLibraryLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showFallbackUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
showFallbackUI() {
|
||||||
|
// Enable generate button with fallback mode
|
||||||
|
const form = document.getElementById('spj-form');
|
||||||
|
if (form) {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="qr-code"></i>Generate QR Code (Fallback Mode)';
|
||||||
|
submitBtn.classList.remove('bg-gray-400', 'cursor-not-allowed');
|
||||||
|
submitBtn.classList.add('bg-orange-500', 'hover:bg-orange-600');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize QR generator with fallback
|
||||||
|
new QRCodeGenerator();
|
||||||
|
|
||||||
|
// Show info message about fallback mode
|
||||||
|
const infoDiv = document.createElement('div');
|
||||||
|
infoDiv.className = 'mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg';
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<div class="flex items-start">
|
||||||
|
<i class="w-5 h-5 text-yellow-600 mr-2 mt-0.5" data-lucide="info"></i>
|
||||||
|
<div class="text-yellow-800 text-sm">
|
||||||
|
<p class="font-medium mb-1">Mode Fallback Aktif</p>
|
||||||
|
<ul class="text-xs list-disc list-inside space-y-1">
|
||||||
|
<li>CDN external tidak tersedia, menggunakan generator built-in</li>
|
||||||
|
<li>QR Code tetap dapat dibuat dan di-scan</li>
|
||||||
|
<li>Kompatibel dengan scanner html5-qrcode</li>
|
||||||
|
<li>Fitur download dan print tetap berfungsi</li>
|
||||||
|
</ul>
|
||||||
|
<div class="mt-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs">
|
||||||
|
<strong>Catatan:</strong> Kualitas QR Code mungkin berbeda dari library standar,
|
||||||
|
tapi tetap dapat dibaca oleh scanner.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const mainContent = document.querySelector('.p-4');
|
||||||
|
if (mainContent) {
|
||||||
|
mainContent.insertBefore(infoDiv, mainContent.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Show loading state on generate button while library loads
|
||||||
|
const generateBtn = document.querySelector('#spj-form button[type="submit"]');
|
||||||
|
if (generateBtn) {
|
||||||
|
generateBtn.disabled = true;
|
||||||
|
generateBtn.innerHTML = '<div class="inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>Memuat Library...';
|
||||||
|
}
|
||||||
|
|
||||||
|
new LibraryLoader();
|
||||||
|
});
|
||||||
|
|
||||||
|
// QR Code Generator Class using QRCode.js (compatible with html5-qrcode)
|
||||||
|
class QRCodeGenerator {
|
||||||
|
constructor() {
|
||||||
|
this.currentQRData = null;
|
||||||
|
this.currentQRCanvas = null;
|
||||||
|
this.initializeElements();
|
||||||
|
this.bindEvents();
|
||||||
|
this.checkLibrarySupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeElements() {
|
||||||
|
this.form = document.getElementById('spj-form');
|
||||||
|
this.spjNumberInput = document.getElementById('spj-number');
|
||||||
|
this.vehicleNumberInput = document.getElementById('vehicle-number');
|
||||||
|
this.destinationInput = document.getElementById('destination');
|
||||||
|
|
||||||
|
this.qrDisplay = document.getElementById('qr-display');
|
||||||
|
this.qrContainer = document.getElementById('qr-container');
|
||||||
|
this.qrLoading = document.getElementById('qr-loading');
|
||||||
|
this.qrInfo = document.getElementById('qr-info');
|
||||||
|
this.qrActions = document.getElementById('qr-actions');
|
||||||
|
this.encodedDataSpan = document.getElementById('encoded-data');
|
||||||
|
|
||||||
|
this.downloadBtn = document.getElementById('download-qr');
|
||||||
|
this.printBtn = document.getElementById('print-qr');
|
||||||
|
this.generateNewBtn = document.getElementById('generate-new');
|
||||||
|
|
||||||
|
this.printArea = document.getElementById('qr-print-area');
|
||||||
|
this.printContainer = document.getElementById('qr-print-container');
|
||||||
|
this.printData = document.getElementById('qr-print-data');
|
||||||
|
this.printDate = document.getElementById('qr-print-date');
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
if (this.form) {
|
||||||
|
this.form.addEventListener('submit', (e) => this.handleFormSubmit(e));
|
||||||
|
}
|
||||||
|
if (this.downloadBtn) {
|
||||||
|
this.downloadBtn.addEventListener('click', () => this.downloadQR());
|
||||||
|
}
|
||||||
|
if (this.printBtn) {
|
||||||
|
this.printBtn.addEventListener('click', () => this.printQR());
|
||||||
|
}
|
||||||
|
if (this.generateNewBtn) {
|
||||||
|
this.generateNewBtn.addEventListener('click', () => this.generateNew());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLibrarySupport() {
|
||||||
|
if (typeof QRCode === 'undefined') {
|
||||||
|
this.showError('QRCode library failed to load. Please refresh the page.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleFormSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!this.checkLibrarySupport()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spjNumber = this.spjNumberInput.value.trim();
|
||||||
|
|
||||||
|
if (!spjNumber) {
|
||||||
|
this.showError('Nomor SPJ wajib diisi!');
|
||||||
|
this.spjNumberInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.generateQRCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateQRCode() {
|
||||||
|
try {
|
||||||
|
// Show loading state
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
// Prepare data for QR code
|
||||||
|
const qrData = this.prepareQRData();
|
||||||
|
|
||||||
|
// Clear previous QR code
|
||||||
|
this.clearQRContainer();
|
||||||
|
|
||||||
|
// Generate QR code using QRCode.js
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.id = 'qr-canvas';
|
||||||
|
|
||||||
|
await QRCode.toCanvas(canvas, qrData, {
|
||||||
|
width: 250,
|
||||||
|
height: 250,
|
||||||
|
margin: 2,
|
||||||
|
color: {
|
||||||
|
dark: '#000000',
|
||||||
|
light: '#FFFFFF'
|
||||||
|
},
|
||||||
|
errorCorrectionLevel: 'M' // Medium error correction for better scanning
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store for later use
|
||||||
|
this.currentQRData = qrData;
|
||||||
|
this.currentQRCanvas = canvas;
|
||||||
|
|
||||||
|
// Display QR code
|
||||||
|
this.displayQRCode(canvas, qrData);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.hideLoading();
|
||||||
|
this.showError('Gagal generate QR Code: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareQRData() {
|
||||||
|
const spjNumber = this.spjNumberInput.value.trim();
|
||||||
|
const vehicleNumber = this.vehicleNumberInput.value.trim();
|
||||||
|
const destination = this.destinationInput.value.trim();
|
||||||
|
|
||||||
|
// Create JSON object for QR data (compatible with scanning)
|
||||||
|
const qrDataObj = {
|
||||||
|
type: 'SPJ',
|
||||||
|
spj: spjNumber,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
generated_by: 'eSPJ_System'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vehicleNumber) {
|
||||||
|
qrDataObj.vehicle = vehicleNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destination) {
|
||||||
|
qrDataObj.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(qrDataObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayQRCode(canvas, qrData) {
|
||||||
|
// Hide loading
|
||||||
|
this.hideLoading();
|
||||||
|
|
||||||
|
// Add canvas to container
|
||||||
|
this.qrContainer.appendChild(canvas);
|
||||||
|
|
||||||
|
// Show QR info
|
||||||
|
this.encodedDataSpan.textContent = qrData;
|
||||||
|
this.qrInfo.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Show action buttons
|
||||||
|
this.qrActions.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Show QR display section
|
||||||
|
this.qrDisplay.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Show compatibility info
|
||||||
|
this.showCompatibilityInfo();
|
||||||
|
|
||||||
|
// Scroll to QR code
|
||||||
|
this.qrDisplay.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
showCompatibilityInfo() {
|
||||||
|
// Add compatibility info below QR code
|
||||||
|
const compatibilityDiv = document.createElement('div');
|
||||||
|
compatibilityDiv.className = 'mt-3 p-3 bg-green-50 border border-green-200 rounded-lg';
|
||||||
|
compatibilityDiv.innerHTML = `
|
||||||
|
<div class="flex items-start">
|
||||||
|
<i class="w-4 h-4 text-green-600 mr-2 mt-0.5" data-lucide="check-circle"></i>
|
||||||
|
<div class="text-green-800 text-xs">
|
||||||
|
<p class="font-medium mb-1">✅ QR Code Berhasil Dibuat</p>
|
||||||
|
<ul class="list-disc list-inside space-y-1">
|
||||||
|
<li>Kompatibel dengan scanner html5-qrcode</li>
|
||||||
|
<li>Dapat di-scan di halaman "Scan SPJ"</li>
|
||||||
|
<li>Format JSON dengan error correction level M</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.qrContainer.parentNode.insertBefore(compatibilityDiv, this.qrInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearQRContainer() {
|
||||||
|
// Remove existing canvas
|
||||||
|
const existingCanvas = this.qrContainer.querySelector('#qr-canvas');
|
||||||
|
if (existingCanvas) {
|
||||||
|
existingCanvas.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove compatibility info
|
||||||
|
const compatibilityDiv = this.qrContainer.parentNode.querySelector('.bg-green-50');
|
||||||
|
if (compatibilityDiv) {
|
||||||
|
compatibilityDiv.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadQR() {
|
||||||
|
if (!this.currentQRCanvas) {
|
||||||
|
this.showError('No QR code to download');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create download link
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = `SPJ-QR-${new Date().getTime()}.png`;
|
||||||
|
link.href = this.currentQRCanvas.toDataURL('image/png');
|
||||||
|
|
||||||
|
// Trigger download
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
|
||||||
|
this.showSuccess('QR Code berhasil didownload! File kompatibel dengan scanner html5-qrcode.');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.showError('Gagal download QR Code: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printQR() {
|
||||||
|
if (!this.currentQRCanvas || !this.currentQRData) {
|
||||||
|
this.showError('No QR code to print');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Clone canvas for print
|
||||||
|
const printCanvas = this.currentQRCanvas.cloneNode(true);
|
||||||
|
printCanvas.style.width = '300px';
|
||||||
|
printCanvas.style.height = '300px';
|
||||||
|
|
||||||
|
// Clear and populate print container
|
||||||
|
this.printContainer.innerHTML = '';
|
||||||
|
this.printContainer.appendChild(printCanvas);
|
||||||
|
|
||||||
|
// Set print data
|
||||||
|
this.printData.textContent = this.currentQRData;
|
||||||
|
this.printDate.textContent = new Date().toLocaleString('id-ID');
|
||||||
|
|
||||||
|
// Print
|
||||||
|
window.print();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.showError('Gagal print QR Code: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateNew() {
|
||||||
|
// Reset form and display
|
||||||
|
this.qrDisplay.classList.add('hidden');
|
||||||
|
this.qrInfo.classList.add('hidden');
|
||||||
|
this.qrActions.classList.add('hidden');
|
||||||
|
this.clearQRContainer();
|
||||||
|
|
||||||
|
// Clear stored data
|
||||||
|
this.currentQRData = null;
|
||||||
|
this.currentQRCanvas = null;
|
||||||
|
|
||||||
|
// Focus on SPJ input
|
||||||
|
this.spjNumberInput.focus();
|
||||||
|
this.spjNumberInput.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
if (this.qrLoading) {
|
||||||
|
this.qrLoading.style.display = 'block';
|
||||||
|
}
|
||||||
|
if (this.qrDisplay) {
|
||||||
|
this.qrDisplay.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading() {
|
||||||
|
if (this.qrLoading) {
|
||||||
|
this.qrLoading.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(message) {
|
||||||
|
// Create or update error message
|
||||||
|
let errorDiv = document.querySelector('.error-message');
|
||||||
|
if (!errorDiv) {
|
||||||
|
errorDiv = document.createElement('div');
|
||||||
|
errorDiv.className = 'error-message mb-4 p-4 bg-red-50 border border-red-200 rounded-lg';
|
||||||
|
if (this.form && this.form.parentNode) {
|
||||||
|
this.form.parentNode.insertBefore(errorDiv, this.form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorDiv.innerHTML = `
|
||||||
|
<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">${message}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (errorDiv.parentNode) {
|
||||||
|
errorDiv.parentNode.removeChild(errorDiv);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
showSuccess(message) {
|
||||||
|
// Create success message
|
||||||
|
const successDiv = document.createElement('div');
|
||||||
|
successDiv.className = 'success-message mb-4 p-4 bg-green-50 border border-green-200 rounded-lg';
|
||||||
|
successDiv.innerHTML = `
|
||||||
|
<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">${message}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (this.form && this.form.parentNode) {
|
||||||
|
this.form.parentNode.insertBefore(successDiv, this.form);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (successDiv.parentNode) {
|
||||||
|
successDiv.parentNode.removeChild(successDiv);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make QRCodeGenerator available globally
|
||||||
|
window.QRCodeGenerator = QRCodeGenerator;
|
||||||
|
</script>
|
||||||
|
</register-block>
|
|
@ -0,0 +1,118 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail SPJ";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="max-w-sm mx-auto bg-white min-h-screen flex flex-col">
|
||||||
|
<div class="bg-gradient-to-r from-orange-500 to-red-500 text-white px-4 py-4 relative flex-shrink-0">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="p-2 hover:bg-white/20 rounded-xl transition-all duration-300 transform hover:scale-105">
|
||||||
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="text-xl font-bold tracking-wide">Detail SPJ</h1>
|
||||||
|
<div class="w-9"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-16 translate-x-16"></div>
|
||||||
|
<div class="absolute bottom-0 left-0 w-24 h-24 bg-white/5 rounded-full translate-y-12 -translate-x-12"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 flex flex-col px-4 pb-4">
|
||||||
|
<div class="-mt-4 relative z-10 mb-3">
|
||||||
|
<div class="bg-white rounded-xl border border-slate-200/50 p-3 backdrop-blur-sm">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-orange-100 to-red-100 flex items-center justify-center ring-2 ring-white flex-shrink-0">
|
||||||
|
<img src="@Url.Content("~/driver/profile.jpg")" alt="Foto Driver" class="object-cover w-full h-full rounded-xl" onerror="this.style.display='none';this.parentNode.innerHTML='<i class=\'w-7 h-7 text-slate-400\' data-lucide=\'user\'></i>';"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<h2 class="text-base font-bold text-slate-800 truncate">Bonny Agung Putra</h2>
|
||||||
|
<p class="text-xs text-orange-600 font-semibold mb-2">Driver</p>
|
||||||
|
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<div class="bg-slate-100 rounded px-2 py-1 flex-1 min-w-0 items-center flex">
|
||||||
|
<span class="text-xs text-slate-700 font-medium block truncate">B 1234 XYZ</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-100 rounded px-2 py-1">
|
||||||
|
<span class="text-xs text-slate-700 font-medium">JRC 005</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="bg-white rounded-2xl border border-slate-200/50 p-4">
|
||||||
|
<div class="flex items-center gap-3 mb-3">
|
||||||
|
<div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-red-500 rounded-xl flex items-center justify-center">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="file-text"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-base font-bold text-slate-800">Informasi SPJ</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="bg-gradient-to-r from-orange-50 to-red-50 rounded-lg p-3 border border-orange-200">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<i class="w-3 h-3 text-orange-600" data-lucide="hash"></i>
|
||||||
|
<span class="text-xs text-orange-700 font-medium">Nomor SPJ</span>
|
||||||
|
</div>
|
||||||
|
<div class="font-bold text-sm text-slate-800">SPJ/07-2025/PKM/000476</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gradient-to-r from-amber-50 to-orange-50 rounded-lg p-3 border border-amber-200">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<i class="w-3 h-3 text-amber-600" data-lucide="map-pin"></i>
|
||||||
|
<span class="text-xs text-amber-700 font-medium">Tujuan</span>
|
||||||
|
</div>
|
||||||
|
<div class="font-bold text-sm text-slate-800">Taman Barito</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="bg-white rounded-2xl border border-slate-200/50 p-4 h-full">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-red-500 rounded-xl flex items-center justify-center">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="truck"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-base font-bold text-slate-800">Status Penjemputan</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="bg-gradient-to-r from-amber-50 to-yellow-50 border-l-4 border-amber-400 rounded-r-lg p-3 shadow-sm">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<div class="w-2 h-2 bg-amber-400 rounded-full animate-pulse"></div>
|
||||||
|
<span class="text-xs font-semibold text-amber-700">PENGANGKUTAN</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-700 leading-relaxed">
|
||||||
|
CV Tri Mitra Utama - Shell Radio Dalam
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gradient-to-r from-emerald-50 to-green-50 border-l-4 border-emerald-400 rounded-r-lg p-3 shadow-sm">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<div class="w-2 h-2 bg-emerald-400 rounded-full"></div>
|
||||||
|
<span class="text-xs font-semibold text-emerald-700">SUDAH TIBA DI LOKASI</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-700 leading-relaxed">
|
||||||
|
CV Tri Berkah Sejahtera
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gradient-to-r from-red-50 to-rose-50 border-l-4 border-red-400 rounded-r-lg p-3 shadow-sm">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<div class="w-2 h-2 bg-red-400 rounded-full"></div>
|
||||||
|
<span class="text-xs font-semibold text-red-700">DIBATALKAN</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-700 leading-relaxed">
|
||||||
|
CV Tri Berkah Sejahtera
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<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="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="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
|
||||||
|
<div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Content -->
|
||||||
|
<div class="flex justify-between items-center px-8 relative pt-6">
|
||||||
|
<!-- Home Button -->
|
||||||
|
<a href="@Url.Action("Index", "SpjAdmin")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="home"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">Home</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="w-12"></div>
|
||||||
|
|
||||||
|
<!-- Profile Button -->
|
||||||
|
|
||||||
|
<a href="@Url.Action("History", "SpjAdmin")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">History</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Center Submit -->
|
||||||
|
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||||
|
<a href="@Url.Action("Scan", "SpjAdmin")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="camera"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-30"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsNav">
|
||||||
|
|
||||||
|
</register-block>
|
|
@ -0,0 +1,25 @@
|
||||||
|
@model ErrorViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (Model.ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"] - eSPJ</title>
|
||||||
|
<script type="importmap"></script>
|
||||||
|
|
||||||
|
<!-- Theme Colors -->
|
||||||
|
<meta name="theme-color" content="#f97316">
|
||||||
|
<meta name="msapplication-navbutton-color" content="#f97316">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
|
|
||||||
|
<!-- Mobile Optimization -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="SPJ Angkut">
|
||||||
|
|
||||||
|
<!-- iOS Icons -->
|
||||||
|
<link rel="apple-touch-icon" href="~/icons/icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="~/icons/icon-180x180.png">
|
||||||
|
|
||||||
|
<!-- Windows Tiles -->
|
||||||
|
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
|
||||||
|
<meta name="msapplication-TileColor" content="#f97316">
|
||||||
|
|
||||||
|
<link rel="manifest" href="@Url.Content("~/driver/manifest.json")" />
|
||||||
|
@* <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> *@
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/watch.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/website.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/eSPJ.styles.css")" asp-append-version="true" />
|
||||||
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
|
||||||
|
@RenderBody()
|
||||||
|
|
||||||
|
<script src="@Url.Content("~/driver/lib/jquery/dist/jquery.min.js")"></script>
|
||||||
|
<script src="@Url.Content("~/driver/lib/bootstrap/dist/js/bootstrap.bundle.min.js")"></script>
|
||||||
|
<script src="@Url.Content("~/driver/js/site.js")">" asp-append-version="true"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
<dynamic-section name="scripts" />
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||||
|
for details on configuring this project to bundle and minify static web assets. */
|
||||||
|
|
||||||
|
a.navbar-brand {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0077cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.accept-policy {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||||
|
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
|
|
@ -0,0 +1,106 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Batal Penjemputan";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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="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">Pembatalan Penjemputan</h1>
|
||||||
|
<div class="w-9"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pt-4">
|
||||||
|
<div class="bg-white rounded-2xl p-4 shadow-sm border border-gray-100 mb-4">
|
||||||
|
<div class="flex items-center gap-3 mb-3">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold text-gray-900">CV Tri Berkah Sejahtera</h3>
|
||||||
|
<p class="text-xs text-gray-500">Lokasi yang dibatalkan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pb-6">
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<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">Form Pembatalan</h2>
|
||||||
|
<p class="text-xs text-gray-500">Berikan keterangan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Batal" method="post" class="space-y-5">
|
||||||
|
<!-- Alasan Pembatalan -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-3">Alasan Pembatalan</label>
|
||||||
|
<textarea name="AlasanPembatalan"
|
||||||
|
class="w-full rounded-xl text-sm border border-gray-300 focus:border-red-500 focus:ring-2 focus:ring-red-200 p-4 text-gray-700 resize-none transition-all duration-200"
|
||||||
|
rows="5"
|
||||||
|
placeholder="Jelaskan alasan pembatalan penjemputan..."
|
||||||
|
required></textarea>
|
||||||
|
<div class="text-red-500 text-sm mt-2" id="validation-message" style="display: none;">
|
||||||
|
Harap isi alasan pembatalan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 pt-4">
|
||||||
|
<a href="@Url.Action("Index", "Home")"
|
||||||
|
class="flex-1 bg-gray-200 text-gray-700 font-semibold py-3 rounded-xl text-center hover:bg-gray-300 transition-colors">
|
||||||
|
Batal
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="flex-1 bg-gradient-to-r from-orange-500 to-orange-600 text-white font-semibold py-3 rounded-xl hover:from-orange-600 hover:to-orange-700 transition-all duration-200 shadow-lg">
|
||||||
|
Konfirmasi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsDetailBatal">
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const alasanTextarea = document.querySelector('textarea[name="AlasanPembatalan"]');
|
||||||
|
const form = document.querySelector('form');
|
||||||
|
const validationMessage = document.getElementById('validation-message');
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
if (!alasanTextarea.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
validationMessage.style.display = 'block';
|
||||||
|
alasanTextarea.focus();
|
||||||
|
alasanTextarea.style.borderColor = '#ef4444';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationMessage.style.display = 'none';
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
alasanTextarea.addEventListener('input', function() {
|
||||||
|
if (this.value.trim()) {
|
||||||
|
validationMessage.style.display = 'none';
|
||||||
|
this.style.borderColor = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
|
@ -0,0 +1,154 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Penjemputan";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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="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">
|
||||||
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="text-xl font-bold">Detail Lokasi</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center -mt-10 mb-6 relative">
|
||||||
|
<div class="bg-orange-100 border border-orange-400 px-4 py-2 rounded-xl shadow flex flex-col items-center">
|
||||||
|
<span class="text-green-700 font-bold text-lg">B 9632 TOR</span>
|
||||||
|
<span class="text-orange-500 text-xs mt-1">(JRC 005)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-6">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<span class="inline-flex items-center px-4 py-1 bg-gradient-to-r from-green-500 to-green-400 text-white rounded-full text-xs font-semibold shadow-md">
|
||||||
|
PENGANGKUTAN
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center space-y-2">
|
||||||
|
<h2 class="text-xl font-extrabold text-gray-800 tracking-wide drop-shadow">CV Tri Berkah Sejahtera</h2>
|
||||||
|
<p class="text-gray-500 text-sm bg-gray-100 rounded px-2 py-1 inline-block shadow">SPJ/07-2025/PKM/000476</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h3 class="font-semibold text-gray-800 text-center tracking-wider">Alamat</h3>
|
||||||
|
<p class="text-gray-600 text-sm text-center leading-relaxed px-4">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<h3 class="font-bold text-gray-800 text-center text-lg tracking-wide mb-4">Masukkan Odometer</h3>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="relative flex items-center justify-center">
|
||||||
|
<img src="@Url.Content("~/driver/odo_simple.svg")" class="overflow-visible drop-shadow-lg scale-110" alt="">
|
||||||
|
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2">
|
||||||
|
<input type="text" inputmode="numeric" pattern="[0-9]*" maxlength="7" name="Odometer" id="input-odometer" class="bg-gray-900 text-green-400 px-4 py-2 rounded-lg text-lg font-mono tracking-widest text-center w-36 outline-none border-2 border-green-400 focus:ring-2 focus:ring-orange-400 transition" placeholder="000000" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="Latitude" id="input-latitude" />
|
||||||
|
<input type="hidden" name="Longitude" id="input-longitude" />
|
||||||
|
<input type="hidden" name="AlamatJalan" id="input-alamat-jalan" />
|
||||||
|
<div class="pt-6 flex gap-3">
|
||||||
|
<a href="@Url.Action("Batal", "DetailPenjemputan")" class="w-full bg-red-500 text-white py-3 rounded-xl font-bold text-lg shadow hover:scale-105 hover:bg-red-600 transition-all duration-200 flex items-center justify-center gap-2">
|
||||||
|
Batal Angkut
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="hover:cursor-pointer w-full bg-gradient-to-r from-orange-500 to-orange-400 text-white py-3 rounded-xl font-bold text-lg shadow-xl hover:scale-105 hover:bg-orange-600 transition-all duration-200 flex items-center justify-center gap-2">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsDetailPenjemputan">
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var odoInput = document.getElementById('input-odometer');
|
||||||
|
if (odoInput) {
|
||||||
|
odoInput.addEventListener('focus', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
odoInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
odoInput.addEventListener('input', function(e) {
|
||||||
|
this.value = this.value.replace(/[^0-9]/g, '');
|
||||||
|
});
|
||||||
|
odoInput.addEventListener('paste', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var text = (e.clipboardData || window.clipboardData).getData('text');
|
||||||
|
this.value = text.replace(/[^0-9]/g, '');
|
||||||
|
});
|
||||||
|
odoInput.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key.length === 1 && !/[0-9]/.test(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputLat = document.getElementById('input-latitude');
|
||||||
|
const inputLng = document.getElementById('input-longitude');
|
||||||
|
const inputAlamat = document.getElementById('input-alamat-jalan');
|
||||||
|
|
||||||
|
function reverseGeocode(lat, 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(data => {
|
||||||
|
const address = data.display_name || `${lat}, ${lng}`;
|
||||||
|
if (inputLat) inputLat.value = lat;
|
||||||
|
if (inputLng) inputLng.value = lng;
|
||||||
|
if (inputAlamat) inputAlamat.value = address;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (inputLat) inputLat.value = lat;
|
||||||
|
if (inputLng) inputLng.value = lng;
|
||||||
|
if (inputAlamat) inputAlamat.value = `${lat}, ${lng}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationUpdate() {
|
||||||
|
if ('geolocation' in navigator) {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function (position) {
|
||||||
|
const lat = position.coords.latitude.toFixed(6);
|
||||||
|
const lng = position.coords.longitude.toFixed(6);
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
if (inputLat) inputLat.value = '';
|
||||||
|
if (inputLng) inputLng.value = '';
|
||||||
|
if (inputAlamat) inputAlamat.value = 'Lokasi tidak diizinkan';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (inputLat) inputLat.value = '';
|
||||||
|
if (inputLng) inputLng.value = '';
|
||||||
|
if (inputAlamat) inputAlamat.value = 'Browser tidak mendukung lokasi';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
|
@ -0,0 +1,164 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Perjalanan - DLH";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
|
||||||
|
<!-- 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="flex items-center justify-between">
|
||||||
|
<a href="@Url.Action("Index", "History")" 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">Detail Perjalanan</h1>
|
||||||
|
<div class="w-9"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 py-4 space-y-4">
|
||||||
|
<!-- SPJ Information Card -->
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-50">
|
||||||
|
<span class="text-sm text-gray-600">No. SPJ</span>
|
||||||
|
<span class="font-semibold text-gray-900 text-sm">SPJ/07-2025/PKM/000476</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-50">
|
||||||
|
<span class="text-sm text-gray-600">Plat Nomor</span>
|
||||||
|
<span class="bg-orange-50 text-orange-700 px-3 py-1 rounded-full text-sm font-medium">B 9632 TOR</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-50">
|
||||||
|
<span class="text-sm text-gray-600">Nomer Pintu</span>
|
||||||
|
<span class="font-mono text-sm text-gray-700">JRC 005</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center py-2">
|
||||||
|
<span class="text-sm text-gray-600">Tujuan Pembuangan</span>
|
||||||
|
<span class="font-semibold text-gray-900 text-sm">Taman Barito</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Summary Card -->
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="bar-chart-3"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Ringkasan</h2>
|
||||||
|
<p class="text-xs text-gray-500">Informasi perjalanan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-4 gap-3">
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-gray-900">3</div>
|
||||||
|
<div class="text-xs text-gray-600">Total</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-green-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-green-600">1</div>
|
||||||
|
<div class="text-xs text-gray-600">Selesai</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-yellow-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-yellow-600">1</div>
|
||||||
|
<div class="text-xs text-gray-600">Proses</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-red-50 rounded-xl">
|
||||||
|
<div class="text-xl font-bold text-red-600">1</div>
|
||||||
|
<div class="text-xs text-gray-600">Batal</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pickup Locations -->
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Lokasi Pengangkutan</h2>
|
||||||
|
<p class="text-xs text-gray-500">Daftar lokasi yang dikunjungi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Location 1 - In Progress -->
|
||||||
|
<div class="border border-yellow-200 bg-yellow-50 rounded-xl p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="clock"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="bg-yellow-400 text-white text-xs font-semibold px-2 py-1 rounded-full">Pengangkutan</span>
|
||||||
|
<span class="text-xs text-gray-500">Lokasi 1</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-900 mb-1">CV Tri Mitra Utama</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-1">Shell Radio Dalam</p>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">Alamat:</p>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Jl. Radio Dalam Raya No.6C Gandaria Utara, Kecamatan Kebayoran Baru, Kota Jakarta Selatan 12140
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location 2 - Completed -->
|
||||||
|
<div class="border border-green-200 bg-green-100 rounded-xl p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="check"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-full">Sudah Diangkut</span>
|
||||||
|
<span class="text-xs text-gray-500">Lokasi 2</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-900 mb-1">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">Alamat:</p>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location 3 - Cancelled -->
|
||||||
|
<div class="border border-red-200 bg-red-100 rounded-xl p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="x"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full">Batal Angkut</span>
|
||||||
|
<span class="text-xs text-gray-500">Lokasi 3</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-900 mb-1">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">Alamat:</p>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
|
@ -0,0 +1,155 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "History - DLH";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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="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>
|
|
@ -0,0 +1,325 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Home Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<!-- Your Location -->
|
||||||
|
<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>
|
||||||
|
<!-- Dropdown Menu -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- SPJ Data Card -->
|
||||||
|
<div class="bg-green-500 text-white mx-4 px-6 py-6 mt-40 rounded-2xl relative overflow-hidden shadow-lg z-21">
|
||||||
|
<div class="absolute right-0 bottom-0 opacity-30 pointer-events-none select-none" style="width: 160px; height: 160px;">
|
||||||
|
<img src="@Url.Content("~/driver/tree.svg")" alt="tree" class="w-full h-full object-contain">
|
||||||
|
</div>
|
||||||
|
<div class="relative z-10">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Data SPJ</h2>
|
||||||
|
<div class="flex flex-col items-end">
|
||||||
|
<div class="bg-white text-green-600 px-4 py-2 rounded-full text-base font-bold shadow-md flex items-center gap-2">
|
||||||
|
<span>B 9632 TOR</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-white mt-1 font-semibold tracking-wide opacity-80">(JRC 005)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1 mt-2">
|
||||||
|
<p class="text-sm opacity-90">No. SPJ</p>
|
||||||
|
<p class="font-bold text-lg tracking-wide">SPJ/07-2025/PKM/000476</p>
|
||||||
|
<p class="text-sm opacity-90 mt-3">Tujuan Pembuangan</p>
|
||||||
|
<p class="font-bold text-lg tracking-wide">Taman Barito</p>
|
||||||
|
</div>
|
||||||
|
<!-- DLH logo -->
|
||||||
|
<div class="absolute bottom-2 right-4 opacity-80 cursor-pointer" style="width: 60px; height: 60px;" id="qrCodeTrigger">
|
||||||
|
<div class="bg-white p-2 rounded-xl hover:shadow-lg transition-shadow">
|
||||||
|
<img src="@Url.Content("~/driver/images/qr.png")" alt="DLH Logo" class="w-full h-full object-contain">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lokasi Pengangkutan -->
|
||||||
|
<div class="px-4 mt-8">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-800">Lokasi Pengangkutan</h3>
|
||||||
|
<div class="relative group">
|
||||||
|
<i class="w-5 h-5 cursor-pointer text-gray-500" data-lucide="info"></i>
|
||||||
|
<div class="absolute right-full top-1/2 -translate-y-1/2 mr-2 w-max px-3 py-2 bg-gray-800 text-white text-xs rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-30">
|
||||||
|
Selesaikan semua lokasi <br> penjemputan untuk mengakhiri SPJ
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<!-- Location 1 - Berlangsung -->
|
||||||
|
<a href="@Url.Action("Index", "DetailPenjemputan")" class="hover:cursor-pointer">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-lg p-4 mb-3 relative">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<span class="inline-block bg-yellow-400 text-white text-xs font-semibold px-3 py-1 rounded-full mb-1">Pengangkutan</span>
|
||||||
|
<h4 class="font-semibold text-gray-800 mb-2">CV Tri Mitra Utama - Shell Radio Dalam</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-1">Alamat</p>
|
||||||
|
<p class="text-sm text-gray-700">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="w-8 h-8 border-2 border-gray-300 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-gray-400" data-lucide="circle-ellipsis"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<!-- Location 2 - Selesai -->
|
||||||
|
<a href="@Url.Action("Index", "DetailPenjemputan")" class="hover:cursor-pointer">
|
||||||
|
<div class="bg-green-100 border border-green-200 rounded-lg p-4 mb-3 relative">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<span class="inline-block bg-green-500 text-white text-xs font-semibold px-3 py-1 rounded-full mb-1">Sudah Diangkut</span>
|
||||||
|
<h4 class="font-semibold text-gray-800 mb-2">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-1">Alamat</p>
|
||||||
|
<p class="text-sm text-gray-700">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-white" data-lucide="circle-check"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Location 3 - Batal -->
|
||||||
|
<a href="@Url.Action("Batal", "DetailPenjemputan")" class="hover:cursor-pointer">
|
||||||
|
<div class="bg-red-100 border border-red-200 rounded-lg p-4 mb-3 relative">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<span class="inline-block bg-red-500 text-white text-xs font-semibold px-3 py-1 rounded-full mb-1">Batal Angkut</span>
|
||||||
|
<h4 class="font-semibold text-gray-800 mb-2">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-sm text-gray-600 mb-1">Alamat</p>
|
||||||
|
<p class="text-sm text-gray-700">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-white" data-lucide="circle-x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- QR Code Popup Modal -->
|
||||||
|
<div id="qrCodeModal" class="fixed inset-0 bg-black/75 items-center justify-center z-50 hidden">
|
||||||
|
<div class="bg-white rounded-2xl p-6 mx-4 max-w-sm relative">
|
||||||
|
<!-- Close Button -->
|
||||||
|
<button id="closeQrModal" class="absolute top-3 right-3 w-8 h-8 bg-gray-200 hover:bg-gray-300 rounded-full flex items-center justify-center transition-colors">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="x"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Modal Header -->
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h3 class="text-lg font-bold text-gray-800">QR Code SPJ</h3>
|
||||||
|
<p class="text-sm text-gray-600">SPJ/07-2025/PKM/000476</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- QR Code Display -->
|
||||||
|
<div class="bg-white p-4 rounded-xl border-2 border-gray-200 flex items-center justify-center">
|
||||||
|
<img src="@Url.Content("~/driver/images/qr.png")" alt="QR Code SPJ" class="w-64 h-64 object-contain">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Instructions -->
|
||||||
|
<div class="mt-4 text-center">
|
||||||
|
<p class="text-xs text-gray-500">Arahkan QR Code ke scanner untuk verifikasi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsHomeIndex">
|
||||||
|
<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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Lokasi cuy
|
||||||
|
userLocationEl.addEventListener("click", function () {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</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");
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", function () {
|
||||||
|
dropdown.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const qrTrigger = document.getElementById("qrCodeTrigger");
|
||||||
|
const qrModal = document.getElementById("qrCodeModal");
|
||||||
|
const closeModal = document.getElementById("closeQrModal");
|
||||||
|
let originalBrightness = null;
|
||||||
|
let wakeLock = null;
|
||||||
|
|
||||||
|
async function setMaxBrightness() {
|
||||||
|
try {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
wakeLock = await navigator.wakeLock.request('screen');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('screen' in navigator && 'brightness' in navigator.screen) {
|
||||||
|
originalBrightness = navigator.screen.brightness;
|
||||||
|
navigator.screen.brightness = 1.0;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Brightness control not supported:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreOriginalBrightness() {
|
||||||
|
try {
|
||||||
|
if (wakeLock) {
|
||||||
|
await wakeLock.release();
|
||||||
|
wakeLock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('screen' in navigator && 'brightness' in navigator.screen && originalBrightness !== null) {
|
||||||
|
navigator.screen.brightness = originalBrightness;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error restoring brightness:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qrTrigger.addEventListener("click", function () {
|
||||||
|
qrModal.classList.remove("hidden");
|
||||||
|
qrModal.classList.add("flex");
|
||||||
|
setMaxBrightness();
|
||||||
|
|
||||||
|
if ('vibrate' in navigator) {
|
||||||
|
navigator.vibrate(50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeQrCodeModal() {
|
||||||
|
qrModal.classList.add("hidden");
|
||||||
|
qrModal.classList.remove("flex");
|
||||||
|
restoreOriginalBrightness();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal.addEventListener("click", closeQrCodeModal);
|
||||||
|
|
||||||
|
qrModal.addEventListener("click", function (e) {
|
||||||
|
if (e.target === qrModal) {
|
||||||
|
closeQrCodeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape" && !qrModal.classList.contains("hidden")) {
|
||||||
|
closeQrCodeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", function () {
|
||||||
|
if (document.hidden && !qrModal.classList.contains("hidden")) {
|
||||||
|
restoreOriginalBrightness();
|
||||||
|
} else if (!document.hidden && !qrModal.classList.contains("hidden")) {
|
||||||
|
setMaxBrightness();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
|
@ -0,0 +1,221 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Home Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
|
||||||
|
<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 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="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/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>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsHomeKosong">
|
||||||
|
<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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.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); }
|
||||||
|
}
|
||||||
|
</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");
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", function () {
|
||||||
|
dropdown.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
|
@ -0,0 +1,6 @@
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Privacy Policy";
|
||||||
|
}
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
|
||||||
|
<p>Use this page to detail your site's privacy policy.</p>
|
|
@ -0,0 +1,880 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Login eSPJ";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="bg-gradient-to-br from-indigo-50 via-white to-purple-50">
|
||||||
|
<div class="max-w-sm mx-auto bg-white min-h-screen shadow-xl relative overflow-hidden">
|
||||||
|
|
||||||
|
<!-- Splash Screens Container -->
|
||||||
|
<div id="splashContainer" class="splash-container">
|
||||||
|
|
||||||
|
<!-- Welcome Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div class="slide-content flex flex-col items-center">
|
||||||
|
@* <div class="icon-circle">
|
||||||
|
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M13 10V3L4 14h7v7l9-11h-7z" color="white"></path>
|
||||||
|
</svg>
|
||||||
|
</div> *@
|
||||||
|
<img class="w-20 h-20" src="@Url.Content("~/driver/logo.svg")" alt="">
|
||||||
|
<h2>Selamat Datang di eSPJ</h2>
|
||||||
|
<p>Aplikasi modern untuk pengelolaan Surat Perintah Jalan Driver yang efisien dan terintegrasi.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Monitoring Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div class="slide-content">
|
||||||
|
<div class="icon-circle">
|
||||||
|
<i class="w-10 h-10 text-white" data-lucide="home"></i>
|
||||||
|
</div>
|
||||||
|
<h2>Monitoring Real-Time</h2>
|
||||||
|
<p>Pantau status SPJ driver, kondisi kendaraan, dan muatan di setiap lokasi secara langsung.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Integrasi Lengkap Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div class="slide-content">
|
||||||
|
<div class="icon-circle">
|
||||||
|
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 4v16m8-8H4" color="white"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>Integrasi Lengkap</h2>
|
||||||
|
<p>Sistem terhubung antara admin, driver, dan manajemen untuk pengelolaan SPJ yang efisien.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div style="width: 100%; display: flex; align-items: center; justify-content: center; min-height: 100vh;">
|
||||||
|
<div class="login-form">
|
||||||
|
<div class="login-icon">
|
||||||
|
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" color="white"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-bottom: 2rem;">
|
||||||
|
<h2 style="font-size: 1.5rem; font-weight: 700; color: #1f2937; margin-bottom: 0.5rem;">Masuk ke eSPJ</h2>
|
||||||
|
<p style="color: #6b7280; font-size: 0.9rem;">Gunakan Single Sign-On untuk akses yang aman</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="sso-btn" onclick="showSSOModal()">
|
||||||
|
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||||
|
</svg>
|
||||||
|
Masuk dengan SSO
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Controls -->
|
||||||
|
<div id="navigationControls" class="navigation">
|
||||||
|
<!-- Dot Navigation -->
|
||||||
|
<div class="dots">
|
||||||
|
<div class="dot active" data-slide="0"></div>
|
||||||
|
<div class="dot" data-slide="1"></div>
|
||||||
|
<div class="dot" data-slide="2"></div>
|
||||||
|
<div class="dot" data-slide="3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<button id="skipBtn" class="btn btn-skip">Lewati</button>
|
||||||
|
<button id="nextBtn" class="btn btn-primary">Selanjutnya</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SSO Webview Modal -->
|
||||||
|
<div id="ssoModal" class="webview-modal">
|
||||||
|
<div class="webview-container">
|
||||||
|
<div class="webview-header">
|
||||||
|
<div class="webview-title">Single Sign-On</div>
|
||||||
|
<button class="close-btn" onclick="closeSSOModal()">
|
||||||
|
<svg width="16" height="16" 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>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style="position: relative; height: calc(100% - 60px);">
|
||||||
|
<div id="loadingSpinner" class="loading-spinner"></div>
|
||||||
|
<iframe id="ssoIframe" class="webview-iframe" src="" style="display: none;"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Update Available Notification -->
|
||||||
|
<div id="updateNotification" class="update-notification" style="display: none;">
|
||||||
|
<div class="update-content">
|
||||||
|
<div class="update-icon">
|
||||||
|
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="update-text">
|
||||||
|
<p>Update tersedia</p>
|
||||||
|
<small>Versi baru aplikasi sudah tersedia</small>
|
||||||
|
</div>
|
||||||
|
<button onclick="applyUpdate()" class="update-btn">Update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Display any server-side validation errors *@
|
||||||
|
@if (ViewData.ModelState.ErrorCount > 0)
|
||||||
|
{
|
||||||
|
<div id="errorNotification" class="error-notification">
|
||||||
|
<div class="error-content">
|
||||||
|
@foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors))
|
||||||
|
{
|
||||||
|
<p>@error.ErrorMessage</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@section Styles {
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.splash-container {
|
||||||
|
width: 400%;
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide {
|
||||||
|
width: 25%;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-content {
|
||||||
|
text-align: center;
|
||||||
|
animation: slideIn 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-circle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
box-shadow: 0 10px 30px rgba(251, 146, 60, 0.3);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide p {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dots {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e5e7eb;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.active {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-skip {
|
||||||
|
background: transparent;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-skip:hover {
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin: 2rem;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||||
|
animation: fadeInUp 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
box-shadow: 0 8px 20px rgba(251, 146, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sso-btn, .w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sso-btn {
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sso-btn:hover {
|
||||||
|
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 90vw;
|
||||||
|
max-width: 400px;
|
||||||
|
height: 80vh;
|
||||||
|
max-height: 600px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
|
||||||
|
animation: modalSlideIn 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes modalSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -60%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-header {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 60px);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #f3f4f6;
|
||||||
|
border-top: 3px solid #fb923c;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes spin {
|
||||||
|
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
padding: 1rem;
|
||||||
|
z-index: 1001;
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-icon {
|
||||||
|
color: #fb923c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-btn {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #fee2e2;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
color: #dc2626;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1001;
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWA Status Bar Safe Area */
|
||||||
|
@@supports (padding-top: env(safe-area-inset-top)) {
|
||||||
|
.max-w-sm {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@@media (max-width: 480px) {
|
||||||
|
.slide {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-container {
|
||||||
|
width: 95vw;
|
||||||
|
height: 85vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
// PWA Service Worker Registration
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/driver/sw.js')
|
||||||
|
.then((registration) => {
|
||||||
|
console.log('SW registered: ', registration);
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
const newWorker = registration.installing;
|
||||||
|
newWorker.addEventListener('statechange', () => {
|
||||||
|
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||||
|
showUpdateNotification();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((registrationError) => {
|
||||||
|
console.log('SW registration failed: ', registrationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splash Screen Navigation
|
||||||
|
let currentSlide = 0;
|
||||||
|
const totalSlides = 4;
|
||||||
|
const container = document.getElementById('splashContainer');
|
||||||
|
const dots = document.querySelectorAll('.dot');
|
||||||
|
const nextBtn = document.getElementById('nextBtn');
|
||||||
|
const skipBtn = document.getElementById('skipBtn');
|
||||||
|
const navigationControls = document.getElementById('navigationControls');
|
||||||
|
|
||||||
|
function updateSlide(slideIndex) {
|
||||||
|
// Ensure slideIndex is within bounds
|
||||||
|
if (slideIndex < 0) slideIndex = 0;
|
||||||
|
if (slideIndex >= totalSlides) slideIndex = totalSlides - 1;
|
||||||
|
|
||||||
|
currentSlide = slideIndex;
|
||||||
|
container.style.transform = `translateX(-${slideIndex * 25}%)`;
|
||||||
|
|
||||||
|
// Update dots
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.classList.toggle('active', index === slideIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update navigation visibility and button text
|
||||||
|
if (slideIndex === totalSlides - 1) {
|
||||||
|
navigationControls.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
navigationControls.style.display = 'block';
|
||||||
|
nextBtn.textContent = slideIndex === totalSlides - 2 ? 'Masuk' : 'Selanjutnya';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() {
|
||||||
|
if (currentSlide < totalSlides - 1) {
|
||||||
|
currentSlide++;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipToLogin() {
|
||||||
|
currentSlide = totalSlides - 1;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
nextBtn.addEventListener('click', nextSlide);
|
||||||
|
skipBtn.addEventListener('click', skipToLogin);
|
||||||
|
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.addEventListener('click', () => {
|
||||||
|
currentSlide = index;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch/Swipe Navigation
|
||||||
|
let startX = 0;
|
||||||
|
let endX = 0;
|
||||||
|
let isTouch = false;
|
||||||
|
|
||||||
|
container.addEventListener('touchstart', (e) => {
|
||||||
|
startX = e.touches[0].clientX;
|
||||||
|
isTouch = true;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
container.addEventListener('touchmove', (e) => {
|
||||||
|
if (!isTouch) return;
|
||||||
|
// Prevent default scrolling behavior during swipe
|
||||||
|
e.preventDefault();
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
container.addEventListener('touchend', (e) => {
|
||||||
|
if (!isTouch) return;
|
||||||
|
endX = e.changedTouches[0].clientX;
|
||||||
|
handleSwipe();
|
||||||
|
isTouch = false;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
function handleSwipe() {
|
||||||
|
const swipeThreshold = 50;
|
||||||
|
const diff = startX - endX;
|
||||||
|
|
||||||
|
if (Math.abs(diff) > swipeThreshold) {
|
||||||
|
if (diff > 0 && currentSlide < totalSlides - 1) {
|
||||||
|
// Swipe left - next slide
|
||||||
|
nextSlide();
|
||||||
|
} else if (diff < 0 && currentSlide > 0) {
|
||||||
|
// Swipe right - previous slide
|
||||||
|
currentSlide--;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSO Modal Functions
|
||||||
|
function showSSOModal() {
|
||||||
|
const modal = document.getElementById('ssoModal');
|
||||||
|
const iframe = document.getElementById('ssoIframe');
|
||||||
|
const spinner = document.getElementById('loadingSpinner');
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.style.display = 'block';
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Show loading spinner
|
||||||
|
spinner.style.display = 'block';
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
|
||||||
|
// Set SSO URL from server-side ViewBag
|
||||||
|
const ssoUrl = '@Html.Raw(ViewBag.SSOLoginUrl ?? "")';
|
||||||
|
if (ssoUrl) {
|
||||||
|
iframe.src = ssoUrl;
|
||||||
|
} else {
|
||||||
|
console.error('SSO URL not provided');
|
||||||
|
closeSSOModal();
|
||||||
|
alert('SSO tidak tersedia saat ini. Silakan gunakan login manual.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle iframe load
|
||||||
|
iframe.onload = function() {
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
iframe.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle iframe error
|
||||||
|
iframe.onerror = function() {
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
alert('Gagal memuat halaman SSO. Silakan coba lagi.');
|
||||||
|
closeSSOModal();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSSOModal() {
|
||||||
|
const modal = document.getElementById('ssoModal');
|
||||||
|
const iframe = document.getElementById('ssoIframe');
|
||||||
|
|
||||||
|
modal.style.display = 'none';
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
iframe.src = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for SSO success message from iframe
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
// Verify origin for security (uncomment and set your SSO domain)
|
||||||
|
// const allowedOrigins = ['https://your-sso-domain.com'];
|
||||||
|
// if (!allowedOrigins.includes(event.origin)) return;
|
||||||
|
|
||||||
|
if (event.data === 'sso-success' || (event.data && event.data.type === 'sso-success')) {
|
||||||
|
closeSSOModal();
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showNotification('Login berhasil! Mengarahkan...', 'success');
|
||||||
|
|
||||||
|
// Store login state
|
||||||
|
if (typeof(Storage) !== "undefined") {
|
||||||
|
sessionStorage.setItem('isLoggedIn', 'true');
|
||||||
|
sessionStorage.setItem('loginTime', new Date().toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect after short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '@Url.Action("Index", "Home")' || '/';
|
||||||
|
}, 1500);
|
||||||
|
} else if (event.data === 'sso-error' || (event.data && event.data.type === 'sso-error')) {
|
||||||
|
closeSSOModal();
|
||||||
|
showNotification('Login gagal. Silakan coba lagi.', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle back button on Android PWA
|
||||||
|
window.addEventListener('popstate', function(event) {
|
||||||
|
const modal = document.getElementById('ssoModal');
|
||||||
|
if (modal.style.display === 'block') {
|
||||||
|
closeSSOModal();
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PWA Update Functions
|
||||||
|
function showUpdateNotification() {
|
||||||
|
const notification = document.getElementById('updateNotification');
|
||||||
|
if (notification) {
|
||||||
|
notification.style.display = 'block';
|
||||||
|
|
||||||
|
// Auto-hide after 10 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.display = 'none';
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyUpdate() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||||
|
if (registration && registration.waiting) {
|
||||||
|
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification System
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `notification notification-${type}`;
|
||||||
|
notification.innerHTML = `
|
||||||
|
<div class="notification-content">
|
||||||
|
<span>${message}</span>
|
||||||
|
<button onclick="this.parentElement.parentElement.remove()">×</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add notification styles
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: ${type === 'success' ? '#dcfce7' : type === 'error' ? '#fee2e2' : '#f3f4f6'};
|
||||||
|
color: ${type === 'success' ? '#166534' : type === 'error' ? '#dc2626' : '#374151'};
|
||||||
|
border: 1px solid ${type === 'success' ? '#bbf7d0' : type === 'error' ? '#fca5a5' : '#d1d5db'};
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1002;
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
max-width: 300px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
// Auto-remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (notification.parentNode) {
|
||||||
|
notification.remove();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle connection status
|
||||||
|
function updateConnectionStatus() {
|
||||||
|
const isOnline = navigator.onLine;
|
||||||
|
|
||||||
|
if (!isOnline) {
|
||||||
|
showNotification('Koneksi internet terputus. Beberapa fitur mungkin tidak tersedia.', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
showNotification('Koneksi internet kembali terhubung.', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('offline', updateConnectionStatus);
|
||||||
|
|
||||||
|
// Initialize everything when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('DOM Content Loaded');
|
||||||
|
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-hide error notifications after page load
|
||||||
|
const errorNotification = document.getElementById('errorNotification');
|
||||||
|
if (errorNotification) {
|
||||||
|
setTimeout(() => {
|
||||||
|
errorNotification.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize splash screen - ALWAYS start from first slide
|
||||||
|
currentSlide = 0;
|
||||||
|
updateSlide(0);
|
||||||
|
|
||||||
|
console.log('Splash screen initialized at slide:', currentSlide);
|
||||||
|
|
||||||
|
// Optional: Check if user is a returning visitor and show a different experience
|
||||||
|
// But don't automatically skip the splash screen
|
||||||
|
const isReturningUser = sessionStorage.getItem('hasSeenSplash') === 'true';
|
||||||
|
if (isReturningUser) {
|
||||||
|
// You can add a "Skip Intro" button or similar UX improvement
|
||||||
|
console.log('Returning user detected');
|
||||||
|
// But still show the splash screen by default
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem('hasSeenSplash', 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PWA Install Prompt
|
||||||
|
let deferredPrompt;
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
// Prevent Chrome 67 and earlier from automatically showing the prompt
|
||||||
|
e.preventDefault();
|
||||||
|
// Stash the event so it can be triggered later
|
||||||
|
deferredPrompt = e;
|
||||||
|
|
||||||
|
// Show install button/notification
|
||||||
|
showInstallPrompt();
|
||||||
|
});
|
||||||
|
|
||||||
|
function showInstallPrompt() {
|
||||||
|
const installNotification = document.createElement('div');
|
||||||
|
installNotification.innerHTML = `
|
||||||
|
<div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: white; padding: 1rem; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); z-index: 1001; text-align: center;">
|
||||||
|
<p style="margin-bottom: 1rem; color: #374151;">Install eSPJ untuk pengalaman yang lebih baik</p>
|
||||||
|
<button onclick="installPWA()" style="background: linear-gradient(135deg, #fb923c, #f59e42); color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; margin-right: 0.5rem; cursor: pointer;">Install</button>
|
||||||
|
<button onclick="this.parentElement.parentElement.remove()" style="background: #6b7280; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; cursor: pointer;">Nanti</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(installNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPWA() {
|
||||||
|
if (deferredPrompt) {
|
||||||
|
deferredPrompt.prompt();
|
||||||
|
deferredPrompt.userChoice.then((result) => {
|
||||||
|
if (result.outcome === 'accepted') {
|
||||||
|
console.log('User accepted the install prompt');
|
||||||
|
showNotification('Aplikasi berhasil diinstall!', 'success');
|
||||||
|
}
|
||||||
|
deferredPrompt = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Profil Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
<!-- Header with Orange Background -->
|
||||||
|
<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">Profil</h1>
|
||||||
|
<div class="w-8"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Picture Section - Overlapping -->
|
||||||
|
<div class="flex justify-center -mt-12 mb-6 relative z-10">
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Profile Picture -->
|
||||||
|
<div class="w-20 h-20 rounded-full border-2 border-white shadow-xl overflow-hidden bg-gray-200">
|
||||||
|
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face"
|
||||||
|
alt="Profile Picture"
|
||||||
|
class="w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Icon -->
|
||||||
|
<button class="absolute bottom-1 right-1 w-7 h-7 bg-white rounded-full shadow-lg flex items-center justify-center border border-gray-100 hover:bg-gray-50 transition-colors">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="edit-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Content -->
|
||||||
|
<div class="p-4 space-y-4 bg-white border border-gray-200 mx-4 rounded-2xl">
|
||||||
|
<!-- Info Akun Header -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-lg font-bold text-gray-800">Info Akun</h2>
|
||||||
|
<button class="text-orange-500 font-medium hover:text-orange-600 transition-colors text-sm">
|
||||||
|
Ubah
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Information -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Nama -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="user"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">Nama</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800">Bonny Agung Putra</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="mail"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">E-mail</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800">bonny@gmail.com</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Phone -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="phone"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">No. HP</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800">+6285777777777</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">Alamat</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt 004 Rw 001 Kel. Klender Kec, Duren Swakit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- Logout Button -->
|
||||||
|
<div class="mx-4 pt-4 pb-4">
|
||||||
|
<button class="w-full bg-orange-500 hover:bg-orange-600 text-white py-3 rounded-xl font-bold text-base shadow-lg transition-all duration-200 hover:scale-105">
|
||||||
|
Keluar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,53 @@
|
||||||
|
<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="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="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
|
||||||
|
<div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Content -->
|
||||||
|
<div class="flex justify-between items-center px-8 relative pt-6">
|
||||||
|
<!-- Home Button -->
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="home"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">Home</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="w-12"></div>
|
||||||
|
|
||||||
|
<!-- Profile Button -->
|
||||||
|
|
||||||
|
<a href="@Url.Action("Index", "History")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">History</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Center Submit -->
|
||||||
|
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||||
|
<a href="@Url.Action("Index", "Submit")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="truck"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@* <!-- ini untuk yang struk -->
|
||||||
|
<a href="@Url.Action("Struk", "Submit")" id="strukBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="file-text"></i>
|
||||||
|
</a> *@
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-30"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsNav">
|
||||||
|
|
||||||
|
</register-block>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full 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="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="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
|
||||||
|
<div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Content -->
|
||||||
|
<div class="flex justify-between items-center px-8 relative pt-6">
|
||||||
|
<!-- Home Button -->
|
||||||
|
<a href="@Url.Action("Kosong", "Home")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="home"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">Home</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="w-12"></div>
|
||||||
|
|
||||||
|
<!-- Profile Button -->
|
||||||
|
|
||||||
|
<a href="@Url.Action("Index", "History")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">History</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Center Submit -->
|
||||||
|
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||||
|
<a href="@Url.Action("Index", "Scan")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="camera"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-30"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsNav">
|
||||||
|
|
||||||
|
</register-block>
|
|
@ -0,0 +1,25 @@
|
||||||
|
@model ErrorViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (Model.ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"] - eSPJ</title>
|
||||||
|
<script type="importmap"></script>
|
||||||
|
|
||||||
|
<!-- Theme Colors -->
|
||||||
|
<meta name="theme-color" content="#f97316">
|
||||||
|
<meta name="msapplication-navbutton-color" content="#f97316">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
|
|
||||||
|
<!-- Mobile Optimization -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="SPJ Angkut">
|
||||||
|
|
||||||
|
<!-- iOS Icons -->
|
||||||
|
<link rel="apple-touch-icon" href="~/icons/icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="~/icons/icon-180x180.png">
|
||||||
|
|
||||||
|
<!-- Windows Tiles -->
|
||||||
|
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
|
||||||
|
<meta name="msapplication-TileColor" content="#f97316">
|
||||||
|
|
||||||
|
<link rel="manifest" href="@Url.Content("~/driver/manifest.json")" />
|
||||||
|
@* <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> *@
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/watch.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/website.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/eSPJ.styles.css")" asp-append-version="true" />
|
||||||
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
|
||||||
|
@RenderBody()
|
||||||
|
|
||||||
|
<script src="@Url.Content("~/driver/lib/jquery/dist/jquery.min.js")"></script>
|
||||||
|
<script src="@Url.Content("~/driver/lib/bootstrap/dist/js/bootstrap.bundle.min.js")"></script>
|
||||||
|
<script src="@Url.Content("~/driver/js/site.js")">" asp-append-version="true"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
<dynamic-section name="scripts" />
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||||
|
for details on configuring this project to bundle and minify static web assets. */
|
||||||
|
|
||||||
|
a.navbar-brand {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0077cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.accept-policy {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||||
|
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
|
|
@ -0,0 +1,509 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Submit Foto Muatan";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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="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">Unggah Foto Muatan</h1>
|
||||||
|
<div class="w-8"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 py-6 -mt-6 relative z-10">
|
||||||
|
<div class="bg-white rounded-2xl p-5 border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-12 h-12 bg-gradient-to-br from-orange-100 to-orange-200 rounded-2xl flex items-center justify-center">
|
||||||
|
<i class="w-6 h-6 text-orange-600" data-lucide="camera"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Foto Muatan Kendaraan</h2>
|
||||||
|
<p class="text-xs text-gray-500 flex items-center gap-1">
|
||||||
|
<i class="w-3 h-3" data-lucide="info"></i>
|
||||||
|
Optional
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<label for="foto-upload" class="cursor-pointer w-full block">
|
||||||
|
<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 class="upload-icon-container">
|
||||||
|
<img class="object-cover" 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>
|
||||||
|
<img id="preview-image" src="#" alt="Preview" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;" />
|
||||||
|
|
||||||
|
<!-- Overlay buttons -->
|
||||||
|
<div class="preview-overlay" id="preview-overlay" style="display:none;">
|
||||||
|
<button type="button" class="overlay-btn" id="edit-preview" aria-label="Edit gambar">
|
||||||
|
<i class="w-5 h-5" data-lucide="edit-3"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="overlay-btn delete" id="close-preview" aria-label="Hapus gambar">
|
||||||
|
<i class="w-5 h-5" data-lucide="trash-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File info -->
|
||||||
|
<div class="file-info" id="file-info" style="display:none;">
|
||||||
|
<span id="file-name">image.jpg</span> • <span id="file-size">0 MB</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Upload progress -->
|
||||||
|
<div class="upload-progress" id="upload-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div id="upload-text">
|
||||||
|
<p class="text-sm text-gray-600 font-medium">Tap untuk unggah foto</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-1">atau drag & drop file di sini</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-2 flex items-center justify-center gap-1">
|
||||||
|
<i class="w-3 h-3" data-lucide="file-type"></i>
|
||||||
|
JPG, JPEG, PNG maksimal 10MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 mb-2">
|
||||||
|
<div class="location-badge rounded-2xl p-4">
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<i class="w-4 h-4 text-blue-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium text-gray-700">Lokasi Anda:</span>
|
||||||
|
<button type="button" id="refresh-location" class="ml-auto p-1 hover:bg-gray-100 rounded-full transition-colors">
|
||||||
|
<i class="w-4 h-4 text-gray-500" data-lucide="refresh-cw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p id="userLocationSubmit" class="font-semibold text-xs tracking-wide cursor-pointer underline text-orange-600 hover:text-orange-800 transition mt-1 flex items-center gap-2">
|
||||||
|
<span>Mendeteksi lokasi...</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-1">Klik lokasi di atas untuk update posisi Anda</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form asp-action="UploadFotoMuatan" method="post" enctype="multipart/form-data" class="mt-6" id="upload-form">
|
||||||
|
<input type="hidden" name="Latitude" id="input-latitude" />
|
||||||
|
<input type="hidden" name="Longitude" id="input-longitude" />
|
||||||
|
<input type="hidden" name="AlamatJalan" id="input-alamat-jalan" />
|
||||||
|
<div class="flex gap-3 pt-4">
|
||||||
|
<button type="submit" id="submit-btn"
|
||||||
|
class="floating-button flex-1 bg-gradient-to-r from-orange-500 to-orange-600 text-white font-semibold py-3 rounded-xl hover:from-orange-600 hover:to-orange-700 transition-all duration-200 shadow-lg flex items-center justify-center gap-2">
|
||||||
|
<i class="w-5 h-5" data-lucide="upload"></i>
|
||||||
|
<span>Unggah</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="styles" key="cssSubmit">
|
||||||
|
<style>
|
||||||
|
.upload-area {
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover {
|
||||||
|
background: linear-gradient(135deg, #fef7ed 0%, #fed7aa 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area.dragover {
|
||||||
|
background: linear-gradient(135deg, #fef7ed 0%, #fed7aa 100%);
|
||||||
|
border-color: #f97316 !important;
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(251, 146, 60, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container:hover .preview-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-btn.delete:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #f97316, #fb923c);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-radius: 0 0 12px 12px;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-animation {
|
||||||
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: .5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-animation {
|
||||||
|
animation: successPulse 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes successPulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container:hover .file-info {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-badge {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-button {
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(251, 146, 60, 0.4);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 20px 40px -5px rgba(251, 146, 60, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon-container {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
background: linear-gradient(135deg, #fef7ed 0%, #fed7aa 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover .upload-icon-container {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover .upload-icon-container i {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</register-block>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsSubmit">
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const userLocationEl = document.getElementById("userLocationSubmit");
|
||||||
|
const inputLat = document.getElementById("input-latitude");
|
||||||
|
const inputLng = document.getElementById("input-longitude");
|
||||||
|
const inputAlamat = document.getElementById("input-alamat-jalan");
|
||||||
|
const refreshLocationBtn = document.getElementById("refresh-location");
|
||||||
|
|
||||||
|
const uploadArea = document.getElementById("upload-area");
|
||||||
|
const fotoInput = document.getElementById("foto-upload");
|
||||||
|
const previewImage = document.getElementById("preview-image");
|
||||||
|
const previewIcon = document.getElementById("preview-icon");
|
||||||
|
const closePreview = document.getElementById("close-preview");
|
||||||
|
const editPreview = document.getElementById("edit-preview");
|
||||||
|
const previewOverlay = document.getElementById("preview-overlay");
|
||||||
|
const defaultState = document.getElementById("default-state");
|
||||||
|
const uploadText = document.getElementById("upload-text");
|
||||||
|
const fileInfo = document.getElementById("file-info");
|
||||||
|
const fileName = document.getElementById("file-name");
|
||||||
|
const fileSize = document.getElementById("file-size");
|
||||||
|
const uploadProgress = document.getElementById("upload-progress");
|
||||||
|
const submitBtn = document.getElementById("submit-btn");
|
||||||
|
|
||||||
|
uploadArea.addEventListener("dragover", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.classList.add("dragover");
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener("dragleave", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.classList.remove("dragover");
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener("drop", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.classList.remove("dragover");
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
if (files.length > 0) {
|
||||||
|
handleFileSelect(files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener("click", function(e) {
|
||||||
|
if (e.target.tagName !== 'LABEL' && !e.target.closest('label')) {
|
||||||
|
fotoInput.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fotoInput.addEventListener("change", function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
handleFileSelect(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleFileSelect(file) {
|
||||||
|
const allowedTypes = ["image/jpeg", "image/png", "image/jpg"];
|
||||||
|
if (!allowedTypes.includes(file.type)) {
|
||||||
|
showAlert("File harus JPG, JPEG, atau PNG.", "error");
|
||||||
|
fotoInput.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
|
showAlert("Ukuran maksimal 10MB.", "error");
|
||||||
|
fotoInput.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showUploadProgress(file);
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(ev) {
|
||||||
|
previewImage.src = ev.target.result;
|
||||||
|
fileName.textContent = file.name;
|
||||||
|
fileSize.textContent = formatFileSize(file.size);
|
||||||
|
|
||||||
|
let progress = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
progress += Math.random() * 15;
|
||||||
|
if (progress >= 100) {
|
||||||
|
progress = 100;
|
||||||
|
clearInterval(interval);
|
||||||
|
setTimeout(() => {
|
||||||
|
showPreview();
|
||||||
|
uploadProgress.style.width = "0%";
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
uploadProgress.style.width = progress + "%";
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUploadProgress(file) {
|
||||||
|
defaultState.style.display = "none";
|
||||||
|
uploadText.style.display = "none";
|
||||||
|
uploadProgress.style.width = "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPreview() {
|
||||||
|
previewImage.style.display = "block";
|
||||||
|
previewOverlay.style.display = "flex";
|
||||||
|
fileInfo.style.display = "block";
|
||||||
|
uploadArea.classList.add("success-animation");
|
||||||
|
showAlert("Foto berhasil dimuat!", "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUpload() {
|
||||||
|
fotoInput.value = "";
|
||||||
|
previewImage.src = "#";
|
||||||
|
previewImage.style.display = "none";
|
||||||
|
previewOverlay.style.display = "none";
|
||||||
|
fileInfo.style.display = "none";
|
||||||
|
defaultState.style.display = "block";
|
||||||
|
uploadText.style.display = "block";
|
||||||
|
uploadArea.classList.remove("success-animation");
|
||||||
|
uploadProgress.style.width = "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
editPreview.addEventListener("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
fotoInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
closePreview.addEventListener("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
resetUpload();
|
||||||
|
});
|
||||||
|
|
||||||
|
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.innerHTML = `<span>${address}</span>`;
|
||||||
|
localStorage.setItem("user_latitude", lat);
|
||||||
|
localStorage.setItem("user_longitude", lng);
|
||||||
|
localStorage.setItem("user_address", address);
|
||||||
|
if (inputLat) inputLat.value = lat;
|
||||||
|
if (inputLng) inputLng.value = lng;
|
||||||
|
if (inputAlamat) inputAlamat.value = address;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
userLocationEl.innerHTML = `<span>${lat}, ${lng}</span>`;
|
||||||
|
if (inputLat) inputLat.value = lat;
|
||||||
|
if (inputLng) inputLng.value = lng;
|
||||||
|
if (inputAlamat) inputAlamat.value = `${lat}, ${lng}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationUpdate() {
|
||||||
|
if ("geolocation" in navigator) {
|
||||||
|
userLocationEl.innerHTML = `<span>Mendeteksi lokasi...</span>`;
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function (position) {
|
||||||
|
const lat = position.coords.latitude.toFixed(6);
|
||||||
|
const lng = position.coords.longitude.toFixed(6);
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
userLocationEl.innerHTML = `<span>Lokasi tidak diizinkan</span>`;
|
||||||
|
if (inputLat) inputLat.value = "";
|
||||||
|
if (inputLng) inputLng.value = "";
|
||||||
|
if (inputAlamat) inputAlamat.value = "Lokasi tidak diizinkan";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
userLocationEl.innerHTML = `<span>Browser tidak mendukung lokasi</span>`;
|
||||||
|
if (inputLat) inputLat.value = "";
|
||||||
|
if (inputLng) inputLng.value = "";
|
||||||
|
if (inputAlamat) inputAlamat.value = "Browser tidak mendukung lokasi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocationUpdate();
|
||||||
|
|
||||||
|
userLocationEl.addEventListener("click", function () {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshLocationBtn.addEventListener("click", function() {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("upload-form").addEventListener("submit", function(e) {
|
||||||
|
const originalContent = submitBtn.innerHTML;
|
||||||
|
submitBtn.innerHTML = `
|
||||||
|
<svg class="animate-spin w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Mengunggah...</span>
|
||||||
|
`;
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
submitBtn.innerHTML = originalContent;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
showAlert("Form berhasil dikirim!", "success");
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert(message, type) {
|
||||||
|
const alertDiv = document.createElement('div');
|
||||||
|
alertDiv.className = `fixed top-4 right-4 z-50 p-4 rounded-lg text-white font-medium transition-all duration-300 ${
|
||||||
|
type === 'success' ? 'bg-green-500' : 'bg-red-500'
|
||||||
|
}`;
|
||||||
|
alertDiv.textContent = message;
|
||||||
|
alertDiv.style.transform = 'translateX(100%)';
|
||||||
|
|
||||||
|
document.body.appendChild(alertDiv);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
alertDiv.style.transform = 'translateX(0)';
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
alertDiv.style.transform = 'translateX(100%)';
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.body.contains(alertDiv)) {
|
||||||
|
document.body.removeChild(alertDiv);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0 hidden md:block">
|
<div class="flex-shrink-0 hidden md:block">
|
||||||
<img src="@Url.Content("~/website/images/bg-header.svg")" alt="Usaha Pesapakawan" class="w-52 h-auto">
|
<img src="@Url.Content("~/website/images/bg-header.png")" alt="Usaha Pesapakawan" class="w-75 h-auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0 hidden md:block">
|
<div class="flex-shrink-0 hidden md:block">
|
||||||
<img src="@Url.Content("~/website/images/bg-header.svg")" alt="Usaha Pesapakawan" class="w-52 h-auto">
|
<img src="@Url.Content("~/website/images/bg-header.png")" alt="Usaha Pesapakawan" class="w-75 h-auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0 hidden md:block">
|
<div class="flex-shrink-0 hidden md:block">
|
||||||
<img src="@Url.Content("~/website/images/bg-header.svg")" alt="Usaha Pesapakawan" class="w-52 h-auto">
|
<img src="@Url.Content("~/website/images/bg-header.png")" alt="Usaha Pesapakawan" class="w-75 h-auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,19 +6,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="text-left absolute top-1/2 left-1/7 z-10 text-white">
|
<div class="text-left absolute top-1/2 left-1/7 z-10 text-white">
|
||||||
<h3 class="text-2xl md:text-4xl font-bold mb-4">Kenapa Harus Melakukan</h3>
|
<h3 class="text-2xl md:text-4xl font-bold mb-4">Kenapa Harus Melakukan</h3>
|
||||||
<h2 class="text-2xl md:text-4xl font-bold">PESAPA KAWAN ?</h2>
|
<h2 class="text-2xl md:text-4xl font-bold">PesapaKawan?</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full lg:w-1/2 md:w-1/2">
|
<div class="w-full lg:w-1/2 md:w-1/2">
|
||||||
<div class="faq-content">
|
<div class="faq-content">
|
||||||
@* <div class="mb-8">
|
<div class="space-y-4 px-4">
|
||||||
<h2 class="text-3xl font-bold bg-gradient-to-r from-green-600 to-blue-600 bg-clip-text text-transparent mb-2">
|
|
||||||
Frequently Asked Questions
|
|
||||||
</h2>
|
|
||||||
<p class="text-gray-600">Pertanyaan yang sering diajukan tentang PESAPA KAWAN</p>
|
|
||||||
</div> *@
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="faq-item border border-gray-200 rounded-lg overflow-hidden">
|
<div class="faq-item border border-gray-200 rounded-lg overflow-hidden">
|
||||||
<button class="faq-question w-full text-left px-6 py-4 bg-blue-50 hover:bg-blue-100 transition-colors duration-200 flex justify-between items-center">
|
<button class="faq-question w-full text-left px-6 py-4 bg-blue-50 hover:bg-blue-100 transition-colors duration-200 flex justify-between items-center">
|
||||||
<span class="font-medium text-gray-800">Kewajiban Pengelolaan Sampah</span>
|
<span class="font-medium text-gray-800">Kewajiban Pengelolaan Sampah</span>
|
||||||
|
@ -97,7 +91,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
question.addEventListener('click', function() {
|
question.addEventListener('click', function() {
|
||||||
const isOpen = !answer.classList.contains('hidden');
|
const isOpen = !answer.classList.contains('hidden');
|
||||||
|
|
||||||
// Close all other FAQ items
|
|
||||||
faqItems.forEach(otherItem => {
|
faqItems.forEach(otherItem => {
|
||||||
if (otherItem !== item) {
|
if (otherItem !== item) {
|
||||||
otherItem.querySelector('.faq-answer').classList.add('hidden');
|
otherItem.querySelector('.faq-answer').classList.add('hidden');
|
||||||
|
@ -105,7 +98,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle current item
|
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
answer.classList.add('hidden');
|
answer.classList.add('hidden');
|
||||||
icon.classList.remove('rotate-180');
|
icon.classList.remove('rotate-180');
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
<section class="relative bg-cover bg-center bg-no-repeat min-h-screen flex items-center justify-center overflow-hidden" style="background-image: url('@Url.Content("~/website/images/bg-hero.jpg")')">
|
<section class="relative bg-cover bg-center bg-no-repeat min-h-screen flex items-center justify-center overflow-hidden" style="background-image: url('@Url.Content("~/website/images/bg-hero.jpg")');">
|
||||||
|
|
||||||
|
<!-- Black overlay with 10% opacity -->
|
||||||
|
@* <div class="absolute inset-0 bg-black/20"></div> *@
|
||||||
|
|
||||||
<div class="relative z-10 text-center text-white px-4">
|
<div class="relative z-10 text-center text-white px-4">
|
||||||
<div class="bg-[#20D3D3] text-white text-sm px-4 rounded-full mb-8 inline-block">
|
<div class="bg-[#20D3D3] text-white text-sm px-4 rounded-full mb-8 inline-block">
|
||||||
Peraturan Gubernur Nomor 102 Tahun 2021
|
Peraturan Gubernur Nomor 102 Tahun 2021
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="text-6xl md:text-7xl font-bold leading-tight">
|
<h1 class="text-4xl md:text-6xl font-bold leading-tight">
|
||||||
PesapaKawan
|
PesapaKawan
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h2 class="text-2xl md:text-3xl font-light mb-8 opacity-90">
|
<h2 class="text-xl md:text-3xl font-light mb-8 opacity-90 px-4">
|
||||||
Pengelolaan Sampah Pada Kawasan dan Perusahaan
|
Pengelolaan Sampah Pada Kawasan dan Perusahaan
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p class="text-lg md:text-xl max-w-2xl mx-auto opacity-80 leading-relaxed">
|
<p class="text-md md:text-xl max-w-2xl px-4 opacity-80 leading-relaxed">
|
||||||
Membangun semangat kolaborasi untuk mewujudkan Jakarta<br>
|
Membangun semangat kolaborasi untuk mewujudkan Jakarta<br>
|
||||||
yang bersih dan masa depan yang lebih baik.
|
yang bersih dan masa depan yang lebih baik.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
<img src="@Url.Content("~/website/images/laksanakan.svg")" alt="Badge" class="absolute -top-17 left-1/2 transform -translate-x-1/2 w-24 h-24 z-10">
|
<img src="@Url.Content("~/website/images/laksanakan.png")" alt="Badge" class="absolute -top-15 left-1/2 transform -translate-x-1/2 w-30 h-auto z-10">
|
||||||
<div class="bg-[#20D3D3] text-white px-4 py-2 inline-block rounded-full text-sm font-medium">
|
<div class="bg-[#20D3D3] text-white px-4 py-2 inline-block rounded-full text-sm font-medium">
|
||||||
CARA MELAKSANAKAN
|
CARA MELAKSANAKAN
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-5xl font-bold text-gray-900 mb-4">Pesapa Kawan</h2>
|
<h2 class="text-4xl md:text-6xl font-bold text-gray-900 my-4">PesapaKawan</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 items-center flex flex-col lg:flex-row justify-center">
|
<div class="mt-8 items-center flex flex-col lg:flex-row justify-center">
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
<img src="@Url.Content("~/website/images/pengelolaan.svg")" alt="Badge" class="absolute -top-18 left-1/2 transform -translate-x-1/2 w-24 h-24 z-10">
|
<img src="@Url.Content("~/website/images/pengelolaan.png")" alt="Badge" class="absolute -top-23 left-1/2 transform -translate-x-1/2 w-auto h-30 z-10">
|
||||||
<div class="bg-[#20D3D3] text-white px-4 py-2 inline-block rounded-full text-sm font-medium">
|
<div class="bg-[#20D3D3] text-white px-4 py-2 inline-block rounded-full text-sm font-medium">
|
||||||
ALUR PENGELOLAAN
|
ALUR PENGELOLAAN
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-5xl max-w-2xl items-center justify-center flex mx-auto font-bold text-gray-900 mb-4">Sampah Kawasan dan Perusahaan Secara Mandiri</h2>
|
<h2 class="text-4xl md:text-6xl max-w-2xl items-center justify-center flex mx-auto font-bold text-gray-900 my-4">Sampah Kawasan dan Perusahaan Secara Mandiri</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 items-center flex flex-col lg:flex-row justify-center">
|
<div class="mt-8 items-center flex flex-col lg:flex-row justify-center">
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
<img src="@Url.Content("~/website/images/peran.svg")" alt="Badge" class="absolute -top-20 left-1/2 transform -translate-x-1/2 w-24 h-24 z-10">
|
<img src="@Url.Content("~/website/images/peran.png")" alt="Badge" class="absolute -top-17 left-1/2 transform -translate-x-1/2 w-30 h-auto z-10">
|
||||||
<div class="bg-[#20D3D3] text-white px-4 py-2 inline-block rounded-full text-sm font-medium">
|
<div class="bg-[#20D3D3] text-white px-4 py-2 inline-block rounded-full text-sm font-medium">
|
||||||
PIHAK YANG BERPERAN
|
PIHAK YANG BERPERAN
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-5xl font-bold text-gray-900 mb-4">Pesapa Kawan</h2>
|
<h2 class="text-4xl md:text-6xl font-bold text-gray-900 mb-4">Pesapa Kawan</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 items-center flex flex-col lg:flex-row justify-center">
|
<div class="mt-8 items-center flex flex-col lg:flex-row justify-center">
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
|
|
||||||
<div class="flex space-x-3 md:space-x-4">
|
<div class="flex space-x-3 md:space-x-4">
|
||||||
<img class="w-12 h-12 md:w-16 md:h-16" src="@Url.Content("~/website/logo-dlh.svg")" alt="">
|
<img class="w-12 h-12 md:w-16 md:h-16" src="@Url.Content("~/website/logo-dlh.svg")" alt="">
|
||||||
<img class="w-12 h-12 md:w-16 md:h-16" src="@Url.Content("~/website/logo-upst.svg")" alt="">
|
|
||||||
</div>
|
</div>
|
||||||
<small class="bg-teal-400 text-white text-xs md:text-sm px-3 md:px-4 py-1 rounded-full inline-block mt-6 md:mt-8">
|
<small class="bg-teal-400 text-white text-xs md:text-sm px-3 md:px-4 py-1 rounded-full inline-block mt-6 md:mt-8">
|
||||||
Tentang
|
Tentang
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<h2 class="text-3xl sm:text-4xl md:text-6xl lg:text-7xl font-bold leading-tight mt-4">
|
<h2 class="text-4xl md:text-6xl font-bold leading-tight mt-4">
|
||||||
PesapaKawan
|
PesapaKawan
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<footer class="bg-cyan-700 py-10 relative overflow-hidden rounded-tr-[200px]">
|
<footer class="bg-cyan-700 py-10 relative overflow-hidden rounded-tr-4xl md:rounded-tr-[200px]">
|
||||||
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 85% 1%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 96%, transparent 96%, transparent 100%), radial-gradient(circle at 14% 15%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 1%, transparent 1%, transparent 100%), radial-gradient(circle at 60% 90%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 20%, transparent 20%, transparent 100%), radial-gradient(circle at 79% 7%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 78%, transparent 78%, transparent 100%), radial-gradient(circle at 55% 65%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 52%, transparent 52%, transparent 100%), linear-gradient(135deg, rgb(0, 163, 227), rgb(6, 182, 212));"></div>
|
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 85% 1%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 96%, transparent 96%, transparent 100%), radial-gradient(circle at 14% 15%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 1%, transparent 1%, transparent 100%), radial-gradient(circle at 60% 90%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 20%, transparent 20%, transparent 100%), radial-gradient(circle at 79% 7%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 78%, transparent 78%, transparent 100%), radial-gradient(circle at 55% 65%, hsla(190, 0%, 93%, 0.05) 0%, hsla(190, 0%, 93%, 0.05) 52%, transparent 52%, transparent 100%), linear-gradient(135deg, rgb(0, 163, 227), rgb(6, 182, 212));"></div>
|
||||||
|
|
||||||
<div class="container mx-auto max-w-6xl px-4 relative z-10">
|
<div class="container mx-auto max-w-6xl px-4 relative z-10">
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-gray-700 mt-8 pt-8">
|
<div class="border-t border-white mt-8 pt-8">
|
||||||
<div class="flex mx-auto justify-center items-center text-gray-300">
|
<div class="flex mx-auto justify-center items-center text-gray-300">
|
||||||
<p class="mb-4 md:mb-0">
|
<p class="mb-4 md:mb-0">
|
||||||
© @DateTime.Now.Year Pesapakawan. All rights reserved.
|
© @DateTime.Now.Year Pesapakawan. All rights reserved.
|
||||||
|
|
|
@ -20,14 +20,14 @@
|
||||||
<svg class="w-4 h-4 transition-transform duration-300 group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
<svg class="w-4 h-4 transition-transform duration-300 group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
||||||
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-amber-300 transition-all duration-300 group-hover:w-full"></span>
|
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-amber-300 transition-all duration-300 group-hover:w-full"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="absolute left-0 mt-2 min-w-[240px] bg-white/95 rounded-2xl shadow-xl border border-amber-100 opacity-0 pointer-events-none transition-all duration-300 z-50 group-hover:opacity-100 group-hover:pointer-events-auto flex flex-col divide-y divide-amber-100" id="dropdown-jasa-menu">
|
<div class="absolute left-0 mt-2 min-w-[240px] bg-white/95 rounded-2xl shadow-xl border border-amber-100 opacity-0 pointer-events-none transition-all duration-300 z-[100] group-hover:opacity-100 group-hover:pointer-events-auto flex flex-col divide-y divide-amber-100 transform origin-top scale-95 group-hover:scale-100" id="dropdown-jasa-menu">
|
||||||
<a href="@Url.Action("PengangkutanSampah", "Website")" class="flex items-center px-5 py-3 text-gray-900 font-semibold hover:bg-amber-50 hover:text-amber-600 transition-all duration-200 rounded-t-2xl">
|
<a href="@Url.Action("PengangkutanSampah", "Website")" class="flex items-center px-5 py-3 text-gray-900 font-semibold hover:bg-amber-50 hover:text-amber-600 transition-all duration-200 rounded-t-2xl">
|
||||||
<span class="mr-2"><svg class="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h8" /></svg></span>
|
<span class="mr-2"><svg class="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h8" /></svg></span>
|
||||||
Pengangkutan Sampah
|
PENGANGKUTAN SAMPAH
|
||||||
</a>
|
</a>
|
||||||
<a href="@Url.Action("PengolahanSampah", "Website")" class="flex items-center px-5 py-3 text-gray-900 font-semibold hover:bg-teal-50 hover:text-teal-600 transition-all duration-200 rounded-b-2xl">
|
<a href="@Url.Action("PengolahanSampah", "Website")" class="flex items-center px-5 py-3 text-gray-900 font-semibold hover:bg-teal-50 hover:text-teal-600 transition-all duration-200 rounded-b-2xl">
|
||||||
<span class="mr-2"><svg class="w-5 h-5 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8" /></svg></span>
|
<span class="mr-2"><svg class="w-5 h-5 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8" /></svg></span>
|
||||||
Pengolahan Sampah
|
PENGOLAHAN SAMPAH
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,15 +59,30 @@
|
||||||
|
|
||||||
<div id="mobile-menu" class="lg:hidden hidden">
|
<div id="mobile-menu" class="lg:hidden hidden">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 bg-white/20 backdrop-blur-md rounded-2xl mt-2 border border-white/10">
|
<div class="px-2 pt-2 pb-3 space-y-1 bg-white/20 backdrop-blur-md rounded-2xl mt-2 border border-white/10">
|
||||||
<a href="@Url.Action("Index", "Website")" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
<a href="/" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
||||||
BERANDA
|
BERANDA
|
||||||
</a>
|
</a>
|
||||||
<a href="@Url.Action("Usaha", "Website")" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
<a href="@Url.Action("Usaha", "Website")" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
||||||
USAHA/KEGIATAN
|
USAHA/KEGIATAN
|
||||||
</a>
|
</a>
|
||||||
<a href="@Url.Action("PenyediaJasa", "Website")" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
<div class="mobile-dropdown">
|
||||||
PENYEDIA JASA
|
<button class="flex justify-between items-center w-full px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300" onclick="toggleMobileSubmenu(this)">
|
||||||
|
<span>PENYEDIA JASA</span>
|
||||||
|
<svg class="w-4 h-4 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="hidden pl-4 mt-1 space-y-1 bg-white/10 rounded-lg">
|
||||||
|
<a href="@Url.Action("PengangkutanSampah", "Website")" class="flex items-center px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg transition-all duration-300">
|
||||||
|
<span class="mr-2 text-amber-400">•</span>
|
||||||
|
Pengangkutan Sampah
|
||||||
</a>
|
</a>
|
||||||
|
<a href="@Url.Action("PengolahanSampah", "Website")" class="flex items-center px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg transition-all duration-300">
|
||||||
|
<span class="mr-2 text-teal-400">•</span>
|
||||||
|
Pengolahan Sampah
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<a href="@Url.Action("Kontak", "Website")" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
<a href="@Url.Action("Kontak", "Website")" class="block px-3 py-2 text-white hover:text-amber-300 hover:bg-white/10 rounded-lg font-medium transition-all duration-300">
|
||||||
KONTAK
|
KONTAK
|
||||||
</a>
|
</a>
|
||||||
|
@ -93,7 +108,6 @@
|
||||||
mobileMenuButton.addEventListener('click', function() {
|
mobileMenuButton.addEventListener('click', function() {
|
||||||
mobileMenu.classList.toggle('hidden');
|
mobileMenu.classList.toggle('hidden');
|
||||||
|
|
||||||
// Animate menu icon
|
|
||||||
const svg = mobileMenuButton.querySelector('svg');
|
const svg = mobileMenuButton.querySelector('svg');
|
||||||
if (mobileMenu.classList.contains('hidden')) {
|
if (mobileMenu.classList.contains('hidden')) {
|
||||||
svg.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>';
|
svg.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>';
|
||||||
|
@ -102,7 +116,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close mobile menu when clicking outside
|
|
||||||
document.addEventListener('click', function(event) {
|
document.addEventListener('click', function(event) {
|
||||||
if (!mobileMenuButton.contains(event.target) && !mobileMenu.contains(event.target)) {
|
if (!mobileMenuButton.contains(event.target) && !mobileMenu.contains(event.target)) {
|
||||||
mobileMenu.classList.add('hidden');
|
mobileMenu.classList.add('hidden');
|
||||||
|
@ -111,7 +124,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close mobile menu when window is resized to desktop
|
|
||||||
window.addEventListener('resize', function() {
|
window.addEventListener('resize', function() {
|
||||||
if (window.innerWidth >= 1024) {
|
if (window.innerWidth >= 1024) {
|
||||||
mobileMenu.classList.add('hidden');
|
mobileMenu.classList.add('hidden');
|
||||||
|
@ -124,50 +136,81 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Dropdown submenu for PENYEDIA JASA
|
|
||||||
const jasaBtn = document.getElementById('dropdown-jasa-button');
|
const jasaBtn = document.getElementById('dropdown-jasa-button');
|
||||||
const jasaMenu = document.getElementById('dropdown-jasa-menu');
|
const jasaMenu = document.getElementById('dropdown-jasa-menu');
|
||||||
const jasaWrapper = document.getElementById('dropdown-jasa');
|
const jasaWrapper = document.getElementById('dropdown-jasa');
|
||||||
|
|
||||||
let submenuTimeout;
|
let submenuTimeout;
|
||||||
|
|
||||||
// Show submenu on hover (desktop)
|
|
||||||
jasaWrapper.addEventListener('mouseenter', function() {
|
jasaWrapper.addEventListener('mouseenter', function() {
|
||||||
clearTimeout(submenuTimeout);
|
clearTimeout(submenuTimeout);
|
||||||
jasaMenu.classList.add('opacity-100', 'pointer-events-auto');
|
jasaMenu.classList.add('opacity-100', 'pointer-events-auto', 'scale-100');
|
||||||
jasaMenu.classList.remove('opacity-0', 'pointer-events-none');
|
jasaMenu.classList.remove('opacity-0', 'pointer-events-none', 'scale-95');
|
||||||
});
|
});
|
||||||
|
|
||||||
jasaWrapper.addEventListener('mouseleave', function() {
|
jasaWrapper.addEventListener('mouseleave', function() {
|
||||||
submenuTimeout = setTimeout(function() {
|
submenuTimeout = setTimeout(function() {
|
||||||
jasaMenu.classList.remove('opacity-100', 'pointer-events-auto');
|
jasaMenu.classList.remove('opacity-100', 'pointer-events-auto', 'scale-100');
|
||||||
jasaMenu.classList.add('opacity-0', 'pointer-events-none');
|
jasaMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
|
||||||
}, 120); // delay to allow moving to submenu
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
jasaMenu.addEventListener('mouseenter', function() {
|
|
||||||
clearTimeout(submenuTimeout);
|
|
||||||
jasaMenu.classList.add('opacity-100', 'pointer-events-auto');
|
|
||||||
jasaMenu.classList.remove('opacity-0', 'pointer-events-none');
|
|
||||||
});
|
|
||||||
jasaMenu.addEventListener('mouseleave', function() {
|
|
||||||
submenuTimeout = setTimeout(function() {
|
|
||||||
jasaMenu.classList.remove('opacity-100', 'pointer-events-auto');
|
|
||||||
jasaMenu.classList.add('opacity-0', 'pointer-events-none');
|
|
||||||
}, 120);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle submenu on click (for accessibility)
|
|
||||||
jasaBtn.addEventListener('click', function(e) {
|
jasaBtn.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const isOpen = jasaMenu.classList.contains('opacity-100');
|
const isOpen = jasaMenu.classList.contains('opacity-100');
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
jasaMenu.classList.remove('opacity-100', 'pointer-events-auto');
|
jasaMenu.classList.remove('opacity-100', 'pointer-events-auto', 'scale-100');
|
||||||
jasaMenu.classList.add('opacity-0', 'pointer-events-none');
|
jasaMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
|
||||||
} else {
|
} else {
|
||||||
jasaMenu.classList.add('opacity-100', 'pointer-events-auto');
|
jasaMenu.classList.add('opacity-100', 'pointer-events-auto', 'scale-100');
|
||||||
jasaMenu.classList.remove('opacity-0', 'pointer-events-none');
|
jasaMenu.classList.remove('opacity-0', 'pointer-events-none', 'scale-95');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (!jasaWrapper.contains(e.target)) {
|
||||||
|
jasaMenu.classList.remove('opacity-100', 'pointer-events-auto', 'scale-100');
|
||||||
|
jasaMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
function toggleMobileSubmenu(button) {
|
||||||
|
const submenu = button.nextElementSibling;
|
||||||
|
|
||||||
|
submenu.classList.toggle('hidden');
|
||||||
|
|
||||||
|
const arrow = button.querySelector('svg');
|
||||||
|
|
||||||
|
if (submenu.classList.contains('hidden')) {
|
||||||
|
arrow.style.transform = 'rotate(0deg)';
|
||||||
|
} else {
|
||||||
|
arrow.style.transform = 'rotate(180deg)';
|
||||||
|
}
|
||||||
|
|
||||||
|
submenu.style.maxHeight = submenu.classList.contains('hidden') ? '0' : submenu.scrollHeight + 'px';
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const mobileDropdownButtons = document.querySelectorAll('.mobile-dropdown button');
|
||||||
|
|
||||||
|
mobileDropdownButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
mobileDropdownButtons.forEach(otherButton => {
|
||||||
|
if (otherButton !== button) {
|
||||||
|
const otherSubmenu = otherButton.nextElementSibling;
|
||||||
|
if (!otherSubmenu.classList.contains('hidden')) {
|
||||||
|
otherSubmenu.classList.add('hidden');
|
||||||
|
const otherArrow = otherButton.querySelector('svg');
|
||||||
|
otherArrow.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</register-block>
|
</register-block>
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
<div class="w-20 h-1 bg-white mt-4 mx-auto md:mx-0 rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0 hidden md:block">
|
<div class="flex-shrink-0 hidden md:block">
|
||||||
<img src="@Url.Content("~/website/images/bg-header.svg")" alt="Usaha Pesapakawan" class="w-52 h-auto">
|
<img src="@Url.Content("~/website/images/bg-header.png")" alt="Usaha Pesapakawan" class="w-75 h-auto">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 9.7 MiB |
After Width: | Height: | Size: 4.8 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 5.8 MiB |
After Width: | Height: | Size: 4.3 MiB |
After Width: | Height: | Size: 3.4 MiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 4.0 MiB |