feat: chart Total Data Terverifikasi & Aktif Transaksi

main
Yuri Dimas 2025-12-09 18:15:20 +07:00
parent 9622ee1809
commit d55bca99b9
No known key found for this signature in database
GPG Key ID: 9FD7E44BC294C68C
3 changed files with 224 additions and 5 deletions

View File

@ -35,9 +35,27 @@ namespace BankSampahApp.Controllers.Main
["Offtaker"] = new() { 1050, 1080, 1100, 1140, 1180, 1220, 1260, 1300, 1340, 1380, 1420, 1480 }
};
var entities = new List<string> { "BSI", "BSU", "Offtaker" };
var verifiedActiveBase = new Dictionary<string, (List<int> Verified, List<int> Active)>
{
["BSI"] = (
new List<int> { 400, 420, 460, 500, 520, 560, 580, 600, 640, 660, 700, 720 },
new List<int> { 300, 320, 350, 370, 390, 420, 440, 460, 500, 520, 550, 580 }
),
["BSU"] = (
new List<int> { 380, 400, 420, 450, 470, 500, 520, 550, 580, 600, 640, 670 },
new List<int> { 250, 270, 300, 330, 350, 380, 400, 420, 450, 480, 500, 530 }
),
["Offtaker"] = (
new List<int> { 600, 620, 640, 660, 700, 720, 750, 780, 820, 850, 880, 910 },
new List<int> { 420, 440, 460, 500, 520, 540, 580, 600, 640, 670, 700, 740 }
)
};
var years = Enumerable.Range(2020, 6).ToList();
var firstYear = years.Min();
var yearlyData = new Dictionary<int, DashboardChartPayload>();
var verifiedActiveYearlyData = new Dictionary<int, Dictionary<string, VerifiedActiveChartPayload>>();
foreach (var year in years)
{
@ -64,12 +82,40 @@ namespace BankSampahApp.Controllers.Main
}
yearlyData[year] = payload;
var entityPayloads = new Dictionary<string, VerifiedActiveChartPayload>();
foreach (var entity in entities)
{
var basePair = verifiedActiveBase[entity];
var verifiedRandom = new Random(HashCode.Combine(year, entity, "verified"));
var activeRandom = new Random(HashCode.Combine(year, entity, "active"));
var verifiedData = basePair.Verified
.Select(baseValue => Math.Max(0, baseValue + verifiedRandom.Next(-60, 90) + offset * 25))
.ToList();
var activeData = basePair.Active
.Select(baseValue => Math.Max(0, baseValue + activeRandom.Next(-40, 75) + offset * 20))
.ToList();
entityPayloads[entity] = new VerifiedActiveChartPayload
{
Labels = new List<string>(monthLabels),
VerifiedData = verifiedData,
ActiveData = activeData
};
}
verifiedActiveYearlyData[year] = entityPayloads;
}
return new DashboardChartViewModel
{
YearlyData = yearlyData,
SelectedYear = yearlyData.Keys.Max()
SelectedYear = yearlyData.Keys.Max(),
VerifiedActiveYearlyData = verifiedActiveYearlyData,
Entities = entities,
SelectedEntity = entities.FirstOrDefault() ?? string.Empty
};
}
}

View File

@ -22,6 +22,21 @@ public class DashboardChartViewModel
/// Daftar tahun yang tersedia untuk filter, disortir menurun.
/// </summary>
public IEnumerable<int> AvailableYears => YearlyData.Keys.OrderByDescending(x => x);
/// <summary>
/// Data grafik verifikasi vs aktif transaksi per tahun dan entitas.
/// </summary>
public Dictionary<int, Dictionary<string, VerifiedActiveChartPayload>> VerifiedActiveYearlyData { get; set; } = new();
/// <summary>
/// Daftar entitas yang dapat difilter.
/// </summary>
public List<string> Entities { get; set; } = new();
/// <summary>
/// Entitas yang dipilih secara default.
/// </summary>
public string SelectedEntity { get; set; } = string.Empty;
}
/// <summary>
@ -48,3 +63,13 @@ public class DashboardChartDataset
public string Label { get; set; } = string.Empty;
public List<int> Data { get; set; } = new();
}
/// <summary>
/// Payload untuk grafik data terverifikasi &amp; aktif transaksi.
/// </summary>
public class VerifiedActiveChartPayload
{
public List<string> Labels { get; set; } = new();
public List<int> VerifiedData { get; set; } = new();
public List<int> ActiveData { get; set; } = new();
}

View File

