refactor: chart Total Sampah per Bulan (BSI, BSU, Offtaker)
parent
62912c2a43
commit
9622ee1809
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using BankSampahApp.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace BankSampahApp.Controllers.Main
|
namespace BankSampahApp.Controllers.Main
|
||||||
{
|
{
|
||||||
|
|
@ -7,12 +8,69 @@ namespace BankSampahApp.Controllers.Main
|
||||||
{
|
{
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
return View("~/Views/Main/Dashboard/Index.cshtml");
|
var model = BuildDashboardChartModel();
|
||||||
|
return View("~/Views/Main/Dashboard/Index.cshtml", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Bsu()
|
public IActionResult Bsu()
|
||||||
{
|
{
|
||||||
return View("~/Views/Main/Dashboard/Bsu.cshtml");
|
return View("~/Views/Main/Dashboard/Bsu.cshtml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dummy chart data agar view dapat menampilkan grafik tanpa ketergantungan pada API.
|
||||||
|
/// </summary>
|
||||||
|
private static DashboardChartViewModel BuildDashboardChartModel()
|
||||||
|
{
|
||||||
|
var monthLabels = new List<string>
|
||||||
|
{
|
||||||
|
"Januari", "Februari", "Maret", "April", "Mei", "Juni",
|
||||||
|
"Juli", "Agustus", "September", "Oktober", "November", "Desember"
|
||||||
|
};
|
||||||
|
|
||||||
|
var baseDatasets = new Dictionary<string, List<int>>
|
||||||
|
{
|
||||||
|
["BSI"] = new() { 520, 540, 560, 590, 620, 650, 680, 710, 740, 770, 800, 830 },
|
||||||
|
["BSU"] = new() { 610, 630, 640, 670, 690, 720, 760, 780, 810, 830, 860, 890 },
|
||||||
|
["Offtaker"] = new() { 1050, 1080, 1100, 1140, 1180, 1220, 1260, 1300, 1340, 1380, 1420, 1480 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var years = Enumerable.Range(2020, 6).ToList();
|
||||||
|
var firstYear = years.Min();
|
||||||
|
var yearlyData = new Dictionary<int, DashboardChartPayload>();
|
||||||
|
|
||||||
|
foreach (var year in years)
|
||||||
|
{
|
||||||
|
var offset = year - firstYear;
|
||||||
|
var payload = new DashboardChartPayload
|
||||||
|
{
|
||||||
|
Labels = new List<string>(monthLabels)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var dataset in baseDatasets)
|
||||||
|
{
|
||||||
|
var random = new Random(HashCode.Combine(year, dataset.Key));
|
||||||
|
payload.Datasets.Add(new DashboardChartDataset
|
||||||
|
{
|
||||||
|
Label = dataset.Key,
|
||||||
|
Data = dataset.Value
|
||||||
|
.Select(baseValue =>
|
||||||
|
{
|
||||||
|
var variation = random.Next(-100, 120) + offset * 30;
|
||||||
|
return Math.Max(0, baseValue + variation);
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
yearlyData[year] = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DashboardChartViewModel
|
||||||
|
{
|
||||||
|
YearlyData = yearlyData,
|
||||||
|
SelectedYear = yearlyData.Keys.Max()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BankSampahApp.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// View model yang menampung data grafik dashboard beserta opsi filter tahunan.
|
||||||
|
/// </summary>
|
||||||
|
public class DashboardChartViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data grafik per tahun (key berupa tahun).
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<int, DashboardChartPayload> YearlyData { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tahun yang dipilih saat halaman pertama kali dimuat.
|
||||||
|
/// </summary>
|
||||||
|
public int SelectedYear { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Daftar tahun yang tersedia untuk filter, disortir menurun.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<int> AvailableYears => YearlyData.Keys.OrderByDescending(x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Struktur data payload untuk chart per tahun.
|
||||||
|
/// </summary>
|
||||||
|
public class DashboardChartPayload
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Label (umumnya nama bulan) yang ditampilkan pada sumbu X.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Labels { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dataset yang ditampilkan pada grafik.
|
||||||
|
/// </summary>
|
||||||
|
public List<DashboardChartDataset> Datasets { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dataset individual untuk Chart.js.
|
||||||
|
/// </summary>
|
||||||
|
public class DashboardChartDataset
|
||||||
|
{
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
public List<int> Data { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
@{
|
@model BankSampahApp.Models.DashboardChartViewModel
|
||||||
|
@using System.Text.Json
|
||||||
|
@{
|
||||||
ViewData["Title"] = "Dashboard";
|
ViewData["Title"] = "Dashboard";
|
||||||
|
var chartJson = JsonSerializer.Serialize(Model.YearlyData, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="flex gap-2 flex-row justify-between md:gap-0 mt-2 mb-2">
|
<div class="flex gap-2 flex-row justify-between md:gap-0 mt-2 mb-2">
|
||||||
|
|
@ -223,14 +229,35 @@
|
||||||
|
|
||||||
<div class="h-6"></div>
|
<div class="h-6"></div>
|
||||||
|
|
||||||
<div class="card bg-white shadow-sm">
|
<div class="card bg-white">
|
||||||
<div class="card-body pt-4 px-3">
|
<div class="card-body">
|
||||||
<div class="flex flex-col gap-2 md:flex-row md:justify-between md:gap-0">
|
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between md:gap-4">
|
||||||
<div class="prose">
|
<div class="prose max-w-2xl">
|
||||||
<span class="text-xl font-semibold text-gray-900 font-['Plus_Jakarta_Sans']">
|
<span class="text-xl font-semibold text-gray-900 font-['Plus_Jakarta_Sans']">
|
||||||
Data Terverifikasi
|
Grafik Total Sampah per Bulan (BSI, BSU, Offtaker)
|
||||||
</span>
|
</span>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
|
Menampilkan total tonase sampah yang dikelola setiap bulan berdasarkan sumber BSI, BSU, dan Offtaker.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<fieldset class="fieldset w-full lg:w-48">
|
||||||
|
<label class="fieldset-label text-gray-500 text-xs uppercase tracking-wide" for="chartYear">
|
||||||
|
Filter Tahun
|
||||||
|
</label>
|
||||||
|
<select id="chartYear" class="select select-sm" onchange="handleYearChange(event)">
|
||||||
|
@foreach (var year in Model.AvailableYears)
|
||||||
|
{
|
||||||
|
if (year == Model.SelectedYear)
|
||||||
|
{
|
||||||
|
<option value="@year" selected>@year</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@year">@year</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full overflow-x-auto">
|
<div class="w-full overflow-x-auto">
|
||||||
<div class="min-w-[900px]">
|
<div class="min-w-[900px]">
|
||||||
|
|
@ -243,4 +270,106 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="~/js/chart.js"></script>
|
<script src="~/js/chart.js"></script>
|
||||||
<script src="/plugins/chartjs/setup.js"></script>
|
<script>
|
||||||
|
const chartDataByYear = @Html.Raw(chartJson);
|
||||||
|
const chartCanvas = document.getElementById('chartBankSampah');
|
||||||
|
const chartCtx = chartCanvas.getContext('2d');
|
||||||
|
let chartInstance = null;
|
||||||
|
|
||||||
|
const datasetStyles = {
|
||||||
|
BSI: {
|
||||||
|
borderColor: '#247332',
|
||||||
|
gradientStart: 'rgba(36, 115, 50, 1)',
|
||||||
|
gradientEnd: 'rgba(136, 194, 147, 0.25)'
|
||||||
|
},
|
||||||
|
BSU: {
|
||||||
|
borderColor: '#386BCA',
|
||||||
|
gradientStart: 'rgba(56, 107, 202, 1)',
|
||||||
|
gradientEnd: 'rgba(138, 164, 242, 0.25)'
|
||||||
|
},
|
||||||
|
Offtaker: {
|
||||||
|
borderColor: '#F79009',
|
||||||
|
gradientStart: 'rgba(247, 144, 9, 1)',
|
||||||
|
gradientEnd: 'rgba(255, 219, 172, 0.25)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultDatasetStyle = {
|
||||||
|
borderColor: '#0F172A',
|
||||||
|
gradientStart: 'rgba(15, 23, 42, 0.85)',
|
||||||
|
gradientEnd: 'rgba(15, 23, 42, 0.15)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildDatasets = (payload) => payload.datasets.map((dataset) => {
|
||||||
|
const style = datasetStyles[dataset.label] ?? defaultDatasetStyle;
|
||||||
|
const gradient = chartCtx.createLinearGradient(0, 0, 0, chartCanvas.clientHeight || 400);
|
||||||
|
gradient.addColorStop(0, style.gradientStart);
|
||||||
|
gradient.addColorStop(1, style.gradientEnd);
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: dataset.label,
|
||||||
|
data: dataset.data,
|
||||||
|
borderColor: style.borderColor,
|
||||||
|
backgroundColor: gradient,
|
||||||
|
borderWidth: 2,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderChart = (year) => {
|
||||||
|
const payload = chartDataByYear[year];
|
||||||
|
if (!payload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedData = {
|
||||||
|
labels: payload.labels,
|
||||||
|
datasets: buildDatasets(payload)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.data = updatedData;
|
||||||
|
chartInstance.update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = new Chart(chartCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: updatedData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (context) => `${context.dataset.label}: ${context.formattedValue} Kg`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
callback: (value) => `${value} Kg`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.handleYearChange = (event) => {
|
||||||
|
renderChart(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
renderChart(@Model.SelectedYear);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue