From a21c903ea7548e1ad0a005721c45846a01a0185e Mon Sep 17 00:00:00 2001 From: muamars Date: Mon, 16 Mar 2026 14:50:14 +0700 Subject: [PATCH] update: pwa dll --- .../DetailController.cs | 516 ++++++- .../HistoryController.cs | 27 + Data/detail-penjemputan.json | 39 +- Data/history-upst.json | 110 ++ Models/DetailPenjemputanModels.cs | 70 + Models/HistoryModels.cs | 24 + Program.cs | 15 +- Services/DetailPenjemputanService.cs | 128 +- Services/HistoryService.cs | 83 ++ .../SpjDriverUpst/History/Index.cshtml | 176 +-- .../Transport/SpjDriverUpst/Home/Index.cshtml | 315 +++-- .../SpjDriverUpst/Login/Index.cshtml | 2 +- .../Shared/Components/_PWAinstall.cshtml | 44 + .../Shared/Partials/_Scripts.cshtml | 200 +++ .../SpjDriverUpst/Shared/_Layout.cshtml | 24 +- wwwroot/driver/css/watch.css | 356 ++++- wwwroot/driver/images/pwa_192.png | Bin 0 -> 16841 bytes wwwroot/driver/images/pwa_512.png | Bin 0 -> 20606 bytes wwwroot/driver/images/truck_icon.png | Bin 0 -> 91372 bytes .../driver/js/detail-penjemputan-non-tps.js | 1150 ++++++++-------- wwwroot/driver/js/detail-penjemputan-tps.js | 1191 ++++++++++------- wwwroot/driver/js/history-upst.js | 291 ++++ wwwroot/driver/json/pengangkutan.json | 12 +- wwwroot/driver/manifest.json | 79 +- wwwroot/driver/offline.html | 67 + wwwroot/driver/serviceworker.js | 77 ++ 26 files changed, 3569 insertions(+), 1427 deletions(-) create mode 100644 Data/history-upst.json create mode 100644 Models/HistoryModels.cs create mode 100644 Services/HistoryService.cs create mode 100644 Views/Admin/Transport/SpjDriverUpst/Shared/Components/_PWAinstall.cshtml create mode 100644 Views/Admin/Transport/SpjDriverUpst/Shared/Partials/_Scripts.cshtml create mode 100644 wwwroot/driver/images/pwa_192.png create mode 100644 wwwroot/driver/images/pwa_512.png create mode 100644 wwwroot/driver/images/truck_icon.png create mode 100644 wwwroot/driver/js/history-upst.js create mode 100644 wwwroot/driver/offline.html create mode 100644 wwwroot/driver/serviceworker.js diff --git a/Controllers/SpjDriverUpstController/DetailController.cs b/Controllers/SpjDriverUpstController/DetailController.cs index 92daf74..b995409 100644 --- a/Controllers/SpjDriverUpstController/DetailController.cs +++ b/Controllers/SpjDriverUpstController/DetailController.cs @@ -15,17 +15,43 @@ namespace eSPJ.Controllers.SpjDriverUpstController private readonly IConfiguration _configuration; private readonly DetailPenjemputanService _detailService; private readonly ILogger _logger; + private readonly IWebHostEnvironment _env; public DetailPenjemputanController( IHttpClientFactory httpClientFactory, IConfiguration configuration, DetailPenjemputanService detailService, - ILogger logger) + ILogger logger, + IWebHostEnvironment env) { _httpClientFactory = httpClientFactory; _configuration = configuration; _detailService = detailService; _logger = logger; + _env = env; + } + + private static string ResolveDraftKey(string? draftKey, string? sessionKey, string? spjDetailId = null, string? lokasiAngkutId = null) + { + var rawKey = !string.IsNullOrWhiteSpace(draftKey) + ? draftKey + : !string.IsNullOrWhiteSpace(sessionKey) + ? sessionKey + : $"non-tps-{spjDetailId}-{lokasiAngkutId}"; + + return string.Concat((rawKey ?? string.Empty).Where(c => char.IsLetterOrDigit(c) || c == '-' || c == '_')); + } + + private string GetUploadDirectory(string dateFolder) + { + var uploadDir = Path.Combine(_env.ContentRootPath, "uploads", "penjemputan", dateFolder); + Directory.CreateDirectory(uploadDir); + return uploadDir; + } + + private static string BuildUploadUrl(string dateFolder, string fileName) + { + return $"/uploads/penjemputan/{dateFolder}/{fileName}"; } [HttpGet("")] @@ -90,6 +116,494 @@ namespace eSPJ.Controllers.SpjDriverUpstController } } + [HttpPost("save-draft-non-tps")] + [IgnoreAntiforgeryToken] + public async Task SaveDraftNonTps([FromBody] DraftSaveRequest request) + { + if (request == null) + return BadRequest(new DraftSaveResponse { Success = false, Message = "Request tidak valid." }); + + request.DraftKey = ResolveDraftKey(request.DraftKey, request.SessionKey, request.SpjDetailId, request.LokasiAngkutId); + request.SessionKey = request.DraftKey; + if (string.IsNullOrWhiteSpace(request.DraftKey)) + return BadRequest(new DraftSaveResponse { Success = false, Message = "Draft key tidak valid." }); + + var result = await _detailService.SaveDraftNonTpsAsync(request); + return result.Success ? Ok(result) : StatusCode(500, result); + } + + [HttpGet("load-draft-non-tps")] + [IgnoreAntiforgeryToken] + public async Task LoadDraftNonTps([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) + { + var key = ResolveDraftKey(draftKey, sessionKey); + if (string.IsNullOrWhiteSpace(key)) + return Ok(new DraftLoadResponse { Success = true, HasDraft = false, Message = "Draft key kosong." }); + var result = await _detailService.LoadDraftNonTpsAsync(key); + return Ok(result); + } + + [HttpDelete("delete-draft-non-tps")] + [IgnoreAntiforgeryToken] + public async Task DeleteDraftNonTps([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) + { + var key = ResolveDraftKey(draftKey, sessionKey); + if (string.IsNullOrWhiteSpace(key)) + return Ok(new { success = false }); + var ok = await _detailService.DeleteDraftNonTpsAsync(key); + return Ok(new { success = ok }); + } + + [HttpPost("save-draft")] + [IgnoreAntiforgeryToken] + public async Task SaveDraft([FromBody] DraftSaveRequest request) + { + if (request == null) + return BadRequest(new DraftSaveResponse { Success = false, Message = "Request tidak valid." }); + + request.DraftKey = ResolveDraftKey(request.DraftKey, request.SessionKey, request.SpjDetailId, request.LokasiAngkutId); + request.SessionKey = request.DraftKey; + if (string.IsNullOrWhiteSpace(request.DraftKey)) + return BadRequest(new DraftSaveResponse { Success = false, Message = "Draft key tidak valid." }); + + var result = await _detailService.SaveDraftTpsAsync(request); + return result.Success ? Ok(result) : StatusCode(500, result); + } + + [HttpGet("load-draft")] + [IgnoreAntiforgeryToken] + public async Task LoadDraft([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) + { + var key = ResolveDraftKey(draftKey, sessionKey); + if (string.IsNullOrWhiteSpace(key)) + return Ok(new DraftLoadResponse { Success = true, HasDraft = false, Message = "Draft key kosong." }); + var result = await _detailService.LoadDraftTpsAsync(key); + return Ok(result); + } + + [HttpDelete("delete-draft")] + [IgnoreAntiforgeryToken] + public async Task DeleteDraft([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) + { + var key = ResolveDraftKey(draftKey, sessionKey); + if (string.IsNullOrWhiteSpace(key)) + return Ok(new { success = false }); + var ok = await _detailService.DeleteDraftTpsAsync(key); + return Ok(new { success = ok }); + } + + + [HttpPost("upload-foto-kedatangan-non-tps")] + [IgnoreAntiforgeryToken] + public async Task UploadFotoKedatanganNonTps( + [FromForm] List? FotoKedatangan, + [FromForm] string? DraftKey, + [FromForm] string? SessionKey, + [FromForm] string? SpjDetailId, + [FromForm] string? LokasiAngkutId, + [FromForm] string? WaktuKedatangan, + [FromForm] string? Latitude, + [FromForm] string? Longitude, + [FromForm] string? AlamatJalan) + { + if (FotoKedatangan == null || FotoKedatangan.Count == 0) + return BadRequest(new { success = false, message = "Tidak ada foto." }); + + var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadDir = GetUploadDirectory(dateFolder); + + var fileNames = new List(); + foreach (var file in FotoKedatangan) + { + if (file.Length == 0) continue; + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + var name = $"kedatangan_{Guid.NewGuid()}{ext}"; + var path = Path.Combine(uploadDir, name); + await using var stream = new FileStream(path, FileMode.Create); + await file.CopyToAsync(stream); + fileNames.Add(name); + } + var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); + + var resolvedDraftKey = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); + if (!string.IsNullOrWhiteSpace(resolvedDraftKey)) + { + var loadResult = await _detailService.LoadDraftNonTpsAsync(resolvedDraftKey); + var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKey, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; + draft.FotoKedatanganFileNames = fileUrls; + draft.FotoKedatanganUploaded = true; + if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; + if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; + if (!string.IsNullOrWhiteSpace(WaktuKedatangan)) draft.WaktuKedatangan = WaktuKedatangan; + if (!string.IsNullOrWhiteSpace(Latitude)) draft.Latitude = Latitude; + if (!string.IsNullOrWhiteSpace(Longitude)) draft.Longitude = Longitude; + if (!string.IsNullOrWhiteSpace(AlamatJalan)) draft.AlamatJalan = AlamatJalan; + await _detailService.SaveDraftNonTpsAsync(new DraftSaveRequest + { + DraftKey = resolvedDraftKey, + SessionKey = resolvedDraftKey, + LokasiAngkutId = draft.LokasiAngkutId, + SpjDetailId = draft.SpjDetailId, + Latitude = draft.Latitude, + Longitude = draft.Longitude, + AlamatJalan = draft.AlamatJalan, + WaktuKedatangan = draft.WaktuKedatangan, + FotoKedatanganFileNames = fileUrls, + FotoKedatanganUploaded = true, + Timbangan = draft.Timbangan, + TotalOrganik = draft.TotalOrganik, + TotalAnorganik = draft.TotalAnorganik, + TotalResidu = draft.TotalResidu, + TotalTimbangan = draft.TotalTimbangan, + FotoPetugasFileNames = draft.FotoPetugasFileNames, + FotoPetugasUploaded = draft.FotoPetugasUploaded, + NamaPetugas = draft.NamaPetugas + }); + } + + return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto kedatangan berhasil diupload." }); + } + + [HttpPost("upload-foto-timbangan-non-tps")] + [IgnoreAntiforgeryToken] + public async Task UploadFotoTimbanganNonTps( + [FromForm] IFormFile? FotoTimbangan, + [FromForm] string? DraftKey, + [FromForm] string? SessionKey, + [FromForm] string? SpjDetailId, + [FromForm] string? LokasiAngkutId, + [FromForm] int ItemIndex, + [FromForm] string? JenisSampah, + [FromForm] decimal Berat) + { + if (FotoTimbangan == null || FotoTimbangan.Length == 0) + return BadRequest(new { success = false, message = "Tidak ada foto." }); + + var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadDir = GetUploadDirectory(dateFolder); + + var ext = Path.GetExtension(FotoTimbangan.FileName).ToLowerInvariant(); + var jenisSafe = (JenisSampah ?? "residu").ToLowerInvariant(); + var beratStr = Berat.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture).Replace('.', '_'); + var name = $"timbangan{ItemIndex + 1}-{jenisSafe}-{beratStr}{ext}"; + var filePath = Path.Combine(uploadDir, name); + await using var stream = new FileStream(filePath, FileMode.Create); + await FotoTimbangan.CopyToAsync(stream); + var fileUrl = BuildUploadUrl(dateFolder, name); + + var resolvedDraftKey = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); + if (!string.IsNullOrWhiteSpace(resolvedDraftKey)) + { + var loadResult = await _detailService.LoadDraftNonTpsAsync(resolvedDraftKey); + var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKey, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; + while (draft.Timbangan.Count <= ItemIndex) + draft.Timbangan.Add(new DraftTimbanganItem()); + if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; + if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; + draft.Timbangan[ItemIndex] = new DraftTimbanganItem + { + FotoFileName = fileUrl, + JenisSampah = JenisSampah ?? "Residu", + Berat = Berat, + Uploaded = true + }; + await _detailService.SaveDraftNonTpsAsync(new DraftSaveRequest + { + DraftKey = resolvedDraftKey, + SessionKey = resolvedDraftKey, + LokasiAngkutId = draft.LokasiAngkutId, + SpjDetailId = draft.SpjDetailId, + Latitude = draft.Latitude, + Longitude = draft.Longitude, + AlamatJalan = draft.AlamatJalan, + WaktuKedatangan = draft.WaktuKedatangan, + FotoKedatanganFileNames = draft.FotoKedatanganFileNames, + FotoKedatanganUploaded = draft.FotoKedatanganUploaded, + Timbangan = draft.Timbangan, + TotalOrganik = draft.TotalOrganik, + TotalAnorganik = draft.TotalAnorganik, + TotalResidu = draft.TotalResidu, + TotalTimbangan = draft.TotalTimbangan, + FotoPetugasFileNames = draft.FotoPetugasFileNames, + FotoPetugasUploaded = draft.FotoPetugasUploaded, + NamaPetugas = draft.NamaPetugas + }); + } + + return Ok(new { success = true, fileName = name, fileUrl, message = $"Foto timbangan #{ItemIndex + 1} berhasil diupload." }); + } + + [HttpPost("upload-foto-petugas-non-tps")] + [IgnoreAntiforgeryToken] + public async Task UploadFotoPetugasNonTps( + [FromForm] List? FotoPetugas, + [FromForm] string? DraftKey, + [FromForm] string? SessionKey, + [FromForm] string? SpjDetailId, + [FromForm] string? LokasiAngkutId, + [FromForm] string? NamaPetugas) + { + if (FotoPetugas == null || FotoPetugas.Count == 0) + return BadRequest(new { success = false, message = "Tidak ada foto." }); + + var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadDir = GetUploadDirectory(dateFolder); + + var fileNames = new List(); + foreach (var file in FotoPetugas) + { + if (file.Length == 0) continue; + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + var name = $"petugas_{Guid.NewGuid()}{ext}"; + var path = Path.Combine(uploadDir, name); + await using var stream = new FileStream(path, FileMode.Create); + await file.CopyToAsync(stream); + fileNames.Add(name); + } + var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); + + var resolvedDraftKey = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); + if (!string.IsNullOrWhiteSpace(resolvedDraftKey)) + { + var loadResult = await _detailService.LoadDraftNonTpsAsync(resolvedDraftKey); + var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKey, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; + draft.FotoPetugasFileNames = fileUrls; + draft.FotoPetugasUploaded = true; + if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; + if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; + if (!string.IsNullOrWhiteSpace(NamaPetugas)) draft.NamaPetugas = NamaPetugas; + await _detailService.SaveDraftNonTpsAsync(new DraftSaveRequest + { + DraftKey = resolvedDraftKey, + SessionKey = resolvedDraftKey, + LokasiAngkutId = draft.LokasiAngkutId, + SpjDetailId = draft.SpjDetailId, + Latitude = draft.Latitude, + Longitude = draft.Longitude, + AlamatJalan = draft.AlamatJalan, + WaktuKedatangan = draft.WaktuKedatangan, + FotoKedatanganFileNames = draft.FotoKedatanganFileNames, + FotoKedatanganUploaded = draft.FotoKedatanganUploaded, + Timbangan = draft.Timbangan, + TotalOrganik = draft.TotalOrganik, + TotalAnorganik = draft.TotalAnorganik, + TotalResidu = draft.TotalResidu, + TotalTimbangan = draft.TotalTimbangan, + FotoPetugasFileNames = fileUrls, + FotoPetugasUploaded = true, + NamaPetugas = draft.NamaPetugas + }); + } + + return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto petugas berhasil diupload." }); + } + + [HttpPost("upload-foto-kedatangan")] + [IgnoreAntiforgeryToken] + public async Task UploadFotoKedatangan( + [FromForm] List? FotoKedatangan, + [FromForm] string? DraftKey, + [FromForm] string? SessionKey, + [FromForm] string? SpjDetailId, + [FromForm] string? LokasiAngkutId, + [FromForm] string? WaktuKedatangan, + [FromForm] string? Latitude, + [FromForm] string? Longitude, + [FromForm] string? AlamatJalan) + { + if (FotoKedatangan == null || FotoKedatangan.Count == 0) + return BadRequest(new { success = false, message = "Tidak ada foto." }); + + var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadDir = GetUploadDirectory(dateFolder); + + var fileNames = new List(); + foreach (var file in FotoKedatangan) + { + if (file.Length == 0) continue; + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + var name = $"kedatangan_{Guid.NewGuid()}{ext}"; + var path = Path.Combine(uploadDir, name); + await using var stream = new FileStream(path, FileMode.Create); + await file.CopyToAsync(stream); + fileNames.Add(name); + } + var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); + + var resolvedDraftKeyTps = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); + if (!string.IsNullOrWhiteSpace(resolvedDraftKeyTps)) + { + var loadResult = await _detailService.LoadDraftTpsAsync(resolvedDraftKeyTps); + var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKeyTps, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; + draft.FotoKedatanganFileNames = fileUrls; + draft.FotoKedatanganUploaded = true; + if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; + if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; + if (!string.IsNullOrWhiteSpace(WaktuKedatangan)) draft.WaktuKedatangan = WaktuKedatangan; + if (!string.IsNullOrWhiteSpace(Latitude)) draft.Latitude = Latitude; + if (!string.IsNullOrWhiteSpace(Longitude)) draft.Longitude = Longitude; + if (!string.IsNullOrWhiteSpace(AlamatJalan)) draft.AlamatJalan = AlamatJalan; + await _detailService.SaveDraftTpsAsync(new DraftSaveRequest + { + DraftKey = resolvedDraftKeyTps, + SessionKey = resolvedDraftKeyTps, + LokasiAngkutId = draft.LokasiAngkutId, + SpjDetailId = draft.SpjDetailId, + Latitude = draft.Latitude, + Longitude = draft.Longitude, + AlamatJalan = draft.AlamatJalan, + WaktuKedatangan = draft.WaktuKedatangan, + FotoKedatanganFileNames = fileUrls, + FotoKedatanganUploaded = true, + Timbangan = draft.Timbangan, + TotalOrganik = draft.TotalOrganik, + TotalAnorganik = draft.TotalAnorganik, + TotalResidu = draft.TotalResidu, + TotalTimbangan = draft.TotalTimbangan, + FotoPetugasFileNames = draft.FotoPetugasFileNames, + FotoPetugasUploaded = draft.FotoPetugasUploaded, + NamaPetugas = draft.NamaPetugas + }); + } + + return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto kedatangan berhasil diupload." }); + } + + [HttpPost("upload-foto-timbangan")] + [IgnoreAntiforgeryToken] + public async Task UploadFotoTimbangan( + [FromForm] IFormFile? FotoTimbangan, + [FromForm] string? DraftKey, + [FromForm] string? SessionKey, + [FromForm] string? SpjDetailId, + [FromForm] string? LokasiAngkutId, + [FromForm] int ItemIndex, + [FromForm] string? JenisSampah, + [FromForm] decimal Berat) + { + if (FotoTimbangan == null || FotoTimbangan.Length == 0) + return BadRequest(new { success = false, message = "Tidak ada foto." }); + + var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadDir = GetUploadDirectory(dateFolder); + + var ext = Path.GetExtension(FotoTimbangan.FileName).ToLowerInvariant(); + var jenisSafe = (JenisSampah ?? "residu").ToLowerInvariant(); + var beratStr = Berat.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture).Replace('.', '_'); + var name = $"timbangan{ItemIndex + 1}-{jenisSafe}-{beratStr}{ext}"; + var filePath = Path.Combine(uploadDir, name); + await using var stream = new FileStream(filePath, FileMode.Create); + await FotoTimbangan.CopyToAsync(stream); + var fileUrl = BuildUploadUrl(dateFolder, name); + + var resolvedDraftKeyTps = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); + if (!string.IsNullOrWhiteSpace(resolvedDraftKeyTps)) + { + var loadResult = await _detailService.LoadDraftTpsAsync(resolvedDraftKeyTps); + var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKeyTps, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; + while (draft.Timbangan.Count <= ItemIndex) + draft.Timbangan.Add(new DraftTimbanganItem()); + if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; + if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; + draft.Timbangan[ItemIndex] = new DraftTimbanganItem + { + FotoFileName = fileUrl, + JenisSampah = JenisSampah ?? "Residu", + Berat = Berat, + Uploaded = true + }; + await _detailService.SaveDraftTpsAsync(new DraftSaveRequest + { + DraftKey = resolvedDraftKeyTps, + SessionKey = resolvedDraftKeyTps, + LokasiAngkutId = draft.LokasiAngkutId, + SpjDetailId = draft.SpjDetailId, + Latitude = draft.Latitude, + Longitude = draft.Longitude, + AlamatJalan = draft.AlamatJalan, + WaktuKedatangan = draft.WaktuKedatangan, + FotoKedatanganFileNames = draft.FotoKedatanganFileNames, + FotoKedatanganUploaded = draft.FotoKedatanganUploaded, + Timbangan = draft.Timbangan, + TotalOrganik = draft.TotalOrganik, + TotalAnorganik = draft.TotalAnorganik, + TotalResidu = draft.TotalResidu, + TotalTimbangan = draft.TotalTimbangan, + FotoPetugasFileNames = draft.FotoPetugasFileNames, + FotoPetugasUploaded = draft.FotoPetugasUploaded, + NamaPetugas = draft.NamaPetugas + }); + } + + return Ok(new { success = true, fileName = name, fileUrl, message = $"Foto timbangan #{ItemIndex + 1} berhasil diupload." }); + } + + [HttpPost("upload-foto-petugas")] + [IgnoreAntiforgeryToken] + public async Task UploadFotoPetugas( + [FromForm] List? FotoPetugas, + [FromForm] string? DraftKey, + [FromForm] string? SessionKey, + [FromForm] string? SpjDetailId, + [FromForm] string? LokasiAngkutId, + [FromForm] string? NamaPetugas) + { + if (FotoPetugas == null || FotoPetugas.Count == 0) + return BadRequest(new { success = false, message = "Tidak ada foto." }); + + var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadDir = GetUploadDirectory(dateFolder); + + var fileNames = new List(); + foreach (var file in FotoPetugas) + { + if (file.Length == 0) continue; + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + var name = $"petugas_{Guid.NewGuid()}{ext}"; + var path = Path.Combine(uploadDir, name); + await using var stream = new FileStream(path, FileMode.Create); + await file.CopyToAsync(stream); + fileNames.Add(name); + } + var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); + + var resolvedDraftKeyTps = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); + if (!string.IsNullOrWhiteSpace(resolvedDraftKeyTps)) + { + var loadResult = await _detailService.LoadDraftTpsAsync(resolvedDraftKeyTps); + var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKeyTps, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; + draft.FotoPetugasFileNames = fileUrls; + draft.FotoPetugasUploaded = true; + if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; + if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; + if (!string.IsNullOrWhiteSpace(NamaPetugas)) draft.NamaPetugas = NamaPetugas; + await _detailService.SaveDraftTpsAsync(new DraftSaveRequest + { + DraftKey = resolvedDraftKeyTps, + SessionKey = resolvedDraftKeyTps, + LokasiAngkutId = draft.LokasiAngkutId, + SpjDetailId = draft.SpjDetailId, + Latitude = draft.Latitude, + Longitude = draft.Longitude, + AlamatJalan = draft.AlamatJalan, + WaktuKedatangan = draft.WaktuKedatangan, + FotoKedatanganFileNames = draft.FotoKedatanganFileNames, + FotoKedatanganUploaded = draft.FotoKedatanganUploaded, + Timbangan = draft.Timbangan, + TotalOrganik = draft.TotalOrganik, + TotalAnorganik = draft.TotalAnorganik, + TotalResidu = draft.TotalResidu, + TotalTimbangan = draft.TotalTimbangan, + FotoPetugasFileNames = fileUrls, + FotoPetugasUploaded = true, + NamaPetugas = draft.NamaPetugas + }); + } + + return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto petugas berhasil diupload." }); + } + + [HttpPost("ocr-timbangan")] [IgnoreAntiforgeryToken] public async Task OcrTimbangan(IFormFile? Foto) diff --git a/Controllers/SpjDriverUpstController/HistoryController.cs b/Controllers/SpjDriverUpstController/HistoryController.cs index 574b564..e049567 100644 --- a/Controllers/SpjDriverUpstController/HistoryController.cs +++ b/Controllers/SpjDriverUpstController/HistoryController.cs @@ -1,10 +1,17 @@ using Microsoft.AspNetCore.Mvc; +using eSPJ.Services; namespace eSPJ.Controllers.SpjDriverUpstController { [Route("upst/history")] public class HistoryController : Controller { + private readonly HistoryService _historyService; + + public HistoryController(HistoryService historyService) + { + _historyService = historyService; + } [HttpGet("")] public IActionResult Index() @@ -12,6 +19,26 @@ namespace eSPJ.Controllers.SpjDriverUpstController return View("~/Views/Admin/Transport/SpjDriverUpst/History/Index.cshtml"); } + [HttpGet("api")] + public async Task GetHistory([FromQuery] string? fromDate = null, [FromQuery] string? toDate = null, [FromQuery] int page = 1, [FromQuery] int pageSize = 5) + { + DateOnly? parsedFromDate = null; + DateOnly? parsedToDate = null; + + if (!string.IsNullOrWhiteSpace(fromDate) && DateOnly.TryParse(fromDate, out var from)) + { + parsedFromDate = from; + } + + if (!string.IsNullOrWhiteSpace(toDate) && DateOnly.TryParse(toDate, out var to)) + { + parsedToDate = to; + } + + var result = await _historyService.GetUpstHistoryAsync(parsedFromDate, parsedToDate, page, pageSize <= 0 ? 5 : pageSize); + return Ok(result); + } + [HttpGet("details/{id}")] public IActionResult Details(int id) { diff --git a/Data/detail-penjemputan.json b/Data/detail-penjemputan.json index fe51488..8a316ba 100644 --- a/Data/detail-penjemputan.json +++ b/Data/detail-penjemputan.json @@ -1 +1,38 @@ -[] +[ + { + "Name": "TPS A", + "Index": 0, + "Latitude": "-6.481670", + "Longitude": "106.854000", + "AlamatJalan": "Cibinong, Bogor, West Java, Java, 16911, Indonesia", + "WaktuKedatangan": "16/03/2026, 09.37.44", + "FotoKedatangan": [ + "kedatangan_ff132727-8942-402e-a612-ac3436e905b9.png" + ], + "FotoKedatanganUploaded": true, + "Timbangan": [ + { + "FotoFileName": "timbangan_0de03ef1-dbdd-4643-a0e6-ac7670b12679.png", + "Berat": [ + 75.23 + ], + "LokasiAngkut": [], + "JenisSampah": [ + 2 + ], + "IsUploaded": true, + "WaktuUpload": "2026-03-16T09:38:37.1840709+07:00" + } + ], + "TotalOrganik": 0, + "TotalAnorganik": 0, + "TotalResidu": 75.23, + "TotalTimbangan": 75.23, + "FotoPetugas": [ + "petugas_dffeaabf-4f30-41dd-9281-e2c33164e2c6.jpg" + ], + "FotoPetugasUploaded": true, + "NamaPetugas": "usmannn", + "Submitted": true + } +] \ No newline at end of file diff --git a/Data/history-upst.json b/Data/history-upst.json new file mode 100644 index 0000000..88f05f9 --- /dev/null +++ b/Data/history-upst.json @@ -0,0 +1,110 @@ +[ + { + "id": 1, + "noSpj": "SPJ/07-2025/PKM/000478", + "plat": "B 5678 ABC", + "kode": "JRC 007", + "tujuan": "Bantar Gebang", + "status": "In Progress", + "tanggalWaktu": "2025-07-28T16:45:00" + }, + { + "id": 2, + "noSpj": "SPJ/07-2025/PKM/000476", + "plat": "B 9632 TOR", + "kode": "JRC 005", + "tujuan": "RDF Rorotan", + "status": "Completed", + "tanggalWaktu": "2025-07-27T14:30:00" + }, + { + "id": 3, + "noSpj": "SPJ/07-2025/PKM/000477", + "plat": "B 1234 XYZ", + "kode": "JRC 006", + "tujuan": "RDF Pesanggarahan", + "status": "Completed", + "tanggalWaktu": "2025-07-26T09:15:00" + }, + { + "id": 4, + "noSpj": "SPJ/07-2025/PKM/000479", + "plat": "B 9876 DEF", + "kode": "JRC 008", + "tujuan": "RDF Sunter", + "status": "Completed", + "tanggalWaktu": "2025-07-25T11:20:00" + }, + { + "id": 5, + "noSpj": "SPJ/07-2025/PKM/000480", + "plat": "B 4321 GHI", + "kode": "JRC 009", + "tujuan": "Bantar Gebang", + "status": "Completed", + "tanggalWaktu": "2025-07-24T08:45:00" + }, + { + "id": 6, + "noSpj": "SPJ/07-2025/PKM/000481", + "plat": "B 7654 JKL", + "kode": "JRC 010", + "tujuan": "RDF Marunda", + "status": "Completed", + "tanggalWaktu": "2025-07-23T10:10:00" + }, + { + "id": 7, + "noSpj": "SPJ/07-2025/PKM/000482", + "plat": "B 2468 MNO", + "kode": "JRC 011", + "tujuan": "Bantar Gebang", + "status": "Completed", + "tanggalWaktu": "2025-07-22T07:25:00" + }, + { + "id": 8, + "noSpj": "SPJ/07-2025/PKM/000483", + "plat": "B 1357 PQR", + "kode": "JRC 012", + "tujuan": "RDF Rorotan", + "status": "In Progress", + "tanggalWaktu": "2025-07-21T13:05:00" + }, + { + "id": 9, + "noSpj": "SPJ/07-2025/PKM/000484", + "plat": "B 8080 STU", + "kode": "JRC 013", + "tujuan": "RDF Pesanggarahan", + "status": "Completed", + "tanggalWaktu": "2025-07-20T15:40:00" + }, + { + "id": 10, + "noSpj": "SPJ/07-2025/PKM/000485", + "plat": "B 9090 VWX", + "kode": "JRC 014", + "tujuan": "RDF Sunter", + "status": "Completed", + "tanggalWaktu": "2025-07-19T12:00:00" + }, + { + "id": 11, + "noSpj": "SPJ/07-2025/PKM/000486", + "plat": "B 2222 YZA", + "kode": "JRC 015", + "tujuan": "Bantar Gebang", + "status": "Completed", + "tanggalWaktu": "2025-07-18T06:55:00" + }, + { + "id": 12, + "noSpj": "SPJ/07-2025/PKM/000487", + "plat": "B 3333 BCD", + "kode": "JRC 016", + "tujuan": "RDF Marunda", + "status": "Completed", + "tanggalWaktu": "2025-07-18T17:30:00" + } +] diff --git a/Models/DetailPenjemputanModels.cs b/Models/DetailPenjemputanModels.cs index 94f20a3..7305e19 100644 --- a/Models/DetailPenjemputanModels.cs +++ b/Models/DetailPenjemputanModels.cs @@ -76,4 +76,74 @@ namespace eSPJ.Models public string? Raw { get; set; } public string Message { get; set; } = string.Empty; } + + public class DraftTimbanganItem + { + public decimal Berat { get; set; } + public string JenisSampah { get; set; } = "Residu"; + public string FotoFileName { get; set; } = string.Empty; + public bool Uploaded { get; set; } + public string OcrInfo { get; set; } = string.Empty; + } + + public class DraftPenjemputanNonTps + { + public string SessionKey { get; set; } = string.Empty; + public string LokasiAngkutId { get; set; } = string.Empty; + public string SpjDetailId { get; set; } = string.Empty; + public string Latitude { get; set; } = string.Empty; + public string Longitude { get; set; } = string.Empty; + public string AlamatJalan { get; set; } = string.Empty; + public string WaktuKedatangan { get; set; } = string.Empty; + public List FotoKedatanganFileNames { get; set; } = new(); + public bool FotoKedatanganUploaded { get; set; } + public List Timbangan { get; set; } = new(); + public decimal TotalOrganik { get; set; } + public decimal TotalAnorganik { get; set; } + public decimal TotalResidu { get; set; } + public decimal TotalTimbangan { get; set; } + public List FotoPetugasFileNames { get; set; } = new(); + public bool FotoPetugasUploaded { get; set; } + public string NamaPetugas { get; set; } = string.Empty; + public bool Submitted { get; set; } + public DateTime UpdatedAt { get; set; } = DateTime.Now; + } + + public class DraftSaveRequest + { + public string DraftKey { get; set; } = string.Empty; + public string SessionKey { get; set; } = string.Empty; + public string LokasiAngkutId { get; set; } = string.Empty; + public string SpjDetailId { get; set; } = string.Empty; + public string Latitude { get; set; } = string.Empty; + public string Longitude { get; set; } = string.Empty; + public string AlamatJalan { get; set; } = string.Empty; + public string WaktuKedatangan { get; set; } = string.Empty; + public bool FotoKedatanganUploaded { get; set; } + public List FotoKedatanganFileNames { get; set; } = new(); + public List Timbangan { get; set; } = new(); + public decimal TotalOrganik { get; set; } + public decimal TotalAnorganik { get; set; } + public decimal TotalResidu { get; set; } + public decimal TotalTimbangan { get; set; } + public bool FotoPetugasUploaded { get; set; } + public List FotoPetugasFileNames { get; set; } = new(); + public string NamaPetugas { get; set; } = string.Empty; + } + + public class DraftSaveResponse + { + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; + public string? DraftKey { get; set; } + public string? SessionKey { get; set; } + } + + public class DraftLoadResponse + { + public bool Success { get; set; } + public bool HasDraft { get; set; } + public DraftPenjemputanNonTps? Draft { get; set; } + public string Message { get; set; } = string.Empty; + } } diff --git a/Models/HistoryModels.cs b/Models/HistoryModels.cs new file mode 100644 index 0000000..c95eeaa --- /dev/null +++ b/Models/HistoryModels.cs @@ -0,0 +1,24 @@ +namespace eSPJ.Models +{ + public class HistoryItem + { + public int Id { get; set; } + public string NoSpj { get; set; } = string.Empty; + public string Plat { get; set; } = string.Empty; + public string Kode { get; set; } = string.Empty; + public string Tujuan { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public DateTime TanggalWaktu { get; set; } + } + + public class HistoryListResponse + { + public List Items { get; set; } = new(); + public int Page { get; set; } + public int PageSize { get; set; } + public int TotalItems { get; set; } + public int TotalPages { get; set; } + public string? FromDate { get; set; } + public string? ToDate { get; set; } + } +} diff --git a/Program.cs b/Program.cs index 0998118..ac2a093 100644 --- a/Program.cs +++ b/Program.cs @@ -8,6 +8,7 @@ builder.Services.AddHttpClient(); // Register custom services builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); @@ -20,8 +21,20 @@ if (!app.Environment.IsDevelopment()) } app.UseHttpsRedirection(); -app.UseRouting(); +app.Use(async (context, next) => +{ + if (context.Request.Path.Equals("/driver/serviceworker.js", StringComparison.OrdinalIgnoreCase)) + { + context.Response.OnStarting(() => + { + context.Response.Headers["Service-Worker-Allowed"] = "/upst"; + return Task.CompletedTask; + }); + } + await next(); +}); +app.UseRouting(); app.UseAuthorization(); app.MapStaticAssets(); diff --git a/Services/DetailPenjemputanService.cs b/Services/DetailPenjemputanService.cs index 2670723..aafd265 100644 --- a/Services/DetailPenjemputanService.cs +++ b/Services/DetailPenjemputanService.cs @@ -114,9 +114,7 @@ namespace eSPJ.Services }; } - var uploadDateFolder = DateTime.Now.ToString("yyyy-MM-dd"); - var uploadPath = Path.Combine(_env.WebRootPath, "uploads", "penjemputan", uploadDateFolder); - var uploadBaseUrl = $"/uploads/penjemputan/{uploadDateFolder}"; + var uploadPath = Path.Combine(_env.ContentRootPath, "uploads", "penjemputan", DateTime.Now.ToString("yyyy-MM-dd")); if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); @@ -216,6 +214,130 @@ namespace eSPJ.Services } } + private string GetDraftFilePath(string prefix, string sessionKey) + { + var dir = Path.Combine(_env.ContentRootPath, "Data", "drafts"); + if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); + var safe = string.Concat(sessionKey.Where(c => char.IsLetterOrDigit(c) || c == '-' || c == '_')); + if (string.IsNullOrEmpty(safe)) safe = "default"; + return Path.Combine(dir, $"draft-{prefix}-{safe}.json"); + } + + private Task SaveDraftAsync(string prefix, DraftSaveRequest request) + { + return SaveDraftInternalAsync(prefix, request); + } + + private async Task SaveDraftInternalAsync(string prefix, DraftSaveRequest request) + { + try + { + var filePath = GetDraftFilePath(prefix, request.SessionKey); + var draft = new DraftPenjemputanNonTps + { + SessionKey = request.SessionKey, + LokasiAngkutId = request.LokasiAngkutId, + SpjDetailId = request.SpjDetailId, + Latitude = request.Latitude, + Longitude = request.Longitude, + AlamatJalan = request.AlamatJalan, + WaktuKedatangan = request.WaktuKedatangan, + FotoKedatanganFileNames = request.FotoKedatanganFileNames, + FotoKedatanganUploaded = request.FotoKedatanganUploaded, + Timbangan = request.Timbangan, + TotalOrganik = request.TotalOrganik, + TotalAnorganik = request.TotalAnorganik, + TotalResidu = request.TotalResidu, + TotalTimbangan = request.TotalTimbangan, + FotoPetugasFileNames = request.FotoPetugasFileNames, + FotoPetugasUploaded = request.FotoPetugasUploaded, + NamaPetugas = request.NamaPetugas, + UpdatedAt = DateTime.Now + }; + + var options = new JsonSerializerOptions { WriteIndented = true }; + var json = JsonSerializer.Serialize(draft, options); + await File.WriteAllTextAsync(filePath, json); + + return new DraftSaveResponse { Success = true, Message = "Draft tersimpan.", DraftKey = request.DraftKey, SessionKey = request.SessionKey }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error saving {Prefix} draft", prefix); + return new DraftSaveResponse { Success = false, Message = $"Gagal menyimpan draft: {ex.Message}" }; + } + } + + public async Task SaveDraftNonTpsAsync(DraftSaveRequest request) + { + return await SaveDraftAsync("non-tps", request); + } + + public async Task SaveDraftTpsAsync(DraftSaveRequest request) + { + return await SaveDraftAsync("tps", request); + } + + public async Task LoadDraftNonTpsAsync(string sessionKey) + { + return await LoadDraftAsync("non-tps", sessionKey); + } + + public async Task LoadDraftTpsAsync(string sessionKey) + { + return await LoadDraftAsync("tps", sessionKey); + } + + private async Task LoadDraftAsync(string prefix, string sessionKey) + { + try + { + var filePath = GetDraftFilePath(prefix, sessionKey); + if (!File.Exists(filePath)) + return new DraftLoadResponse { Success = true, HasDraft = false, Message = "Tidak ada draft." }; + + var json = await File.ReadAllTextAsync(filePath); + var draft = JsonSerializer.Deserialize(json, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + if (draft == null) + return new DraftLoadResponse { Success = true, HasDraft = false, Message = "Draft kosong." }; + + return new DraftLoadResponse { Success = true, HasDraft = true, Draft = draft }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading {Prefix} draft", prefix); + return new DraftLoadResponse { Success = false, HasDraft = false, Message = $"Gagal memuat draft: {ex.Message}" }; + } + } + + public async Task DeleteDraftNonTpsAsync(string sessionKey) + { + return await DeleteDraftAsync("non-tps", sessionKey); + } + + public async Task DeleteDraftTpsAsync(string sessionKey) + { + return await DeleteDraftAsync("tps", sessionKey); + } + + private async Task DeleteDraftAsync(string prefix, string sessionKey) + { + try + { + var filePath = GetDraftFilePath(prefix, sessionKey); + if (File.Exists(filePath)) File.Delete(filePath); + await Task.CompletedTask; + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting {Prefix} draft", prefix); + return false; + } + } + public async Task ProcessOcrTimbanganAsync(IFormFile foto) { try diff --git a/Services/HistoryService.cs b/Services/HistoryService.cs new file mode 100644 index 0000000..7df48ff --- /dev/null +++ b/Services/HistoryService.cs @@ -0,0 +1,83 @@ +using System.Text.Json; +using eSPJ.Models; + +namespace eSPJ.Services +{ + public class HistoryService + { + private readonly string _historyUpstFilePath; + private readonly ILogger _logger; + + public HistoryService(IWebHostEnvironment env, ILogger logger) + { + _logger = logger; + _historyUpstFilePath = Path.Combine(env.ContentRootPath, "Data", "history-upst.json"); + } + + public async Task GetUpstHistoryAsync(DateOnly? fromDate, DateOnly? toDate, int page = 1, int pageSize = 5) + { + page = page < 1 ? 1 : page; + pageSize = pageSize <= 0 ? 5 : pageSize; + + var items = await ReadUpstHistoryAsync(); + var query = items.AsEnumerable(); + + if (fromDate.HasValue) + { + query = query.Where(item => DateOnly.FromDateTime(item.TanggalWaktu) >= fromDate.Value); + } + + if (toDate.HasValue) + { + query = query.Where(item => DateOnly.FromDateTime(item.TanggalWaktu) <= toDate.Value); + } + + var ordered = query + .OrderByDescending(item => item.TanggalWaktu) + .ToList(); + + var totalItems = ordered.Count; + var totalPages = totalItems == 0 ? 1 : (int)Math.Ceiling(totalItems / (double)pageSize); + var safePage = Math.Min(page, totalPages); + var pagedItems = ordered + .Skip((safePage - 1) * pageSize) + .Take(pageSize) + .ToList(); + + return new HistoryListResponse + { + Items = pagedItems, + Page = safePage, + PageSize = pageSize, + TotalItems = totalItems, + TotalPages = totalPages, + FromDate = fromDate?.ToString("yyyy-MM-dd"), + ToDate = toDate?.ToString("yyyy-MM-dd") + }; + } + + private async Task> ReadUpstHistoryAsync() + { + try + { + if (!File.Exists(_historyUpstFilePath)) + { + return new List(); + } + + var json = await File.ReadAllTextAsync(_historyUpstFilePath); + var items = JsonSerializer.Deserialize>(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + return items ?? new List(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading UPST history JSON"); + return new List(); + } + } + } +} diff --git a/Views/Admin/Transport/SpjDriverUpst/History/Index.cshtml b/Views/Admin/Transport/SpjDriverUpst/History/Index.cshtml index a4f7aad..0a7c91d 100644 --- a/Views/Admin/Transport/SpjDriverUpst/History/Index.cshtml +++ b/Views/Admin/Transport/SpjDriverUpst/History/Index.cshtml @@ -3,7 +3,7 @@ ViewData["Title"] = "History - DLH"; } -
+
@@ -18,134 +18,58 @@
- - @{ - 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" - } - }; - } - -
- @foreach (var spj in spjList) - { - -
- -
-
-

Nomor Dokumen

-

@spj.NoSpj

-
- - @if (spj.Status == "Completed") - { - - Selesai - - } - else - { - - Proses - - } -
- -
-
- -
-
-
-

@spj.Plat

- @spj.Waktu -
-
- @spj.Kode - @spj.Tanggal -
-
-
- -
-
-
-
- Tujuan Akhir - @spj.Tujuan -
-
-
- -
-
- +
+
+ +
+ + +
+
+ +
+
+ +
+ Memuat riwayat perjalanan... +
+ + + +
+ +
- - - - @*
-
- -
-

Belum Ada Riwayat

-

Riwayat perjalanan Anda akan muncul di sini setelah melakukan perjalanan pertama.

-
*@ -
\ No newline at end of file +
+ +@section Scripts { + +} \ No newline at end of file diff --git a/Views/Admin/Transport/SpjDriverUpst/Home/Index.cshtml b/Views/Admin/Transport/SpjDriverUpst/Home/Index.cshtml index ce9a7f9..d251c38 100644 --- a/Views/Admin/Transport/SpjDriverUpst/Home/Index.cshtml +++ b/Views/Admin/Transport/SpjDriverUpst/Home/Index.cshtml @@ -33,80 +33,95 @@
-
-
-
-
- -
-
-

Lokasi Saat Ini

-

Mendeteksi lokasi...

-
- +
+ +
+
+
+
-
- -
- - -
- -
-
-
-

Nomor SPJ

-

SPJ/07-2025/PKM/000476

-
-
- B 9632 TOR -
+
+

Lokasi Saat Ini

+

Mendeteksi lokasi...

- -
-
-
-
-

Tujuan Pembuangan

-

JRC Rorotan

-

(JRC 005)

-
-
- - -
- - -
-
- - +
+ +
+ + +
+ +
+ +
+ +
+
+

Nomor SPJ

+

SPJ/07-2025/PKM/000476

+
+
+ B 9632 TOR +
+
+ +
+ + +
+
+

Tujuan Pembuangan

+

JRC Rorotan

+ + JRC 005 + +
+ + +
+
+
+ + + + +
+
+
+ +
@@ -300,7 +315,6 @@ document.addEventListener("DOMContentLoaded", function () { getLocationUpdate(); } - // Update Lokasi cuy userLocationEl.addEventListener("click", function () { getLocationUpdate(); }); @@ -358,30 +372,131 @@ document.addEventListener("DOMContentLoaded", function () { let mapInstance = null; let mapBounds = null; let mapInitialized = false; + + let userMarker = null; + let liveLocationWatchId = null; + let lastUserLatLng = null; + let lastKnownHeading = 0; function fitAllLocations() { if (!mapInstance || !mapBounds) return; - mapInstance.invalidateSize(); - if (Array.isArray(mapBounds) && mapBounds.length === 1) { mapInstance.setView(mapBounds[0], 16); return; } + mapInstance.fitBounds(mapBounds, { padding: [24, 24], maxZoom: 17 }); + } - mapInstance.fitBounds(mapBounds, { - padding: [24, 24], - maxZoom: 17 + function toRadians(deg) { + return deg * Math.PI / 180; + } + + function toDegrees(rad) { + return rad * 180 / Math.PI; + } + + function getBearing(from, to) { + const [lat1, lng1] = from; + const [lat2, lng2] = to; + + const phi1 = toRadians(lat1); + const phi2 = toRadians(lat2); + const lambda1 = toRadians(lng1); + const lambda2 = toRadians(lng2); + + const y = Math.sin(lambda2 - lambda1) * Math.cos(phi2); + const x = + Math.cos(phi1) * Math.sin(phi2) - + Math.sin(phi1) * Math.cos(phi2) * Math.cos(lambda2 - lambda1); + + return (toDegrees(Math.atan2(y, x)) + 360) % 360; + } + + function createTruckDivIcon(rotationDeg) { + const carIconHtml = ` +
+ Truck +
`; + + return L.divIcon({ + className: 'bg-transparent border-0', + html: carIconHtml, + iconSize: [40, 40], + iconAnchor: [20, 20], + popupAnchor: [0, -15] }); } + function handleUserPosition(position) { + if (!mapInstance) return; + + const lat = position.coords.latitude; + const lng = position.coords.longitude; + const nextLatLng = [lat, lng]; + + let heading = Number.isFinite(position.coords.heading) + ? position.coords.heading + : null; + + if (!Number.isFinite(heading) && lastUserLatLng) { + heading = getBearing(lastUserLatLng, nextLatLng); + } + + if (Number.isFinite(heading)) { + lastKnownHeading = heading; + } + + const rotationDeg = (lastKnownHeading + 180) % 360; + const truckIcon = createTruckDivIcon(rotationDeg); + + if (userMarker) { + userMarker.setLatLng(nextLatLng); + userMarker.setIcon(truckIcon); + } else { + userMarker = L.marker(nextLatLng, { icon: truckIcon, zIndexOffset: 1000 }) + .addTo(mapInstance) + .bindPopup("
Posisi Anda
Diperbarui otomatis via GPS
"); + } + + lastUserLatLng = nextLatLng; + } + + function updateUserLocationOnMap() { + if (!mapInstance || !("geolocation" in navigator)) return; + + navigator.geolocation.getCurrentPosition( + handleUserPosition, + function (error) { + console.warn("Gagal mengambil GPS untuk live tracking:", error); + }, + { enableHighAccuracy: true, maximumAge: 0, timeout: 15000 } + ); + } + + function startLiveTracking() { + if (!mapInstance || !("geolocation" in navigator) || liveLocationWatchId !== null) return; + + liveLocationWatchId = navigator.geolocation.watchPosition( + handleUserPosition, + function (error) { + console.warn("Gagal mengambil GPS untuk live tracking:", error); + }, + { enableHighAccuracy: true, maximumAge: 0, timeout: 15000 } + ); + } + function ensureLeaflet() { return new Promise((resolve, reject) => { if (window.L && typeof window.L.map === "function") { resolve(); return; } - if (!document.getElementById("leaflet-css")) { const css = document.createElement("link"); css.id = "leaflet-css"; @@ -389,14 +504,12 @@ document.addEventListener("DOMContentLoaded", function () { css.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; document.head.appendChild(css); } - const existingScript = document.getElementById("leaflet-js"); if (existingScript) { existingScript.addEventListener("load", resolve, { once: true }); existingScript.addEventListener("error", reject, { once: true }); return; } - const script = document.createElement("script"); script.id = "leaflet-js"; script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"; @@ -418,7 +531,6 @@ document.addEventListener("DOMContentLoaded", function () { function normalizeLatLng(item) { const lat = toNumber(item.latitude); const lng = toNumber(item.longitude); - if (isValidLatLng(lat, lng)) return { lat, lng }; if (isValidLatLng(lng, lat)) return { lat: lng, lng: lat }; return null; @@ -446,23 +558,15 @@ document.addEventListener("DOMContentLoaded", function () { async function getRoadRouteLatLngs(latLngs) { if (!Array.isArray(latLngs) || latLngs.length < 2) return null; - try { - const coordString = latLngs - .map(([lat, lng]) => `${lng},${lat}`) - .join(";"); - + const coordString = latLngs.map(([lat, lng]) => `${lng},${lat}`).join(";"); const url = `https://router.project-osrm.org/route/v1/driving/${coordString}?overview=full&geometries=geojson&steps=false&alternatives=false`; const res = await fetch(url); if (!res.ok) return null; - const data = await res.json(); const coords = data?.routes?.[0]?.geometry?.coordinates; if (!Array.isArray(coords) || coords.length < 2) return null; - - return coords - .map(pair => [toNumber(pair?.[1]), toNumber(pair?.[0])]) - .filter(([lat, lng]) => isValidLatLng(lat, lng)); + return coords.map(pair => [toNumber(pair?.[1]), toNumber(pair?.[0])]).filter(([lat, lng]) => isValidLatLng(lat, lng)); } catch { return null; } @@ -471,13 +575,12 @@ document.addEventListener("DOMContentLoaded", function () { async function initMap() { try { if (mapInitialized && mapInstance) { - setTimeout(() => { - fitAllLocations(); - }, 80); + setTimeout(() => fitAllLocations(), 80); return; } await ensureLeaflet(); + const res = await fetch("@Url.Content("~/driver/json/pengangkutan.json")"); const payload = await res.json(); const rows = Array.isArray(payload?.data) ? payload.data : []; @@ -513,13 +616,25 @@ document.addEventListener("DOMContentLoaded", function () { const latLngs = points.map(p => [p.lat, p.lng]); points.forEach((point, index) => { + const popupContent = ` +
+ ${index + 1}. ${point.name || "Lokasi"} + ${point.alamat || "-"} + + BUKA DI GMAPS + +
+ `; + L.marker([point.lat, point.lng], { icon: pickupIcon }) .addTo(mapInstance) - .bindPopup(`${index + 1}. ${point.name || "Lokasi"}
${point.alamat || "-"}`); + .bindPopup(popupContent); }); let routeLatLngs = latLngs; - if (latLngs.length > 1) { const routed = await getRoadRouteLatLngs(latLngs); if (routed && routed.length > 1) { @@ -537,9 +652,9 @@ document.addEventListener("DOMContentLoaded", function () { fitAllLocations(); mapInitialized = true; - setTimeout(() => { - fitAllLocations(); - }, 80); + updateUserLocationOnMap(); + startLiveTracking(); + } catch (err) { mapEl.innerHTML = '
Gagal memuat peta
'; console.error("Map init error:", err); @@ -563,9 +678,17 @@ document.addEventListener("DOMContentLoaded", function () { if (mapsCardPanel && !mapsCardPanel.classList.contains("hidden")) { initMap(); } + + window.addEventListener("beforeunload", function () { + if (liveLocationWatchId !== null && "geolocation" in navigator) { + navigator.geolocation.clearWatch(liveLocationWatchId); + liveLocationWatchId = null; + } + }); }); + + + + + + + \ No newline at end of file diff --git a/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml b/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml index 1341a48..9d945a4 100644 --- a/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml +++ b/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml @@ -7,8 +7,8 @@ - - + + @@ -18,12 +18,12 @@ - - + + - - + + @* *@ @@ -40,15 +40,9 @@ @RenderBody() - - - - - + @await Html.PartialAsync("~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_PWAinstall.cshtml") + @await Html.PartialAsync("~/Views/Admin/Transport/SpjDriverUpst/Shared/Partials/_Scripts.cshtml") + @await RenderSectionAsync("Scripts", required: false) diff --git a/wwwroot/driver/css/watch.css b/wwwroot/driver/css/watch.css index c8ae70d..d75fb2a 100644 --- a/wwwroot/driver/css/watch.css +++ b/wwwroot/driver/css/watch.css @@ -77,11 +77,14 @@ --color-slate-50: oklch(98.4% 0.003 247.858); --color-slate-100: oklch(96.8% 0.007 247.896); --color-slate-200: oklch(92.9% 0.013 255.508); + --color-slate-300: oklch(86.9% 0.022 252.894); --color-slate-400: oklch(70.4% 0.04 256.788); + --color-slate-500: oklch(55.4% 0.046 257.417); --color-slate-600: oklch(44.6% 0.043 257.281); --color-slate-700: oklch(37.2% 0.044 257.287); --color-slate-800: oklch(27.9% 0.041 260.031); --color-slate-900: oklch(20.8% 0.042 265.755); + --color-slate-950: oklch(12.9% 0.042 264.695); --color-gray-50: oklch(98.5% 0.002 247.839); --color-gray-100: oklch(96.7% 0.003 264.542); --color-gray-200: oklch(92.8% 0.006 264.531); @@ -97,6 +100,7 @@ --spacing: 0.25rem; --container-xs: 20rem; --container-sm: 24rem; + --container-md: 28rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); --text-sm: 0.875rem; @@ -109,6 +113,8 @@ --text-xl--line-height: calc(1.75 / 1.25); --text-2xl: 1.5rem; --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; @@ -127,6 +133,7 @@ --radius-xl: 0.75rem; --radius-2xl: 1rem; --radius-3xl: 1.5rem; + --radius-4xl: 2rem; --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15); --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1); @@ -324,6 +331,9 @@ .inset-0 { inset: calc(var(--spacing) * 0); } + .inset-x-4 { + inset-inline: calc(var(--spacing) * 4); + } .start-0 { inset-inline-start: calc(var(--spacing) * 0); } @@ -369,9 +379,6 @@ .top-6 { top: calc(var(--spacing) * 6); } - .top-9 { - top: calc(var(--spacing) * 9); - } .top-12 { top: calc(var(--spacing) * 12); } @@ -390,6 +397,9 @@ .-right-1 { right: calc(var(--spacing) * -1); } + .-right-6 { + right: calc(var(--spacing) * -6); + } .right-0 { right: calc(var(--spacing) * 0); } @@ -402,29 +412,32 @@ .right-4 { right: calc(var(--spacing) * 4); } + .right-5 { + right: calc(var(--spacing) * 5); + } .right-6 { right: calc(var(--spacing) * 6); } .right-8 { right: calc(var(--spacing) * 8); } - .right-9 { - right: calc(var(--spacing) * 9); - } .right-16 { right: calc(var(--spacing) * 16); } .right-full { right: 100%; } + .-bottom-0 { + bottom: calc(var(--spacing) * -0); + } .-bottom-0\.5 { bottom: calc(var(--spacing) * -0.5); } .-bottom-1 { bottom: calc(var(--spacing) * -1); } - .-bottom-4 { - bottom: calc(var(--spacing) * -4); + .-bottom-6 { + bottom: calc(var(--spacing) * -6); } .bottom-0 { bottom: calc(var(--spacing) * 0); @@ -435,24 +448,30 @@ .bottom-2 { bottom: calc(var(--spacing) * 2); } + .bottom-5 { + bottom: calc(var(--spacing) * 5); + } .bottom-8 { bottom: calc(var(--spacing) * 8); } .bottom-16 { bottom: calc(var(--spacing) * 16); } + .bottom-28 { + bottom: calc(var(--spacing) * 28); + } .bottom-50 { bottom: calc(var(--spacing) * 50); } .bottom-100 { bottom: calc(var(--spacing) * 100); } - .-left-4 { - left: calc(var(--spacing) * -4); - } .left-0 { left: calc(var(--spacing) * 0); } + .left-1 { + left: calc(var(--spacing) * 1); + } .left-1\/2 { left: calc(1/2 * 100%); } @@ -501,6 +520,12 @@ .z-99 { z-index: 99; } + .z-120 { + z-index: 120; + } + .z-130 { + z-index: 130; + } .z-\[100\] { z-index: 100; } @@ -762,6 +787,12 @@ .-mr-16 { margin-right: calc(var(--spacing) * -16); } + .mr-1 { + margin-right: calc(var(--spacing) * 1); + } + .mr-1\.5 { + margin-right: calc(var(--spacing) * 1.5); + } .mr-2 { margin-right: calc(var(--spacing) * 2); } @@ -798,11 +829,11 @@ .ml-auto { margin-left: auto; } - .line-clamp-3 { + .line-clamp-2 { overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; - -webkit-line-clamp: 3; + -webkit-line-clamp: 2; } .\!hidden { display: none !important; @@ -849,6 +880,9 @@ .aspect-square { aspect-ratio: 1 / 1; } + .h-0 { + height: calc(var(--spacing) * 0); + } .h-0\.5 { height: calc(var(--spacing) * 0.5); } @@ -876,6 +910,9 @@ .h-8 { height: calc(var(--spacing) * 8); } + .h-9 { + height: calc(var(--spacing) * 9); + } .h-10 { height: calc(var(--spacing) * 10); } @@ -930,14 +967,11 @@ .h-100 { height: calc(var(--spacing) * 100); } - .h-\[236px\] { - height: 236px; - } .h-\[250px\] { height: 250px; } - .h-\[300px\] { - height: 300px; + .h-\[340px\] { + height: 340px; } .h-auto { height: auto; @@ -1020,6 +1054,9 @@ .w-36 { width: calc(var(--spacing) * 36); } + .w-40 { + width: calc(var(--spacing) * 40); + } .w-48 { width: calc(var(--spacing) * 48); } @@ -1050,6 +1087,12 @@ .w-max { width: max-content; } + .max-w-full { + max-width: 100%; + } + .max-w-md { + max-width: var(--container-md); + } .max-w-sm { max-width: var(--container-sm); } @@ -1059,6 +1102,12 @@ .min-w-0 { min-width: calc(var(--spacing) * 0); } + .min-w-\[150px\] { + min-width: 150px; + } + .min-w-max { + min-width: max-content; + } .flex-1 { flex: 1; } @@ -1095,6 +1144,10 @@ .border-collapse { border-collapse: collapse; } + .-translate-x-1 { + --tw-translate-x: calc(var(--spacing) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } .-translate-x-1\/2 { --tw-translate-x: calc(calc(1/2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); @@ -1107,6 +1160,10 @@ --tw-translate-x: calc(var(--spacing) * 16); translate: var(--tw-translate-x) var(--tw-translate-y); } + .-translate-y-1 { + --tw-translate-y: calc(var(--spacing) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } .-translate-y-1\/2 { --tw-translate-y: calc(calc(1/2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); @@ -1209,6 +1266,9 @@ .justify-center { justify-content: center; } + .justify-end { + justify-content: flex-end; + } .gap-0 { gap: calc(var(--spacing) * 0); } @@ -1227,6 +1287,16 @@ .gap-5 { gap: calc(var(--spacing) * 5); } + .gap-6 { + gap: calc(var(--spacing) * 6); + } + .space-y-0 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse))); + } + } .space-y-0\.5 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -1276,6 +1346,13 @@ margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); } } + .space-y-8 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse))); + } + } .space-x-2 { :where(& > :not(:last-child)) { --tw-space-x-reverse: 0; @@ -1353,8 +1430,11 @@ .rounded-3xl { border-radius: var(--radius-3xl); } - .rounded-\[24px\] { - border-radius: 24px; + .rounded-4xl { + border-radius: var(--radius-4xl); + } + .rounded-\[28px\] { + border-radius: 28px; } .rounded-\[32px\] { border-radius: 32px; @@ -1503,6 +1583,9 @@ .border-gray-400 { border-color: var(--color-gray-400); } + .border-gray-500 { + border-color: var(--color-gray-500); + } .border-green-100 { border-color: var(--color-green-100); } @@ -1515,15 +1598,12 @@ .border-green-400 { border-color: var(--color-green-400); } - .border-green-600\/20 { - border-color: color-mix(in srgb, oklch(62.7% 0.194 149.214) 20%, transparent); - @supports (color: color-mix(in lab, red, red)) { - border-color: color-mix(in oklab, var(--color-green-600) 20%, transparent); - } - } .border-lime-400 { border-color: var(--color-lime-400); } + .border-orange-100 { + border-color: var(--color-orange-100); + } .border-orange-200 { border-color: var(--color-orange-200); } @@ -1560,6 +1640,12 @@ .border-white { border-color: var(--color-white); } + .border-white\/5 { + border-color: color-mix(in srgb, #fff 5%, transparent); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-white) 5%, transparent); + } + } .border-white\/10 { border-color: color-mix(in srgb, #fff 10%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1578,12 +1664,6 @@ border-color: color-mix(in oklab, var(--color-white) 30%, transparent); } } - .border-white\/70 { - border-color: color-mix(in srgb, #fff 70%, transparent); - @supports (color: color-mix(in lab, red, red)) { - border-color: color-mix(in oklab, var(--color-white) 70%, transparent); - } - } .border-yellow-100 { border-color: var(--color-yellow-100); } @@ -1608,6 +1688,12 @@ .bg-black { background-color: var(--color-black); } + .bg-black\/10 { + background-color: color-mix(in srgb, #000 10%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-black) 10%, transparent); + } + } .bg-black\/20 { background-color: color-mix(in srgb, #000 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1644,6 +1730,12 @@ .bg-blue-500 { background-color: var(--color-blue-500); } + .bg-blue-600 { + background-color: var(--color-blue-600); + } + .bg-cyan-400 { + background-color: var(--color-cyan-400); + } .bg-cyan-400\/10 { background-color: color-mix(in srgb, oklch(78.9% 0.154 211.53) 10%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1707,6 +1799,9 @@ .bg-indigo-300 { background-color: var(--color-indigo-300); } + .bg-lime-500 { + background-color: var(--color-lime-500); + } .bg-lime-500\/15 { background-color: color-mix(in srgb, oklch(76.8% 0.233 130.85) 15%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1728,6 +1823,9 @@ .bg-orange-500 { background-color: var(--color-orange-500); } + .bg-orange-700 { + background-color: var(--color-orange-700); + } .bg-orange-700\/30 { background-color: color-mix(in srgb, oklch(55.3% 0.195 38.402) 30%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1752,12 +1850,33 @@ .bg-red-500 { background-color: var(--color-red-500); } + .bg-slate-50 { + background-color: var(--color-slate-50); + } .bg-slate-100 { background-color: var(--color-slate-100); } + .bg-slate-200 { + background-color: var(--color-slate-200); + } + .bg-slate-300 { + background-color: var(--color-slate-300); + } .bg-slate-400 { background-color: var(--color-slate-400); } + .bg-slate-900 { + background-color: var(--color-slate-900); + } + .bg-slate-950 { + background-color: var(--color-slate-950); + } + .bg-slate-950\/60 { + background-color: color-mix(in srgb, oklch(12.9% 0.042 264.695) 60%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-slate-950) 60%, transparent); + } + } .bg-transparent { background-color: transparent; } @@ -1776,6 +1895,12 @@ background-color: color-mix(in oklab, var(--color-white) 10%, transparent); } } + .bg-white\/15 { + background-color: color-mix(in srgb, #fff 15%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-white) 15%, transparent); + } + } .bg-white\/20 { background-color: color-mix(in srgb, #fff 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1788,12 +1913,6 @@ background-color: color-mix(in oklab, var(--color-white) 70%, transparent); } } - .bg-white\/95 { - background-color: color-mix(in srgb, #fff 95%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-white) 95%, transparent); - } - } .bg-yellow-50 { background-color: var(--color-yellow-50); } @@ -2067,6 +2186,9 @@ .p-1 { padding: calc(var(--spacing) * 1); } + .p-1\.5 { + padding: calc(var(--spacing) * 1.5); + } .p-2 { padding: calc(var(--spacing) * 2); } @@ -2118,6 +2240,9 @@ .py-1 { padding-block: calc(var(--spacing) * 1); } + .py-1\.5 { + padding-block: calc(var(--spacing) * 1.5); + } .py-2 { padding-block: calc(var(--spacing) * 2); } @@ -2205,6 +2330,12 @@ .pt-10 { padding-top: calc(var(--spacing) * 10); } + .pt-12 { + padding-top: calc(var(--spacing) * 12); + } + .pr-4 { + padding-right: calc(var(--spacing) * 4); + } .pb-0 { padding-bottom: calc(var(--spacing) * 0); } @@ -2290,6 +2421,10 @@ font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); } + .text-3xl { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } .text-base { font-size: var(--text-base); line-height: var(--tw-leading, var(--text-base--line-height)); @@ -2363,6 +2498,14 @@ --tw-tracking: 0.3em; letter-spacing: 0.3em; } + .tracking-\[0\.22em\] { + --tw-tracking: 0.22em; + letter-spacing: 0.22em; + } + .tracking-\[0\.24em\] { + --tw-tracking: 0.24em; + letter-spacing: 0.24em; + } .tracking-tight { --tw-tracking: var(--tracking-tight); letter-spacing: var(--tracking-tight); @@ -2395,6 +2538,15 @@ .break-all { word-break: break-all; } + .whitespace-nowrap { + white-space: nowrap; + } + .\!text-white { + color: var(--color-white) !important; + } + .text-amber-500 { + color: var(--color-amber-500); + } .text-amber-600 { color: var(--color-amber-600); } @@ -2425,6 +2577,12 @@ .text-gray-500 { color: var(--color-gray-500); } + .text-gray-500\/80 { + color: color-mix(in srgb, oklch(55.1% 0.027 264.364) 80%, transparent); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-gray-500) 80%, transparent); + } + } .text-gray-600 { color: var(--color-gray-600); } @@ -2437,15 +2595,12 @@ .text-gray-900 { color: var(--color-gray-900); } + .text-green-50 { + color: var(--color-green-50); + } .text-green-100 { color: var(--color-green-100); } - .text-green-100\/70 { - color: color-mix(in srgb, oklch(96.2% 0.044 156.743) 70%, transparent); - @supports (color: color-mix(in lab, red, red)) { - color: color-mix(in oklab, var(--color-green-100) 70%, transparent); - } - } .text-green-400 { color: var(--color-green-400); } @@ -2488,6 +2643,9 @@ .text-orange-700 { color: var(--color-orange-700); } + .text-red-400 { + color: var(--color-red-400); + } .text-red-500 { color: var(--color-red-500); } @@ -2506,9 +2664,15 @@ .text-red-800 { color: var(--color-red-800); } + .text-slate-50 { + color: var(--color-slate-50); + } .text-slate-400 { color: var(--color-slate-400); } + .text-slate-500 { + color: var(--color-slate-500); + } .text-slate-600 { color: var(--color-slate-600); } @@ -2518,6 +2682,9 @@ .text-slate-800 { color: var(--color-slate-800); } + .text-slate-900 { + color: var(--color-slate-900); + } .text-transparent { color: transparent; } @@ -2530,6 +2697,12 @@ color: color-mix(in oklab, var(--color-white) 70%, transparent); } } + .text-white\/80 { + color: color-mix(in srgb, #fff 80%, transparent); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-white) 80%, transparent); + } + } .text-white\/90 { color: color-mix(in srgb, #fff 90%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -2638,6 +2811,10 @@ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .ring-1 { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .ring-2 { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -2652,6 +2829,15 @@ --tw-shadow-color: color-mix(in oklab, var(--color-gray-200) var(--tw-shadow-alpha), transparent); } } + .ring-black { + --tw-ring-color: var(--color-black); + } + .ring-black\/5 { + --tw-ring-color: color-mix(in srgb, #000 5%, transparent); + @supports (color: color-mix(in lab, red, red)) { + --tw-ring-color: color-mix(in oklab, var(--color-black) 5%, transparent); + } + } .ring-gray-200 { --tw-ring-color: var(--color-gray-200); } @@ -2704,11 +2890,6 @@ .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } - .backdrop-blur { - --tw-backdrop-blur: blur(8px); - -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - } .backdrop-blur-lg { --tw-backdrop-blur: blur(var(--blur-lg)); -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); @@ -2775,6 +2956,10 @@ --tw-duration: 300ms; transition-duration: 300ms; } + .duration-500 { + --tw-duration: 500ms; + transition-duration: 500ms; + } .ease-in { --tw-ease: var(--ease-in); transition-timing-function: var(--ease-in); @@ -2864,6 +3049,11 @@ scale: var(--tw-scale-x) var(--tw-scale-y); } } + .group-active\:rotate-180 { + &:is(:where(.group):active *) { + rotate: 180deg; + } + } .group-active\:bg-gray-50 { &:is(:where(.group):active *) { background-color: var(--color-gray-50); @@ -2974,6 +3164,13 @@ } } } + .hover\:border-green-400 { + &:hover { + @media (hover: hover) { + border-color: var(--color-green-400); + } + } + } .hover\:border-orange-200 { &:hover { @media (hover: hover) { @@ -2995,6 +3192,13 @@ } } } + .hover\:bg-blue-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } .hover\:bg-gray-50 { &:hover { @media (hover: hover) { @@ -3065,6 +3269,13 @@ } } } + .hover\:bg-slate-800 { + &:hover { + @media (hover: hover) { + background-color: var(--color-slate-800); + } + } + } .hover\:bg-white\/10 { &:hover { @media (hover: hover) { @@ -3276,6 +3487,23 @@ outline-style: none; } } + .focus-visible\:ring-2 { + &:focus-visible { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus-visible\:ring-slate-400 { + &:focus-visible { + --tw-ring-color: var(--color-slate-400); + } + } + .focus-visible\:outline-none { + &:focus-visible { + --tw-outline-style: none; + outline-style: none; + } + } .active\:scale-90 { &:active { --tw-scale-x: 90%; @@ -3292,6 +3520,36 @@ scale: var(--tw-scale-x) var(--tw-scale-y); } } + .disabled\:cursor-not-allowed { + &:disabled { + cursor: not-allowed; + } + } + .disabled\:opacity-50 { + &:disabled { + opacity: 50%; + } + } + .sm\:flex-1 { + @media (width >= 40rem) { + flex: 1; + } + } + .sm\:grid-cols-2 { + @media (width >= 40rem) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + .sm\:flex-row { + @media (width >= 40rem) { + flex-direction: row; + } + } + .sm\:items-center { + @media (width >= 40rem) { + align-items: center; + } + } .lg\:max-w-sm { @media (width >= 64rem) { max-width: var(--container-sm); diff --git a/wwwroot/driver/images/pwa_192.png b/wwwroot/driver/images/pwa_192.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf9d2ef6393646e95d4747f00a31ae8be2ee24f GIT binary patch literal 16841 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� zLQ*hZFa7NEwb&8Pyc#j~??rm;v65U&eLs_Xt%scadcF@rxh9X7a-{Ay7KVN$H}&VD ztg0+7T@6iXjQGKHf~x(a)u?YYKlV}mWD~L7F)7PK>_#`DX6tWIFq3(2=Y&7bHD}@F z<3*64jW48ExapiGSqKR`r@xOqc5}kmrx1w(qTzA);Mlz$kZ7%=zN$>)rgZb?Fqwc_e8k$eY4J?QWpV8mJ@Aqj zI5gcc6fjP~nq00=$1DBUn=Dg;H`q+NsHJ2oh(bj}pI^Py>wVkGk-a5_$;8MPceQYC zcE!7}yGVyQl!|Jo&9}5-Hg#X(etG5kU%mYrvSaE*7Y-TZmVjZQ{w6B_Zot|%WHJmV zYDf`zd8mJdM?x@)-BcDs7-)=hb-d|PSZR1=EwV*MV=l~fr;RvuOI9b(`PinE188_P zJ9YuVS4~A&!S(({aqmm@Gp9KE!@~J5-#O6p zgn0OvSw-x)@hW*2 z-gvDqoH(x9r*b^lIcSkxg$+TqIDGed&*2m(9}|PX?{W}h4P3_^+nc>3p0`a|(7@;F z*jt~vY0*7x_49UBnR58RCl+SYZs%Ta^$s}DcwyM>`kcDh%}wc${!HByQESj3z~TxPc-Xw`h9U@S*4 z(r8B!6Q9}!PB3=QKJ-$>?6`fZ4TpZHv_GVEHZTpy&clyVN}L0^5{?Kl5<@i&-vhko3kX zX48>5$$l7D@Z7>7S{l3yS?3GA^yZs z^(Ta|QURwN5`4f)qRI7Y=1rNCnlQ@=`7{cgXj2YvI0eIu&)l|^o|-|D?UJnxw(7T4 zh-JE2%Fu`-7iV^{Kz{N`@>D&89UK3`-ezbeG&NOOzcmaN)bEE7y!$b)Kv5aqpKz`qFbesKm01>VH4fLx~GghD< z<>bFV!)ghEN6rSu3y2ri&6<+7cL0$bM)%%tOq!5A`mQoRYr^d7Ey+DaFydr{d=M#! zf1#p(>;uekw)%vTU)>cI4p9ntQJIbZK#o9GIDs&h>U=4j_xlX{H^mwFUZCLz02u

L!wb4(9Id@~9JOgNV3n#oEbH2pE9%cvl19k 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 ItFG_=0`lOx*8l(j literal 0 HcmV?d00001 diff --git a/wwwroot/driver/images/pwa_512.png b/wwwroot/driver/images/pwa_512.png new file mode 100644 index 0000000000000000000000000000000000000000..8934036b98bc2eb49a7f219e72b981ebd1feca00 GIT binary patch literal 20606 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�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�!E5>-}tht-M?nh7-_2XIC;1S-#L_o;yc?kWG8ivFFP`2#>xwYfRF}y(k z`403(gOBFsRVN{4q$)$qn~FDvOG`*e41EA@eBM56D* zx4;{$IA5gPe#8J|;LVr-sZ*OCE<@UI@ymET;T1cG!Fz53>4n;rpGNg$vjg%b%cKi< zBi5Y-4tsWW7^SA8AIsIuO{oXQaeTWp9(rIcIj5|&>shGEAx!is-(Byk~sjRg2 zIQEdPF;*-(mCltO3nk1Cb)V#ENIuHWb7(xD0iu!xlmFMa<%>-j`#go|2XFtL9~rwm zt|AvQXH~W!H>fhKPV%VN(x6E{cUR%5>u=foRgNFK+Wz|ZTx(-0AyW{D& zy_I9M{jt?#)F|t6wC8<*XB+i%0Bp-@O3BE0SJOXXd^r9#1<1r5Z6HxHI$Ofgp-n{& zh9)EDuWOt8rm3@&<{*_odxw=SSM}oORIz3@Y0I1zqeOrnz}iOQmjH%eEq;)K4&X`* zxqz|#qL&!X3ag5`lr*&=iu~e%{dQ&EVldhj@viYREZ-22nCE}7kP}vIx{|Opmtu8;onuO;tA;;(!!DTYUX7G(2e>##U7_3~u{J60<9v#$r%lM?4^B)lH8 zH7L8p`JRGM8EQj)L_puA&EhYht+HKg7K}CjnpZ-n0@#q_kag|V8R*-~4_+2BtZC^e z`*i#i5PRxcvtLYT>}wmuDRdHOq5#1wtjKH+#h z$%1+~>`Q=Gioa0=gl6vfulOw~>-6K=lU>bf)>A*g>bKYKZC=)nDFAP>C+DfE3_gw- zM2Pd<4|5(}2N5OY45tW%l-7 z%ys{%A!b!?>61V6(~MWGf$9se$c2;1z|_yyZqQ?5`%OEa_YPJeowix^)<|nzw}McS zIwLRmL>K&>F-T=uE5(BGrx{;tj_EYXjs&tiQQt|=$=x}@Z|XLA0>l{9cdSb9jkjm? z*Ji|RPOy3v|45(0phmaFEj%q(|Edx3_}7;WI1VmuIgu58>(hsK>4V#ja=yjR6Q09Vucv$64VWq90|ody68x|`1jp7wR_5q@s2ABY1Vo1fD2(}E9Z);ZW-qb{`* z_osI;$zWpTG@bfsVaKe`K^VkkE2hXhqKx4@#?P%b?$ZKHJURrP_ul!aUo`u#`gDML z{ACwlU$%g;;HEHu(lf@Ie59DaEDykAph@+fZJ)p?Z-Ra}YvJOKSn~nP79LaxzSTru zj0X= zg|x&~3IjIljAJ3O9{ z5hA0L=s_vBaQA}|=nMEV+xRdi6j#I5A9LSU(@y6EM0C?6=4k-!=~D*qBZKPlJql%7 zhm$QC^te^TkHCX~EMO6E-v`(){w>7?cV7@2NVAtut>8$UsK?(K*nwNJ%m(R944`WF zG<+Q(wz0#LEgg*fdUsVWZVE)lrngra(1(6k{+NnQjpL`4k$+M)u8@nN?+{$;~t z58jeFDd}90dyr_=T-ZC9rUd{{V+&76iY-+20&z30HV4t?o1i674!$+b4-yg zlg~cTcrGI85SR;e?`2fx1twDT9Jlu}n*fXq zgs-%WAI{1z7km{HxZc&W25as(qxQDr$JwNvn)JI@&8teq>->9@DE@i?(?#|1(N!=3 zvGTMvq4AGCfj8uE=vW;yWEVEEzOlBWf>*z^oZzt;rFM1`ThG+q?$NpKQ51;66fqu) zEIn)*w|gh=Z95+Do(qV14a$iCY)$dXn>GA0LUr(Kll1yVa(#(O!iI#82;8dEqMk04 zu+CX-At}w_V@*1=nc5Neik~JB+?1wMz&|_64bIskZ>1KNA_x#WJ0UNzfn+ALja=wT z1LbNXGE9t;?P=Lo!Eko*Y^Li&B zWK(D2Q8DY9MBToK5Quq` z4W7{s{qUmVjT-ksCOdcn;;;q(BIs1-QJYBW6n{0aMh$cZkBEW|6%eB^Pk?^#KFedX z%|kfj0EdAM1e8UgNu zz}U5cYl{i*!Qzpe{wy%@vMDp}lUfs^l@)37zFdj8SKmgHL!g}K3%#t%{JVuhr!plu z2ZQ$`piyPL(GH7{$w}NS>zO7-KX)eFtHoueiogql!{gI%A(4>YiJ;>Zo5uBx=<1Am z`K%x7Fg!VR$U)DTQ2)`hz5^E6rn&Fp;ab3HibBZsqXN!9h_L&l88z{E8gf^0wC+}V zOl4CkRFLi2_1mKlWXjpE!o|jgur+!+C4aeI;v$I8iO03%m98+#04jY--aJ-uF*1 zRMTj(5b7|w2ailk3e}7WAqXRf-qqY3I&Gwp3XA^!t+~SrSNE0^58OIK1@~N(lyu@v z2`rHB(+a55Z1bDhdfro*Jy<^s2uV2_snGwos3aw;ej?xRa`!Gn51|w39nI@n$?O*_ zhqLMNtMl#)@ZV#v5tkexn|!z$)g5Pf4d-eL1NSa&&IuF4$PyEM-o?&AVfy}KT!MFc z*hEa?d}^QdKF_Nb0ny-OPG^wdvlynV{oBEdZH`)4RJAs_ntmp;@-OdH`e$Fe-rRfo zHE?(XDNtMdjP@(Fu|V~zjYqK=5JHQbTAaj`YpBr##~Pomdj9NGdreBmY-MwjE@&#S zdLF2tn5sDRuf11u@(+Z*Plso?L^}mof2RwD%zlk|U7b!zsYwn=pO6Qt{Dk8Hf(*`X zEn~J@Dk$FgL$C3Gwsd#vOkmZK+mV4pyLS9_e*9UKWYBkqJ+-{_x*f?lu#%eTL4|KbOjQTqm-*+XVmWE8!U3#@A4$}-_h&FsIF zb>yItvh)LwssPER26G8*l#By_7mS6AOW09wzzbp{=;T~lgyo`EB_I(5^#C2&+imVT zG6!y_EO|&og@7H9BZ^w`4WlKluKKq_E5eb7GAojLW?e&$eZPRzE`G{%M_4fRf2o;#WqWIqx^Gt{4 z-rzhe|EB-qIONJTXlm_1>O3ZZ25H!Y+==^XKpqJ!d)ivQI!g|?f1H*QzFw^v7x&{e zFjWJ{Toj#U5v5CJMF+-WHeN?&2ZIX7dk9bayIuyEF-M6rD>AF;O%TH!{avv%Md3?$ zD%wOo8f{u_PUOro#GWrsl8vJJct0kix+`#dKGLTH{oVJ(SOsxL zl{-{AH;3LIT)4b4TdnsM-%Hq8Lq7~$@I6^iuLNu#b3Ibzi=fw5{%4K?2p#tDn?)7% zxGQzyx;ynW{7Z5W%n`MY?m?!Egq_b~ zoJULy&-D{BFduG~I0c>zaR+JPJ(89sUZeK@jZ zFGWciK4&AqDXUXKsiSUfNn+cMV@jt(1>^q+`w-6-8axkJ{fax|PVqxdbK^aqm{r%- z$?choXb=tws&5z#EAHbp0#>emRO-6b%wr3%T7Torpo);DoG8-90!xS>Vb_Hv$2=3D%!HI*rjfo$Kk;^}#q>13gI- z62RYi@wMspGK3f2+9MR`h2Wscymxwnm0P3*|L)6u+N7yjk$5k8@t8$g9ofX?xNPw5 zM}3T!zm|j~nYc)QGJtxE0ZZYVQ%^s%ZuL$N{JhDVWC0b9X*Lq{da?wo%T ziM~arr9tULKk+y@P%JIV4i9er5?EgwGCUFno+!qc4iBcj%X=5z#N+a#t-MWsk1!=JfaJ)-jB7OUbK*<&9c+dj|cKX(8?f3 z?_g}-QK_BO%(ukdtGPa)`7L#|X;ZHG!|d-7Qu&aZc8cb%`|4agv&{{Yp!431+_>ft ztM#kSH8G83!vnhzyCkWzXF{cC&WP(8pQlsA-M!Q$&jeoZ|AG&;jkDTU^8*o6K9@%* zBbuW*bbFBD;1=xZKD;^DO6?~c2k|s9uvsza^H@Q(OVQ_-maVBg-K2D?YszphL*&8d zw;L1IY^Fi_EJNvs_q7a+_&mL6(=w#F#T)<1TXy+{nl^`yjj16s#o*UVhy!-t+X!qn zYi@O2vDaA^oz;aFiyyP!kY-*cv&EuTia z*ddD4xzFEleEEkfa}WhYh7GF@LoG?o0B>MK>C4&VCxcRglI=bJ+K_f(-&*08CHy!{ zbMnqpxI8X0H|1lskK`R%Y=`Uc?LzM5fxe!r>07QJqHbPA^~XjvA$}KW^_4y-^;1ro zuFJtG!H`D=sgsPS{vuTZI_Lk6OwTq4--taOhG{&WOPy<|GaShK)j~;K!^1?nuWaE< zcnEaCd`}FYxxW+g=~2etZD3XKi`rs-?E>jgz`zlDW748lO2b({t<$kn-4(&rb&@rm z(p;goO{jL#TcxQyiXB7>uig`Flf0(7t|d-9t>`>59Lzf@({c;9Wh0hW6IG7ecc z`sv!p3U#d74Eu4g@0o8LaK8G3Iu)7l@Cbj{>C=B|#sik7KY^tv2xv-ya;Lw`zu@Xo zjUZJZ2I1~J2YNap0rtQHsc!HWv!ij&NvQ9p(2JU`m7B%|X2j+CU|-n0Il8f@yTv@0 z;a8ztj6CZMrH|eEKgI&js*lfk#H$=}N3={3?bzg)eh#jk%_OL&bT zl+JMeXEgx+*`zoSM=&oQT`F`&Ij*^h@48V7vZy$-dGHvTsyPx8T-nMkT*7G`OY~p{ zak2uc*56+SDJ;XTq!;XMt7e33+vO0t^K)}4Jt;m4ySvE!Xmls{T*Go?(>Fhj1*oPM zp!Tz&%%G3JRC%S6+865_?Og5L-fi8tPILTT&iDo1a)^%sLea;<;|WG+t)J!&eQuMt z=;tex5lyWn``^xwxp`7;uU+gwE;0};g8m;5826{HXEFlR_4v%YA9scx|3)0qbUt?0 z#MM7sNi~s!HTFVxcr4{dEo5EENJF#Fu` z6x4BH={t3_AzysHFmqrI>A2EMb|}TE54uAaF0sKC1E{zZrCIF09-JtYgDC!?DbEI zpo`5`!V#1;Fo6!0Iv9j^ty`;w8NF3u)xPvJm+5-@ zDT<23>Wc@2I`?A&7fj5q1DzQIjsdgJVb$k#*@%P>Fq_6>Yt!>Q*Wq3Nc=N11uuMhg zo74jiAKsdoT|BaK2@(4Q&%e8VoajCyeafJW@MZt={Pq8ZW3cFehQ~3lgO3t|mZf~r z<{jvXzBog4HR^b@2K_K|R(Ln7dnd)!r=_Ih5B)A378?3NS_y*1NsjcfvANK#`nhm& zmg{!U9e7i+VNWutM<@7Be{KL@*`vewOVWIWW23UOLeqe)j;YHqE%&Tj(VCO!=&=| zEM-4#Xl_#=h7G$63nZIFk7dovtRwde1IRU*pA8n@{oq>1Vyl(A{ht=qZMDMB!ZNq4rTtMFY@z$MkW5$|KS9ir(S>J;zHYL!QZL9 z`PgIfw!3?!t8{hZH)wR|eq9{z35K2(nEoy;Egi;ekB@~xj`2zvU;5j#MqWp3YG#Errz)T2^CUGeI6iYI9Z?w{X zz6VhxCISyI=a{&RPq=w?ogPld=XPFNTZ*e&rG6*km&SOG4051Px#&_Q1PhWC$;mNmoQ#y5Fq|XdI{U(2 z0UlODo3gv=pG|IRLM|0{A19>TOAx4xo==);YHC#M7LJbln)^Ct6XPLwe7nIx_oH*K z6Rzr<>jfKea(fTN9**uEm|0m_?SDxV@nd1ovnpj`-S>XQ`ilVLFEEI?aYm1w{T1Xp z8!jUu6RR{M0rL#VSA!n*^&*U&P=KmTakf~FKIKD}VUB<9<>jAaXy@~?uHG$k<=$Q%qd7txIf_;(?9ROs zL^mjmks%0Hl9EvM;ul|EFj2Bb(c)u$0~2D;v8XhlBae ze-K=Shv?egQ3;F`7Zwf(FR2Fm3q15sHy9i4MVRT!TN{XVosM%o4V(iT$4483yUkyO zNec)Fd@1`@US2L+u0fXSf`+RcbW7H|A89z-Hf%yBj^A264p=>Ah*=oUf%d*D0^0Qa zIRP%ltKP2fFMC0miuW^kSWhT=CDdyo3v*iVNtYLG67c;b9K?NRmbFZo4@arf!W-M|v2KG$b(6V%qkA=Fj;ca1qt zMTsLu(yPA+h*Q7QhJIMg_!cBh`|usaoCW-mSq5fQWqjqBj74NYPk;ryI6?>dBNa@* zrl=M_d&5Zx{vaI_AWZF*{DC%96@yTkcIGQNBcNivUqKj5;1BeQ7`-SGX&$Y_ue5+7 zb%AK96s=3>dDPhnHH?F8B_8_8#W>ZV*h}A774(JXyKhR)OG6&d7w*u(WY{m}W@l#$ z2%%6YZi%FgX4_>pSCx6rYKy-GN8!Y5Y>N6xuEIm3kO+A>1=cI&M5QWRYi%`*UJz00 zcMQPj$g#+jYHEgJy@JTp5IHiBsIu@gby`7R(l@5LykycQ{;O#JZ&j95866phxD;lY z`Do2hRl-f{SY{cF)redzM~jARmLZ4PHO0e*M0$dTv?eINBGX$-erCcfN9`0yY{fIR0wOP9^@@#R z!;Ie8RAnSku<{<%l}=WR^02nCLTg-4Cq(kyVcI7xF`PfwCGtbF@!!s3PztfH^t(#8kzsb*dx~9Ryz*OF-up zN)a{2_#8}|SOy%%$zS0c7>mrBUgqV!HW2ypt8WUX63Qe;al4S&MSZu8#tINPjQxv^ zuhE%20rt~lSLxDCNMcc$Vp)7qNnnpyp3KOhe%o%LPlNik*yayuoF)A2;nBgn{NsEn z^ciXOJsHf>!dvb;T=&G0E{aX0{9G7LII_(saR61s*Jo;!*1V!i&-OJ zT}#4ppyw+IQu0^uRrn5=#IGNerJ+8>K?J>^MN+zFNb}@i1;s47Lk`eO%$b0hF4ZI? zRn}E_q71O*bw=zjHhTUNe72)8;-tUS=t?ZIn|xa9k{%~*%tCTR!`JO9p7ZZjZ;6vHb3N)b-dW?y^71oyH9qMOG#zYi`w#ih) z?CtvS?YcOK4c3)tI6`}*f{LOIB?kDB!=g^&$5S7fnBJr&D_c~jP&-F(p^Gl&ZRc>B z*rj~hZnlizWTl~u+4$*tg7c5#H91n(XS+53c#j#9jey-;pL=awD95MnAFh(ndLuf9 z(`Rva5!r5b3m&}kuHcs=j>gB)icVzA0t};8k^{XB;V?)}jd!jojfpkR2gGa`jxT^D z#;~9tQBB4M&9LpS8O(mb$Hn-IO|En}85g@^oc<_=77O%U6JW<0RBWUlVP%&2_KW}| zG|WDvYpYET{FEw+@go_oHB#}O@F-I?3xuKOP8;=;i?VF)EA*Fe;#G-HcogUrBYbNZ zJLE}h&ivL$0P%A!LMkvF(M;$)RIaF$j$hkQS9@g_y#3IcArI{4%B< zuf}YV(0QgoiREU^>Jd)yj=zE7@q%XWcG_-1~Pa@V{w@mOA3)2xlP3OSUqHJwBc~w`?;jQBM4@C%uQ zZhWrwanx0@rZCxm^#bhUH094Y413XrMORu{BvFnrQak_HOii-9EsN4yrK&Ivu;i!j z1>u*Kr^}ZW>ha1EgB{J~pxU7?2owc$n!iX^+t!*saahk=OkT*C*^;0`3e`kC@8a8Pe;$Vim|@HYPm61my64!P z>U_BU=XlWnmhXVn?%2HZCz}z!^9)Ycc~ti)NqrJYw&h8rop^nJOBJLwJ_j{|slhLG zQFwEsusx>&Xr1)Tx3*SUJFTpP;VLy{h9ebvrJl9Cn_nz`ZgMN_vPyoR4M7T>=LUWj zrfco8b}zt;DXQRD-HCF)KGz`gcX{&%tNKni|FJZBoO9*AFAgg@;-!pklj8^MlcP{PNp)DWwKH}j_3vwWj)ht^i6ytN_+YiQz{j!fKhN8R~&Pt=#z z=8E_s2x+jQWSa)%8=lncX6sT}LUL3T)QNweT?|j)z;1Tv`t6Z6Id7I!VU8$+tC@bt zLA!G4FsnyEg7)WT(6PJbY1aCI&^zHVy z;<=;6W|d!JD*xn5U3wE!FTPsHUkqk^UPMRpz~la;c8n;Jt)?O<;c%7M2>;^#ly^8u zD+f(wq-PsSC901=+~j#|HQ3sQkz=8TKU8^#mKz#U)QS}Y{|V@BDrWZNg99`Z z$lAS}Pp%Q`0;91&2x;7Ay1DF=W;dUfrX#*&c~7ktsmhoPdg)*`pH%0ObMHFZSwNYP zMk>jUS{+Q@i`~9@p%#gaA;TZ%Jxj33X>$b3R_B#*mJC4tLN47pO9P>2W5 zJ@-R@^*=BFb-IsOJMR>;awd`tfFJ_+#i>e?%OgHV@pgCAUJd9^a~XbbOTviI7)oO)zsp}&<;6Jfipl4yMKW)II8f=yRJ>&{vEUNjAm!h$DX4k zQHcn+z$O+wl0||LfTBh1U z%6y)?L8pzqw#gMM!!RsfFfGR_(~(|<$h5`EPoY8=Ez$oHVkzI~2E#O^m22MBixLHT zHEWfciq0R8`pZ|MCxz3F-8WA*3+Z{ECPBUpLf^P z+M-yz9Soupjm~dfITYq+ORq=R3x`#CLOxQ8^&K5VnBS%!v#5hpVz49keU8DHvSHoJ zZGldV*(aW*w@F3oNY*sfw9ip>KmY%U&~vxyb#{Ro1X5t!@{rkmNs_BNv@808v`tUa z-H0kqPWs#WLGzc%Tph_10+--y2(SQ$?F{+8C|)@DTh$Q1DK9~BP>&mH{(XzDZ(1CV zC|g1emV_Ugnk$bWL2z%9HVK!J-iBH$p6hMUd7ZF_ww;BsEo}!&%3AW3dqs|Yf6GrH z#ymnQAZ#kuv$c(F4jAo)&Q$&sQwYkQVF*)Y%o_JzE{HpU*use zd6P9;Zof^asTb0_Zc4M0Vs!0RGb8{sQnJ87kv?Xec-8QWL3zMv(&qk^X|bj?)WCqG z;-DjF?xX@SEfQiRQakKh7JOa3bCvgWh>8t7pU-4|SRs>iXZ)O?o#Z$g!TNfP!m0aW z|JJi6aNVdI!4=l?9RlR`AAA4g4YQ@#5SPw}EI!fzpS8B1Z{Wq{fD$g`ee1rJk5(-u zp$K{GjV(!#D6)|j;+=wan4EWYmKBeIe^rz9wQFG=w}`7`oMqY#SKPCaIT=(D6v zi!}0I*Rs`O!A~-ze!9omy8NS$YNDPd-N$82xNsbM+bso3mo!YAEAquhbybHXV{Vm12|x)woQ#9WA$A&PH~*oA2_)VG*wk8^u<--d?j*w@P*$;FF`U_BD$2}L__3cg|V1&e3ofqD+4e+)qj|5AlY-&e1x9& z8Go4dd^c72qh@u!Gcmp*IYF7Lji1^NMVt>lwP-v&Lh}wbBn9f|8a%Gyyf3Q z`b)Mjs4-W_&f&mXt`OJfa6t#h2$%J^suK!?MkBOdfjs0<<7yl|Ot$1~uTZ!w(BY-y zdF6N7C&gYJl6#to#mh$5+%z|&d;Y-pS+W>O59Zc-do*e_6|-(dy^_HfzWpXi;xevc z^tIj6QZbAt35!t6CYPXMY!LB~URMV-M2-rAY1L+xeWdU0HR|Vwb4UYZMa5VZ39}k> z$0sQy$k}5U)KF!VT*=KNFO&q5IQjVC+0CgsECU-a1)Cjc0IOO242&gTMM>Z^o~;cTYi23i8P~#bp|1l$}@hQ4i`UPbKa&bO5?KX){d;Ii`lQ7mk9qmu*At zQ#BGi>HFM$448*lz-q7k@(%@=1sRJhF7<|QlgMenvVf9r06EP+6gehWr* z*%QBW%lFxJ#tW!Xg1E^65rR*Ns8>=t<=?+&<0%T-{IL^mx3pa7pvo8uf>xT=di38I zBlO~_boBgV2?oAm*Oj!F1+`QeI$7ru1O#{uv08tjO=*f=64{Ilu(T-lA}v=KY2Oo0 zM1IiompE!X7)A0~TEta3!&y=XL86IKpZJpl*yiUK+c=vQ_U-jOcb=U+N?61nN3|)? zCP_QnxSC1AiRCSA)#XcwwW1qbcghST*<3qpP#g$>d(0^w($v}D>-foL)a{wn@BG!z z0%|u|u=P!aMQ5Fnk0Z?CKx9Z{{*ZRQYxel?ZJ3(Jn7t z#n*+*>^{OYa*uC&ciZ52H5})Bu?iP$gBdQYER(FeA+gyA>lMQA%G5*+pSb`H0j?Zi z_+dWZp#?acs*s#caOxId95ta6{vFUbd9d6nYcliElcg&vEU!n-Lrl~i@Ds`vi5)8a!R%=4K{9}8R_`Kh#$e^sUEKtS_J=Lu4VzrD3 z5VB_TaFKSfoolY<;F6qjivMlVrDjou}K zsEH&*?>$QNPW0Zp_wsw5`@7e>zWe^x_s_K~Ygo?1-upQAv9I$y$@pD6sEm4K$TB@A zxgu71hv@6MIcCeeb|OW575eCGY+^4{hS3`msfi3fva6oA^Rs%sHIkftuIQb%CduG5 z51+;E;hKcA@O35jM|rK)@XeFV(gOE2KKq?NKuuio`Ja2E`FXT(pWh{;fNtVz4?x*S zHLlT_81CCQ);n?+`)@fMR5H6wk*3CTvU7fYlW`f_@a}*1H~vpCylDnNqynUMsNU<> z!x8%(B)6Ty+UmDmVP1X*`20X+=5GBxq+63*+hy3&qMC-s^fUdWLGUM@PKNVTtF}8ynpNsu9Pl z_W>^OkZ;^w1$70I9tE7jRXgGJG}csnXuvY$;CG#hRtE+`?#W)khu~-|JJa|i6U}-z z`O<9j)0BzX_J_~VfHww+s)ye68kq~-({EiBX`%(z0H&BeP7d!JO|IZpvh4W;+?sAg zotnQ9$(ATXYB#@KW#FrDv#K9$QfGcz7~AC_A4S03`1PX4xAFa5@ZDlbtOV`uMeE>0 zsc&t&A|Z*E%ETXlFl;>k2ZW*DzyC$i90Mlqf0~ofhw4Tf3g@>ZTn%wvEsgGeV}sZ4 zN^brryDX(p?D7fQUw6=0S*ssCmwMvl`=Q=3553XkTr}rZBz{HxqNQQK!A#uJFLM{& z>C`)%P{=mRe6lI{@4|+157sUhuRQfJD*mtFMg3kCa7f)lI}Tz?bXNg?`8|4r#reh1 zu)Z@uw|wL7wzSr$KU3<{A4>nsHSCa&27b~~)k(wqCi3kyX|??;r;paL5T?{Q68h{n zy!bAM;MnYMO0K)hhr2XGMl7`P${zY?Td&RU_OCp%h6@GqNLA>&C#@>IRQzr@ zrtE*byE}>Pa+8}Cs`XL5*pxM$_5;TDpU-CJ=T0Bke2C>NtWWMrw4~=O1LAM#<9$z( z^gp%7wzVgRs$1mVXJ!94he;N{cV3@4eM*|7ygRTN_uWq2nu^UJ9i%2-rmwE<(BH*V zP5bDVY-DP)#$E_qKX1$FH1}{Y!`v;VG&n*IoG&5js@~Q!oc2h7OYdQ^&y8TRe%mH|Sp^JC#WF=t78zYE^$4FwDZcUxEhV&s7j^e#c%3z* z5EY8CKKV1rh@~vg^0?mcq(isTD`V2))i*_(aO8Wyj#G)#{6PW#tg*kI44m3aLnugh z4L+kC&Sa&H+whgnd-s<^zpavy?OiQHAYf}&_r7v8zS)!-oBQ?oEP*a=Ir%jA;9uP# zN34(kZP<-_qi3hlk2hEOgF=$OwRXNIs!>+K9_k5e-2CFs{GaVR-=3U}G1Yqm{+Ihn zi=HjNpx{RG{w3q>wD8E(QlS3bx~rtfox~1NCVY#v-ImST8))q4Kfb&DL$}zWKQaAD zU{sFpQSX*;d-wZ?v05C&N^s(+Q_DTDD?P1oLLno6;xAp#e@_ms4Wnp)zydbLLKQjt zdhN#zOvp-9qd16};Hd)Dn?YAgU3t`D6g%M|o>fl)zGbPWJel+5oem#>RwCV%9x?&h ztL&sV_p{~jQFUj#$H#%L`!Hpff8)sO(B8f5@TQB^XmhS?HAsqcZ!TY$V~M@Rn-8X~ zXi~>8maHZBSrPmUavW1tQ+AthCwpc2e6LfAvomRtX1VvQU7yYy*gtYn(&#x|$T5C> zU_(WhRUVpj-w|uCENyo1`|kqNy87$?aoPn?$OS+l_4md?h|u%rL(%(V*5|$RA51#edyExapJv}!{Jt^Tc6|QSGQQiH$!P}0s!{n3xMocR z5Eb88>pD|8mWI;#y!hCU><>4|0QmBlv6NXN_+X z`pLO_{qO;u(Cd9LpJ)l`)O2Ptn-KYGA9b}ciS#0#>#R^6k(3bIC z1?+E+@bXDnV~>vGe5$_e?z}d+W^>==pU&UTm5!I5BIoe9JAPff)_JZR?-2g- z<*Wg%&&748-|hdNzMt=2?9C;a&Oo04V}Nw$6Xk=F^6xQpVymkDhpkz&S2mlhV*r9x zWPricBk{g&=qf~t%jrhQ+bEGx5O15{U-Z_i9T5)Jq9h0$P`0v@#ug2%yQay!`S|@y zE}kcm9=C=nsw}C%#DHOCHzsAHTkMBu?c=dz0Ky!c2(_oiC$|!n-A=R;2xSFO)g=SyNT#W%QLo`+YbH-5Q^_+m)Ex+afe&0eFcV^ zz$?JN>KMI@J@*I|liQ~r=ko^0;iL*kyk9Oec=;;WO`qB@k7YPO?(4DjsJFZPk3X5j zR#^k#z}5;)mdq@#vQ?~Xt@#E< z>jV}+8q}9`)c}86+n43h$F;6#jt8`TSp>tIECX zYdrs>AGBAS!rT`#N73XqCFbhP-Dq`uReLIq@>}6GsnetX`IP~nAC~m}zx^=73jy8D zWallrgO6N4G%_TXxv!VP#EV1#l%4y7R zPjy4Xe=DxvEEu{3h@%t}t&)=`&a1!b$>$(Z!Unj#cHGtQM)%^SHXs@(~MT~ zxWQ9~*M&NKahCBcDse=@T`?l>-Ee;rHX=UqVJNk|6$+52`_e*Q95y+H^zn!YN+q%0=9+a5( zFt2%}8>!Q+5&81Ptby-I?>&P5^YHv0!FxyH6mS$&{?m#qo{lVW8^6i)xE1wZbmjJ1 zU6dNV&00$$vF|&TD>#_#oDvr5yPmQ6yZD56h8bTl0%puj=sF7w|_`5K>XeaD-3WqrCQUIaF;zziY~Qu1)Y)Wy@J3q3foeFe(H1mpDa z$?h&}1lhi$Cda~#j*%htQ=!=8`u7J@`{6rpw(@{tOmGUBbt?UM+nlfMEVz_PQK$gL zV^(2kE~{}q@hm*5#xay+q^-~8WuVW{;6nY?GfgyAMU!<1Ci4v4g11|~`d02{&K$+W zj3zqp=c3TWw}5v8dIm3omq(7vrxks#T>N-$*=~2kKD@ljr1G4L>5GXogQ0nF^c4VI z>eXsL@7CR}|9fG#ySrOJP>}2!@Y-CY?e`a-3zU_f|B6Y^_B##R#Hz~{{;0zvihA07 zd(k%SuOs5RMmTq#vEv}>_T(S2m{K~c**nr_Y@MriZzPdt%H+#&_$n1GsNjze51Ma^ zp6Bz^VUQwAD=?)6U=k@4pb_Dv!oVbDd!a>O0Y`2!NliFZJQU9NpGP+-61BD|2=EdX zVlwlXVTH~#D-z|-y?6=bd$bV{q>Fp& ztViX=2_i8-R80Hm^JYieX}x!=IO}HzLyX7ejUNYgMayXXp?4m=40p%zu!@GqvSP{yt?^mr3h+KjSeuNw-Nmb@N+P z2Sts{G@JKX%9MJX;XQQm`__m}D~wDTwchVF~L6--qA% zocEuJr{Xma>6JkD@Hr#P_K(fk6sBvMTJV<6@m}Kp^(120{Z0P0Bp1{I{40YV|E>rp znh6q$k(G$_lBmGM#9_8gW}9yedADn5d-T-ykwZ^iK z8Ky37#uPYAQO%R#@2Nfst2OMze<=58o=r$duzUUb@qO@-4!!=ap`3vJ^p-~0)NL$m zGsrtjn3c?#P&TRUw!N?J&6}SKxkL+sjX$%Z!|quBncJXiVp`i+w3<~3TxMwx8QojX zpF*%+g@A9EQ$+EVD=x&*2FyVGk+L!JR1$n@47=(mR<6hM*?aSC*{6sv_=25K32!hP zHQcl<;4l%~F)m990)Z(Ux@g2*Dc*ND3$Q&3prtug zODl4V_Zz&2(!P7VBaK5a>rp!BO=wy9>|+EEQ5u=MWQn+dKFUa5$-EkzEjW=V0^>OF z^n!F2L*!E=F{s96i-)=(A{dnn`ah*KFXsAJj@)wy>~ly(oKdTaVYY8123Q9#+;7*g zTzzix{mVa^{8{XK-Nl2X2&#_z|0e>^zwOZN($*gRa@FP?5E@mU5zF@6dO|>S-pF-U zk}cjbDeNcHs_2kARf8jxiT&CP;?lZP5SI0TYD_jo7X%Js7Lj~FA+(1|#0bEwehdmV zP7%S1@g*k$m9Rv=X*rvV8JGlOu)2qne*!Jn*B`GEWE0yI`o#8?L?RB111NxH5vg{g zG_qD0x`DfF6R2hs0Wn%EF<((BlekEXZ5&Od1%e~=D78CqPjiR9{~VY90%As@FoQ6u ziq*E~iJmD<|7czUWu>=b+Ff0E4AOq{BYhk|fa6d>|JTD0GlnkUghYuf>Rq#S61~2M zOUz+?dKdYw+_D6uwf-txqSN4f@!4GUKhZ2e0kX!%O!Rsn*3$uGq9L@uO9u^}f#1 z+`8{UgT8D9IM4=9;VDlXhbb^I%7DIkn8D=nLg1KFNHIVG6Q$uzSh*3^3+K@Qkne}q ztwnsq9K^`un?YcvCNpG2$p9*jeCy0zrTm{_ zXz;SFr@)bX;l|tS+XGsF<=XN8R*2vV@KoWb1ZWTll|a7)wv)6)|3GuqV${+gS|z;q zTOg|AJ8NW$wEu7=_I)1St~729*)SE8W-tYXR1t9;zjHu-*Hn%IkZ^|0=c`0pO$*Xs&ur(>_lh@7*bh7~ib5Nri z8FrLGF8D@%hFr_chrhViEXdHRspAH@^`mu0YT1u&R60L>vFmHaznAM8C@`E23y-sl zEK3Z(bGQ9L=R&EfQu;ucH8GI-@CmfH$EDtAHa5P45A0oMldm&CnGDXetyfMmL81bD zft}oPNln-=MAHJwD{;b`@lghvMU7xInXpCz}8Ah z)Zy(UXU~EHSz!oqaB5*c=tPhgdH)g6vZ?r`S+>~rY$@A`(fAl+UFQ$vbT?q4tGRr( z=fm!=iH}kn#C;9RvtHW|mK$;=JD;sQs3AGdJl>#}UxqrEm|+b;WE+pvjb zsf1Bs=VPxw_85?U%VB2D)S*W_wPcRsbZMC3SCmCw=YNQv*-(FErl6FqCx8){ z=}iEL5mfGl6%}i-E)U@7xHn@$U`nOyrR`@g!-@%aEk{L3bONB@iBi6z7R5L6L6koi zx_SIz)~A73HIm@@8^>>_YDJ2OP!tAM=xBTAS^la>*=wCkExf+R0N+cxQJii|lS@L1 z!Kzpj-sHPkt-Jm|j{Q|{E0N(opSu$hl%GF$*<1OL&2xUqdRLPsRb}@FiTMZ%`%P+4^9R#5o5oXP zzc&t9Hroc4R~>dsfw=kA0}7ANPsPk;*&OD;?Fh_3CP1J{6X+5wi{(}m=9!1|KW-l8 zo$$h>t|K1gMmljkn#c4m3vd)Ox0EOX_4!&V(fBOEUMJ`DNjp7I+eYyL*aG!~Smc_q zNWol$D4)u#Um+Znq8kb+^7fclFDlN#dAgNLiwOp z4fD7pB$XA}_wGe$1hY zmQ_jBPP1N5=WS~PgO>WQw%61XN3j(fB~@4+vr$2WWP8IBc%^D6VeRf zZszuA-W3<%x~Ql{Mn&Z?!U}_(1tZg+LydK3)FxN{rb2b+!r8yyA89|gw7}L?L5qzA zabHXvVYEg!=@^T@>_KAS?I+OR^#2{KJ9VGCcCULiGI=l@ z#MZ{XvYo(X{q2SonZE!kMxb;6;-jvm ziO)%E;&&~$IeRN!bJctIF6?%_?8!lZ@J+L>llg4L=F?XYN3A8;CQW6Da zt8M|2`>dFJ8x126qt^xK!ny);@uIcD@4=?#a#4<0XzlX(Sb2W`TGLcNA^;qIRq%*g zO|}}<13#{5p3n=YS2EL^)Ox?)XAGmX!^o$~t$xT>3g>9iItveylHrxhBrF@t8DQhk zewEY|>II(nW_;)M=gTu24m1h_oO1PZ!NOZhrNRogAecO+Wk%pd9iBDeQYHVNE(#Bb z3JMjR1nfRu#?^9vqUI26SrLHZnLvv<-}67Y+92$RVUX75?AW@&XT;=gBkSb%-uH5! z14jRgLb{Y-0qkZ?9wzB~O0c`91R>~BLANrmpw;BxN}{_ZVL#79)jBs54+ZhX?pRgL zH*XeZjtEJ_XcbdQJmVn zxnuEcowQw2LttP=T(LOv%_KAO{3eaQjL!Lj-h2+j8|u`I+lg3)aO<^pEyBzX@gk zgIaP8$}sgUW|8!+olAb^Gp+Am)4=q-64pNo_W9c8k01%W3RCW8huA(0h5 zT9@djNYD{C1PSwnX}w2bK#3P{bTBD7p-R^&23EjB8V5>24J3y7yt}#Iqh0afJqejd zqch@OzoafT$It8MT`rG}%Dg6*v!pJ{ILd$Xnfjjm4FO8W)P3prW?|Ip^TpVA+yAA@ z&U3#zIyZH-;<2O=q5eLe606HF2ao!>vRfI4guDk1H@Of*{Bouu3VM0&SQJ zg}7?tFlz&CYI(!urVQXO?1bMhtMaSWZKQGLjm==gX_dae z+jvK`{n82d)xUQ`aa)s~yKi0{327%G$a}E3!@jQtKFCwxBr)4v16#t(s(Vn#2r@wO zVuGL)Fhs@09jg`y44**w^<4|N&@u0_-Z!$?h$a+ZaSP9h3a4a`7!tL_PUPi?w`BK z;qm-sz3}z&e|HFWivKtEj`X)*_lB8x?2MS+j@Qxd=VTsia`+2ny6->bs$IvZ^rZUI z(*&bEG!U^NN7(WSU`9v0qV7O4=aIWz%|M70bEu2c#)YeM~uy}$!S=;Fb`t*nzS z=7B^!;I1;X+O^rWL0$`BT8H%C4WNK_u^EKN!$FNQv_crrn=`kd;3|}bYCT9@gdhwX zs5(ZC7@ZFnlk4{T&<5VQ^Uh0a7VicX4~@!F=}bw(i!a{@XYkiMjVKXr^DR zTKy%U^V|6T(exz1Skr5>@ZkEhINQ#Q_Y)$?J+4>NLMj>sUQK7aMHe0Qj1F_PSz(eZ zoC6cJp#)o~B^jDm_uL2EeW_{e1HPMzQT;fe58nzAV33T&sZ_zVL|DkGU>YNE z{$z)=i>`k9{OhL7Vy1RYCr`Al+oI z>+MeCMd1%Xb!Y!N?BO{2t8Z%jp9SBWHDWd2`+AG_e9!-lo1D_kecuut^!BUXYEC~| zCT}QzAST>2Ic@e1VwFXHw`e|hm-(#5#3RN+q-03Ew_^@YG)e7uo|-f;Rg3IyK#}}J@}q{4>>lPgI1eW9 zKGH{suBNpJ`+aM2tRr&i%*6e5vP&{k#XTXMPG1XM)7;-MB`ov!8Nk&pGxC9R$0yBy z>(kLqAC3FlzjaQ);U}lTC39!ZvA=PAk+$9Z1x%w}!63Od<>#s~s zdR8foV%Hv=`Wf21F@42T6MA<#xh!ylsi@Nb<8ayMg8MdJwR`i7e8$vZX+OZ%?rzpw z#BCk%;OrVb_`9MtXfW6uDr+d2Y8ec6UwxH zoWl>|(sIw;M8UO!FmPAECu{{^cmW|BCJq?FV*tn|>IEEV8wRzRtO~5jjJ5@aO>JiO z6LNzcj*}OlZ6-owY5Pco_k(J{A`C|rVU9)olY&|VM3NI|rOoQGYbxM#dP*?owJTbh zI?v^{RTf7q-V(L@0&<3wgr`;W&|CVIxsm;oD7qqu( z`t$f%;}JRC-7y^&CEI^;|G%@wUGg~6KKqbrZ{j3!yy27Q4WrFb%i676KKYw}N!ja1 zHRW6_Y0sXr4^i!CeKqc_;r~%>=acc>zH|MwgF;&tY%UU zUg&ouh*BFAq>2%Uf>aRTSZaarJ_lE!z$%CUxJnC(Q;#Vbj|mcl)L1YJX%0_d!fMH| z1js7L&>XA1v_hJbzXO7W1LAaugN%Z~umA?63|t2Xq=Z1?Jb)mfHj4pHX=|mb*<_j? zE3G0vW>Si=G=G~n>|!+N{?4?3Zp;@awxL0jb}6PSNwHh|or7Ia)qZ-%PvXutp69)9 zx}@$V6n|fqOCJ3a{{IH#O!D~99=IXgefKg`xS`Cmza6)_@$wDL7?{XwP25$Lpg4Du z2j_uo3Kfb(RKG~2pf-JGTfu&cgA%GSrTo=To zbYE`zU_jLrK~Pe|aIo58*r9=iAaxXdq>=z9w9Tz92sHppu)cyinFvX zvp9__*2mjpmOI$e0>5cSQ9vUW;cp%>g#ICeSCE1uoNlj9#4qz?RPwiHZ<`J)KF+52 z-wqLZ_nY3`2s%|{2K>MH&P31nZ0+t~inVd2zHaV(&Rs}<=O_D7(%Ht8x3~B~kgs@} zvc5!;&neF4vTB7>+G-cIi*53RH!0oL^0Mk5Qaw3PEt32#K~j#&pDz?&a!l1&nH63` zQVF&m4^RmowYGFA^?Pph+bSl)ECfrp9){{&=OjqOqmQM5GQefhSHE~Mf<9L)#oNHbW+6ttEEGE(YY+X>+t(?fBM9e8K%l4er zJO?i!kwSq7iiRhdi`cFU6T~#(DCS@&%KGzwsUoFO_xg_kfzMHeL^8j9q7yM%@+{L# zUEO3_diK%#*FClh%lRJaxUp{gO6nt9NaJTJSkbxcJe+^F;{D%o|H~gg;vkRO&(4}y z3{zcpn0&8%k<@K}_%GJKFa7RR)ztI-NFw;YGyitqncLa?AFrdmvfV;V@~qbMrbBi9 z*ImL*KU{WO{yeA@&L&+@J3iA^l zTOoZON?N4|Y(kyq7f=|=*BDIx=vAer;d<*fjLuX>8BGFx00mtVn5-{Dn9RY%e@JJz zVVFV&W)%eH8Y-gs8U&)!K&O$zj>yeGgM|ng1Hcd+o5LyMqL7b|v~_beSXAiGo)+no zP`tM30#R=w5zoVejidC%oHDt$0iMy0~eC^RT{Q6M_~!ulSX zdlz?PAW7d(9MIF%y^2yN?owv~XTMMAoEnZ`)zz%qA%?su%NuK>KASc2=OMTWS?}>q zDw)I0{v3BxKs1UIyLy=LdMPYY8D!xq5s4(leC$@*Dh3gWa0CN$WD`Ve5pIC@4~j#{ zrVxbULBm2JFQd|s?Pgu!Jg|T?46OGIx|B=~vLpp8Xt`oIJ987PSy=^7c|}9rWJF$c zndeK-ii!ctd%oldc(7cL3}{-;&NeO7oD$BZgcGP8_z7xbmsjrL22@3TutS(PjVia6aut*Zj?qd;N_% z5)=q%?ic@QC0zd{>93ZQH`HG5Zp~jN#@@7hm;JIodoy=>Xmh#OaBS>*(QzYguNbB| zCyE@rp!Dcy-9P4>E;;e*8+*{jKJxXojCHO6XYJrYa|ZsRIUhj)@lh*t%t`D6^WH}a zvT|CHF6;t@&COL9P>3ZG%@6Pm=mRj0fuj+?fxmn*h-Bs{w1E|U6f@E?02K_A;bX;9 zvX-w>rVmC$v?+JsF;IrNP%xmI1JTW&1f*RsQ=jUtWGJbOjxt54km>6F>LHWR)%lgD z&C;!-qm-BaRL2q$u5DoOf+;N&i$83_cR}OX`yrlvQ{NA+t5*&tBa7a+4=?tqOc%KQ zY&3n2AIuMG0tXY!-?n_dU1g77b+k{l1Jp0Uy%-d&IoY<65<4I%TkR}sNV7WZl-li; zy0{oupKKoor3>#mMgQi^_0J6XjY_W*eos}NZN{ld9ye)Bh~R?7yQQZA$uYU zoBQ>UY#ulnFN(2AIwG)DfbbD+yA~u~JtEK{ipMNRq=^M37az=Ppn#OWUp?X(`!nEn zSmjh8F5xN+9C3+=507o0e#whb`|1!^l(AlzRt9JM@=8EIqX6YFw0E|VObkp{a-<3< z3FFi=`Kmi(mjgt3gve^hoDL6TKDFhxt!#JT=D~~VY!z44dyuyB7;@+X(&gT29 z+*xfmiPL#pzDU-{SkO0>`uEX)+wJZmOe%r)u8oz(XO(Qwr;Wq(@Sp5Q6E7vxtEq>k zp3GAJ?oUIhP>a8Z_&>u@K|IJUk%FRP#PI$?r^HG9pw#m2$KN-u?uq)p|23|b++GCf zD3+~NaqIYa`pe!#vhw#|Jz?&yf3Xx4ImGl4ERndslZb=c$ zw@@oJj>VIEYpg%2tL|RVn>O_9ZPL09S!S*5k8!Knt`}>M=hN2+2gjNn3VvhO9M#C44x&X2XBjE3Myt5(bzI5_Cm8DriyOSI9MU63ixIbZO+dG z#arZg$l3SA*~c$#SU^A>)=FOpwF{LcNDCbn;5_-5V)W8X#5Tn=`ec69#^QIR-bmMY)_=xY^k?3OYTh%@>7{53 za1XZ2QtI=A9&YMGMXBTQi%Lzup1uUXUFF#8ppT8$F^O>E=DPrl5g=?S?_Hs{k_Qo* z4i{J4H_Ojs2KscYM9d6AIk~uc+@1|=Q6G%%*koVrIQgC7Hl8o-;AWjS_)n2H-o&^U z&i~xFiR$wY>y%cLX*GihjRFfk(m=s%UcX5yuQD#L3;yM-nuaByRCfFzIu zD5xvpX-8gE!bvDv|T4h~2zLKGwJQiIpA<5vo zwe*2|w{cUqJk2gm0d*g);QNX|rVK0hIPsr*e>LmwZ@WclBW`BxhC@B^DX~SFIk0&8 z-@vrfa+vU32NkW+!EX7^vX#>fNm=bbO-lChgM!#`pJntH*6ac2kw+$1;r>*@cN^pH z`+qciIrwft!PrD>=BhmcW#g5DC`$ig`Ai5ag)0Lf5+F421Ld$Ou;S7E$|`|O+A0}L zcmSku5R*Aop_&Saxk>3&h@47l#yuzT^#EYTYYi|3Q?Ka6CGhkDpQnhcF4`hwP5Sgl zfPhsVJRt{mTi}Vwk&_)EWl$S|vWK$Z6I!G@!>p9p35LMTp|Ai)YH7KJR}3I!Xc$&a zw9}HHQ+A~3T_4qRet`>qshpYjfxvy)!Q{r@+7@kov-{m`g2lZ||CB-pjhk#OINn#-~wd4w6TG zcPXy4St*&_i#drXNEB~`;MSPup0W44n-k$JBD(0gJK$20%Vqb&bSV#3GU20?*fAe} z$>)uyZ$s^${x%;k?|8p=o7Zrd>15~Q0G(D^;F&{f}v+6;^p zU$j}6D;b!zRVGka>~SS>U=buAGsvL@)0_dMD2q2DfITgsE{n&;6&&3vN2&si7=|&2 z51ZkNghQxV(wc-2X^W`-EunqIy<4Y)nMU8thqzd}{{AHWyUV{%*d_d~=s!?<9BwTW zHF#z;`V7_$W0Olo)$KNj(H^CAYX7B4PJS4n&BO99Dp-49^Texrd#HL2;|uvlFrT?^zL(-7edAVWKftN6iR4Ol?$fi&D$O_;3hQ021Cynh0S4 z$-|z7Lv#YsTBIO)HTIBj45~k-k@^4iE&!euhKvS;jd%gkAKtCzprB^0`ZeN8s4F1FmP?+}-*
0IogyqWJTQLDxl!RdPBb0&2{?|x;T9cc3K{V! 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 X=t^=6_>f_l#s?mvzNuabffW89dRvZ^ literal 0 HcmV?d00001 diff --git a/wwwroot/driver/js/detail-penjemputan-non-tps.js b/wwwroot/driver/js/detail-penjemputan-non-tps.js index 0a292d7..1804d59 100644 --- a/wwwroot/driver/js/detail-penjemputan-non-tps.js +++ b/wwwroot/driver/js/detail-penjemputan-non-tps.js @@ -1,269 +1,189 @@ -document.addEventListener("DOMContentLoaded", function () { - const grandTotalDisplay = document.getElementById("grand-total-timbangan"); - const grandTotalOrganikDisplay = document.getElementById( - "grand-total-organik", - ); - const grandTotalAnorganikDisplay = document.getElementById( - "grand-total-anorganik", - ); - const grandTotalResiduDisplay = document.getElementById("grand-total-residu"); - const tpsContentContainer = document.getElementById("tps-content"); +document.addEventListener('DOMContentLoaded', async function() { + const grandTotalDisplay = document.getElementById('grand-total-timbangan'); + const grandTotalOrganikDisplay = document.getElementById('grand-total-organik'); + const grandTotalAnorganikDisplay = document.getElementById('grand-total-anorganik'); + const grandTotalResiduDisplay = document.getElementById('grand-total-residu'); + const tpsContentContainer = document.getElementById('tps-content'); - let activeTpsIndex = 0; - let tpsData = []; - let nomorSpj = "SPJ/07-2025/PKM/000476"; + let activeTpsIndex = 0; + let tpsData = []; + let nomorSpj = 'SPJ/07-2025/PKM/000476'; + let draftRequestKey = ''; - const STORAGE_KEY = "detailPenjemputanNonTpsState"; - - function saveState() { - try { - const stateCopy = JSON.parse( - JSON.stringify({ activeTpsIndex, tpsData, nomorSpj }, (key, value) => { - if (value instanceof File) { - return { name: value.name, size: value.size, type: value.type }; - } - return value; - }), - ); - localStorage.setItem(STORAGE_KEY, JSON.stringify(stateCopy)); - } catch (e) { - console.warn("Failed to save state to localStorage:", e); + function buildDraftRequestKey(tps) { + const spjDetailId = (tps?.spjDetailId || '').trim(); + const lokasiAngkutId = (tps?.lokasiAngkutId || '').trim(); + if (!spjDetailId && !lokasiAngkutId) return ''; + return `non-tps-${spjDetailId || 'no-spj'}-${lokasiAngkutId || 'no-lokasi'}`.replace(/[^a-zA-Z0-9_-]/g, ''); } - } - function loadState() { - try { - const saved = localStorage.getItem(STORAGE_KEY); - if (saved) { - const parsed = JSON.parse(saved); - if (parsed.tpsData && parsed.tpsData.length > 0) { - tpsData = parsed.tpsData; + let autoSaveTimer = null; + let autoSaveStatusEl = null; + + function scheduleAutoSave() { + clearTimeout(autoSaveTimer); + showAutoSaveStatus('menyimpan...'); + autoSaveTimer = setTimeout(autoSaveDraft, 1000); + } + + function showAutoSaveStatus(msg, isOk = false) { + if (!autoSaveStatusEl) { + autoSaveStatusEl = document.getElementById('auto-save-status'); } - if (parsed.nomorSpj) nomorSpj = parsed.nomorSpj; - } - } catch (e) { - console.warn("Failed to load state from localStorage:", e); - } - } - - function clearState() { - localStorage.removeItem(STORAGE_KEY); - } - - function isBrowserFile(value) { - return typeof File !== "undefined" && value instanceof File; - } - - function getFirstValue(value) { - return Array.isArray(value) ? value[0] : value; - } - - function resolveStoredPhoto(value) { - if (!value) { - return null; + if (!autoSaveStatusEl) return; + autoSaveStatusEl.textContent = msg; + autoSaveStatusEl.className = isOk + ? 'text-[11px] text-green-600 text-center font-medium transition-opacity' + : 'text-[11px] text-amber-500 text-center font-medium transition-opacity'; + autoSaveStatusEl.style.opacity = '1'; + if (isOk) setTimeout(() => { autoSaveStatusEl.style.opacity = '0'; }, 2500); } - if (isBrowserFile(value)) { - return value; + async function autoSaveDraft() { + const tps = tpsData[activeTpsIndex]; + if (!tps) return; + + const payload = { + draftKey: draftRequestKey, + lokasiAngkutId: tps.lokasiAngkutId || '', + spjDetailId: tps.spjDetailId || '', + latitude: tps.latitude || '', + longitude: tps.longitude || '', + alamatJalan: tps.alamatJalan || '', + waktuKedatangan: tps.waktuKedatangan || '', + fotoKedatanganFileNames: tps.fotoKedatanganFileNames || [], + fotoKedatanganUploaded: tps.fotoKedatanganUploaded || false, + timbangan: (tps.timbangan || []).map(t => ({ + berat: (t.berat && t.berat.length > 0) ? t.berat[0] : 0, + jenisSampah: (t.jenisSampah && t.jenisSampah.length > 0) ? t.jenisSampah[0] : 'Residu', + fotoFileName: t.fotoFileName || '', + uploaded: t.uploaded || false, + ocrInfo: t.ocrInfo || '' + })), + totalOrganik: tps.totalOrganik || 0, + totalAnorganik: tps.totalAnorganik || 0, + totalResidu: tps.totalResidu || 0, + totalTimbangan: tps.totalTimbangan || 0, + fotoPetugasFileNames: tps.fotoPetugasFileNames || [], + fotoPetugasUploaded: tps.fotoPetugasUploaded || false, + namaPetugas: tps.namaPetugas || '' + }; + + try { + const res = await fetch('/upst/detail-penjemputan/save-draft-non-tps', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + const data = await res.json(); + showAutoSaveStatus(data.success ? '✓ Draft tersimpan' : '✗ Gagal simpan', data.success); + } catch { + showAutoSaveStatus('✗ Gagal simpan draft'); + } } - if (typeof value === "string") { - return { - url: value, - name: value.split("/").pop() || "Foto", - size: 0, - source: "api", - }; + async function loadDraftFromServer() { + if (!draftRequestKey) return; + try { + const res = await fetch(`/upst/detail-penjemputan/load-draft-non-tps?draftKey=${encodeURIComponent(draftRequestKey)}`); + if (!res.ok) return; + const data = await res.json(); + if (!data.success || !data.hasDraft || !data.draft) return; + + const d = data.draft; + const tps = tpsData[activeTpsIndex]; + if (!tps) return; + + if (d.lokasiAngkutId) tps.lokasiAngkutId = d.lokasiAngkutId; + if (d.spjDetailId) tps.spjDetailId = d.spjDetailId; + if (d.latitude) tps.latitude = d.latitude; + if (d.longitude) tps.longitude = d.longitude; + if (d.alamatJalan) tps.alamatJalan = d.alamatJalan; + if (d.waktuKedatangan) tps.waktuKedatangan = d.waktuKedatangan; + tps.fotoKedatanganFileNames = d.fotoKedatanganFileNames || []; + tps.fotoKedatanganUploaded = d.fotoKedatanganUploaded || false; + tps.fotoPetugasFileNames = d.fotoPetugasFileNames || []; + tps.fotoPetugasUploaded = d.fotoPetugasUploaded || false; + tps.namaPetugas = d.namaPetugas || ''; + tps.totalOrganik = d.totalOrganik || 0; + tps.totalAnorganik = d.totalAnorganik || 0; + tps.totalResidu = d.totalResidu || 0; + tps.totalTimbangan = d.totalTimbangan || 0; + + if (d.timbangan && d.timbangan.length > 0) { + tps.timbangan = d.timbangan.map(t => ({ + file: null, + fotoFileName: t.fotoFileName || '', + berat: [t.berat || 0], + jenisSampah: [t.jenisSampah || 'Residu'], + lokasiAngkut: [], + uploaded: t.uploaded || false, + ocrInfo: t.ocrInfo || 'OCR: diproses.' + })); + } + + patchFormFromDraft(); + showAutoSaveStatus('✓ Draft dipulihkan', true); + } catch (err) { + console.warn('Gagal memuat draft dari server:', err); + } } - if (typeof value !== "object") { - return null; + function patchFormFromDraft() { + const tps = tpsData[activeTpsIndex]; + const form = tpsContentContainer.querySelector('form'); + if (!form) return; + + const hiddenLat = form.querySelector('.tps-latitude'); + const hiddenLng = form.querySelector('.tps-longitude'); + const hiddenAlamat = form.querySelector('.tps-alamat-jalan'); + if (hiddenLat) hiddenLat.value = tps.latitude; + if (hiddenLng) hiddenLng.value = tps.longitude; + if (hiddenAlamat) hiddenAlamat.value = tps.alamatJalan; + + const latDisplay = form.querySelector('.tps-display-latitude'); + const lngDisplay = form.querySelector('.tps-display-longitude'); + const waktuDisplay = form.querySelector('.tps-waktu-kedatangan'); + if (latDisplay && tps.latitude) latDisplay.value = tps.latitude; + if (lngDisplay && tps.longitude) lngDisplay.value = tps.longitude; + if (waktuDisplay && tps.waktuKedatangan) waktuDisplay.value = tps.waktuKedatangan; + + const namaPetugasInput = form.querySelector('.tps-nama-petugas'); + if (namaPetugasInput && tps.namaPetugas) namaPetugasInput.value = tps.namaPetugas; + + refreshKedatanganUploadState(form); + refreshPetugasUploadState(form); + + if (tps.timbangan.length > 0) { + const repeater = form.querySelector('.tps-timbangan-repeater'); + if (repeater) { + repeater.innerHTML = ''; + tps.timbangan.forEach(timb => createTimbanganItem(repeater, timb)); + } + } + + const previewKedatangan = form.querySelector('.tps-preview-kedatangan'); + if (previewKedatangan) { + if (tps.fotoKedatangan.length > 0) { + renderStoredPhotos(tps.fotoKedatangan, previewKedatangan); + } else if (tps.fotoKedatanganUploaded && tps.fotoKedatanganFileNames.length > 0) { + renderServerImagePreview(tps.fotoKedatanganFileNames, previewKedatangan); + } + } + + const previewPetugas = form.querySelector('.tps-preview-petugas'); + if (previewPetugas) { + if (tps.fotoPetugas.length > 0) { + renderStoredPhotos(tps.fotoPetugas, previewPetugas); + } else if (tps.fotoPetugasUploaded && tps.fotoPetugasFileNames.length > 0) { + renderServerImagePreview(tps.fotoPetugasFileNames, previewPetugas); + } + } + + refreshSubmitButtonState(form); + updateTpsTotalTimbangan(); } - const url = - value.url || - value.fileUrl || - value.path || - value.filePath || - value.src || - value.fotoUrl || - value.photoUrl || - value.fotoFileName || - value.fileName || - ""; - - return { - ...value, - url, - name: - value.name || - value.fileName || - value.file_name || - (url ? url.split("/").pop() : "Foto"), - size: Number(value.size || value.fileSize || 0) || 0, - source: value.source || "api", - }; - } - - function getStoredPhotoUrl(value) { - const photo = resolveStoredPhoto(value); - if (!photo) { - return ""; - } - - if (isBrowserFile(photo)) { - return URL.createObjectURL(photo); - } - - return photo.url || ""; - } - - function getStoredPhotoName(value, fallbackName = "Foto") { - const photo = resolveStoredPhoto(value); - if (!photo) { - return fallbackName; - } - - return isBrowserFile(photo) - ? photo.name || fallbackName - : photo.name || fallbackName; - } - - function getStoredPhotoSize(value) { - const photo = resolveStoredPhoto(value); - if (!photo) { - return 0; - } - - return isBrowserFile(photo) - ? photo.size || 0 - : Number(photo.size || 0) || 0; - } - - function hasStoredPhoto(value) { - const photo = resolveStoredPhoto(value); - if (!photo) { - return false; - } - - return isBrowserFile(photo) || Boolean(photo.url || photo.name); - } - - function normalizePhotoList(value) { - if (!Array.isArray(value)) { - return []; - } - - return value.map(resolveStoredPhoto).filter(Boolean); - } - - function normalizeTimbanganItem(item) { - if (!item) { - return { - file: null, - berat: [0], - jenisSampah: [DEFAULT_JENIS], - lokasiAngkut: [], - uploaded: false, - ocrInfo: "OCR: belum diproses.", - }; - } - - const file = resolveStoredPhoto( - item.file || - item.foto || - item.photo || - item.fotoUrl || - item.photoUrl || - item.fotoFileName, - ); - const berat = Number(getFirstValue(item.berat) ?? item.weight ?? 0) || 0; - const jenisSampah = - getFirstValue(item.jenisSampah) || item.JenisSampah || DEFAULT_JENIS; - const uploaded = Boolean( - item.uploaded ?? - item.isUploaded ?? - item.IsUploaded ?? - hasStoredPhoto(file), - ); - - return { - ...item, - file, - berat: [berat], - jenisSampah: [jenisSampah], - lokasiAngkut: item.lokasiAngkut || [], - uploaded, - ocrInfo: - item.ocrInfo || - item.OcrInfo || - (uploaded ? "Foto dari server." : "OCR: belum diproses."), - }; - } - - function hydrateSingleTpsFromApi(detail) { - const tps = tpsData[0]; - if (!tps || !detail) { - return; - } - - const fotoKedatangan = normalizePhotoList( - detail.fotoKedatangan || detail.FotoKedatangan, - ); - const fotoPetugas = normalizePhotoList( - detail.fotoPetugas || detail.FotoPetugas, - ); - const apiTimbangan = detail.timbangan || detail.Timbangan; - const timbangan = Array.isArray(apiTimbangan) - ? apiTimbangan.map(normalizeTimbanganItem) - : tps.timbangan; - - tps.name = detail.namaTps || detail.tpsName || detail.name || tps.name; - tps.lokasiAngkutId = - detail.lokasiAngkutId || detail.LokasiAngkutID || tps.lokasiAngkutId; - tps.spjDetailId = - detail.spjDetailId || detail.SpjDetailID || tps.spjDetailId; - tps.latitude = detail.latitude || detail.Latitude || tps.latitude; - tps.longitude = detail.longitude || detail.Longitude || tps.longitude; - tps.alamatJalan = - detail.alamatJalan || detail.AlamatJalan || tps.alamatJalan; - tps.waktuKedatangan = - detail.waktuKedatangan || detail.WaktuKedatangan || tps.waktuKedatangan; - tps.fotoKedatangan = fotoKedatangan.length - ? fotoKedatangan - : tps.fotoKedatangan; - tps.fotoKedatanganUploaded = - Boolean(detail.fotoKedatanganUploaded ?? detail.FotoKedatanganUploaded) || - fotoKedatangan.length > 0 || - tps.fotoKedatanganUploaded; - tps.timbangan = timbangan; - tps.totalOrganik = - Number(detail.totalOrganik ?? detail.TotalOrganik ?? tps.totalOrganik) || - 0; - tps.totalAnorganik = - Number( - detail.totalAnorganik ?? detail.TotalAnorganik ?? tps.totalAnorganik, - ) || 0; - tps.totalResidu = - Number(detail.totalResidu ?? detail.TotalResidu ?? tps.totalResidu) || 0; - tps.totalTimbangan = - Number( - detail.totalTimbangan ?? detail.TotalTimbangan ?? tps.totalTimbangan, - ) || 0; - tps.fotoPetugas = fotoPetugas.length ? fotoPetugas : tps.fotoPetugas; - tps.fotoPetugasUploaded = - Boolean(detail.fotoPetugasUploaded ?? detail.FotoPetugasUploaded) || - fotoPetugas.length > 0 || - tps.fotoPetugasUploaded; - tps.namaPetugas = - detail.namaPetugas || detail.NamaPetugas || tps.namaPetugas; - tps.submitted = Boolean( - detail.submitted ?? detail.Submitted ?? tps.submitted, - ); - - saveState(); - } - const OCR_AREAS = [ { id: "A", @@ -295,38 +215,31 @@ document.addEventListener("DOMContentLoaded", function () { const DETAIL_DATA_URL = "/driver/json/detail-penjemputan-non-tps.json"; const DEFAULT_TPS_NAME = "Lokasi Pengangkutan 1"; - function initializeLocation() { - loadState(); - if (tpsData.length > 0) { - renderTpsForm(); - return; + function initializeLocation() { + tpsData = [{ + name: DEFAULT_TPS_NAME, + index: 0, + lokasiAngkutId: '', + spjDetailId: '', + latitude: '', + longitude: '', + alamatJalan: '', + waktuKedatangan: '', + fotoKedatangan: [], + fotoKedatanganFileNames: [], + fotoKedatanganUploaded: false, + timbangan: [], + totalOrganik: 0, + totalAnorganik: 0, + totalResidu: 0, + totalTimbangan: 0, + fotoPetugas: [], + fotoPetugasFileNames: [], + fotoPetugasUploaded: false, + namaPetugas: '', + submitted: false + }]; } - tpsData = [ - { - name: DEFAULT_TPS_NAME, - index: 0, - lokasiAngkutId: "", - spjDetailId: "", - latitude: "", - longitude: "", - alamatJalan: "", - waktuKedatangan: "", - fotoKedatangan: [], - fotoKedatanganUploaded: false, - timbangan: [], - totalOrganik: 0, - totalAnorganik: 0, - totalResidu: 0, - totalTimbangan: 0, - fotoPetugas: [], - fotoPetugasUploaded: false, - namaPetugas: "", - submitted: false, - }, - ]; - - renderTpsForm(); - } async function loadDetailData() { try { @@ -341,24 +254,26 @@ document.addEventListener("DOMContentLoaded", function () { detail.namaTps || detail.tpsName || detail.name || DEFAULT_TPS_NAME; const namaPerusahaan = detail.namaPerusahaan || detail.companyName || ""; - if (tpsData[0]) { - tpsData[0].name = namaTps; - tpsData[0].lokasiAngkutId = - detail.lokasiAngkutId || - detail.LokasiAngkutID || - tpsData[0].lokasiAngkutId; - tpsData[0].spjDetailId = - detail.spjDetailId || detail.SpjDetailID || tpsData[0].spjDetailId; - } + if (tpsData[0]) { + tpsData[0].name = namaTps; + tpsData[0].lokasiAngkutId = detail.lokasiAngkutId || detail.LokasiAngkutID || tpsData[0].lokasiAngkutId; + tpsData[0].spjDetailId = detail.spjDetailId || detail.SpjDetailID || tpsData[0].spjDetailId; + draftRequestKey = buildDraftRequestKey(tpsData[0]); + } - hydrateSingleTpsFromApi(detail); - nomorSpj = detail.nomorSpj || nomorSpj; - applyDetailDataToView(detail, namaTps, namaPerusahaan); - renderTpsForm(); - } catch (error) { - console.warn("Gagal memuat detail penjemputan non-TPS:", error); + nomorSpj = detail.nomorSpj || nomorSpj; + applyDetailDataToView(detail, namaTps, namaPerusahaan); + const _form = tpsContentContainer.querySelector('form'); + if (_form) { + const lokasiInput = _form.querySelector('.tps-lokasi-angkut-id'); + const spjInput = _form.querySelector('.tps-spj-detail-id'); + if (lokasiInput) lokasiInput.value = tpsData[0].lokasiAngkutId; + if (spjInput) spjInput.value = tpsData[0].spjDetailId; + } + } catch (error) { + console.warn('Gagal memuat detail penjemputan non-TPS:', error); + } } - } function applyDetailDataToView(detail, namaTps, namaPerusahaan) { const titleEl = document.getElementById("detail-page-title"); @@ -470,7 +385,8 @@ document.addEventListener("DOMContentLoaded", function () { Batal

- ${submitState.canSubmit ? "" : `

${submitState.message}

`} + ${submitState.canSubmit ? '' : `

${submitState.message}

`} +

`; @@ -484,16 +400,24 @@ document.addEventListener("DOMContentLoaded", function () { const form = tpsContentContainer.querySelector("form"); if (!form) return; - const previewKedatangan = form.querySelector(".tps-preview-kedatangan"); - if (previewKedatangan && tps.fotoKedatangan.length > 0) { - renderStoredPhotos(tps.fotoKedatangan, previewKedatangan); - } + const previewKedatangan = form.querySelector('.tps-preview-kedatangan'); + if (previewKedatangan) { + if (tps.fotoKedatangan.length > 0) { + renderStoredPhotos(tps.fotoKedatangan, previewKedatangan); + } else if (tps.fotoKedatanganUploaded && tps.fotoKedatanganFileNames.length > 0) { + renderServerImagePreview(tps.fotoKedatanganFileNames, previewKedatangan); + } + } - const previewPetugas = form.querySelector(".tps-preview-petugas"); - if (previewPetugas && tps.fotoPetugas.length > 0) { - renderStoredPhotos(tps.fotoPetugas, previewPetugas); + const previewPetugas = form.querySelector('.tps-preview-petugas'); + if (previewPetugas) { + if (tps.fotoPetugas.length > 0) { + renderStoredPhotos(tps.fotoPetugas, previewPetugas); + } else if (tps.fotoPetugasUploaded && tps.fotoPetugasFileNames.length > 0) { + renderServerImagePreview(tps.fotoPetugasFileNames, previewPetugas); + } + } } - } function renderStoredPhotos(files, container) { container.innerHTML = ""; @@ -514,10 +438,6 @@ document.addEventListener("DOMContentLoaded", function () {
Preview ${index + 1}
-
-

${index + 1}. ${safeName}

-

${fileSize > 0 ? formatFileSize(fileSize) : "Tersimpan di server"}

-
`; const img = item.querySelector(".preview-multi-image"); @@ -539,28 +459,32 @@ document.addEventListener("DOMContentLoaded", function () { const namaPetugasInput = form.querySelector(".tps-nama-petugas"); const btnAddTimbangan = form.querySelector(".tps-btn-add-timbangan"); - fotoKedatanganInput.addEventListener("change", function () { - tps.fotoKedatangan = Array.from(this.files); - tps.fotoKedatanganUploaded = false; - updateWaktuKedatangan(); - updateMultiPreview(this, form.querySelector(".tps-preview-kedatangan")); - refreshKedatanganUploadState(form); - saveState(); - }); + fotoKedatanganInput.addEventListener('change', function() { + tps.fotoKedatangan = Array.from(this.files); + tps.fotoKedatanganUploaded = false; + updateWaktuKedatangan(); + updateMultiPreview(this, form.querySelector('.tps-preview-kedatangan')); + refreshKedatanganUploadState(form); + scheduleAutoSave(); + }); - fotoPetugasInput.addEventListener("change", function () { - tps.fotoPetugas = Array.from(this.files); - tps.fotoPetugasUploaded = false; - updateMultiPreview(this, form.querySelector(".tps-preview-petugas")); - refreshPetugasUploadState(form); - saveState(); - }); + fotoPetugasInput.addEventListener('change', function() { + tps.fotoPetugas = Array.from(this.files); + tps.fotoPetugasUploaded = false; + updateMultiPreview(this, form.querySelector('.tps-preview-petugas')); + refreshPetugasUploadState(form); + scheduleAutoSave(); + }); - namaPetugasInput.addEventListener("input", function () { - tps.namaPetugas = this.value; - refreshPetugasUploadState(form); - saveState(); - }); + namaPetugasInput.addEventListener('input', function() { + tps.namaPetugas = this.value; + refreshPetugasUploadState(form); + }); + + namaPetugasInput.addEventListener('blur', function() { + tps.namaPetugas = this.value; + scheduleAutoSave(); + }); btnAddTimbangan.addEventListener("click", function () { createTimbanganItem(form.querySelector(".tps-timbangan-repeater")); @@ -695,10 +619,6 @@ document.addEventListener("DOMContentLoaded", function () {
Preview ${index + 1}
-
-

${index + 1}. ${safeName}

-

${formatFileSize(file.size)}

-
`; const img = item.querySelector(".preview-multi-image"); @@ -1026,17 +946,17 @@ document.addEventListener("DOMContentLoaded", function () { saveState(); } - function getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight) { - if (!hasFile) { - return '

Pilih foto timbangan terlebih dahulu

'; - } - - if (isUploaded) { - return ` + function getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight) { + if (isUploaded) { + return `
✓ Foto timbangan sudah diupload

Jika ingin revisi, pilih file baru diatas. Status upload akan tereset otomatis.

`; - } + } + + if (!hasFile) { + return '

Pilih foto timbangan terlebih dahulu

'; + } if (!hasValidWeight) { return ` @@ -1114,26 +1034,19 @@ document.addEventListener("DOMContentLoaded", function () { refreshSubmitButtonState(form); } - function isTimbanganItemReady(timbanganItem) { - const weight = - timbanganItem?.berat && timbanganItem.berat.length > 0 - ? timbanganItem.berat[0] - : 0; - return ( - hasStoredPhoto(timbanganItem?.file) && - Boolean(timbanganItem?.uploaded) && - weight > 0 - ); - } - - function getSubmitState(tps) { - if (!tps.fotoKedatangan.length || !tps.fotoKedatanganUploaded) { - return { - canSubmit: false, - message: "Silakan upload foto kedatangan terlebih dahulu.", - }; + function isTimbanganItemReady(timbanganItem) { + const weight = timbanganItem?.berat && timbanganItem.berat.length > 0 ? timbanganItem.berat[0] : 0; + const hasFile = Boolean(timbanganItem?.file) || Boolean(timbanganItem?.fotoFileName); + return hasFile && Boolean(timbanganItem?.uploaded) && weight > 0; } + function getSubmitState(tps) { + if (!tps.fotoKedatanganUploaded) { + if (!tps.fotoKedatangan.length) + return { canSubmit: false, message: 'Silakan pilih dan upload foto kedatangan.' }; + return { canSubmit: false, message: 'Silakan upload foto kedatangan terlebih dahulu.' }; + } + if (!tps.timbangan.length) { return { canSubmit: false, @@ -1149,12 +1062,11 @@ document.addEventListener("DOMContentLoaded", function () { }; } - if (!tps.fotoPetugas.length || !tps.fotoPetugasUploaded) { - return { - canSubmit: false, - message: "Silakan upload foto petugas terlebih dahulu", - }; - } + if (!tps.fotoPetugasUploaded) { + if (!tps.fotoPetugas.length) + return { canSubmit: false, message: 'Silakan pilih dan upload foto petugas.' }; + return { canSubmit: false, message: 'Silakan upload foto petugas terlebih dahulu.' }; + } if (!tps.namaPetugas.trim()) { return { @@ -1200,21 +1112,18 @@ document.addEventListener("DOMContentLoaded", function () { const stateContainer = item.querySelector(".timbangan-upload-state"); if (!stateContainer) return; - const repeater = item.parentElement; - const itemIndex = repeater - ? Array.from(repeater.children).indexOf(item) - : -1; - const tps = tpsData[activeTpsIndex]; - const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null; - const fileInput = item.querySelector(".input-foto-timbangan"); - const hasFile = hasStoredPhoto(currentData?.file || fileInput?.files?.[0]); - const isUploaded = Boolean(currentData?.uploaded); - const weightInputValue = item.querySelector(".input-berat-timbangan-value"); - const currentWeight = - currentData?.berat && currentData.berat.length > 0 - ? currentData.berat[0] - : parseWeightInput(weightInputValue?.value || "0"); - const hasValidWeight = currentWeight > 0; + const repeater = item.parentElement; + const itemIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1; + const tps = tpsData[activeTpsIndex]; + const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null; + const fileInput = item.querySelector('.input-foto-timbangan'); + const hasFile = Boolean(currentData?.file || fileInput?.files?.[0] || currentData?.fotoFileName); + const isUploaded = Boolean(currentData?.uploaded); + const weightInputValue = item.querySelector('.input-berat-timbangan-value'); + const currentWeight = currentData?.berat && currentData.berat.length > 0 + ? currentData.berat[0] + : parseWeightInput(weightInputValue?.value || '0'); + const hasValidWeight = currentWeight > 0; stateContainer.innerHTML = getTimbanganUploadStateMarkup( hasFile, @@ -1258,24 +1167,12 @@ document.addEventListener("DOMContentLoaded", function () { "timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50"; item.dataset.photoNumber = photoNumber; - const weight = existingData - ? existingData.berat && existingData.berat.length > 0 - ? existingData.berat[0] - : 0 - : 0; - const jenisSampah = existingData - ? existingData.jenisSampah && existingData.jenisSampah.length > 0 - ? existingData.jenisSampah[0] - : DEFAULT_JENIS - : DEFAULT_JENIS; - const hasFile = hasStoredPhoto(existingData && existingData.file); - const isUploaded = Boolean(existingData && existingData.uploaded); - const ocrInfoText = - existingData && existingData.ocrInfo - ? existingData.ocrInfo - : hasFile - ? "OCR: diproses." - : "OCR: belum diproses."; + const weight = existingData ? (existingData.berat && existingData.berat.length > 0 ? existingData.berat[0] : 0) : 0; + const jenisSampah = existingData ? (existingData.jenisSampah && existingData.jenisSampah.length > 0 ? existingData.jenisSampah[0] : DEFAULT_JENIS) : DEFAULT_JENIS; + const hasFileBlob = Boolean(existingData?.file); + const hasFile = Boolean(existingData?.file || existingData?.fotoFileName); + const isUploaded = Boolean(existingData?.uploaded); + const ocrInfoText = existingData && existingData.ocrInfo ? existingData.ocrInfo : (hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.'); item.innerHTML = `
@@ -1283,7 +1180,7 @@ document.addEventListener("DOMContentLoaded", function () {
-
+
Preview foto timbangan
@@ -1315,26 +1212,21 @@ document.addEventListener("DOMContentLoaded", function () { const jenisSampahSelect = item.querySelector(".input-jenis-sampah"); const removeBtn = item.querySelector(".btn-remove-timbangan"); - if (existingData && hasStoredPhoto(existingData.file)) { - const existingPhoto = resolveStoredPhoto(existingData.file); - const photoUrl = getStoredPhotoUrl(existingPhoto); - previewImage.src = photoUrl; - previewWrap.classList.remove("hidden"); - if (isBrowserFile(existingPhoto)) { - previewImage.onload = function () { - URL.revokeObjectURL(photoUrl); - }; - } - } + if (existingData && existingData.file) { + const localUrl = URL.createObjectURL(existingData.file); + previewImage.src = localUrl; + previewWrap.classList.remove('hidden'); + previewImage.onload = function() { URL.revokeObjectURL(localUrl); }; + } else if (existingData && existingData.fotoFileName && existingData.fotoFileName.startsWith('/')) { + previewImage.src = existingData.fotoFileName; + previewWrap.classList.remove('hidden'); + } - fileInput.addEventListener("change", async function () { - if (fileInput.files && fileInput.files[0]) { - const originalFile = fileInput.files[0]; - const photoNumber = Number( - item.dataset.photoNumber || - Array.from(repeater.children).indexOf(item) + 1, - ); - const watermarkedFile = await applyWatermark(originalFile, photoNumber); + fileInput.addEventListener('change', async function() { + if (fileInput.files && fileInput.files[0]) { + const originalFile = fileInput.files[0]; + const photoNumber = Number(item.dataset.photoNumber || (Array.from(repeater.children).indexOf(item) + 1)); + const watermarkedFile = await applyWatermark(originalFile, photoNumber); const dataTransfer = new DataTransfer(); dataTransfer.items.add(watermarkedFile); @@ -1375,41 +1267,44 @@ document.addEventListener("DOMContentLoaded", function () { if (form) refreshSubmitButtonState(form); }); - weightInputDisplay.addEventListener("blur", function () { - const parsed = parseWeightInput(this.value); - if (parsed > 0) { - this.value = formatWeightDisplay(parsed); - weightInputValue.value = parsed.toFixed(2); - } else { - this.value = ""; - weightInputValue.value = "0.00"; - } - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - refreshTimbanganUploadState(item); - const form = tpsContentContainer.querySelector("form"); - if (form) refreshSubmitButtonState(form); - }); + weightInputDisplay.addEventListener('blur', function() { + const parsed = parseWeightInput(this.value); + if (parsed > 0) { + this.value = formatWeightDisplay(parsed); + weightInputValue.value = parsed.toFixed(2); + } else { + this.value = ''; + weightInputValue.value = '0.00'; + } + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + refreshTimbanganUploadState(item); + const form = tpsContentContainer.querySelector('form'); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); + }); - jenisSampahSelect.addEventListener("change", function () { - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - const form = tpsContentContainer.querySelector("form"); - if (form) refreshSubmitButtonState(form); - }); + jenisSampahSelect.addEventListener('change', function() { + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + const form = tpsContentContainer.querySelector('form'); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); + }); - removeBtn.addEventListener("click", function () { - item.remove(); - const form = tpsContentContainer.querySelector("form"); - const rep = form ? form.querySelector(".tps-timbangan-repeater") : null; - if (rep) { - renumberTimbanganItems(rep); - if (rep.children.length === 0) createTimbanganItem(rep); - } - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - if (form) refreshSubmitButtonState(form); - }); + removeBtn.addEventListener('click', function() { + item.remove(); + const form = tpsContentContainer.querySelector('form'); + const rep = form ? form.querySelector('.tps-timbangan-repeater') : null; + if (rep) { + renumberTimbanganItems(rep); + if (rep.children.length === 0) createTimbanganItem(rep); + } + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); + }); repeater.appendChild(item); refreshTimbanganUploadState(item); @@ -1425,28 +1320,25 @@ document.addEventListener("DOMContentLoaded", function () { const items = repeater.querySelectorAll(".timbangan-item"); const previousTimbangan = [...tps.timbangan]; - tps.timbangan = []; - items.forEach((item, index) => { - const fileInput = item.querySelector(".input-foto-timbangan"); - const weightValue = item.querySelector(".input-berat-timbangan-value"); - const weight = parseWeightInput(weightValue.value); - const jenisSampah = item.querySelector(".input-jenis-sampah").value; - const ocrInfo = - item.querySelector(".input-ocr-info")?.textContent || - "OCR: belum diproses."; - - tps.timbangan.push({ - file: fileInput.files[0] || previousTimbangan[index]?.file || null, - berat: [weight], - jenisSampah: [jenisSampah], - lokasiAngkut: [], - uploaded: previousTimbangan[index]?.uploaded ?? false, - ocrInfo, - }); - }); - - saveState(); - } + tps.timbangan = []; + items.forEach((item, index) => { + const fileInput = item.querySelector('.input-foto-timbangan'); + const weightValue = item.querySelector('.input-berat-timbangan-value'); + const weight = parseWeightInput(weightValue.value); + const jenisSampah = item.querySelector('.input-jenis-sampah').value; + const ocrInfo = item.querySelector('.input-ocr-info')?.textContent || 'OCR: belum diproses.'; + + tps.timbangan.push({ + file: fileInput.files[0] || previousTimbangan[index]?.file || null, + fotoFileName: previousTimbangan[index]?.fotoFileName || '', + berat: [weight], + jenisSampah: [jenisSampah], + lokasiAngkut: [], + uploaded: previousTimbangan[index]?.uploaded ?? false, + ocrInfo + }); + }); + } function buildSubmitFormData(tps) { const formData = new FormData(); @@ -1493,47 +1385,19 @@ document.addEventListener("DOMContentLoaded", function () { return formData; } - function uploadSingleFotoTimbangan(itemIndex, targetItem = null) { - const tps = tpsData[activeTpsIndex]; - if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) { - alert("Belum ada foto timbangan yang dipilih!"); - return; - } - const weight = - tps.timbangan[itemIndex].berat && - tps.timbangan[itemIndex].berat.length > 0 - ? tps.timbangan[itemIndex].berat[0] - : 0; - if (weight <= 0) { - alert( - "Berat belum valid. Isi manual dulu sebelum upload foto timbangan.", - ); - return; - } - - const _ext = ( - tps.timbangan[itemIndex].file.name.split(".").pop() || "jpg" - ).toLowerCase(); - const _jenis = ( - tps.timbangan[itemIndex].jenisSampah[0] || "residu" - ).toLowerCase(); - const _beratStr = parseFloat(weight.toFixed(2)) - .toString() - .replace(".", "_"); - const _newName = `timbangan${itemIndex + 1}-${_jenis}-${_beratStr}.${_ext}`; - tps.timbangan[itemIndex].file = new File( - [tps.timbangan[itemIndex].file], - _newName, - { - type: tps.timbangan[itemIndex].file.type, - lastModified: tps.timbangan[itemIndex].file.lastModified, - }, - ); - - alert( - `Upload foto timbangan #${itemIndex + 1}\nBerat: ${formatWeightDisplay(weight)} kg\n(Implementasi upload ke server)`, - ); - tps.timbangan[itemIndex].uploaded = true; + async function uploadSingleFotoTimbangan(itemIndex, targetItem = null) { + const tps = tpsData[activeTpsIndex]; + if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) { + showToast('Belum ada foto timbangan yang dipilih!', 'error'); + return; + } + const weight = tps.timbangan[itemIndex].berat && tps.timbangan[itemIndex].berat.length > 0 + ? tps.timbangan[itemIndex].berat[0] + : 0; + if (weight <= 0) { + showToast('Isi berat manual dulu sebelum upload foto timbangan.', 'error'); + return; + } if (!targetItem) { const form = tpsContentContainer.querySelector("form"); @@ -1546,78 +1410,212 @@ document.addEventListener("DOMContentLoaded", function () { targetItem = items[itemIndex] || null; } - if (targetItem) { - refreshTimbanganUploadState(targetItem); + const uploadBtn = targetItem ? targetItem.querySelector('.btn-upload-timbangan') : null; + if (uploadBtn) { uploadBtn.disabled = true; uploadBtn.textContent = 'Mengupload...'; } + + const formData = new FormData(); + formData.append('FotoTimbangan', tps.timbangan[itemIndex].file); + formData.append('DraftKey', draftRequestKey); + formData.append('SpjDetailId', tps.spjDetailId || ''); + formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); + formData.append('ItemIndex', itemIndex); + formData.append('JenisSampah', (tps.timbangan[itemIndex].jenisSampah && tps.timbangan[itemIndex].jenisSampah[0]) || DEFAULT_JENIS); + formData.append('Berat', weight.toFixed(2)); + + try { + const res = await fetch('/upst/detail-penjemputan/upload-foto-timbangan-non-tps', { method: 'POST', body: formData }); + const data = await res.json(); + if (res.ok && data.success) { + tps.timbangan[itemIndex].uploaded = true; + tps.timbangan[itemIndex].fotoFileName = data.fileUrl || data.fileName || ''; + const fotoInputTimb = targetItem?.querySelector('.input-foto-timbangan'); + if (fotoInputTimb) fotoInputTimb.value = ''; + showToast(data.message || `Foto timbangan #${itemIndex + 1} berhasil diupload.`, 'success'); + } else { + showToast(data.message || 'Gagal upload foto timbangan.', 'error'); + if (uploadBtn) { uploadBtn.disabled = false; uploadBtn.textContent = 'Upload Foto Timbangan Ini'; } + } + } catch { + showToast('Koneksi gagal saat upload foto timbangan.', 'error'); + if (uploadBtn) { uploadBtn.disabled = false; uploadBtn.textContent = 'Upload Foto Timbangan Ini'; } + } + + if (targetItem) refreshTimbanganUploadState(targetItem); + syncTimbanganToTpsData(); + const form = tpsContentContainer.querySelector('form'); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); } - syncTimbanganToTpsData(); - const form = tpsContentContainer.querySelector("form"); - if (form) refreshSubmitButtonState(form); - saveState(); - } + async function uploadFotoKedatangan() { + const tps = tpsData[activeTpsIndex]; + if (tps.fotoKedatangan.length === 0) { + showToast('Belum ada foto kedatangan yang dipilih!', 'error'); + return; + } - function uploadFotoKedatangan() { - const tps = tpsData[activeTpsIndex]; - if (tps.fotoKedatangan.length === 0) { - alert("Belum ada foto kedatangan yang dipilih!"); - return; + const form = tpsContentContainer.querySelector('form'); + const btn = form ? form.querySelector('.tps-btn-upload-kedatangan') : null; + if (btn) { btn.disabled = true; btn.textContent = 'Mengupload...'; } + + const formData = new FormData(); + tps.fotoKedatangan.forEach(f => formData.append('FotoKedatangan', f)); + formData.append('DraftKey', draftRequestKey); + formData.append('SpjDetailId', tps.spjDetailId || ''); + formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); + formData.append('WaktuKedatangan', tps.waktuKedatangan || ''); + formData.append('Latitude', tps.latitude || ''); + formData.append('Longitude', tps.longitude || ''); + formData.append('AlamatJalan', tps.alamatJalan || ''); + + try { + const res = await fetch('/upst/detail-penjemputan/upload-foto-kedatangan-non-tps', { method: 'POST', body: formData }); + const data = await res.json(); + if (res.ok && data.success) { + tps.fotoKedatanganUploaded = true; + tps.fotoKedatanganFileNames = data.fileUrls || data.fileNames || []; + showToast(data.message || 'Foto kedatangan berhasil diupload.', 'success'); + if (form) { + const fotoInput = form.querySelector('.tps-foto-kedatangan'); + if (fotoInput) fotoInput.value = ''; + refreshKedatanganUploadState(form); + } + scheduleAutoSave(); + } else { + showToast(data.message || 'Gagal upload foto kedatangan.', 'error'); + if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`; } + } + } catch { + showToast('Koneksi gagal saat upload foto kedatangan.', 'error'); + if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`; } + } } - alert( - `Upload ${tps.fotoKedatangan.length} foto kedatangan\n(Implementasi upload ke server)`, - ); - tps.fotoKedatanganUploaded = true; - const form = tpsContentContainer.querySelector("form"); - if (form) refreshKedatanganUploadState(form); - saveState(); - } - function uploadFotoPetugas() { - const tps = tpsData[activeTpsIndex]; - if (tps.fotoPetugas.length === 0) { - alert("Belum ada foto petugas yang dipilih!"); - return; + async function uploadFotoPetugas() { + const tps = tpsData[activeTpsIndex]; + if (tps.fotoPetugas.length === 0) { + showToast('Belum ada foto petugas yang dipilih!', 'error'); + return; + } + if (!tps.namaPetugas.trim()) { + showToast('Nama petugas wajib diisi sebelum upload foto petugas!', 'error'); + return; + } + + const form = tpsContentContainer.querySelector('form'); + const btn = form ? form.querySelector('.tps-btn-upload-petugas:not([disabled])') : null; + if (btn) { btn.disabled = true; btn.textContent = 'Mengupload...'; } + + const formData = new FormData(); + tps.fotoPetugas.forEach(f => formData.append('FotoPetugas', f)); + formData.append('DraftKey', draftRequestKey); + formData.append('SpjDetailId', tps.spjDetailId || ''); + formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); + formData.append('NamaPetugas', tps.namaPetugas); + + try { + const res = await fetch('/upst/detail-penjemputan/upload-foto-petugas-non-tps', { method: 'POST', body: formData }); + const data = await res.json(); + if (res.ok && data.success) { + tps.fotoPetugasUploaded = true; + tps.fotoPetugasFileNames = data.fileUrls || data.fileNames || []; + showToast(data.message || 'Foto petugas berhasil diupload.', 'success'); + if (form) { + const fotoInput = form.querySelector('.tps-foto-petugas'); + if (fotoInput) fotoInput.value = ''; + refreshPetugasUploadState(form); + } + scheduleAutoSave(); + } else { + showToast(data.message || 'Gagal upload foto petugas.', 'error'); + if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`; } + } + } catch { + showToast('Koneksi gagal saat upload foto petugas.', 'error'); + if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`; } + } } - if (!tps.namaPetugas.trim()) { - alert("Nama petugas wajib diisi sebelum upload foto petugas!"); - return; - } - alert( - `Upload ${tps.fotoPetugas.length} foto petugas untuk ${tps.name}\n(Implementasi upload ke server)`, - ); - tps.fotoPetugasUploaded = true; - const form = tpsContentContainer.querySelector("form"); - if (form) refreshPetugasUploadState(form); - saveState(); - } - function submitTpsData() { - const tps = tpsData[activeTpsIndex]; - const submitState = getSubmitState(tps); - if (!submitState.canSubmit) return alert(submitState.message); + async function submitTpsData() { + const tps = tpsData[activeTpsIndex]; + const submitState = getSubmitState(tps); + if (!submitState.canSubmit) { showToast(submitState.message, 'error'); return; } - alert( - `Validasi OK (${tps.name}).\n- Organik: ${formatWeightDisplay(tps.totalOrganik)} kg\n- Anorganik: ${formatWeightDisplay(tps.totalAnorganik)} kg\n- Residu: ${formatWeightDisplay(tps.totalResidu)} kg\n- Total: ${formatWeightDisplay(tps.totalTimbangan)} kg\n- Petugas: ${tps.namaPetugas}`, - ); - tps.submitted = true; - saveState(); - clearState(); + const form = tpsContentContainer.querySelector('form'); + const submitBtn = form ? form.querySelector('button[type="submit"]') : null; + if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Menyimpan...'; } - /* const formData = buildSubmitFormData(tps); - fetch('/upst/detail-penjemputan', { method: 'POST', body: formData }) - .then(response => { - if (response.ok) { - clearState(); - } - }); - */ - } + + try { + const res = await fetch('/upst/detail-penjemputan', { method: 'POST', body: formData }); + if (res.ok || res.redirected) { + tps.submitted = true; + if (draftRequestKey) { + await fetch(`/upst/detail-penjemputan/delete-draft-non-tps?draftKey=${encodeURIComponent(draftRequestKey)}`, { method: 'DELETE' }); + } + showToast('Data berhasil disimpan!', 'success'); + setTimeout(() => { window.location.href = '/upst/detail-penjemputan/detail-selesai-tanpa-tps'; }, 1500); + } else { + const txt = await res.text(); + showToast('Gagal submit: ' + (txt || res.statusText), 'error'); + if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Submit'; } + } + } catch { + showToast('Koneksi gagal saat submit.', 'error'); + if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Submit'; } + } + } + + function showToast(message, type = 'info') { + let container = document.getElementById('espj-toast-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'espj-toast-container'; + container.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;'; + document.body.appendChild(container); + } + const toast = document.createElement('div'); + const bg = type === 'success' ? '#16a34a' : type === 'error' ? '#dc2626' : '#2563eb'; + toast.style.cssText = `background:${bg};color:#fff;padding:10px 18px;border-radius:16px;font-size:13px;font-weight:600;box-shadow:0 4px 24px rgba(0,0,0,.18);opacity:0;transition:opacity .25s;max-width:320px;text-align:center;`; + toast.textContent = message; + container.appendChild(toast); + requestAnimationFrame(() => { toast.style.opacity = '1'; }); + setTimeout(() => { + toast.style.opacity = '0'; + setTimeout(() => toast.remove(), 300); + }, 3200); + } const nomorSpjEl = document.querySelector(".text-gray-600.font-mono"); if (nomorSpjEl && nomorSpjEl.textContent) { nomorSpj = nomorSpjEl.textContent.trim(); } - initializeLocation(); - loadDetailData(); + initializeLocation(); + await loadDetailData(); + await loadDraftFromServer(); + renderTpsForm(); + + function renderServerImagePreview(fileUrls, container) { + container.innerHTML = ''; + if (!fileUrls || fileUrls.length === 0) return; + container.className = 'space-y-2'; + fileUrls.forEach((url, index) => { + const item = document.createElement('div'); + item.className = 'rounded-xl border border-gray-200 overflow-hidden bg-black'; + const isUrl = typeof url === 'string' && (url.startsWith('/') || url.startsWith('http')); + if (isUrl) { + item.innerHTML = ` +
+ Foto ${index + 1} +
+ `; + } else { + item.className = 'rounded-xl border border-green-200 bg-green-50 h-44 flex items-center justify-center'; + item.innerHTML = `

✓ Foto ${index + 1}

`; + } + container.appendChild(item); + }); + } }); diff --git a/wwwroot/driver/js/detail-penjemputan-tps.js b/wwwroot/driver/js/detail-penjemputan-tps.js index 7040e19..75675e0 100644 --- a/wwwroot/driver/js/detail-penjemputan-tps.js +++ b/wwwroot/driver/js/detail-penjemputan-tps.js @@ -1,36 +1,25 @@ const DetailPenjemputan = (function () { "use strict"; - const CONFIG = { - OCR_AREAS: [ - { - id: "A", - x: 0.34, - y: 0.35, - w: 0.4, - h: 0.11, - color: "border-lime-400 bg-lime-500/15", - }, - { - id: "B", - x: 0.31, - y: 0.33, - w: 0.45, - h: 0.14, - color: "border-amber-300 bg-amber-400/10", - }, - { - id: "C", - x: 0.29, - y: 0.31, - w: 0.49, - h: 0.17, - color: "border-cyan-300 bg-cyan-400/10", - }, - ], - JENIS_SAMPAH: ["Organik", "Anorganik", "Residu"], - DEFAULT_JENIS: "Residu", - }; + const CONFIG = { + OCR_AREAS: [ + { id: 'A', x: 0.34, y: 0.35, w: 0.40, h: 0.11, color: 'border-lime-400 bg-lime-500/15' }, + { id: 'B', x: 0.31, y: 0.33, w: 0.45, h: 0.14, color: 'border-amber-300 bg-amber-400/10' }, + { id: 'C', x: 0.29, y: 0.31, w: 0.49, h: 0.17, color: 'border-cyan-300 bg-cyan-400/10' } + ], + JENIS_SAMPAH: ['Organik', 'Anorganik', 'Residu'], + DEFAULT_JENIS: 'Residu' + }; + + const ENDPOINTS = { + saveDraft: '/upst/detail-penjemputan/save-draft', + loadDraft: '/upst/detail-penjemputan/load-draft', + deleteDraft: '/upst/detail-penjemputan/delete-draft', + uploadKedatangan: '/upst/detail-penjemputan/upload-foto-kedatangan', + uploadTimbangan: '/upst/detail-penjemputan/upload-foto-timbangan', + uploadPetugas: '/upst/detail-penjemputan/upload-foto-petugas', + submit: '/upst/detail-penjemputan' + }; let state = { activeTpsIndex: 0, @@ -364,11 +353,13 @@ const DetailPenjemputan = (function () { totalResiduDisplay: null, }; - function init(tpsList) { - loadState(); - initElements(); - initializeLocation(tpsList); - } + let autoSaveTimer = null; + let autoSaveStatusEl = null; + + async function init(tpsList) { + initElements(); + await initializeLocation(tpsList); + } function initElements() { elements.grandTotalDisplay = document.getElementById( @@ -395,42 +386,145 @@ const DetailPenjemputan = (function () { } } - function initializeLocation(tpsList) { - state.availableTpsList = tpsList || []; - if (elements.tpsSelectionContainer) { - elements.tpsSelectionContainer.style.display = "none"; + function buildDraftKey(tps) { + const spjDetailId = (tps?.spjDetailId || '').trim(); + const lokasiAngkutId = (tps?.lokasiAngkutId || '').trim(); + if (!spjDetailId && !lokasiAngkutId) return ''; + return `tps-${spjDetailId || 'no-spj'}-${lokasiAngkutId || 'no-lokasi'}`.replace(/[^a-zA-Z0-9_-]/g, ''); } - if (state.tpsData.length > 0) { - state.selectedTpsList = state.tpsData.map((item) => item.name); - state.activeTpsIndex = Math.min( - state.activeTpsIndex, - Math.max(state.tpsData.length - 1, 0), - ); - elements.tpsTabsContainer.style.display = "block"; - - if (state.tpsData.length === 1) { - renderSingleForm(); - } else { - renderTabs(); - renderTpsForm(); - } - - updateAllTotals(); - return; + function getActiveTps() { + return state.tpsData[state.activeTpsIndex] || null; } - if (state.availableTpsList.length === 0) { - state.selectedTpsList = ["1 Lokasi TPS"]; - initializeTpsData(state.selectedTpsList); - elements.tpsTabsContainer.style.display = "block"; - renderSingleForm(); - return; + function getAutoSaveStatusEl() { + if (!autoSaveStatusEl || !document.body.contains(autoSaveStatusEl)) { + autoSaveStatusEl = document.getElementById('auto-save-status'); + } + return autoSaveStatusEl; } - state.selectedTpsList = [...state.availableTpsList]; - initializeTpsData(state.selectedTpsList); - elements.tpsTabsContainer.style.display = "block"; + function showAutoSaveStatus(msg, isOk = false) { + const statusEl = getAutoSaveStatusEl(); + if (!statusEl) return; + statusEl.textContent = msg; + statusEl.className = isOk + ? 'text-[11px] text-green-600 text-center font-medium transition-opacity' + : 'text-[11px] text-amber-500 text-center font-medium transition-opacity'; + statusEl.style.opacity = '1'; + if (isOk) setTimeout(() => { statusEl.style.opacity = '0'; }, 2500); + } + + function scheduleAutoSave() { + clearTimeout(autoSaveTimer); + showAutoSaveStatus('menyimpan...'); + autoSaveTimer = setTimeout(autoSaveDraft, 1000); + } + + async function autoSaveDraft() { + const tps = getActiveTps(); + if (!tps || !tps.draftKey) return; + + const payload = { + draftKey: tps.draftKey, + lokasiAngkutId: tps.lokasiAngkutId || '', + spjDetailId: tps.spjDetailId || '', + latitude: tps.latitude || '', + longitude: tps.longitude || '', + alamatJalan: tps.alamatJalan || '', + waktuKedatangan: tps.waktuKedatangan || '', + fotoKedatanganFileNames: tps.fotoKedatanganFileNames || [], + fotoKedatanganUploaded: tps.fotoKedatanganUploaded || false, + timbangan: (tps.timbangan || []).map(item => ({ + berat: item.weight || 0, + jenisSampah: item.jenisSampah || CONFIG.DEFAULT_JENIS, + fotoFileName: item.fotoFileName || '', + uploaded: item.uploaded || false, + ocrInfo: item.ocrInfo || '' + })), + totalOrganik: tps.totalOrganik || 0, + totalAnorganik: tps.totalAnorganik || 0, + totalResidu: tps.totalResidu || 0, + totalTimbangan: tps.totalTimbangan || 0, + fotoPetugasFileNames: tps.fotoPetugasFileNames || [], + fotoPetugasUploaded: tps.fotoPetugasUploaded || false, + namaPetugas: tps.namaPetugas || '' + }; + + try { + const res = await fetch(ENDPOINTS.saveDraft, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + const data = await res.json(); + showAutoSaveStatus(data.success ? '✓ Draft tersimpan' : '✗ Gagal simpan', data.success); + } catch (_) { + showAutoSaveStatus('✗ Gagal simpan draft'); + } + } + + async function loadDraftForTps(tps) { + if (!tps || !tps.draftKey) return; + + try { + const res = await fetch(`${ENDPOINTS.loadDraft}?draftKey=${encodeURIComponent(tps.draftKey)}`); + if (!res.ok) return; + const data = await res.json(); + if (!data.success || !data.hasDraft || !data.draft) return; + + const draft = data.draft; + tps.lokasiAngkutId = draft.lokasiAngkutId || tps.lokasiAngkutId; + tps.spjDetailId = draft.spjDetailId || tps.spjDetailId; + tps.latitude = draft.latitude || ''; + tps.longitude = draft.longitude || ''; + tps.alamatJalan = draft.alamatJalan || ''; + tps.waktuKedatangan = draft.waktuKedatangan || ''; + tps.fotoKedatanganFileNames = draft.fotoKedatanganFileNames || []; + tps.fotoKedatanganUploaded = draft.fotoKedatanganUploaded || false; + tps.fotoPetugasFileNames = draft.fotoPetugasFileNames || []; + tps.fotoPetugasUploaded = draft.fotoPetugasUploaded || false; + tps.namaPetugas = draft.namaPetugas || ''; + tps.totalOrganik = draft.totalOrganik || 0; + tps.totalAnorganik = draft.totalAnorganik || 0; + tps.totalResidu = draft.totalResidu || 0; + tps.totalTimbangan = draft.totalTimbangan || 0; + tps.timbangan = (draft.timbangan || []).map(item => ({ + file: null, + fotoFileName: item.fotoFileName || '', + weight: item.berat || 0, + jenisSampah: item.jenisSampah || CONFIG.DEFAULT_JENIS, + uploaded: item.uploaded || false, + ocrInfo: item.ocrInfo || 'OCR: belum diproses.' + })); + } catch (error) { + console.warn('Gagal memuat draft TPS:', error); + } + } + + async function loadDraftsForAllTps() { + await Promise.all(state.tpsData.map(loadDraftForTps)); + } + + async function initializeLocation(tpsList) { + state.availableTpsList = tpsList || []; + if (elements.tpsSelectionContainer) { + elements.tpsSelectionContainer.style.display = 'none'; + } + + if (state.availableTpsList.length === 0) { + state.selectedTpsList = ['1 Lokasi TPS']; + initializeTpsData(state.selectedTpsList); + await loadDraftsForAllTps(); + elements.tpsTabsContainer.style.display = 'block'; + renderSingleForm(); + return; + } + + state.selectedTpsList = [...state.availableTpsList]; + initializeTpsData(state.selectedTpsList); + await loadDraftsForAllTps(); + elements.tpsTabsContainer.style.display = 'block'; if (state.selectedTpsList.length === 1) { renderSingleForm(); @@ -440,39 +534,33 @@ const DetailPenjemputan = (function () { } } - function initializeTpsData(tpsNames) { - state.tpsData = tpsNames.map((tpsItem, index) => ({ - name: - typeof tpsItem === "string" - ? tpsItem - : tpsItem?.name || tpsItem?.Name || `TPS ${index + 1}`, - index: index, - lokasiAngkutId: - typeof tpsItem === "string" - ? "" - : tpsItem?.lokasiAngkutId || tpsItem?.LokasiAngkutID || "", - spjDetailId: - typeof tpsItem === "string" - ? "" - : tpsItem?.spjDetailId || tpsItem?.SpjDetailID || "", - latitude: "", - longitude: "", - alamatJalan: "", - waktuKedatangan: "", - fotoKedatangan: [], - fotoKedatanganUploaded: false, - timbangan: [], - totalOrganik: 0, - totalAnorganik: 0, - totalResidu: 0, - totalTimbangan: 0, - fotoPetugas: [], - fotoPetugasUploaded: false, - namaPetugas: "", - submitted: false, - })); - state.hasRequestedLocation = new Array(tpsNames.length).fill(false); - } + function initializeTpsData(tpsNames) { + state.tpsData = tpsNames.map((tpsItem, index) => ({ + name: typeof tpsItem === 'string' ? tpsItem : (tpsItem?.name || tpsItem?.Name || `TPS ${index + 1}`), + index: index, + lokasiAngkutId: typeof tpsItem === 'string' ? '' : (tpsItem?.lokasiAngkutId || tpsItem?.LokasiAngkutID || ''), + spjDetailId: typeof tpsItem === 'string' ? '' : (tpsItem?.spjDetailId || tpsItem?.SpjDetailID || ''), + draftKey: '', + latitude: '', + longitude: '', + alamatJalan: '', + waktuKedatangan: '', + fotoKedatangan: [], + fotoKedatanganFileNames: [], + fotoKedatanganUploaded: false, + timbangan: [], + totalOrganik: 0, + totalAnorganik: 0, + totalResidu: 0, + totalTimbangan: 0, + fotoPetugas: [], + fotoPetugasFileNames: [], + fotoPetugasUploaded: false, + namaPetugas: '', + submitted: false + })).map(tps => ({ ...tps, draftKey: buildDraftKey(tps) })); + state.hasRequestedLocation = new Array(tpsNames.length).fill(false); + } function renderTpsSelection() { elements.tpsCheckboxesContainer.innerHTML = ""; @@ -498,29 +586,28 @@ const DetailPenjemputan = (function () { }); } - function handleConfirmTps() { - const checkboxes = elements.tpsCheckboxesContainer.querySelectorAll( - 'input[type="checkbox"]:checked', - ); - state.selectedTpsList = Array.from(checkboxes).map((cb) => cb.value); - - if (state.selectedTpsList.length === 0) { - alert("Pilih minimal 1 TPS untuk diangkut!"); - return; + async function handleConfirmTps() { + const checkboxes = elements.tpsCheckboxesContainer.querySelectorAll('input[type="checkbox"]:checked'); + state.selectedTpsList = Array.from(checkboxes).map(cb => cb.value); + + if (state.selectedTpsList.length === 0) { + showToast('Pilih minimal 1 TPS untuk diangkut!', 'error'); + return; + } + + initializeTpsData(state.selectedTpsList); + await loadDraftsForAllTps(); + elements.tpsSelectionContainer.style.display = 'none'; + elements.tpsTabsContainer.style.display = 'block'; + + if (state.selectedTpsList.length === 1) { + renderSingleForm(); + } else { + renderTabs(); + renderTpsForm(); + } } - initializeTpsData(state.selectedTpsList); - elements.tpsSelectionContainer.style.display = "none"; - elements.tpsTabsContainer.style.display = "block"; - - if (state.selectedTpsList.length === 1) { - renderSingleForm(); - } else { - renderTabs(); - renderTpsForm(); - } - } - function renderSingleForm() { elements.tpsTabsEl.style.display = "none"; state.activeTpsIndex = 0; @@ -601,7 +688,8 @@ const DetailPenjemputan = (function () { Batal
- ${submitState.canSubmit ? "" : `

${submitState.message}

`} + ${submitState.canSubmit ? '' : `

${submitState.message}

`} +

`; @@ -700,34 +788,39 @@ const DetailPenjemputan = (function () { const namaPetugasInput = form.querySelector(".tps-nama-petugas"); const btnAddTimbangan = form.querySelector(".tps-btn-add-timbangan"); - fotoKedatanganInput.addEventListener("change", function () { - tps.fotoKedatangan = Array.from(this.files); - tps.fotoKedatanganUploaded = false; - updateWaktuKedatangan(); - updateMultiPreview(this, form.querySelector(".tps-preview-kedatangan")); - refreshKedatanganUploadState(form); - saveState(); - }); + fotoKedatanganInput.addEventListener('change', function() { + tps.fotoKedatangan = Array.from(this.files); + tps.fotoKedatanganUploaded = false; + updateWaktuKedatangan(); + updateMultiPreview(this, form.querySelector('.tps-preview-kedatangan')); + refreshKedatanganUploadState(form); + scheduleAutoSave(); + }); - fotoPetugasInput.addEventListener("change", function () { - tps.fotoPetugas = Array.from(this.files); - tps.fotoPetugasUploaded = false; - updateMultiPreview(this, form.querySelector(".tps-preview-petugas")); - refreshPetugasUploadState(form); - saveState(); - }); + fotoPetugasInput.addEventListener('change', function() { + tps.fotoPetugas = Array.from(this.files); + tps.fotoPetugasUploaded = false; + updateMultiPreview(this, form.querySelector('.tps-preview-petugas')); + refreshPetugasUploadState(form); + scheduleAutoSave(); + }); - namaPetugasInput.addEventListener("input", function () { - tps.namaPetugas = this.value; - refreshPetugasUploadState(form); - saveState(); - }); + namaPetugasInput.addEventListener('input', function() { + tps.namaPetugas = this.value; + refreshPetugasUploadState(form); + }); - btnAddTimbangan.addEventListener("click", function () { - createTimbanganItem(form.querySelector(".tps-timbangan-repeater")); - syncTimbanganToTpsData(); - refreshSubmitButtonState(form); - }); + namaPetugasInput.addEventListener('blur', function() { + tps.namaPetugas = this.value; + scheduleAutoSave(); + }); + + btnAddTimbangan.addEventListener('click', function() { + createTimbanganItem(form.querySelector('.tps-timbangan-repeater')); + syncTimbanganToTpsData(); + refreshSubmitButtonState(form); + scheduleAutoSave(); + }); const btnUploadKedatangan = form.querySelector( ".tps-btn-upload-kedatangan", @@ -766,16 +859,24 @@ const DetailPenjemputan = (function () { const form = elements.tpsContentContainer.querySelector("form"); if (!form) return; - const previewKedatangan = form.querySelector(".tps-preview-kedatangan"); - if (previewKedatangan && tps.fotoKedatangan.length > 0) { - renderStoredPhotos(tps.fotoKedatangan, previewKedatangan); - } + const previewKedatangan = form.querySelector('.tps-preview-kedatangan'); + if (previewKedatangan) { + if (tps.fotoKedatangan.length > 0) { + renderStoredPhotos(tps.fotoKedatangan, previewKedatangan); + } else if (tps.fotoKedatanganUploaded && tps.fotoKedatanganFileNames.length > 0) { + renderServerImagePreview(tps.fotoKedatanganFileNames, previewKedatangan); + } + } - const previewPetugas = form.querySelector(".tps-preview-petugas"); - if (previewPetugas && tps.fotoPetugas.length > 0) { - renderStoredPhotos(tps.fotoPetugas, previewPetugas); + const previewPetugas = form.querySelector('.tps-preview-petugas'); + if (previewPetugas) { + if (tps.fotoPetugas.length > 0) { + renderStoredPhotos(tps.fotoPetugas, previewPetugas); + } else if (tps.fotoPetugasUploaded && tps.fotoPetugasFileNames.length > 0) { + renderServerImagePreview(tps.fotoPetugasFileNames, previewPetugas); + } + } } - } function renderStoredPhotos(files, container) { container.innerHTML = ""; @@ -786,21 +887,12 @@ const DetailPenjemputan = (function () { item.className = "rounded-xl border border-gray-200 overflow-hidden bg-black"; - const imageUrl = getStoredPhotoUrl(file); - const safeName = getStoredPhotoName(file, `Foto ${index + 1}`).replace( - /"/g, - """, - ); - const fileSize = getStoredPhotoSize(file); - item.innerHTML = ` + const imageUrl = URL.createObjectURL(file); + item.innerHTML = `
Preview ${index + 1}
-
-

${index + 1}. ${safeName}

-

${fileSize > 0 ? formatFileSize(fileSize) : "Tersimpan di server"}

-
- `; + `; const img = item.querySelector(".preview-multi-image"); if (img && isBrowserFile(resolveStoredPhoto(file))) { @@ -863,21 +955,22 @@ const DetailPenjemputan = (function () { }); } - function updateTpsLocation(lat, lng, address) { - const tps = state.tpsData[state.activeTpsIndex]; - tps.latitude = lat; - tps.longitude = lng; - tps.alamatJalan = address; + function updateTpsLocation(lat, lng, address) { + const tps = state.tpsData[state.activeTpsIndex]; + tps.latitude = lat; + tps.longitude = lng; + tps.alamatJalan = address; + + const form = elements.tpsContentContainer.querySelector('form'); + if (form) { + const latInput = form.querySelector('.tps-display-latitude'); + const lngInput = form.querySelector('.tps-display-longitude'); + if (latInput) latInput.value = lat; + if (lngInput) lngInput.value = lng; + } - const form = elements.tpsContentContainer.querySelector("form"); - if (form) { - const latInput = form.querySelector(".tps-display-latitude"); - const lngInput = form.querySelector(".tps-display-longitude"); - if (latInput) latInput.value = lat; - if (lngInput) lngInput.value = lng; + scheduleAutoSave(); } - saveState(); - } function updateMultiPreview(input, previewContainer) { if (!input || !previewContainer) return; @@ -892,56 +985,69 @@ const DetailPenjemputan = (function () { item.className = "rounded-xl border border-gray-200 overflow-hidden bg-black"; - const imageUrl = URL.createObjectURL(file); - const safeName = file.name.replace(/"/g, """); - item.innerHTML = ` + const imageUrl = URL.createObjectURL(file); + item.innerHTML = `
Preview ${index + 1}
-
-

${index + 1}. ${safeName}

-

${formatFileSize(file.size)}

-
`; - const img = item.querySelector(".preview-multi-image"); - if (img) { - img.onload = function () { - URL.revokeObjectURL(imageUrl); - }; - } - previewContainer.appendChild(item); - }); - } + const img = item.querySelector('.preview-multi-image'); + if (img) { + img.onload = function() { + URL.revokeObjectURL(imageUrl); + }; + } + previewContainer.appendChild(item); + }); + } - function createTimbanganItem(repeater, existingData = null) { - const photoNumber = repeater.children.length + 1; + function renderServerImagePreview(fileUrls, container) { + container.innerHTML = ''; + if (!fileUrls || fileUrls.length === 0) return; + container.className = 'space-y-2'; - const item = document.createElement("div"); - item.className = - "timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50"; - item.dataset.photoNumber = photoNumber; + fileUrls.forEach((url, index) => { + const item = document.createElement('div'); + item.className = 'rounded-xl border border-gray-200 overflow-hidden bg-black'; + const isUrl = typeof url === 'string' && (url.startsWith('/') || url.startsWith('http')); - const weight = existingData ? existingData.weight : 0; - const jenisSampah = existingData - ? existingData.jenisSampah - : CONFIG.DEFAULT_JENIS; - const hasFile = hasStoredPhoto(existingData && existingData.file); - const isUploaded = Boolean(existingData && existingData.uploaded); - const ocrInfoText = - existingData && existingData.ocrInfo - ? existingData.ocrInfo - : hasFile - ? "OCR: diproses." - : "OCR: belum diproses."; + if (isUrl) { + item.innerHTML = ` +
+ Foto ${index + 1} +
+ `; + } else { + item.className = 'rounded-xl border border-green-200 bg-green-50 h-44 flex items-center justify-center'; + item.innerHTML = `

✓ Foto ${index + 1}

`; + } - item.innerHTML = ` + container.appendChild(item); + }); + } + + function createTimbanganItem(repeater, existingData = null) { + const photoNumber = repeater.children.length + 1; + + const item = document.createElement('div'); + item.className = 'timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50'; + item.dataset.photoNumber = photoNumber; + + const weight = existingData ? (existingData.weight || 0) : 0; + const jenisSampah = existingData ? (existingData.jenisSampah || CONFIG.DEFAULT_JENIS) : CONFIG.DEFAULT_JENIS; + const hasFileBlob = Boolean(existingData?.file); + const hasFile = Boolean(existingData?.file || existingData?.fotoFileName); + const isUploaded = Boolean(existingData?.uploaded); + const ocrInfoText = existingData && existingData.ocrInfo ? existingData.ocrInfo : (hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.'); + + item.innerHTML = `

Item Timbangan #${photoNumber}

-
+
Preview foto timbangan

${ocrInfoText}

@@ -972,62 +1078,64 @@ const DetailPenjemputan = (function () { const jenisSampahSelect = item.querySelector(".input-jenis-sampah"); const removeBtn = item.querySelector(".btn-remove-timbangan"); - if (existingData && hasStoredPhoto(existingData.file)) { - const existingPhoto = resolveStoredPhoto(existingData.file); - const photoUrl = getStoredPhotoUrl(existingPhoto); - previewImage.src = photoUrl; - previewWrap.classList.remove("hidden"); - if (isBrowserFile(existingPhoto)) { - previewImage.onload = function () { - URL.revokeObjectURL(photoUrl); - }; - } - } - - fileInput.addEventListener("change", async function () { - if (fileInput.files && fileInput.files[0]) { - const originalFile = fileInput.files[0]; - - const watermarkedFile = await applyWatermark(originalFile, photoNumber); - - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(watermarkedFile); - fileInput.files = dataTransfer.files; - - const localUrl = URL.createObjectURL(watermarkedFile); - previewImage.src = localUrl; - previewWrap.classList.remove("hidden"); - previewImage.onload = function () { - URL.revokeObjectURL(localUrl); - }; - - await autoFillWeight(watermarkedFile, weightInputDisplay, ocrInfoEl); - const parsed = parseWeightInput(weightInputDisplay.value); - weightInputValue.value = parsed.toFixed(2); - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - - const tps = state.tpsData[state.activeTpsIndex]; - const itemIndex = Array.from(repeater.children).indexOf(item); - if (itemIndex >= 0 && tps.timbangan[itemIndex]) { - tps.timbangan[itemIndex].uploaded = false; - refreshTimbanganUploadState(item); - saveState(); + if (existingData && existingData.file) { + const localUrl = URL.createObjectURL(existingData.file); + previewImage.src = localUrl; + previewWrap.classList.remove('hidden'); + previewImage.onload = function() { + URL.revokeObjectURL(localUrl); + }; + } else if (existingData && existingData.fotoFileName && existingData.fotoFileName.startsWith('/')) { + previewImage.src = existingData.fotoFileName; + previewWrap.classList.remove('hidden'); } - } - }); - weightInputDisplay.addEventListener("input", function () { - const cleaned = this.value.replace(/[^0-9.,]/g, ""); - this.value = cleaned; - const parsed = parseWeightInput(cleaned); - weightInputValue.value = parsed.toFixed(2); - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - refreshTimbanganUploadState(item); - const form = elements.tpsContentContainer.querySelector("form"); - if (form) refreshSubmitButtonState(form); - }); + fileInput.addEventListener('change', async function() { + if (fileInput.files && fileInput.files[0]) { + const originalFile = fileInput.files[0]; + + const watermarkedFile = await applyWatermark(originalFile, photoNumber); + + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(watermarkedFile); + fileInput.files = dataTransfer.files; + + const localUrl = URL.createObjectURL(watermarkedFile); + previewImage.src = localUrl; + previewWrap.classList.remove('hidden'); + previewImage.onload = function() { + URL.revokeObjectURL(localUrl); + }; + + await autoFillWeight(watermarkedFile, weightInputDisplay, ocrInfoEl); + const parsed = parseWeightInput(weightInputDisplay.value); + weightInputValue.value = parsed.toFixed(2); + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + + const tps = state.tpsData[state.activeTpsIndex]; + const itemIndex = Array.from(repeater.children).indexOf(item); + if (itemIndex >= 0 && tps.timbangan[itemIndex]) { + tps.timbangan[itemIndex].uploaded = false; + tps.timbangan[itemIndex].fotoFileName = ''; + refreshTimbanganUploadState(item); + } + scheduleAutoSave(); + } + }); + + weightInputDisplay.addEventListener('input', function() { + const cleaned = this.value.replace(/[^0-9.,]/g, ''); + this.value = cleaned; + const parsed = parseWeightInput(cleaned); + weightInputValue.value = parsed.toFixed(2); + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + refreshTimbanganUploadState(item); + const form = elements.tpsContentContainer.querySelector('form'); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); + }); weightInputDisplay.addEventListener("blur", function () { const parsed = parseWeightInput(this.value); @@ -1045,31 +1153,31 @@ const DetailPenjemputan = (function () { if (form) refreshSubmitButtonState(form); }); - jenisSampahSelect.addEventListener("change", function () { - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - const form = elements.tpsContentContainer.querySelector("form"); - if (form) refreshSubmitButtonState(form); - }); + jenisSampahSelect.addEventListener('change', function() { + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + const form = elements.tpsContentContainer.querySelector('form'); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); + }); - removeBtn.addEventListener("click", function () { - item.remove(); - const form = elements.tpsContentContainer.querySelector("form"); - const repeater = form - ? form.querySelector(".tps-timbangan-repeater") - : null; - - if (repeater) { - renumberTimbanganItems(repeater); - if (repeater.children.length === 0) { - createTimbanganItem(repeater); - } - } - - updateTpsTotalTimbangan(); - syncTimbanganToTpsData(); - if (form) refreshSubmitButtonState(form); - }); + removeBtn.addEventListener('click', function() { + item.remove(); + const form = elements.tpsContentContainer.querySelector('form'); + const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null; + + if (repeater) { + renumberTimbanganItems(repeater); + if (repeater.children.length === 0) { + createTimbanganItem(repeater); + } + } + + updateTpsTotalTimbangan(); + syncTimbanganToTpsData(); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); + }); repeater.appendChild(item); refreshTimbanganUploadState(item); @@ -1164,22 +1272,18 @@ const DetailPenjemputan = (function () { refreshSubmitButtonState(form); } - function isTimbanganItemReady(timbanganItem) { - return ( - hasStoredPhoto(timbanganItem?.file) && - Boolean(timbanganItem?.uploaded) && - (timbanganItem?.weight || 0) > 0 - ); - } - - function getSubmitState(tps) { - if (!tps.fotoKedatangan.length || !tps.fotoKedatanganUploaded) { - return { - canSubmit: false, - message: "Silakan upload foto kedatangan terlebih dahulu.", - }; + function isTimbanganItemReady(timbanganItem) { + return Boolean(timbanganItem?.file || timbanganItem?.fotoFileName) && Boolean(timbanganItem?.uploaded) && (timbanganItem?.weight || 0) > 0; } + function getSubmitState(tps) { + if (!tps.fotoKedatanganUploaded) { + if (!tps.fotoKedatangan.length) { + return { canSubmit: false, message: 'Silakan pilih dan upload foto kedatangan terlebih dahulu.' }; + } + return { canSubmit: false, message: 'Silakan upload foto kedatangan terlebih dahulu.' }; + } + if (!tps.timbangan.length) { return { canSubmit: false, @@ -1195,12 +1299,12 @@ const DetailPenjemputan = (function () { }; } - if (!tps.fotoPetugas.length || !tps.fotoPetugasUploaded) { - return { - canSubmit: false, - message: "Silakan upload foto petugas terlebih dahulu", - }; - } + if (!tps.fotoPetugasUploaded) { + if (!tps.fotoPetugas.length) { + return { canSubmit: false, message: 'Silakan pilih dan upload foto petugas terlebih dahulu.' }; + } + return { canSubmit: false, message: 'Silakan upload foto petugas terlebih dahulu' }; + } if (!tps.namaPetugas.trim()) { return { @@ -1245,19 +1349,16 @@ const DetailPenjemputan = (function () { const stateContainer = item.querySelector(".timbangan-upload-state"); if (!stateContainer) return; - const repeater = item.parentElement; - const itemIndex = repeater - ? Array.from(repeater.children).indexOf(item) - : -1; - const tps = state.tpsData[state.activeTpsIndex]; - const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null; - const fileInput = item.querySelector(".input-foto-timbangan"); - const hasFile = hasStoredPhoto(currentData?.file || fileInput?.files?.[0]); - const isUploaded = Boolean(currentData?.uploaded); - const weightInputValue = item.querySelector(".input-berat-timbangan-value"); - const currentWeight = - currentData?.weight ?? parseWeightInput(weightInputValue?.value || "0"); - const hasValidWeight = currentWeight > 0; + const repeater = item.parentElement; + const itemIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1; + const tps = state.tpsData[state.activeTpsIndex]; + const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null; + const fileInput = item.querySelector('.input-foto-timbangan'); + const hasFile = Boolean(currentData?.file || fileInput?.files?.[0] || currentData?.fotoFileName); + const isUploaded = Boolean(currentData?.uploaded); + const weightInputValue = item.querySelector('.input-berat-timbangan-value'); + const currentWeight = currentData?.weight ?? parseWeightInput(weightInputValue?.value || '0'); + const hasValidWeight = currentWeight > 0; stateContainer.innerHTML = getTimbanganUploadStateMarkup( hasFile, @@ -1672,125 +1773,205 @@ const DetailPenjemputan = (function () { const form = elements.tpsContentContainer.querySelector("form"); if (!form) return; - const repeater = form.querySelector(".tps-timbangan-repeater"); - const items = repeater.querySelectorAll(".timbangan-item"); - const previousTimbangan = [...tps.timbangan]; - - tps.timbangan = []; - items.forEach((item, index) => { - const fileInput = item.querySelector(".input-foto-timbangan"); - const weightValue = item.querySelector(".input-berat-timbangan-value"); - const jenisSampahSelect = item.querySelector(".input-jenis-sampah"); - const ocrInfo = - item.querySelector(".input-ocr-info")?.textContent || - "OCR: belum diproses."; - const existingData = previousTimbangan[index]; - - tps.timbangan.push({ - file: fileInput.files[0] || (existingData ? existingData.file : null), - weight: parseWeightInput(weightValue.value), - jenisSampah: jenisSampahSelect.value, - uploaded: existingData ? existingData.uploaded : false, - ocrInfo, - }); - }); - - saveState(); - } - - function uploadSingleFotoTimbangan(itemIndex, targetItem = null) { - const tps = state.tpsData[state.activeTpsIndex]; - - if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) { - alert("Belum ada foto timbangan yang dipilih!"); - return; + const repeater = form.querySelector('.tps-timbangan-repeater'); + const items = repeater.querySelectorAll('.timbangan-item'); + const previousTimbangan = [...tps.timbangan]; + + tps.timbangan = []; + items.forEach((item, index) => { + const fileInput = item.querySelector('.input-foto-timbangan'); + const weightValue = item.querySelector('.input-berat-timbangan-value'); + const jenisSampahSelect = item.querySelector('.input-jenis-sampah'); + const ocrInfo = item.querySelector('.input-ocr-info')?.textContent || 'OCR: belum diproses.'; + const existingData = previousTimbangan[index]; + + tps.timbangan.push({ + file: fileInput.files[0] || (existingData ? existingData.file : null), + fotoFileName: existingData ? existingData.fotoFileName || '' : '', + weight: parseWeightInput(weightValue.value), + jenisSampah: jenisSampahSelect.value, + uploaded: existingData ? existingData.uploaded : false, + ocrInfo + }); + }); } - const timbanganItem = tps.timbangan[itemIndex]; - if (timbanganItem.weight <= 0) { - alert( - "Berat belum valid. Isi manual dulu sebelum upload foto timbangan.", - ); - return; - } + async function uploadSingleFotoTimbangan(itemIndex, targetItem = null) { + const tps = state.tpsData[state.activeTpsIndex]; + + if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) { + showToast('Belum ada foto timbangan yang dipilih!', 'error'); + return; + } + + const timbanganItem = tps.timbangan[itemIndex]; + if (timbanganItem.weight <= 0) { + showToast('Berat belum valid. Isi manual dulu sebelum upload foto timbangan.', 'error'); + return; + } - const _ext = ( - timbanganItem.file.name.split(".").pop() || "jpg" - ).toLowerCase(); - const _jenis = timbanganItem.jenisSampah.toLowerCase(); - const _beratStr = parseFloat(timbanganItem.weight.toFixed(2)) - .toString() - .replace(".", "_"); - const _newName = `timbangan${itemIndex + 1}-${_jenis}-${_beratStr}.${_ext}`; - timbanganItem.file = new File([timbanganItem.file], _newName, { - type: timbanganItem.file.type, - lastModified: timbanganItem.file.lastModified, - }); + if (!targetItem) { + const form = elements.tpsContentContainer.querySelector('form'); + const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null; + const items = repeater ? repeater.querySelectorAll('.timbangan-item') : []; + targetItem = items[itemIndex] || null; + } - alert( - `Upload foto timbangan #${itemIndex + 1} untuk ${tps.name}\nJenis: ${timbanganItem.jenisSampah}\nBerat: ${timbanganItem.weight} kg\n(Implementasi upload ke server)`, - ); + const uploadBtn = targetItem ? targetItem.querySelector('.btn-upload-timbangan') : null; + if (uploadBtn) { + uploadBtn.disabled = true; + uploadBtn.textContent = 'Mengupload...'; + } - timbanganItem.uploaded = true; + const formData = new FormData(); + formData.append('FotoTimbangan', timbanganItem.file); + formData.append('DraftKey', tps.draftKey || ''); + formData.append('SpjDetailId', tps.spjDetailId || ''); + formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); + formData.append('ItemIndex', itemIndex); + formData.append('JenisSampah', timbanganItem.jenisSampah || CONFIG.DEFAULT_JENIS); + formData.append('Berat', Number(timbanganItem.weight || 0).toFixed(2)); - if (!targetItem) { - const form = elements.tpsContentContainer.querySelector("form"); - const repeater = form - ? form.querySelector(".tps-timbangan-repeater") - : null; - const items = repeater - ? repeater.querySelectorAll(".timbangan-item") - : []; - targetItem = items[itemIndex] || null; - } + try { + const res = await fetch(ENDPOINTS.uploadTimbangan, { method: 'POST', body: formData }); + const data = await res.json(); + if (res.ok && data.success) { + timbanganItem.uploaded = true; + timbanganItem.fotoFileName = data.fileUrl || data.fileName || ''; + const fotoInputTimb = targetItem?.querySelector('.input-foto-timbangan'); + if (fotoInputTimb) fotoInputTimb.value = ''; + showToast(data.message || `Foto timbangan #${itemIndex + 1} berhasil diupload.`, 'success'); + } else { + showToast(data.message || 'Gagal upload foto timbangan.', 'error'); + if (uploadBtn) { + uploadBtn.disabled = false; + uploadBtn.textContent = 'Upload Foto Timbangan Ini'; + } + } + } catch (_) { + showToast('Koneksi gagal saat upload foto timbangan.', 'error'); + if (uploadBtn) { + uploadBtn.disabled = false; + uploadBtn.textContent = 'Upload Foto Timbangan Ini'; + } + } if (targetItem) { refreshTimbanganUploadState(targetItem); } - syncTimbanganToTpsData(); - const form = elements.tpsContentContainer.querySelector("form"); - if (form) refreshSubmitButtonState(form); - saveState(); - } - - function uploadFotoKedatangan() { - const tps = state.tpsData[state.activeTpsIndex]; - if (tps.fotoKedatangan.length === 0) { - alert("Belum ada foto kedatangan yang dipilih!"); - return; + syncTimbanganToTpsData(); + const form = elements.tpsContentContainer.querySelector('form'); + if (form) refreshSubmitButtonState(form); + scheduleAutoSave(); } - alert( - `Upload ${tps.fotoKedatangan.length} foto kedatangan untuk ${tps.name}\n(Implementasi upload ke server)`, - ); + async function uploadFotoKedatangan() { + const tps = state.tpsData[state.activeTpsIndex]; + if (tps.fotoKedatangan.length === 0) { + showToast('Belum ada foto kedatangan yang dipilih!', 'error'); + return; + } - tps.fotoKedatanganUploaded = true; - const form = elements.tpsContentContainer.querySelector("form"); - if (form) refreshKedatanganUploadState(form); - saveState(); - } + const form = elements.tpsContentContainer.querySelector('form'); + const btn = form ? form.querySelector('.tps-btn-upload-kedatangan') : null; + if (btn) { + btn.disabled = true; + btn.textContent = 'Mengupload...'; + } - function uploadFotoPetugas() { - const tps = state.tpsData[state.activeTpsIndex]; - if (tps.fotoPetugas.length === 0) { - alert("Belum ada foto petugas yang dipilih!"); - return; - } - if (!tps.namaPetugas.trim()) { - alert("Nama petugas wajib diisi sebelum upload foto petugas!"); - return; + const formData = new FormData(); + tps.fotoKedatangan.forEach(file => formData.append('FotoKedatangan', file)); + formData.append('DraftKey', tps.draftKey || ''); + formData.append('SpjDetailId', tps.spjDetailId || ''); + formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); + formData.append('WaktuKedatangan', tps.waktuKedatangan || ''); + formData.append('Latitude', tps.latitude || ''); + formData.append('Longitude', tps.longitude || ''); + formData.append('AlamatJalan', tps.alamatJalan || ''); + + try { + const res = await fetch(ENDPOINTS.uploadKedatangan, { method: 'POST', body: formData }); + const data = await res.json(); + if (res.ok && data.success) { + tps.fotoKedatanganUploaded = true; + tps.fotoKedatanganFileNames = data.fileUrls || data.fileNames || []; + showToast(data.message || 'Foto kedatangan berhasil diupload.', 'success'); + if (form) { + const fotoInput = form.querySelector('.tps-foto-kedatangan'); + if (fotoInput) fotoInput.value = ''; + refreshKedatanganUploadState(form); + } + scheduleAutoSave(); + } else { + showToast(data.message || 'Gagal upload foto kedatangan.', 'error'); + if (btn) { + btn.disabled = false; + btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`; + } + } + } catch (_) { + showToast('Koneksi gagal saat upload foto kedatangan.', 'error'); + if (btn) { + btn.disabled = false; + btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`; + } + } } - alert( - `Upload ${tps.fotoPetugas.length} foto petugas untuk ${tps.name}\n(Implementasi upload ke server)`, - ); + async function uploadFotoPetugas() { + const tps = state.tpsData[state.activeTpsIndex]; + if (tps.fotoPetugas.length === 0) { + showToast('Belum ada foto petugas yang dipilih!', 'error'); + return; + } + if (!tps.namaPetugas.trim()) { + showToast('Nama petugas wajib diisi sebelum upload foto petugas!', 'error'); + return; + } - tps.fotoPetugasUploaded = true; - const form = elements.tpsContentContainer.querySelector("form"); - if (form) refreshPetugasUploadState(form); - saveState(); - } + const form = elements.tpsContentContainer.querySelector('form'); + const btn = form ? form.querySelector('.tps-btn-upload-petugas:not([disabled])') : null; + if (btn) { + btn.disabled = true; + btn.textContent = 'Mengupload...'; + } + + const formData = new FormData(); + tps.fotoPetugas.forEach(file => formData.append('FotoPetugas', file)); + formData.append('DraftKey', tps.draftKey || ''); + formData.append('SpjDetailId', tps.spjDetailId || ''); + formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); + formData.append('NamaPetugas', tps.namaPetugas || ''); + + try { + const res = await fetch(ENDPOINTS.uploadPetugas, { method: 'POST', body: formData }); + const data = await res.json(); + if (res.ok && data.success) { + tps.fotoPetugasUploaded = true; + tps.fotoPetugasFileNames = data.fileUrls || data.fileNames || []; + showToast(data.message || 'Foto petugas berhasil diupload.', 'success'); + if (form) { + const fotoInput = form.querySelector('.tps-foto-petugas'); + if (fotoInput) fotoInput.value = ''; + refreshPetugasUploadState(form); + } + scheduleAutoSave(); + } else { + showToast(data.message || 'Gagal upload foto petugas.', 'error'); + if (btn) { + btn.disabled = false; + btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`; + } + } + } catch (_) { + showToast('Koneksi gagal saat upload foto petugas.', 'error'); + if (btn) { + btn.disabled = false; + btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`; + } + } + } function buildSubmitFormData(tps) { const formData = new FormData(); @@ -1852,51 +2033,63 @@ const DetailPenjemputan = (function () { } } - function submitTpsData() { - const tps = state.tpsData[state.activeTpsIndex]; - const submitState = getSubmitState(tps); - if (!submitState.canSubmit) { - alert(submitState.message); - return; - } + async function submitTpsData() { + const tps = state.tpsData[state.activeTpsIndex]; + const submitState = getSubmitState(tps); + if (!submitState.canSubmit) { + showToast(submitState.message, 'error'); + return; + } - // MODE STATIK (aktif sekarang) - // Cuma validasi + tandai TPS selesai, ga kirim ke backend. - markTpsSubmitted(tps); - clearStateIfAllSubmitted(); - alert( - `Validasi ${tps.name} OK. Data belum dikirim ke server (mode statik).`, - ); + const form = elements.tpsContentContainer.querySelector('form'); + const submitBtn = form ? form.querySelector('button[type="submit"]') : null; + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.textContent = `Menyimpan${state.selectedTpsList.length > 1 ? ' ' + tps.name : ''}...`; + } - // MODE PRODUCTION (aktifkan kalau backend udah ready mas ebik) - /* const formData = buildSubmitFormData(tps); - fetch('/upst/detail-penjemputan', { - method: 'POST', - body: formData - }) - .then(async response => { - if (response.ok) { + try { + const response = await fetch(ENDPOINTS.submit, { + method: 'POST', + body: formData + }); + + if (response.ok || response.redirected) { markTpsSubmitted(tps); - clearStateIfAllSubmitted(); - alert(`Data ${tps.name} berhasil disimpan!`); - window.location.reload(); + if (tps.draftKey) { + await fetch(`${ENDPOINTS.deleteDraft}?draftKey=${encodeURIComponent(tps.draftKey)}`, { method: 'DELETE' }); + } + + showToast(`Data ${tps.name} berhasil disimpan!`, 'success'); + + const allSubmitted = state.tpsData.every(item => item.submitted); + if (allSubmitted) { + setTimeout(() => { + window.location.href = '/upst/detail-penjemputan/detail-selesai'; + }, 1200); + } else { + renderTabs(); + renderTpsForm(); + } } else { const errorText = await response.text(); - if (response.status === 400) { - alert('Sesi submit tidak valid. Silakan refresh halaman lalu coba lagi.'); - } else { - alert(errorText || 'Gagal menyimpan data. Silakan coba lagi.'); + showToast(errorText || 'Gagal menyimpan data. Silakan coba lagi.', 'error'); + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.textContent = `Submit${state.selectedTpsList.length > 1 ? ' ' + tps.name : ''}`; } } - }) - .catch(error => { + } catch (error) { console.error('Error:', error); - alert('Terjadi kesalahan saat menyimpan data.'); - }); - */ - } + showToast('Terjadi kesalahan saat menyimpan data.', 'error'); + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.textContent = `Submit${state.selectedTpsList.length > 1 ? ' ' + tps.name : ''}`; + } + } + } function formatWeightDisplay(value) { if (isNaN(value)) return "0,00"; @@ -1914,10 +2107,35 @@ const DetailPenjemputan = (function () { return isNaN(parsed) ? 0 : parsed; } - function formatFileSize(bytes) { - const mb = bytes / (1024 * 1024); - return `${mb.toFixed(2)} MB`; - } + function formatFileSize(bytes) { + const mb = bytes / (1024 * 1024); + return `${mb.toFixed(2)} MB`; + } + + function showToast(message, type = 'info') { + let container = document.getElementById('espj-toast-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'espj-toast-container'; + container.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;'; + document.body.appendChild(container); + } + + const toast = document.createElement('div'); + const bg = type === 'success' ? '#16a34a' : type === 'error' ? '#dc2626' : '#2563eb'; + toast.style.cssText = `background:${bg};color:#fff;padding:10px 18px;border-radius:16px;font-size:13px;font-weight:600;box-shadow:0 4px 24px rgba(0,0,0,.18);opacity:0;transition:opacity .25s;max-width:320px;text-align:center;`; + toast.textContent = message; + container.appendChild(toast); + + requestAnimationFrame(() => { + toast.style.opacity = '1'; + }); + + setTimeout(() => { + toast.style.opacity = '0'; + setTimeout(() => toast.remove(), 300); + }, 3200); + } return { init: init, @@ -1929,29 +2147,26 @@ const DetailPenjemputan = (function () { }; })(); -document.addEventListener("DOMContentLoaded", async function () { - try { - const response = await fetch("/driver/json/tps-list.json"); - const data = await response.json(); - const tpsList = data.tpsList.map((tps) => ({ - name: tps.name, - lokasiAngkutId: tps.lokasiAngkutId, - spjDetailId: tps.spjDetailId, - id: tps.id, - })); - DetailPenjemputan.init(tpsList); - if (data.draftPenjemputan || data.tpsData) { - DetailPenjemputan.hydrateFromApi(data.draftPenjemputan || data.tpsData); +document.addEventListener('DOMContentLoaded', async function() { + try { + const response = await fetch('/driver/json/tps-list.json'); + const data = await response.json(); + const tpsList = data.tpsList.map(tps => ({ + name: tps.name, + lokasiAngkutId: tps.lokasiAngkutId, + spjDetailId: tps.spjDetailId, + id: tps.id + })); + await DetailPenjemputan.init(tpsList); + } catch (error) { + console.error('Error loading TPS list:', error); } - } catch (error) { - console.error("Error loading TPS list:", error); - } - - const platNomorEl = document.getElementById("plat-nomor"); - if (platNomorEl) { - const nomorSpjEl = document.querySelector(".text-gray-600.font-mono"); - if (nomorSpjEl) { - DetailPenjemputan.setNomorSpj(nomorSpjEl.textContent.trim()); + + const platNomorEl = document.getElementById('plat-nomor'); + if (platNomorEl) { + const nomorSpjEl = document.querySelector('.text-gray-600.font-mono'); + if (nomorSpjEl) { + DetailPenjemputan.setNomorSpj(nomorSpjEl.textContent.trim()); + } } - } }); diff --git a/wwwroot/driver/js/history-upst.js b/wwwroot/driver/js/history-upst.js new file mode 100644 index 0000000..0fb95b3 --- /dev/null +++ b/wwwroot/driver/js/history-upst.js @@ -0,0 +1,291 @@ +document.addEventListener('DOMContentLoaded', function () { + const pageEl = document.getElementById('history-page'); + if (!pageEl) return; + + const apiUrl = pageEl.dataset.historyApi; + const detailBase = pageEl.dataset.historyDetailBase || '/upst/history/details/__id__'; + + const loadingEl = document.getElementById('history-loading'); + const emptyEl = document.getElementById('history-empty'); + const listEl = document.getElementById('history-list'); + const paginationEl = document.getElementById('history-pagination'); + const pageInfoEl = document.getElementById('history-page-info'); + const totalInfoEl = document.getElementById('history-total-info'); + const pageButtonsEl = document.getElementById('history-page-buttons'); + const prevBtn = document.getElementById('history-prev-page'); + const nextBtn = document.getElementById('history-next-page'); + const fromDateInput = document.getElementById('history-from-date-filter'); + const toDateInput = document.getElementById('history-to-date-filter'); + const applyFilterBtn = document.getElementById('history-apply-filter'); + const resetFilterBtn = document.getElementById('history-reset-filter'); + + const state = { + page: 1, + pageSize: 5, + totalPages: 1, + totalItems: 0, + fromDate: '', + toDate: '' + }; + + function formatTanggal(dateString) { + const date = new Date(dateString); + return new Intl.DateTimeFormat('id-ID', { + day: '2-digit', + month: 'short', + year: 'numeric' + }).format(date); + } + + function formatWaktu(dateString) { + const date = new Date(dateString); + return new Intl.DateTimeFormat('id-ID', { + hour: '2-digit', + minute: '2-digit' + }).format(date); + } + + function getStatusMarkup(status) { + if ((status || '').toLowerCase() === 'completed') { + return 'Selesai'; + } + + return 'Proses'; + } + + function buildDetailUrl(id) { + return detailBase.replace('__id__', String(id)); + } + + function renderItems(items) { + listEl.innerHTML = ''; + + items.forEach(function (item) { + const card = document.createElement('a'); + card.href = buildDetailUrl(item.id); + card.className = 'block group'; + card.innerHTML = ` +
+
+
+

Nomor Dokumen

+

${item.noSpj}

+
+ ${getStatusMarkup(item.status)} +
+ +
+
+ +
+
+
+

${item.plat}

+ ${formatWaktu(item.tanggalWaktu)} +
+
+ ${item.kode} + ${formatTanggal(item.tanggalWaktu)} +
+
+
+ +
+
+
+
+ Tujuan Akhir + ${item.tujuan} +
+
+
+ +
+
+
+ `; + listEl.appendChild(card); + }); + + if (window.lucide) { + window.lucide.createIcons(); + } + } + + function renderPagination() { + const hasItems = state.totalItems > 0; + paginationEl.classList.toggle('hidden', !hasItems); + if (!hasItems) return; + + pageInfoEl.textContent = `Halaman ${state.page} dari ${state.totalPages}`; + totalInfoEl.textContent = `${state.totalItems} data`; + prevBtn.disabled = state.page <= 1; + nextBtn.disabled = state.page >= state.totalPages; + + pageButtonsEl.innerHTML = ''; + const pages = []; + const startPage = Math.max(1, state.page - 2); + const endPage = Math.min(state.totalPages, state.page + 2); + + if (startPage > 1) { + pages.push(1); + if (startPage > 2) pages.push('ellipsis-left'); + } + + for (let page = startPage; page <= endPage; page++) { + pages.push(page); + } + + if (endPage < state.totalPages) { + if (endPage < state.totalPages - 1) pages.push('ellipsis-right'); + pages.push(state.totalPages); + } + + pages.forEach(function (page) { + if (typeof page !== 'number') { + const ellipsis = document.createElement('span'); + ellipsis.className = 'w-10 h-10 flex items-center justify-center text-xs font-black text-gray-400'; + ellipsis.textContent = '...'; + pageButtonsEl.appendChild(ellipsis); + return; + } + + const btn = document.createElement('button'); + btn.type = 'button'; + btn.textContent = String(page); + btn.className = page === state.page + ? 'w-10 h-10 rounded-2xl bg-upst text-white text-xs font-black' + : 'w-10 h-10 rounded-2xl border border-gray-200 text-gray-700 text-xs font-black bg-white'; + btn.addEventListener('click', function () { + if (page === state.page) return; + loadHistory(page); + }); + pageButtonsEl.appendChild(btn); + }); + } + + function setLoading(isLoading) { + loadingEl.classList.toggle('hidden', !isLoading); + if (isLoading) { + emptyEl.classList.add('hidden'); + listEl.innerHTML = ''; + paginationEl.classList.add('hidden'); + } + } + + function showEmpty() { + listEl.innerHTML = ''; + emptyEl.classList.remove('hidden'); + paginationEl.classList.add('hidden'); + if (window.lucide) { + window.lucide.createIcons(); + } + } + + async function loadHistory(page) { + state.page = page || 1; + setLoading(true); + + try { + const params = new URLSearchParams({ + page: String(state.page), + pageSize: String(state.pageSize) + }); + + if (state.fromDate) { + params.set('fromDate', state.fromDate); + } + + if (state.toDate) { + params.set('toDate', state.toDate); + } + + const response = await fetch(`${apiUrl}?${params.toString()}`, { cache: 'no-store' }); + if (!response.ok) { + throw new Error('Gagal memuat history'); + } + + const data = await response.json(); + state.page = data.page || 1; + state.totalPages = data.totalPages || 1; + state.totalItems = data.totalItems || 0; + + loadingEl.classList.add('hidden'); + if (!data.items || data.items.length === 0) { + showEmpty(); + return; + } + + emptyEl.classList.add('hidden'); + renderItems(data.items); + renderPagination(); + } catch (error) { + console.error(error); + loadingEl.classList.add('hidden'); + emptyEl.classList.remove('hidden'); + emptyEl.innerHTML = ` +
+ +
+

Gagal Memuat Riwayat

+

Silakan coba lagi beberapa saat.

+ `; + if (window.lucide) { + window.lucide.createIcons(); + } + } + } + + function applyDateRangeFilter() { + const nextFromDate = fromDateInput?.value || ''; + const nextToDate = toDateInput?.value || ''; + + if (nextFromDate && nextToDate && nextFromDate > nextToDate) { + const temp = nextFromDate; + state.fromDate = nextToDate; + state.toDate = temp; + if (fromDateInput) fromDateInput.value = state.fromDate; + if (toDateInput) toDateInput.value = state.toDate; + } else { + state.fromDate = nextFromDate; + state.toDate = nextToDate; + } + + loadHistory(1); + } + + applyFilterBtn?.addEventListener('click', function () { + applyDateRangeFilter(); + }); + + resetFilterBtn?.addEventListener('click', function () { + state.fromDate = ''; + state.toDate = ''; + if (fromDateInput) fromDateInput.value = ''; + if (toDateInput) toDateInput.value = ''; + loadHistory(1); + }); + + [fromDateInput, toDateInput].forEach(function (input) { + input?.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + applyDateRangeFilter(); + } + }); + }); + + prevBtn?.addEventListener('click', function () { + if (state.page > 1) { + loadHistory(state.page - 1); + } + }); + + nextBtn?.addEventListener('click', function () { + if (state.page < state.totalPages) { + loadHistory(state.page + 1); + } + }); + + loadHistory(1); +}); diff --git a/wwwroot/driver/json/pengangkutan.json b/wwwroot/driver/json/pengangkutan.json index fbd9102..2d40542 100644 --- a/wwwroot/driver/json/pengangkutan.json +++ b/wwwroot/driver/json/pengangkutan.json @@ -3,20 +3,20 @@ { "name": "CV Tri Mitra Utama - Shell Radio Dalam", "alamat": "Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470", - "longitude": -6.260066361357777, - "latitude": 106.78918653869111 + "longitude": -6.26012668512782, + "latitude": 106.8712511969409 }, { "name": "Jakarta Islamic Hospital", "alamat": "Gang Masjid Baitusalam, Jl. E No.19, RT.10/RW.11, Cipinang, Kec. Pulo Gadung, Kota Jakarta Timur, Daerah Khusus Ibukota Jakarta 13240", - "longitude": -6.168850536704003, - "latitude": 106.87091508600079 + "longitude": -6.2550852145095694, + "latitude": 106.86310593093889 }, { "name": "Puskesmas Kelurahan Klender 1", "alamat": "Jl. Pertanian Tengah No.7, RT.4/RW.2, Klender, Kec. Duren Sawit, Kota Jakarta Timur, Daerah Khusus Ibukota Jakarta 13430", - "longitude": -6.2146980384303845, - "latitude": 106.89777460376108 + "longitude": -6.268768227222534, + "latitude": 106.87026643078956 } ] } diff --git a/wwwroot/driver/manifest.json b/wwwroot/driver/manifest.json index 3853628..778c965 100644 --- a/wwwroot/driver/manifest.json +++ b/wwwroot/driver/manifest.json @@ -1,93 +1,44 @@ { + "id": "/upst", "name": "eSPJ - Surat Perjalanan Dinas", "short_name": "eSPJ", "description": "Aplikasi pengelolaan Surat Perjalanan Dinas yang modern dan efisien", - "start_url": "/", + "start_url": "/upst", "display": "standalone", "background_color": "#ffffff", "theme_color": "#fb923c", "orientation": "portrait-primary", - "scope": "/", + "scope": "/upst", "lang": "id", "dir": "ltr", "icons": [ { - "src": "icon-72.png", - "sizes": "72x72", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icon-96.png", - "sizes": "96x96", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icon-128.png", - "sizes": "128x128", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icon-144.png", - "sizes": "144x144", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icon-152.png", - "sizes": "152x152", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icon-192.png", + "src": "/driver/images/pwa_192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, { - "src": "icon-384.png", - "sizes": "384x384", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icon-512.png", + "src": "/driver/images/pwa_512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } ], - "screenshots": [ - { - "src": "screenshot-wide.png", - "sizes": "1280x720", - "type": "image/png", - "form_factor": "wide" - }, - { - "src": "screenshot-narrow.png", - "sizes": "360x640", - "type": "image/png", - "form_factor": "narrow" - } - ], "shortcuts": [ { - "name": "Login", - "short_name": "Login", - "description": "Masuk ke aplikasi eSPJ", - "url": "/login", - "icons": [{ "src": "icon-96.png", "sizes": "96x96" }] + "name": "Home UPST", + "short_name": "Home", + "description": "Buka dashboard utama UPST", + "url": "/upst", + "icons": [{ "src": "/driver/images/pwa_192.png", "sizes": "192x192", "type": "image/png" }] }, { - "name": "Dashboard", - "short_name": "Dashboard", - "description": "Buka dashboard utama", - "url": "/dashboard", - "icons": [{ "src": "icon-96.png", "sizes": "96x96" }] + "name": "Halaman Kosong", + "short_name": "Kosong", + "description": "Buka halaman alternatif UPST", + "url": "/upst/kosong", + "icons": [{ "src": "/driver/images/pwa_192.png", "sizes": "192x192", "type": "image/png" }] } ] } diff --git a/wwwroot/driver/offline.html b/wwwroot/driver/offline.html new file mode 100644 index 0000000..6647768 --- /dev/null +++ b/wwwroot/driver/offline.html @@ -0,0 +1,67 @@ + + + + + + + Offline - DLH DKI Jakarta + + + + + + +
+
+
+ +
+ + + +
+
+ +
+

Koneksi Terputus

+

+ Sepertinya perangkatmu sedang tidak terhubung ke internet. Silakan cek koneksi Wi-Fi atau data seluler + kamu. +

+
+ +
+ +
+ +
+
+

DLH DKI Jakarta

+
+
+ + + + \ No newline at end of file diff --git a/wwwroot/driver/serviceworker.js b/wwwroot/driver/serviceworker.js new file mode 100644 index 0000000..fba86a9 --- /dev/null +++ b/wwwroot/driver/serviceworker.js @@ -0,0 +1,77 @@ +const CACHE_NAME = "espj-upst-pwa-v1"; +const OFFLINE_URL = "/driver/offline.html"; +const PRECACHE_URLS = [ + "/upst", + "/upst/kosong", + "/driver/manifest.json", + "/driver/favicon.ico", + "/driver/images/pwa_192.png", + "/driver/images/pwa_512.png", + OFFLINE_URL +]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) + ); + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all( + keys + .filter((key) => key !== CACHE_NAME) + .map((key) => caches.delete(key)) + ) + ) + ); + + self.clients.claim(); +}); + +self.addEventListener("fetch", (event) => { + const { request } = event; + const url = new URL(request.url); + + if (request.method !== "GET") { + return; + } + + if (request.mode === "navigate") { + event.respondWith( + fetch(request) + .then((response) => { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone)); + return response; + }) + .catch(async () => { + const cachedPage = await caches.match(request); + return cachedPage || caches.match(OFFLINE_URL); + }) + ); + return; + } + + if (url.origin !== self.location.origin) { + return; + } + + event.respondWith( + caches.match(request).then((cachedResponse) => { + const networkFetch = fetch(request) + .then((response) => { + if (response && response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone)); + } + return response; + }) + .catch(() => cachedResponse); + + return cachedResponse || networkFetch; + }) + ); +});