@ -2,10 +2,9 @@
@using System.Text.Json
@{
ViewData["Title"] = "Dashboard";
var chartJson = JsonSerializer.Serialize(Model.YearlyData, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
var serializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
var chartJson = JsonSerializer.Serialize(Model.YearlyData, serializerOptions);
var verificationTrendJson = JsonSerializer.Serialize(Model.VerifiedActiveYearlyData, serializerOptions);
}
<div class="flex gap-2 flex-row justify-between md:gap-0 mt-2 mb-2">
@ -269,6 +268,68 @@
</div>
</div>
<div class="h-6"></div>
<div class="card bg-white">
<div class="card-body">
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div class="prose max-w-2xl">
<span class="text-xl font-semibold text-gray-900 font-['Plus_Jakarta_Sans']">
Total Data Terverifikasi & Aktif Transaksi
</span>
<p class="mt-1 text-sm text-gray-500">
Pantau tren jumlah entitas yang sudah terverifikasi dan aktif bertransaksi setiap bulan.
</p>
</div>
<div class="flex flex-col gap-3 md:flex-row">
<fieldset class="fieldset w-full md:w-40">
<label class="fieldset-label text-gray-500 text-xs uppercase tracking-wide" for="verificationYear">
Filter Tahun
</label>
<select id="verificationYear" class="select select-sm" onchange="handleVerificationFilterChange()">
@foreach (var year in Model.AvailableYears)
{
if (year == Model.SelectedYear)
{
<option value="@year" selected>@year</option>
}
else
{
<option value="@year">@year</option>
}
}
</select>
</fieldset>
<fieldset class="fieldset w-full md:w-48">
<label class="fieldset-label text-xs uppercase tracking-wide text-gray-500" for="verificationEntity">
Jenis Entitas
</label>
<select id="verificationEntity" class="select select-sm" onchange="handleVerificationFilterChange()">
@foreach (var entity in Model.Entities)
{
if (entity == Model.SelectedEntity)
{
<option value="@entity" selected>@entity</option>
}
else
{
<option value="@entity">@entity</option>
}
}
</select>
</fieldset>
</div>
</div>
<div class="w-full overflow-x-auto">
<div class="min-w-[900px]">
<div class="h-64 md:h-96 relative">
<canvas id="chartVerificationTrend" class="w-full h-full"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="~/js/chart.js"></script>
<script>
const chartDataByYear = @Html.Raw(chartJson);
@ -365,11 +426,98 @@
});
};
const verificationChartData = @Html.Raw(verificationTrendJson);
const verificationChartCanvas = document.getElementById('chartVerificationTrend');
const verificationChartCtx = verificationChartCanvas ? verificationChartCanvas.getContext('2d') : null;
let verificationChartInstance = null;
const buildVerificationDatasets = (payload) => [
{
label: 'Data Terverifikasi',
data: payload.verifiedData,
backgroundColor: '#22C55E',
borderColor: '#16A34A',
borderWidth: 1
},
{
label: 'Data Aktif Transaksi',
data: payload.activeData,
backgroundColor: '#3B82F6',
borderColor: '#2563EB',
borderWidth: 1
}
];
const renderVerificationChart = (year, entity) => {
if (!verificationChartCtx) {
return;
}
const yearData = verificationChartData[year];
const payload = yearData ? yearData[entity] : null;
if (!payload) {
return;
}
const updatedData = {
labels: payload.labels,
datasets: buildVerificationDatasets(payload)
};
if (verificationChartInstance) {
verificationChartInstance.data = updatedData;
verificationChartInstance.update();
return;
}
verificationChartInstance = new Chart(verificationChartCtx, {
type: 'bar',
data: updatedData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
},
tooltip: {
callbacks: {
label: (context) => `${context.dataset.label}: ${context.formattedValue} Unit`
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
ticks: {
callback: (value) => `${value} Unit`
}
}
}
},
});
};
window.handleYearChange = (event) => {
renderChart(event.target.value);
};
window.handleVerificationFilterChange = () => {
const yearSelect = document.getElementById('verificationYear');
const entitySelect = document.getElementById('verificationEntity');
if (!yearSelect || !entitySelect) {
return;
}
renderVerificationChart(yearSelect.value, entitySelect.value);
};
document.addEventListener('DOMContentLoaded', () => {
renderChart(@Model.SelectedYear);
renderVerificationChart(@Model.SelectedYear, @Html.Raw(JsonSerializer.Serialize(Model.SelectedEntity)));
});
</script>