update: submit struk bg
parent
5e8b735ea9
commit
b95e93d3c2
|
|
@ -1,10 +1,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace eSPJ.Controllers.SpjDriverUpstController
|
||||
{
|
||||
[Route("upst/submit")]
|
||||
public class SubmitController : Controller
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
|
||||
public SubmitController(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
IWebHostEnvironment env)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
|
|
@ -12,70 +28,235 @@ namespace eSPJ.Controllers.SpjDriverUpstController
|
|||
return View("~/Views/Admin/Transport/SpjDriverUpst/Submit/Index.cshtml");
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("struk")]
|
||||
public IActionResult Struk()
|
||||
{
|
||||
return View("~/Views/Admin/Transport/SpjDriverUpst/Submit/Struk.cshtml");
|
||||
}
|
||||
|
||||
[HttpPost("ocr-struk")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> OcrStruk(IFormFile? Foto)
|
||||
{
|
||||
if (Foto == null || Foto.Length == 0)
|
||||
return BadRequest(new { success = false, message = "Foto tidak ditemukan." });
|
||||
|
||||
if (Foto.Length > 10 * 1024 * 1024)
|
||||
return BadRequest(new { success = false, message = "Ukuran foto terlalu besar. Maksimal 10MB." });
|
||||
|
||||
var apiKey = _configuration["OpenRouter:OCRkey"];
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
return StatusCode(500, new { success = false, message = "OpenRouter API key belum diset." });
|
||||
|
||||
byte[] fileBytes;
|
||||
await using (var ms = new MemoryStream())
|
||||
{
|
||||
await Foto.CopyToAsync(ms);
|
||||
fileBytes = ms.ToArray();
|
||||
}
|
||||
|
||||
var mimeType = string.IsNullOrWhiteSpace(Foto.ContentType) ? "image/jpeg" : Foto.ContentType;
|
||||
var base64 = Convert.ToBase64String(fileBytes);
|
||||
var dataUrl = $"data:{mimeType};base64,{base64}";
|
||||
|
||||
var payload = new
|
||||
{
|
||||
model = "google/gemini-2.5-flash-image",
|
||||
temperature = 0,
|
||||
messages = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
role = "user",
|
||||
content = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
type = "text",
|
||||
text = @"Baca data dari foto struk timbang kendaraan ini. Ekstrak semua data yang tersedia ke dalam format JSON berikut:
|
||||
{
|
||||
""nomorStruk"": ""<nomor struk tanpa prefix, contoh: 8001441>"",
|
||||
""nomorPolisi"": ""<nomor polisi kendaraan, contoh: B 9125 PJA>"",
|
||||
""penugasan"": ""<area penugasan, contoh: JAKARTA BARAT>"",
|
||||
""waktuMasuk"": ""<waktu masuk format YYYY-MM-DD, HH:MM:SS>"",
|
||||
""waktuKeluar"": ""<waktu keluar format YYYY-MM-DD, HH:MM:SS>"",
|
||||
""beratMasuk"": <berat masuk dalam kg sebagai angka integer tanpa desimal>,
|
||||
""beratKeluar"": <berat keluar dalam kg sebagai angka integer tanpa desimal>,
|
||||
""beratNett"": <berat netto dalam kg sebagai angka integer tanpa desimal>
|
||||
}
|
||||
|
||||
Catatan penting:
|
||||
- nomorStruk: ambil angka utama saja, abaikan prefix bulan seperti '03_' atau '03 '
|
||||
- waktu: konversi ke format YYYY-MM-DD, HH:MM:SS (contoh: 2025-08-04, 08:13:51)
|
||||
- berat: hanya angka integer tanpa satuan 'kg'
|
||||
- Jika data tidak ada atau tidak terbaca, isi dengan null
|
||||
- Kembalikan HANYA JSON valid tanpa penjelasan atau markdown code block"
|
||||
},
|
||||
new
|
||||
{
|
||||
type = "image_url",
|
||||
image_url = new { url = dataUrl }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "https://openrouter.ai/api/v1/chat/completions");
|
||||
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {apiKey}");
|
||||
request.Headers.TryAddWithoutValidation("Accept", "application/json");
|
||||
request.Headers.TryAddWithoutValidation("HTTP-Referer", "https://pesapakawan.dinaslhdki.id");
|
||||
request.Headers.TryAddWithoutValidation("X-Title", "eSPJ OCR Struk");
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
using var response = await client.SendAsync(request);
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return StatusCode((int)response.StatusCode, new
|
||||
{
|
||||
success = false,
|
||||
message = "OpenRouter request gagal.",
|
||||
detail = responseText
|
||||
});
|
||||
}
|
||||
|
||||
using var doc = JsonDocument.Parse(responseText);
|
||||
var content = doc.RootElement
|
||||
.GetProperty("choices")[0]
|
||||
.GetProperty("message")
|
||||
.GetProperty("content")
|
||||
.GetString() ?? "";
|
||||
|
||||
var jsonMatch = Regex.Match(content, @"\{[\s\S]*\}");
|
||||
if (!jsonMatch.Success)
|
||||
return Ok(new { success = false, message = "AI tidak mengembalikan data valid.", raw = content });
|
||||
|
||||
try
|
||||
{
|
||||
using var dataDoc = JsonDocument.Parse(jsonMatch.Value);
|
||||
var root = dataDoc.RootElement;
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
nomorStruk = GetStringValue(root, "nomorStruk"),
|
||||
nomorPolisi = GetStringValue(root, "nomorPolisi"),
|
||||
penugasan = GetStringValue(root, "penugasan"),
|
||||
waktuMasuk = GetStringValue(root, "waktuMasuk"),
|
||||
waktuKeluar = GetStringValue(root, "waktuKeluar"),
|
||||
beratMasuk = GetIntValue(root, "beratMasuk"),
|
||||
beratKeluar = GetIntValue(root, "beratKeluar"),
|
||||
beratNett = GetIntValue(root, "beratNett")
|
||||
},
|
||||
raw = content
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Ok(new { success = false, message = "Gagal memproses respons AI.", raw = content });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("struk")]
|
||||
public IActionResult ProcessStruk(string NomorStruk, string NomorPolisi, string Penugasan,
|
||||
string WaktuMasuk, string WaktuKeluar, int? BeratMasuk, int? BeratKeluar, int BeratNett)
|
||||
public async Task<IActionResult> ProcessStruk(
|
||||
string? NomorStruk,
|
||||
string? NomorPolisi,
|
||||
string? Penugasan,
|
||||
string? WaktuMasuk,
|
||||
string? WaktuKeluar,
|
||||
int? BeratMasuk,
|
||||
int? BeratKeluar,
|
||||
int? BeratNett,
|
||||
string? Timbang,
|
||||
IFormFile? FotoStruk)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate required inputs
|
||||
if (string.IsNullOrEmpty(NomorStruk) || BeratNett <= 0)
|
||||
if (string.IsNullOrWhiteSpace(NomorStruk))
|
||||
{
|
||||
TempData["Error"] = "Nomor struk dan berat nett harus diisi.";
|
||||
TempData["Error"] = "Nomor struk wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
// Validate receipt number format (numbers only, 7+ digits)
|
||||
if (!System.Text.RegularExpressions.Regex.IsMatch(NomorStruk, @"^\d{7,}$"))
|
||||
if (!Regex.IsMatch(NomorStruk, @"^\d{3,}$"))
|
||||
{
|
||||
TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 7 digit.";
|
||||
TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 3 digit.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
// Validate weight range
|
||||
if (BeratNett < 100 || BeratNett > 50000)
|
||||
if (string.IsNullOrWhiteSpace(NomorPolisi))
|
||||
{
|
||||
TempData["Error"] = "Berat nett harus antara 100 kg - 50,000 kg.";
|
||||
TempData["Error"] = "Nomor polisi wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
// Validate optional weights
|
||||
if (BeratMasuk.HasValue && (BeratMasuk < 0 || BeratMasuk > 100000))
|
||||
if (string.IsNullOrWhiteSpace(Penugasan))
|
||||
{
|
||||
TempData["Error"] = "Berat masuk tidak valid.";
|
||||
TempData["Error"] = "Penugasan wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
if (BeratKeluar.HasValue && (BeratKeluar < 0 || BeratKeluar > 100000))
|
||||
if (string.IsNullOrWhiteSpace(WaktuMasuk))
|
||||
{
|
||||
TempData["Error"] = "Berat keluar tidak valid.";
|
||||
TempData["Error"] = "Waktu masuk wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
// Here you would normally save to database
|
||||
// For now, just simulate success with all data
|
||||
var submitData = new
|
||||
if (string.IsNullOrWhiteSpace(WaktuKeluar))
|
||||
{
|
||||
NomorStruk,
|
||||
NomorPolisi = NomorPolisi ?? "N/A",
|
||||
Penugasan = Penugasan ?? "N/A",
|
||||
WaktuMasuk = WaktuMasuk ?? "N/A",
|
||||
WaktuKeluar = WaktuKeluar ?? "N/A",
|
||||
BeratMasuk = BeratMasuk?.ToString() ?? "N/A",
|
||||
BeratKeluar = BeratKeluar?.ToString() ?? "N/A",
|
||||
BeratNett
|
||||
};
|
||||
TempData["Error"] = "Waktu keluar wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, Nett: {BeratNett} kg";
|
||||
if (!BeratMasuk.HasValue || BeratMasuk <= 0)
|
||||
{
|
||||
TempData["Error"] = "Berat masuk wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
if (!BeratKeluar.HasValue || BeratKeluar <= 0)
|
||||
{
|
||||
TempData["Error"] = "Berat keluar wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
if (!BeratNett.HasValue || BeratNett <= 0)
|
||||
{
|
||||
TempData["Error"] = "Berat nett wajib diisi.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
if (FotoStruk == null || FotoStruk.Length == 0)
|
||||
{
|
||||
TempData["Error"] = "Foto struk wajib dilampirkan.";
|
||||
return RedirectToAction("Struk");
|
||||
}
|
||||
|
||||
string? fotoStrukUrl = null;
|
||||
if (FotoStruk != null && FotoStruk.Length > 0)
|
||||
{
|
||||
var datePart = DateTime.Now.ToString("yyyy-MM-dd");
|
||||
var uploadPath = Path.Combine(_env.ContentRootPath, "uploads", "struk", datePart);
|
||||
if (!Directory.Exists(uploadPath))
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
|
||||
var ext = Path.GetExtension(FotoStruk.FileName).ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(ext)) ext = ".jpg";
|
||||
var fileName = $"struk_{NomorStruk}_{Guid.NewGuid():N}{ext}";
|
||||
var filePath = Path.Combine(uploadPath, fileName);
|
||||
await using var stream = new FileStream(filePath, FileMode.Create);
|
||||
await FotoStruk.CopyToAsync(stream);
|
||||
fotoStrukUrl = $"/uploads/struk/{datePart}/{fileName}";
|
||||
}
|
||||
|
||||
var timbangLabel = Timbang == "RDF" ? "Timbangan RDF" : "Timbangan TPA";
|
||||
TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, {timbangLabel}, Nett: {BeratNett!.Value} kg";
|
||||
return RedirectToAction("Index", "Home");
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
@ -83,5 +264,24 @@ namespace eSPJ.Controllers.SpjDriverUpstController
|
|||
return RedirectToAction("Struk");
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetStringValue(JsonElement root, string key)
|
||||
{
|
||||
if (root.TryGetProperty(key, out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
return prop.GetString();
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetIntValue(JsonElement root, string key)
|
||||
{
|
||||
if (root.TryGetProperty(key, out var prop))
|
||||
{
|
||||
if (prop.ValueKind == JsonValueKind.Number && prop.TryGetInt32(out var val))
|
||||
return val;
|
||||
if (prop.ValueKind == JsonValueKind.String && int.TryParse(prop.GetString(), out var strVal))
|
||||
return strVal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[10px] font-black uppercase tracking-[0.22em] text-gray-400">Install App</p>
|
||||
<h3 class="mt-1 text-sm font-black tracking-tight">Pasang eSPJ di perangkat</h3>
|
||||
<h3 class="mt-1 text-sm font-black tracking-tight">PKM UPST di perangkat</h3>
|
||||
<p id="pwaInstallDescription" class="mt-1 text-[11px] leading-relaxed text-gray-500/80">Akses lebih cepat langsung dari home screen tanpa buka browser dulu.</p>
|
||||
</div>
|
||||
<button id="pwaInstallDismiss" type="button" class="flex h-9 w-9 items-center justify-center rounded-xl bg-white/10 text-lg font-bold text-gray-500/80 transition active:scale-90">×</button>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<div id="pwaInstallHelpModal" class="fixed inset-0 z-130 hidden items-end justify-center bg-slate-950/60 p-4 backdrop-blur-sm sm:items-center">
|
||||
<div class="w-full max-w-sm rounded-4xl bg-white p-6 shadow-2xl">
|
||||
<p class="text-[10px] font-black uppercase tracking-[0.24em] text-upst">Cara Install</p>
|
||||
<h3 class="mt-2 text-lg font-black tracking-tight text-slate-900">Pasang eSPJ ke home screen</h3>
|
||||
<h3 class="mt-2 text-lg font-black tracking-tight text-slate-900">Pasang PKM UPST ke home screen</h3>
|
||||
<p id="pwaInstallHelpText" class="mt-3 text-sm leading-relaxed text-slate-600">Gunakan menu browser lalu pilih install aplikasi.</p>
|
||||
|
||||
<div id="pwaInstallHelpSteps" class="mt-5 grid gap-2 rounded-2xl bg-slate-50 p-4 text-xs leading-relaxed text-slate-600">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -434,6 +434,9 @@
|
|||
.right-full {
|
||||
right: 100%;
|
||||
}
|
||||
.-bottom-0 {
|
||||
bottom: calc(var(--spacing) * -0);
|
||||
}
|
||||
.-bottom-0\.5 {
|
||||
bottom: calc(var(--spacing) * -0.5);
|
||||
}
|
||||
|
|
@ -479,6 +482,9 @@
|
|||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
.left-1 {
|
||||
left: calc(var(--spacing) * 1);
|
||||
}
|
||||
.left-1\/2 {
|
||||
left: calc(1/2 * 100%);
|
||||
}
|
||||
|
|
@ -794,6 +800,9 @@
|
|||
.-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);
|
||||
}
|
||||
|
|
@ -884,6 +893,9 @@
|
|||
.aspect-square {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.h-0 {
|
||||
height: calc(var(--spacing) * 0);
|
||||
}
|
||||
.h-0\.5 {
|
||||
height: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
|
|
@ -989,6 +1001,9 @@
|
|||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.max-h-48 {
|
||||
max-height: calc(var(--spacing) * 48);
|
||||
}
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
|
@ -1166,6 +1181,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);
|
||||
|
|
@ -1178,6 +1197,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);
|
||||
|
|
@ -1304,6 +1327,13 @@
|
|||
.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;
|
||||
|
|
@ -1740,6 +1770,9 @@
|
|||
.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)) {
|
||||
|
|
@ -1803,6 +1836,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)) {
|
||||
|
|
@ -1824,6 +1860,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)) {
|
||||
|
|
@ -1866,6 +1905,9 @@
|
|||
.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)) {
|
||||
|
|
@ -2837,6 +2879,12 @@
|
|||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + 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);
|
||||
}
|
||||
.shadow-black {
|
||||
--tw-shadow-color: #000;
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, var(--color-black) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
.shadow-black\/20 {
|
||||
--tw-shadow-color: color-mix(in srgb, #000 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
|
|
@ -2849,6 +2897,9 @@
|
|||
--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)) {
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 15 KiB |
Loading…
Reference in New Issue