update: fixing

main
marszayn 2025-08-14 16:02:56 +07:00
parent cfde440612
commit dcd202ca5f
57 changed files with 9977 additions and 250 deletions

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
} }

View File

@ -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");
}
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

View File

@ -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>
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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');

View File

@ -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">
Pesapa Kawan 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>

View File

@ -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">

View File

@ -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">

View File

@ -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">

View File

@ -6,14 +6,13 @@
<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">
Pesapa Kawan PesapaKawan
</h2> </h2>
</div> </div>

View File

@ -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">
&copy; @DateTime.Now.Year Pesapakawan. All rights reserved. &copy; @DateTime.Now.Year Pesapakawan. All rights reserved.

View File

@ -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)">
</a> <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 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>

View File

@ -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>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.