524 lines
20 KiB
Plaintext
524 lines
20 KiB
Plaintext
@model BankSampahApp.Models.DashboardChartViewModel
|
|
@using System.Text.Json
|
|
@{
|
|
ViewData["Title"] = "Dashboard";
|
|
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">
|
|
<div class="prose">
|
|
<span class="text-xl font-semibold text-black">Dashboard Dinas</span>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button class="btn btn-sm rounded-full bg-white" onclick="modal_filter.showModal()">
|
|
<i class="ph ph-funnel-simple"></i>
|
|
Filter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="h-6"></div>
|
|
|
|
<div class="card bg-white md:bg-[url(/images/image-welcome-dashboard.png)] md:bg-local md:bg-right md:bg-no-repeat">
|
|
<div class="card-body gap-0">
|
|
<span class="text-gray-500">Selamat Datang</span>
|
|
<span class="bg-linear-to-r from-[#089A7B] to-[#46C881] bg-clip-text text-2xl font-semibold text-transparent">Admin Dimas</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="h-6"></div>
|
|
|
|
<!-- Modal Filter -->
|
|
<dialog id="modal_filter" class="modal modal-bottom sm:modal-middle">
|
|
<div class="modal-box w-full sm:max-w-sm">
|
|
<h3 class="text-lg font-bold">Filter</h3>
|
|
<form id="formFilter" onsubmit="applyFilter(event)">
|
|
<fieldset class="fieldset">
|
|
<legend class="fieldset-legend">
|
|
Kota
|
|
</legend>
|
|
<select id="kabupaten" class="select w-full" required>
|
|
<option value="" disabled selected>Pilih salah satu</option>
|
|
<option value="Kota Adm Jakarta Timur">Kota Adm Jakarta Timur</option>
|
|
<option value="Kota Adm Jakarta Barat">Kota Adm Jakarta Barat</option>
|
|
<option value="Kota Adm Jakarta Selatan">Kota Adm Jakarta Selatan</option>
|
|
<option value="Kota Adm Jakarta Utara">Kota Adm Jakarta Utara</option>
|
|
<option value="Kota Adm Jakarta Pusat">Kota Adm Jakarta Pusat</option>
|
|
</select>
|
|
</fieldset>
|
|
<fieldset class="fieldset">
|
|
<legend class="fieldset-legend">Kecamatan</legend>
|
|
<select id="filterKecamatan" class="select w-full">
|
|
<option value="">Pilih salah satu</option>
|
|
</select>
|
|
</fieldset>
|
|
<fieldset class="fieldset">
|
|
<legend class="fieldset-legend">Kelurahan</legend>
|
|
<select id="filterKelurahan" class="select w-full">
|
|
<option value="">Pilih salah satu</option>
|
|
</select>
|
|
</fieldset>
|
|
<fieldset class="fieldset">
|
|
<legend class="fieldset-legend">Rukun Warga(RW)</legend>
|
|
<select id="filterRW" class="select w-full">
|
|
<option value="">Pilih salah satu</option>
|
|
</select>
|
|
</fieldset>
|
|
<div class="modal-action">
|
|
<button type="button" class="btn btn-outline rounded-full" onclick="modal_filter.close()">Reset</button>
|
|
<button type="submit" class="btn bg-bank-sampah-primary-500 rounded-full text-white">Terapkan</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</dialog>
|
|
<!-- /modal filter -->
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
|
<div class="card bg-linear-to-tl from-[#88C293] to-[#247332] text-white">
|
|
<div class="card-body">
|
|
<div class="card-title">
|
|
<i class="ph ph-building-office text-2xl"></i>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="flex w-3/5 flex-col gap-0 self-end">
|
|
<span>Total BSI</span>
|
|
<span class="text-3xl">1203 Unit</span>
|
|
</div>
|
|
<div class="divider divider-horizontal before:bg-white after:bg-white"></div>
|
|
<div class="grid w-2/5 grid-rows-3 gap-2 text-[12px]">
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Sudah Verifikasi</span>
|
|
<span>145 Unit</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Belum Verifikasi</span>
|
|
<span>5432 Unit</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card bg-linear-to-tl from-indigo-200 to-blue-700 text-white">
|
|
<div class="card-body">
|
|
<div class="card-title">
|
|
<i class="ph ph-building-office text-2xl"></i>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="flex w-3/5 flex-col gap-0 self-end">
|
|
<span>Total BSU</span>
|
|
<span class="text-3xl">1203 Unit</span>
|
|
</div>
|
|
<div class="divider divider-horizontal before:bg-white after:bg-white"></div>
|
|
<div class="grid w-2/5 grid-rows-3 gap-2 text-[12px]">
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Sudah Aktif</span>
|
|
<span>145 Unit</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Belum Aktif</span>
|
|
<span>5432 Unit</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Ditolak</span>
|
|
<span>5432 Unit</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card bg-linear-to-tl from-amber-200 to-amber-700 text-white">
|
|
<div class="card-body">
|
|
<div class="card-title">
|
|
<i class="ph ph-factory text-2xl"></i>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="flex w-3/5 flex-col gap-0 self-end">
|
|
<span>Total Offtaker</span>
|
|
<span class="text-3xl">39 Unit</span>
|
|
</div>
|
|
<div class="divider divider-horizontal before:bg-white after:bg-white"></div>
|
|
<div class="grid w-2/5 grid-rows-3 gap-2 text-[12px]">
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Sudah Aktif</span>
|
|
<span>145 Unit</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Belum Aktif</span>
|
|
<span>5432 Unit</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Ditolak</span>
|
|
<span>5432 Unit</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card bg-linear-to-tl from-violet-200 to-violet-700 text-white">
|
|
<div class="card-body">
|
|
<div class="card-title">
|
|
<i class="ph ph-users-three text-2xl"></i>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="flex w-3/5 flex-col gap-0 self-end">
|
|
<span>Total Nasabah</span>
|
|
<span class="text-3xl">2780 Unit</span>
|
|
</div>
|
|
<div class="divider divider-horizontal before:bg-white after:bg-white"></div>
|
|
<div class="grid w-2/5 grid-rows-3 gap-2 text-[12px]">
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Sudah Aktif</span>
|
|
<span>145 Unit</span>
|
|
</div>
|
|
<div class="flex flex-col">
|
|
<span class="font-semibold">Belum Aktif</span>
|
|
<span>5432 Unit</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="h-6"></div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div class="stats bg-white">
|
|
<div class="stat">
|
|
<div class="stat-title">Total Sampah BSI</div>
|
|
<div class="stat-value">5,812 Kg</div>
|
|
<div class="stat-desc">
|
|
<span class="text-green-600">
|
|
<i class="ph ph-arrow-up"></i>
|
|
40%
|
|
</span>
|
|
vs bulan kemarin
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="stats bg-white">
|
|
<div class="stat">
|
|
<div class="stat-title">Total Sampah BSU</div>
|
|
<div class="stat-value">5,812 Kg</div>
|
|
<div class="stat-desc">
|
|
<span class="text-green-600">
|
|
<i class="ph ph-arrow-up"></i>
|
|
40%
|
|
</span>
|
|
vs bulan kemarin
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="stats bg-white">
|
|
<div class="stat">
|
|
<div class="stat-title">Total Sampah Offtaker</div>
|
|
<div class="stat-value">89.4 Kg</div>
|
|
<div class="stat-desc">
|
|
<span class="text-green-600">
|
|
<i class="ph ph-arrow-up"></i>
|
|
40%
|
|
</span>
|
|
vs bulan kemarin
|
|
</div>
|
|
</div>
|
|
</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 md:gap-4">
|
|
<div class="prose max-w-2xl">
|
|
<span class="text-xl font-semibold text-gray-900 font-['Plus_Jakarta_Sans']">
|
|
Grafik Total Sampah per Bulan (BSI, BSU, Offtaker)
|
|
</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>
|
|
<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 class="w-full overflow-x-auto">
|
|
<div class="min-w-[900px]">
|
|
<div class="h-64 md:h-96 relative">
|
|
<canvas id="chartBankSampah" class="w-full h-full"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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);
|
|
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`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
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>
|