main
Ilham Wara Nugroho 2026-01-09 10:24:53 +07:00
parent 5aa9413e63
commit 9f596bf9e4
13 changed files with 1459 additions and 319 deletions

View File

@ -1,214 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Calculation;
use App\Enums\SigdStatus;
use App\Models\ActivityLock;
use App\Models\ReferenceWs;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class EmisiCalculationQueue extends Command
{
protected $signature = 'calculation:process';
protected $description = 'Process for the worksheet emission calculation queue';
private $logFilePath;
public function __construct()
{
parent::__construct();
}
public function handle()
{
set_time_limit(0);
try {
$processing = Calculation::where('calculation_status', SigdStatus::PROSES)->rowActive()->first();
if ($processing) {
return;
}
$calculation = Calculation::where('calculation_status', SigdStatus::PENDING)
->rowActive()->orderBy('created_at', 'asc')->first();
if (!$calculation) {
// $this->logMessage('info', 'Semua kalkulasi emisi telah berhasil diselesaikan.');
return;
}
$calculation->update([
'executed_time' => now(),
'calculation_status' => SigdStatus::PROSES,
]);
$this->createLogFile($calculation);
$this->logMessage('info', 'Kalkulasi Emisi untuk Tahun Inventory: ' . $calculation->inventory_year . ', id: ' . $calculation->id);
$isLocked = ActivityLock::isLocked($calculation->inventory_year);
if ($isLocked) {
$this->logMessage('warning', 'Kalkulasi Emisi untuk Tahun Inventory: ' . $calculation->inventory_year . ', id: ' . $calculation->id . ' sedang dikunci dan tidak dapat melakukan kalkulasi.');
$calculation->update([
'finished_time' => now(),
'calculation_status' => SigdStatus::GAGAL,
]);
return;
}
if ($calculation->energy) $this->executeService('energy', $calculation);
if ($calculation->agriculture) $this->executeService('agriculture', $calculation);
if ($calculation->folu) $this->executeService('folu', $calculation);
if ($calculation->waste) $this->executeService('waste', $calculation);
if ($calculation->ippu) $this->executeService('ippu', $calculation);
// Execute Energy GPC emission calculation
$this->energyGPCCalc($calculation);
// Execute mapping IPCC to GPC
$this->gpcMapping($calculation);
$calculation->update([
'finished_time' => now(),
'calculation_status' => SigdStatus::SELESAI,
]);
$this->logMessage('info', 'Kalkulasi Emisi untuk Tahun Inventory: ' . $calculation->inventory_year . ', id: ' . $calculation->id . ' berhasil diselesaikan.');
} catch (Exception $e) {
$this->logMessage('error', 'Terjadi kesalahan saat kalkulasi emisi, id: ' . $calculation->id . '. Error: ' . $e->getMessage());
throw $e;
}
}
private function createLogFile(Calculation $calculation)
{
$logDirectory = storage_path('logs/calculation/');
$this->logMessage('info', 'Checking if log directory exists: ' . $logDirectory);
if (!File::exists($logDirectory)) {
$this->logMessage('info', 'Directory not found. Creating directory: ' . $logDirectory);
File::makeDirectory($logDirectory, 0755, true);
}
$logFileName = $logDirectory . $calculation->inventory_year . '_' . $calculation->id . '.log';
file_put_contents($logFileName, '');
$this->logFilePath = $logFileName;
$this->logMessage('info', 'Log file created: ' . $logFileName);
}
private function executeService($sector, $calculation)
{
// if ($calculation->$sector) {
$wsList = ReferenceWs::where('sector', $sector)
->whereNotNull('code')->rowActive()->get();
foreach ($wsList as $ws) {
if ($ws->code == '4d1_ef') {
continue;
}
$service = "\\App\\Services\\Emisi\\" . class_basename($ws->model) . "Service";
$this->logMessage('service yang dijalankan', "{$service}");
try {
$serviceClass = app($service);
if (method_exists($service, 'save')) {
$serviceClass->save($ws->code, $calculation->inventory_year);
$this->logMessage('info', "Kalkulasi Emisi pada Worksheet {$ws->ws_code}. {$ws->ws_title} telah berhasil diselesaikan.");
} else {
$this->logMessage('error', "{$service} tidak memiliki method proses kalkulasi.");
}
} catch (\Exception $e) {
$this->logMessage('error', "Error saat menjalankan {$service} pada sektor {$sector}: " . $e->getMessage());
$calculation->update([
'executed_time' => null,
'finished_time' => null,
'calculation_status' => SigdStatus::PENDING,
]);
throw $e;
}
}
// }
}
private function gpcMapping($calculation)
{
$service = "\\App\\Services\\Emisi\\GpcMappingService";
try {
$serviceClass = app($service);
if (method_exists($service, 'save')) {
$serviceClass->save($calculation->inventory_year);
$this->logMessage('info', "Mapping dari Emisi IPCC ke GPC telah berhasil diproses");
} else {
$this->logMessage('error', get_class($serviceClass) . " tidak memiliki method save.");
}
} catch (\Exception $e) {
$this->logMessage('error', "Error saat menjalankan " . get_class($serviceClass) . ": " . $e->getMessage());
$calculation->update([
'executed_time' => null,
'finished_time' => null,
'calculation_status' => SigdStatus::PENDING,
]);
throw $e;
}
}
private function energyGPCCalc($calculation)
{
$service = "\\App\\Services\\Emisi\\EnergyGPCService";
try {
$serviceClass = app($service);
if (method_exists($service, 'save')) {
$serviceClass->save($calculation->inventory_year);
$this->logMessage('info', "Kalkulasi Emisi Energi GPC telah berhasil diproses");
} else {
$this->logMessage('error', get_class($serviceClass) . " tidak memiliki method save.");
}
} catch (\Exception $e) {
$this->logMessage('error', "Error saat menjalankan " . get_class($serviceClass) . ": " . $e->getMessage());
$calculation->update([
'executed_time' => null,
'finished_time' => null,
'calculation_status' => SigdStatus::PENDING,
]);
throw $e;
}
}
private function logMessage($level, $message)
{
$this->line($message); // Output to console
// Log to the file
$formattedMessage = '[' . now() . '] ' . strtoupper($level) . ': ' . $message . PHP_EOL;
if ($this->logFilePath) {
file_put_contents($this->logFilePath, $formattedMessage, FILE_APPEND);
}
$this->info($message);
// Also log to Laravel's default log channel
if ($level === 'error') {
Log::error($message);
}
// } else {
// Log::info($message);
// }
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers;
use App\Services\DashboardAdaptationService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class DashboardAdaptasiController extends Controller
{
protected $title = 'Dashboard Adaptasi';
// protected $template = 'modules.dashboard.adaptasi';
protected $route = 'modules.dashboard.adaptasi';
protected DashboardAdaptationService $adaptationService;
public function __construct(DashboardAdaptationService $adaptationService)
{
$this->adaptationService = $adaptationService;
}
public function aksi(Request $request)
{
$year = (int) $request->input('adaptationYear', date('Y'));
try {
$dashboardData = $this->adaptationService->getDashboardData($year);
return view('auth.dashboard-adaptation-aksi', [
'title' => $this->title,
'route' => $this->route,
'stats' => $dashboardData['stats'],
'charts' => $dashboardData['charts'],
'today' => Carbon::today()->translatedFormat('d F Y'),
]);
} catch(\Exception $e) {
Log::error('Error loading aksi mitigasi dashboard', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return back()->withErrors(['error' => 'Gagal memuat dashboard aksi mitigasi.']);
}
}
public function index(Request $request)
{
$year = (int) $request->input('adaptationYear', date('Y'));
try {
$dashboardData = $this->adaptationService->getDashboardData($year);
return view('auth.dashboard-adaptation', [
'title' => $this->title,
'route' => $this->route,
'selectedYear' => $dashboardData['adaptationYear'],
'tableData' => $dashboardData['tableData'], // ⬅️ collection biasa
'barData' => $dashboardData['barData'],
'realisasiData' => $dashboardData['realisasiData'],
'budgetData' => $dashboardData['budgetData'],
]);
} catch (\Exception $e) {
Log::error('Error loading dashboard adaptation data', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
// optional: kasih feedback ke user biar nggak blank
return back()->withErrors(['error' => 'Gagal memuat dashboard adaptasi.']);
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\DashboardService;
use Illuminate\Support\Facades\Log;
class DashboardController extends Controller
{
protected $title = 'Dashboard Inventory';
protected $template = 'auth';
protected $route = 'modules.dashboard.inventory';
protected $emissionService;
public function __construct(DashboardService $emissionService)
{
$this->emissionService = $emissionService;
}
public function index(Request $request)
{
try {
$data['inventoryYear'] = $request->input('year', date('Y'));
$data['dashboardData'] = $this->emissionService->getDashboardData($data['inventoryYear']);
$data['title'] = $this->title;
$data['route'] = $this->route;
return view($this->template.'.dashboard', $data);
} catch (\Exception $e) {
Log::error('Error loading dashboard data', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
}
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers;
use App\Services\DashboardMitigationService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class DashboardMitigasiController extends Controller
{
protected $title = 'Dashboard Mitigasi';
// protected $template = 'modules.dashboard.mitigasi';
protected $route = 'modules.dashboard.mitigasi';
protected DashboardMitigationService $mitigationService;
public function __construct(DashboardMitigationService $mitigationService)
{
$this->mitigationService = $mitigationService;
}
public function aksi(Request $request)
{
$year = (int) $request->input('mitigationYear', date('Y'));
try {
$dashboardData = $this->mitigationService->getDashboardData($year);
return view('auth.dashboard-mitigation-aksi', [
'title' => $this->title,
'route' => $this->route,
'stats' => $dashboardData['stats'],
'charts' => $dashboardData['charts'],
'today' => Carbon::today()->translatedFormat('d F Y'),
]);
} catch(\Exception $e) {
Log::error('Error loading aksi mitigasi dashboard', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return back()->withErrors(['error' => 'Gagal memuat dashboard aksi mitigasi.']);
}
}
public function index(Request $request)
{
$year = (int) $request->input('mitigationYear', date('Y'));
try {
$dashboardData = $this->mitigationService->getDashboardData($year);
return view('auth.dashboard-mitigation', [
'title' => $this->title,
'route' => $this->route,
'selectedYear' => $dashboardData['mitigationYear'],
'tableData' => $dashboardData['tableData'],
'barData' => $dashboardData['barData'],
'budgetData' => $dashboardData['budgetData'],
'realisasiData' => $dashboardData['realisasiData'],
]);
} catch(\Exception $e) {
Log::error('Error loading dashboard mitigation data', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
// optional: kasih feedback ke user biar nggak blank
return back()->withErrors(['error' => 'Gagal memuat dashboard mitigasi.']);
}
}
}

View File

@ -1,101 +0,0 @@
<?php
namespace App\Services;
use App\Models\ActivityCrf;
use App\Models\ReferenceGwp;
use Illuminate\Support\Collection;
class DashboardService
{
public function getDashboardData($inventoryYear)
{
$gwpData = $this->getActiveGwpRecords()->keyBy('ghg_code');
$years = activityYearRange($inventoryYear, false, 10);
$crfData = ActivityCrf::with(['ws.form', 'sektor'])
->where('inventory_year', $inventoryYear)
->whereBetween('activity_year', [$years[0], end($years)])
->orderBy('ws_code', 'asc')->get();
$aggregatedData = $this->aggregateCrfData($crfData, $gwpData);
return [
'cardData' => $this->calculateCardData($aggregatedData, $inventoryYear),
'barData' => $this->calculateBarData($aggregatedData, $years),
'pieData' => $this->calculatePieData($aggregatedData, $inventoryYear),
'tableData' => $this->calculateTableData($aggregatedData, $inventoryYear),
'inventoryYear' => $inventoryYear,
];
}
private function aggregateCrfData(Collection $crfData, Collection $gwpData)
{
return $crfData->map(function ($data) use ($gwpData) {
$sector = $data->sector;
$name = $sector === 'energy' ? $data->ws->form->name : $data->sektor->name;
return [
'year' => $data->activity_year,
'sector' => $sector,
'name' => $name,
'co2eq' => $data->co2eq,
'co2' => $data->co2,
'ch4' => $data->ch4,
'n2o' => $data->n2o,
];
});
}
private function calculateCardData(Collection $aggregatedData, $inventoryYear)
{
$sectorSums = $aggregatedData->where('year', $inventoryYear - 1)->groupBy('sector')->map->sum('co2eq');
return [
'totalEmisi' => $aggregatedData->where('year', $inventoryYear - 1)->sum('co2eq'),
'emisiEnergi' => $sectorSums->get('energy', 0),
'emisiPertanian' => $sectorSums->get('agriculture', 0),
'emisiLahan' => $sectorSums->get('folu', 0),
'emisiLimbah' => $sectorSums->get('waste', 0),
];
}
private function calculateBarData(Collection $aggregatedData, array $years)
{
return array_map(function ($year) use ($aggregatedData) {
$yearlyData = $aggregatedData->where('year', $year)->groupBy('name')->map->sum('co2eq');
$total = $yearlyData->sum();
return [
'year' => $year,
'total' => $total,
'emisi' => $yearlyData,
];
}, $years);
}
private function calculatePieData(Collection $aggregatedData, $inventoryYear)
{
return $aggregatedData->where('year', $inventoryYear - 1)->groupBy('name')->map->sum('co2eq');
}
private function calculateTableData(Collection $aggregatedData, $inventoryYear)
{
return $aggregatedData->where('year', $inventoryYear - 1)->groupBy('name')->map(function ($groupedData) {
return [
'co2eq' => $groupedData->sum('co2eq'),
'co2' => $groupedData->sum('co2'),
'ch4' => $groupedData->sum('ch4'),
'n2o' => $groupedData->sum('n2o'),
];
});
}
private function getActiveGwpRecords()
{
return ReferenceGwp::whereHas('ar', function ($query) {
$query->isActive();
})->get();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class AdaptationYearSelect extends Component
{
public $selectedYear;
public $name;
public function __construct($selectedYear = null, $name = null)
{
$this->selectedYear = $selectedYear;
$this->name = $name;
}
public function render(): View|Closure|string
{
return view('modules.components.adaptation-year-select');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class MitigationYearSelect extends Component
{
public $selectedYear;
public $name;
public function __construct($selectedYear = null, $name = null)
{
$this->selectedYear = $selectedYear;
$this->name = $name;
}
public function render(): View|Closure|string
{
return view('modules.components.mitigation-year-select');
}
}

View File

@ -0,0 +1,142 @@
@extends('layouts.master')
@section('title', 'Aksi Perubahan Iklim')
@section('content')
<div class="container-fluid my-4" style="font-family: 'Inter', sans-serif;">
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0 font-weight-bold text-teal">AKSI PERUBAHAN IKLIM</h5>
<small class="text-muted">
Last update: <span class="text-danger">{{ $today }}</span>
</small>
</div>
<!-- Statistik Atas -->
<div class="row text-center mb-4">
<div class="col-md-4 mb-4">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL AKSI</h6>
<h2 class="text-success">{{ number_format($stats['total_aksi'], 0, ',', '.') }}</h2>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL ALOKASI</h6>
<h2 class="text-success">{{ number_format($stats['total_anggaran']/1000000000, 1, ',', '.') }}</h2>
<small>MILIAR IDR</small>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL REALISASI</h6>
<h2 class="text-success">{{ number_format($stats['total_alokasi']/1000000000, 1, ',', '.') }}</h2>
<small>MILIAR IDR</small>
</div>
</div>
</div>
@php
$colors = ['#a98ded', '#5ab4e5', '#f6d55c', '#ed6a5a', '#6cc070', '#ff8c42'];
$labelsAksi = collect($charts['aksi'])->pluck('sektor')->toArray();
$valuesAksi = collect($charts['aksi'])->pluck('persen')->toArray();
$labelsAnggaran = collect($charts['anggaran'])->pluck('sektor')->toArray();
$valuesAnggaran = collect($charts['anggaran'])->pluck('persen')->toArray();
$labelsAlokasi = collect($charts['alokasi'])->pluck('sektor')->toArray();
$valuesAlokasi = collect($charts['alokasi'])->pluck('persen')->toArray();
@endphp
<!-- Bagian Chart -->
<div class="row">
<!-- Chart Aksi -->
<div class="col-md-4 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL AKSI</div>
<div class="card-body">
<canvas id="chartAksi"></canvas>
<div class="mt-4">
<small>
@foreach($charts['aksi'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
<!-- Chart Anggaran -->
<div class="col-md-4 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL ALOKASI</div>
<div class="card-body">
<canvas id="chartAnggaran"></canvas>
<div class="mt-4">
<small>
@foreach($charts['anggaran'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
<!-- Chart Alokasi -->
<div class="col-md-4 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL REALISASI</div>
<div class="card-body">
<canvas id="chartAlokasi"></canvas>
<div class="mt-4">
<small>
@foreach($charts['alokasi'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('js')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const colors = @json($colors);
const configPie = (labels, data) => ({
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: colors.slice(0, labels.length),
}]
}
});
new Chart(document.getElementById('chartAksi'), configPie(@json($labelsAksi), @json($valuesAksi)));
new Chart(document.getElementById('chartAnggaran'), configPie(@json($labelsAnggaran), @json($valuesAnggaran)));
new Chart(document.getElementById('chartAlokasi'), configPie(@json($labelsAlokasi), @json($valuesAlokasi)));
</script>
@endsection

View File

@ -0,0 +1,287 @@
@extends('layouts.master')
@section('content')
<div class="card shadow-sm" style="font-family: 'Inter', sans-serif;">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0 font-weight-bold">{{ @$title }}</h5>
<form
method="GET"
id="yearForm"
action="{{ route($route.'.index') }}"
class="d-inline"
>
<x-adaptation-year-select
:selectedYear="$selectedYear"
name="adaptationYear"
onchange="document.getElementById('yearForm').submit()"
/>
</form>
</div>
<div class="card-body">
<!-- Alerts -->
<div class="row flex justify-content-end items-center ml-6 mb-4">
<div class="flex gap-2 mr-4">
<a href="{{ route('modules.formAdaptasi.create', ['reset' => 1]) }}">
<button class="button-action btn btn-success">Aksi Baru</button>
</a>
<a href="{{ route($route.'.aksi') }}">
<button class="button-action btn btn-primary">
Aksi Perubahan Iklim
</button>
</a>
</div>
</div>
<br/>
{{-- Charts Row --}}
<div class="row mb-5">
<div class="col-lg-4">
<p class="h6">AKSI</p>
<canvas id="aksiChart" height="100"></canvas>
</div>
<div class="col-lg-4">
<p class="h6">ALOKASI (Miliar Rupiah)</p>
<canvas id="anggaranChart" height="100"></canvas>
</div>
<div class="col-lg-4">
<p class="h6">REALISASI (Miliar Rupiah)</p>
<canvas id="realisasiChart" height="100"></canvas>
</div>
</div>
{{-- Table --}}
<div class="table-responsive">
<table id="adaptasiTable" class="table table-bordered table-striped align-middle">
<thead class="table-info text-white text-center">
<tr>
<th>Nama Kegiatan</th>
<th>Tahun</th>
<th>Bidang PRK</th>
<th>Sub- Bidang PRK</th>
<th style="width:80px;">Aksi</th>
</tr>
</thead>
<tbody>
@foreach($tableData as $a)
<tr>
<td>{{ $a->nama_kegiatan }}</td>
<td class="text-center">{{ $a->tahun_kegiatan }}</td>
<td>{{ $a->sektor }}</td>
<td>{{ $a->sub_sektor }}</td>
<td class="text-center">
<a href="{{ route($route.'adaptasi.view', $a->id) }}" class="text-secondary me-2">
<i class="ti ti-eye"></i>
</a>
<a href="{{ route($route.'adaptasi.edit', $a->id) }}" class="text-secondary">
<i class="ti ti-pencil"></i>
</a>
</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
@endsection
@section('css')
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css"/>
<style>
#map { min-height: 600px; }
.leaflet-container { background: #fff; }
</style>
@endsection
@section('js')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// === DataTables init ===
$('#adaptasiTable').DataTable({
pageLength: 10,
ordering: true,
searching: true,
lengthChange: true,
language: {
search: "Cari:",
lengthMenu: "Tampilkan _MENU_ data per halaman",
zeroRecords: "Tidak ada data ditemukan",
emptyTable: "Tidak ada data.",
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
infoEmpty: "Tidak ada data tersedia",
infoFiltered: "(difilter dari _MAX_ total data)"
},
footerCallback: function (row, data, start, end, display) {
$(this.api().column(0).footer()).html('TOTAL (Semua Tahun {{ $selectedYear }})');
}
});
// === Util warna untuk dataset ===
function getColor(i) {
const palette = [
'rgba(255,99,132,0.6)',
'rgba(54,162,235,0.6)',
'rgba(255,206,86,0.6)',
'rgba(75,192,192,0.6)',
'rgba(153,102,255,0.6)',
'rgba(255,159,64,0.6)',
'rgba(199,199,199,0.6)',
'rgba(100,181,246,0.6)',
'rgba(76,175,80,0.6)',
'rgba(121,85,72,0.6)'
];
return palette[i % palette.length];
}
// === Formatter Miliar Rupiah (2 desimal, trailing zero dipangkas oleh Intl) ===
// function fmtMiliarFromBillion(b) {
// return new Intl.NumberFormat('id-ID', { maximumFractionDigits: 2 }).format(b) + ' M';
// }
function parseIdrToNumber(v) {
if (typeof v === 'number') return v;
if (v == null) return 0;
const s = String(v).replace(/\s/g,'').replace(/\./g,'').replace(',', '.');
const n = Number(s);
return isNaN(n) ? 0 : n;
}
function toBillion(v) { return parseIdrToNumber(v) / 1_000_000_000; }
function pickDecimals(maxAbs) {
if (maxAbs >= 1) return 2;
if (maxAbs >= 0.1) return 3;
if (maxAbs >= 0.01) return 4;
if (maxAbs >= 0.001) return 5;
return 6;
}
function fmtMiliar(v, d) {
return new Intl.NumberFormat('id-ID', { maximumFractionDigits: d }).format(v) + ' M';
}
// === Chart AKSI (bukan rupiah) ===
const barData = @json($barData);
const years = barData.map(d => d.year);
const sectors = barData.length ? Object.keys(barData[0].jumlah || {}) : [];
const aksiDatasets = sectors.map((sec, idx) => ({
label: sec,
data: years.map(y => {
const row = barData.find(d => d.year === y) || {};
return (row.jumlah && row.jumlah[sec]) ? row.jumlah[sec] : 0;
}),
backgroundColor: getColor(idx),
borderColor: getColor(idx).replace('0.6','1'),
borderWidth: 1
}));
new Chart(document.getElementById('aksiChart'), {
type: 'bar',
data: { labels: years, datasets: aksiDatasets },
options: {
plugins: {
legend: { position: 'top' },
tooltip: { callbacks: { label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y}` } }
},
scales: {
x: { stacked: true },
y: { stacked: true, beginAtZero: true, title: { display: true, text: 'Jumlah Kegiatan' } }
}
}
});
// === Chart Anggaran & Realisasi (Rupiah → Miliar) ===
const budget = @json($budgetData);
const realisasi = @json($realisasiData);
const labels = ['APBN','APBD','Swasta','Lain-lain'];
const anggaranValuesB = [
toBillion(budget?.apbn), toBillion(budget?.apbd),
toBillion(budget?.swasta), toBillion(budget?.lainlain)
];
const realisasiValuesB = [
toBillion(realisasi?.apbn), toBillion(realisasi?.apbd),
toBillion(realisasi?.swasta), toBillion(realisasi?.lainlain)
];
// Tentukan jumlah desimal per chart berdasarkan nilai maksimum
const decAnggaran = pickDecimals(Math.max(...anggaranValuesB.map(Math.abs), 0));
const decRealisasi = pickDecimals(Math.max(...realisasiValuesB.map(Math.abs), 0));
const colors = ['#0575E6','#FFA500','#8A2BE2','#00BFA6'];
const realisasiColors = ['#7ed957','#ffd966','#bdb2ff','#80cbc4'];
function createBarChart(canvasId, datasetLabel, valuesB, colorsArr, decimals) {
const ctx = document.getElementById(canvasId).getContext('2d');
const datasets = labels.map((label, i) => ({
label: label,
data: [valuesB[i]], // satu nilai per kategori
backgroundColor: colorsArr[i],
datalabels: {
anchor: 'end',
align: 'right',
formatter: v => fmtMiliar(v, decimals),
clip: false
}
}));
new Chart(ctx, {
type: 'bar',
data: {
labels: [datasetLabel], // cuma 1 kategori "Anggaran" atau "Realisasi"
datasets: datasets
},
options: {
indexAxis: 'y',
plugins: {
legend: { display: true, position: 'top' },
tooltip: {
callbacks: {
label: (ctx) => {
const v = ctx.parsed.x ?? ctx.parsed.y ?? 0;
return `${ctx.dataset.label}: ${fmtMiliar(v, decimals)}`;
}
}
}
},
scales: {
x: {
beginAtZero: true,
suggestedMax: Math.max(...valuesB) * 1.1 || 1,
title: { display: true, text: 'Miliar Rupiah (M)' },
ticks: {
callback: (value) => fmtMiliar(value, decimals)
}
},
y: { ticks: { autoSkip: false } }
}
},
plugins: [ ChartDataLabels ]
});
}
createBarChart('anggaranChart', '', anggaranValuesB, colors, decAnggaran);
createBarChart('realisasiChart', '', realisasiValuesB, realisasiColors, decRealisasi);
});
</script>
@endsection

View File

@ -0,0 +1,174 @@
@extends('layouts.master')
@section('title', 'Aksi Perubahan Iklim')
@section('content')
<div class="container-fluid my-4" style="font-family: 'Inter', sans-serif;">
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0 font-weight-bold text-teal">AKSI PERUBAHAN IKLIM</h5>
<small class="text-muted">
Last update: <span class="text-danger">{{ $today }}</span>
</small>
</div>
<!-- Statistik Atas -->
<div class="row text-center mb-4">
<div class="col-md-3 mb-3">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL AKSI</h6>
<h2 class="text-success">{{ number_format($stats['total_aksi'], 0, ',', '.') }}</h2>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL PENURUNAN EMISI</h6>
<h2 class="text-success">{{ number_format($stats['total_emisi'], 0, ',', '.') }}</h2>
<small>TON CO₂</small>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL ALOKASI</h6>
<h2 class="text-success">{{ number_format($stats['total_anggaran']/1000000000, 1, ',', '.') }}</h2>
<small>MILIAR IDR</small>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="p-3 border rounded bg-light">
<h6 class="text-muted">TOTAL REALISASI</h6>
<h2 class="text-success">{{ number_format($stats['total_alokasi']/1000000000, 1, ',', '.') }}</h2>
<small>MILIAR IDR</small>
</div>
</div>
</div>
@php
$colors = ['#a98ded', '#5ab4e5', '#f6d55c', '#ed6a5a', '#6cc070', '#ff8c42'];
$labelsAksi = collect($charts['aksi'])->pluck('sektor')->toArray();
$valuesAksi = collect($charts['aksi'])->pluck('persen')->toArray();
$labelsEmisi = collect($charts['emisi'])->pluck('sektor')->toArray();
$valuesEmisi = collect($charts['emisi'])->pluck('persen')->toArray();
$labelsAnggaran = collect($charts['anggaran'])->pluck('sektor')->toArray();
$valuesAnggaran = collect($charts['anggaran'])->pluck('persen')->toArray();
$labelsAlokasi = collect($charts['alokasi'])->pluck('sektor')->toArray();
$valuesAlokasi = collect($charts['alokasi'])->pluck('persen')->toArray();
@endphp
<!-- Bagian Chart -->
<div class="row">
<!-- Chart Aksi -->
<div class="col-md-3 mb-3">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL AKSI</div>
<div class="card-body">
<canvas id="chartAksi"></canvas>
<div class="mt-3">
<small>
@foreach($charts['aksi'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
<!-- Chart Emisi -->
<div class="col-md-3 mb-3">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL PENURUNAN EMISI</div>
<div class="card-body">
<canvas id="chartEmisi"></canvas>
<div class="mt-3">
<small>
@foreach($charts['emisi'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
<!-- Chart Anggaran -->
<div class="col-md-3 mb-3">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL ALOKASI</div>
<div class="card-body">
<canvas id="chartAnggaran"></canvas>
<div class="mt-3">
<small>
@foreach($charts['anggaran'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
<!-- Chart Alokasi -->
<div class="col-md-3 mb-3">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white text-center">TOTAL REALISASI</div>
<div class="card-body">
<canvas id="chartAlokasi"></canvas>
<div class="mt-3">
<small>
@foreach($charts['alokasi'] as $i => $row)
<span class="d-block">
<span style="color: {{ $colors[$i % count($colors)] }};">
{{ $row['persen'] }}%
</span> {{ $row['sektor'] }}
</span>
@endforeach
</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('js')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const colors = @json($colors);
const configPie = (labels, data) => ({
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: colors.slice(0, labels.length),
}]
}
});
new Chart(document.getElementById('chartAksi'), configPie(@json($labelsAksi), @json($valuesAksi)));
new Chart(document.getElementById('chartEmisi'), configPie(@json($labelsEmisi), @json($valuesEmisi)));
new Chart(document.getElementById('chartAnggaran'), configPie(@json($labelsAnggaran), @json($valuesAnggaran)));
new Chart(document.getElementById('chartAlokasi'), configPie(@json($labelsAlokasi), @json($valuesAlokasi)));
</script>
@endsection

View File

@ -0,0 +1,285 @@
@extends('layouts.master')
@section('content')
<div class="card shadow-sm" style="font-family: 'Inter', sans-serif;">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Dasbor Mitigasi</h5>
<form
method="GET"
id="yearForm"
action="{{ route('modules.dashboard.mitigasi.index') }}"
class="d-inline"
>
<x-mitigation-year-select
:selectedYear="$selectedYear"
name="mitigationYear"
onchange="document.getElementById('yearForm').submit()"
/>
</form>
</div>
<div class="card-body">
<div class="row flex justify-content-end items-center ml-6 mb-4">
<div class="flex gap-2 mr-4">
<a href="{{ route('modules.formMitigasi.create', ['reset' => 1]) }}">
<button class="button-action btn btn-success">
Aksi Baru
</button>
</a>
<a href="{{ route($route.'.aksi') }}">
<button class="button-action btn btn-primary text-white hover:bg-green-700">
Aksi Perubahan Iklim
</button>
</a>
</div>
</div>
<br/>
{{-- Charts Row --}}
<div class="row mb-5">
<div class="col-lg-4">
<p class="h6">AKSI</p>
<canvas id="aksiChart" height="100"></canvas>
</div>
<div class="col-lg-4">
<p class="h6">ALOKASI (Miliar Rupiah)</p>
<canvas id="anggaranChart" height="100"></canvas>
</div>
<div class="col-lg-4">
<p class="h6">REALISASI (Miliar Rupiah)</p>
<canvas id="realisasiChart" height="100"></canvas>
</div>
</div>
{{-- Table --}}
<div class="table-responsive">
<table id="mitigasiTable" class="table table-bordered table-striped align-middle">
<thead class="table-info text-white text-center">
<tr>
<th>Tipe Kegiatan</th>
<th>Nama Kegiatan</th>
<th>Tahun</th>
<th>Sektor</th>
<th>Sub-Sektor</th>
<th>Kategori</th>
<th class="text-end">Penurunan Emisi</th>
<th class="text-center">Revisi</th>
<th style="width:80px;"></th>
</tr>
</thead>
<tbody>
@foreach($tableData as $m)
<tr>
<td>{{ $m->tipe_kegiatan }}</td>
<td>{{ Str::limit($m->nama_kegiatan, 60) }}</td>
<td class="text-center">{{ $m->tahun_kegiatan }}</td>
<td>{{ $m->sektor }}</td>
<td>{{ $m->sub_sektor }}</td>
<td>{{ $m->kategori_perhitungan }}</td>
<td class="text-end">
{{ $m->emission_factor }}
</td>
<td class="text-center">{{ $m->revisi ?? '--' }}</td>
<td class="text-center">
<a href="{{ route($route.'.view', $m->id) }}" class="text-secondary me-2">
<i class="ti ti-eye"></i>
</a>
<a href="{{ route($route.'.edit', $m->id) }}" class="text-secondary">
<i class="ti ti-pencil"></i>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endsection
@section('css')
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css"/>
@endsection
@section('js')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// === DataTables (unchanged) ===
$('#mitigasiTable').DataTable({
pageLength: 10, ordering: true, searching: true, lengthChange: true,
columnDefs: [
{ targets: 6, className: 'text-end' },
{ targets: 7, className: 'text-center' },
{ targets: 8, className: 'text-center', orderable: false, searchable: false }
],
language: {
search: "Cari:", lengthMenu: "Tampilkan _MENU_ data per halaman",
zeroRecords: "Tidak ada data ditemukan", emptyTable: "Tidak ada data.",
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
infoEmpty: "Tidak ada data tersedia",
infoFiltered: "(difilter dari _MAX_ total data)"
},
footerCallback: function () {
const api = this.api();
$(api.column(0).footer()).html('TOTAL (Semua Tahun {{ $selectedYear }})');
const parseIdNumber = (v) => {
if (typeof v === 'number') return v;
if (!v) return 0;
const s = String(v).replace(/\./g, '').replace(',', '.');
const n = parseFloat(s);
return isNaN(n) ? 0 : n;
};
const total = api.column(6, { search: 'applied' }).data().toArray()
.reduce((sum, val) => sum + parseIdNumber(val), 0);
const formatted = new Intl.NumberFormat('id-ID', { maximumFractionDigits: 6 }).format(total);
$(api.column(6).footer()).html(formatted);
}
});
// === Colors ===
function getColor(i) {
const p = [
'rgba(255,99,132,0.6)','rgba(54,162,235,0.6)','rgba(255,206,86,0.6)',
'rgba(75,192,192,0.6)','rgba(153,102,255,0.6)','rgba(255,159,64,0.6)',
'rgba(199,199,199,0.6)','rgba(100,181,246,0.6)','rgba(76,175,80,0.6)',
'rgba(121,85,72,0.6)'
];
return p[i % p.length];
}
// === Parser & Formatter (IDR → Miliar, desimal adaptif) ===
function parseIdrToNumber(v) {
if (typeof v === 'number') return v;
if (v == null) return 0;
const s = String(v).replace(/\s/g,'').replace(/\./g,'').replace(',', '.');
const n = Number(s);
return isNaN(n) ? 0 : n;
}
function toBillion(v) { return parseIdrToNumber(v) / 1_000_000_000; }
function pickDecimals(maxAbs) {
if (maxAbs >= 1) return 2;
if (maxAbs >= 0.1) return 3;
if (maxAbs >= 0.01) return 4;
if (maxAbs >= 0.001) return 5;
return 6;
}
function fmtMiliar(v, d) {
return new Intl.NumberFormat('id-ID', { maximumFractionDigits: d }).format(v) + ' M';
}
// === Chart AKSI (bukan rupiah) ===
const barData = @json($barData);
const years = barData.map(d => d.year);
const sectors = barData.length ? Object.keys(barData[0].jumlah || {}) : [];
const aksiDatasets = sectors.map((sec, idx) => ({
label: sec,
data: years.map(y => {
const row = barData.find(d => d.year === y) || {};
return (row.jumlah && row.jumlah[sec]) ? row.jumlah[sec] : 0;
}),
backgroundColor: getColor(idx),
borderColor: getColor(idx).replace('0.6','1'),
borderWidth: 1
}));
new Chart(document.getElementById('aksiChart'), {
type: 'bar',
data: { labels: years, datasets: aksiDatasets },
options: {
plugins: {
legend: { position: 'top' },
tooltip: { callbacks: { label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y}` } }
},
scales: {
x: { stacked: true },
y: { stacked: true, beginAtZero: true, title: { display: true, text: 'Jumlah Kegiatan' } }
}
}
});
// === Chart Anggaran & Realisasi (Rupiah → Miliar) ===
const budget = @json($budgetData);
const realisasi = @json($realisasiData);
const labels = ['APBN','APBD','Swasta','Lain-lain'];
const anggaranValuesB = [
toBillion(budget?.apbn), toBillion(budget?.apbd),
toBillion(budget?.swasta), toBillion(budget?.lainlain)
];
const realisasiValuesB = [
toBillion(realisasi?.apbn), toBillion(realisasi?.apbd),
toBillion(realisasi?.swasta), toBillion(realisasi?.lainlain)
];
// Tentukan jumlah desimal per chart berdasarkan nilai maksimum
const decAnggaran = pickDecimals(Math.max(...anggaranValuesB.map(Math.abs), 0));
const decRealisasi = pickDecimals(Math.max(...realisasiValuesB.map(Math.abs), 0));
const colors = ['#0575E6','#FFA500','#8A2BE2','#00BFA6'];
const realisasiColors = ['#7ed957','#ffd966','#bdb2ff','#80cbc4'];
function createBarChart(canvasId, datasetLabel, valuesB, colorsArr, decimals) {
const ctx = document.getElementById(canvasId).getContext('2d');
const datasets = labels.map((label, i) => ({
label: label,
data: [valuesB[i]], // satu nilai per kategori
backgroundColor: colorsArr[i],
datalabels: {
anchor: 'end',
align: 'right',
formatter: v => fmtMiliar(v, decimals),
clip: false
}
}));
new Chart(ctx, {
type: 'bar',
data: {
labels: [datasetLabel], // cuma 1 kategori "Anggaran" atau "Realisasi"
datasets: datasets
},
options: {
indexAxis: 'y',
plugins: {
legend: { display: true, position: 'top' },
tooltip: {
callbacks: {
label: (ctx) => {
const v = ctx.parsed.x ?? ctx.parsed.y ?? 0;
return `${ctx.dataset.label}: ${fmtMiliar(v, decimals)}`;
}
}
}
},
scales: {
x: {
beginAtZero: true,
suggestedMax: Math.max(...valuesB) * 1.1 || 1,
title: { display: true, text: 'Miliar Rupiah (M)' },
ticks: {
callback: (value) => fmtMiliar(value, decimals)
}
},
y: { ticks: { autoSkip: false } }
}
},
plugins: [ ChartDataLabels ]
});
}
createBarChart('anggaranChart', '', anggaranValuesB, colors, decAnggaran);
createBarChart('realisasiChart', '', realisasiValuesB, realisasiColors, decRealisasi);
});
</script>
@endsection

View File

@ -0,0 +1,335 @@
<?php
$cardData = $dashboardData['cardData'];
$tableData = $dashboardData['tableData'];
$pieData = $dashboardData['pieData'];
$barData = $dashboardData['barData'];
?>
@extends('layouts.master')
@section('content')
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0 font-weight-bold">{{ @$title }}</h5>
</div>
<div class="card-body">
<!-- Filter Tahun Inventory -->
<div class="row justify-content-end mb-4">
<div class="col-md-2">
<x-inventory-year-select :selected-year="$inventoryYear" />
</div>
</div>
<!-- Cards -->
<div class="row justify-content-center">
<div class="col-lg-2 col-md-4 mb-4">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<h6 class="card-title text-muted">Total Emisi</h6>
<p class="card-text" style="font-size: 20px;" id="totalEmisi">
{{ getFormattedValue(@$cardData['totalEmisi'], 2) }}</p>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4 mb-4">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<h6 class="card-title text-muted">Emisi Sektor Energi</h6>
<p class="card-text" style="font-size: 20px;" id="emisiEnergi">
{{ getFormattedValue(@$cardData['emisiEnergi'], 2) }}</p>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4 mb-4">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<h6 class="card-title text-muted">Emisi Sektor Pertanian</h6>
<p class="card-text" style="font-size: 20px;" id="emisiPertanian">
{{ getFormattedValue(@$cardData['emisiPertanian'], 2) }}</p>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4 mb-4">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<h6 class="card-title text-muted">Emisi Sektor Lahan</h6>
<p class="card-text" style="font-size: 20px;" id="emisiLahan">
{{ getFormattedValue(@$cardData['emisiLahan'], 2) }}</p>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4 mb-4">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<h6 class="card-title text-muted">Emisi Sektor Limbah</h6>
<p class="card-text" style="font-size: 20px;" id="emisiLimbah">
{{ getFormattedValue(@$cardData['emisiLimbah'], 2) }}</p>
</div>
</div>
</div>
</div>
<br />
<!-- Charts Row -->
<div class="row mb-4">
<!-- Bar Chart -->
<div class="col-lg-6 mb-4">
<canvas id="barEmissionChart" height="160"></canvas>
</div>
<!-- Pie Chart -->
<div class="col-lg-6 mb-4">
<canvas id="pieEmissionChart" height="80"></canvas>
</div>
</div>
<!-- Emissions Table -->
<div class="row mt-4">
<div class="col-12">
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead class="table-info text-center">
<tr>
<th style="width: 10px;"></th>
<th class="align-middle">Emissions</th>
<th width="120">CO<sub>2</sub> Eq<br>(Gg)</th>
<th width="120">CO<sub>2</sub><br>(Gg)</th>
<th width="120">CH<sub>4</sub><br>(Gg)</th>
<th width="120">N<sub>2</sub>O<br>(Gg)</th>
</tr>
</thead>
<tbody>
@php
$totalCo2eq = 0;
$totalCo2 = 0;
$totalCh4 = 0;
$totalN2o = 0;
$no = 1;
@endphp
@if(@$tableData)
@foreach ($tableData as $sector => $emissions)
@php
$totalCo2eq += $emissions['co2eq'];
$totalCo2 += $emissions['co2'];
$totalCh4 += $emissions['ch4'];
$totalN2o += $emissions['n2o'];
@endphp
<tr class="text-right">
<td class="text-left">{{ $no++ }}</td>
<td class="text-left">{{ $sector }}</td>
<td>{{ getFormattedValue($emissions['co2eq'], 2) }}</td>
<td>{{ getFormattedValue($emissions['co2'], 2) }}</td>
<td>{{ getFormattedValue($emissions['ch4'], 2) }}</td>
<td>{{ getFormattedValue($emissions['n2o'], 2) }}</td>
</tr>
@endforeach
@endif
<tr class="text-right">
<td class="text-left"></td>
<td class="text-left"><strong>TOTAL</strong></td>
<td><strong>{{ getFormattedValue($totalCo2eq, 2) }}</strong></td>
<td><strong>{{ getFormattedValue($totalCo2, 2) }}</strong></td>
<td><strong>{{ getFormattedValue($totalCh4, 2) }}</strong></td>
<td><strong>{{ getFormattedValue($totalN2o, 2) }}</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('js')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<script>
$(document).ready(function() {
$('#inventoryYear').select2({
placeholder: 'Pilih Tahun',
width: '100%'
});
$('#inventoryYear').on('change', function() {
var selectedYear = $(this).val();
window.location.href = '{{ url()->current() }}?year=' + selectedYear;
});
// Define color palette
function getColor(index) {
const colors = [
'rgba(255, 99, 132, 0.6)', // Red
'rgba(54, 162, 235, 0.6)', // Blue
'rgba(255, 206, 86, 0.6)', // Yellow
'rgba(75, 192, 192, 0.6)', // Green
'rgba(153, 102, 255, 0.6)', // Purple
'rgba(205, 159, 64, 0.6)', // Orange
'rgba(199, 199, 199, 0.6)', // Gray
'rgba(0, 255, 0, 0.6)', // Light Green
'rgba(83, 102, 120, 0.6)', // Dark Gray
];
return colors[index % colors.length];
}
// Prepare data for the bar chart
var emissionsByYear = @json(@$barData);
var labels = emissionsByYear.map(function(e) {
return e.year;
});
var data = {
labels: labels,
datasets: []
};
var yearTotalMap = {};
emissionsByYear.forEach(function(yearData) {
yearTotalMap[yearData.year] = yearData.total;
});
// Collecting sector keys (categories) for chart labels
var sectorKeys = Object.keys(emissionsByYear[0].emisi);
sectorKeys.forEach(function(key, index) {
var sectorData = emissionsByYear.map(function(e) {
return e.emisi[key] || 0;
});
data.datasets.push({
label: key.charAt(0).toUpperCase() + key.slice(1),
data: sectorData,
backgroundColor: getColor(index),
borderColor: getColor(index).replace('0.6', '1'),
borderWidth: 1
});
});
var ctx = document.getElementById('barEmissionChart').getContext('2d');
var barChart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
}
},
plugins: {
tooltip: {
callbacks: {
title: function(tooltipItems) {
// Use the first tooltip item to get the year
return 'Year: ' + tooltipItems[0].label;
},
label: function(context) {
var label = context.dataset.label || '';
var value = context.raw.toFixed(2);
var year = labels[context.dataIndex];
var total = yearTotalMap[year].toFixed(2);
return [
label + ': ' + value + ' Gg CO2',
'Total: ' + total + ' Gg CO2'
];
}
}
},
legend: {
position: 'top',
align: 'start',
usePointStyle: true,
labels: {
boxWidth: 20,
},
},
},
responsive: true,
layout: {
padding: {
left: 40 // Adjust this value to create space above the chart to avoid overlap with y-axis labels
}
},
// maintainAspectRatio: false
}
});
var pieEmissions = @json(@$pieData);
var pieData = {
labels: Object.keys(pieEmissions),
datasets: [{
data: Object.values(pieEmissions),
backgroundColor: Object.keys(pieEmissions).map((_, index) => getColor(index)),
borderColor: Object.keys(pieEmissions).map((_, index) => getColor(index).replace(
'0.6', '1')),
borderWidth: 1
}]
};
var pieCtx = document.getElementById('pieEmissionChart').getContext('2d');
var pieChart = new Chart(pieCtx, {
type: 'doughnut',
data: pieData,
// plugins: [ChartDataLabels],
options: {
plugins: {
// datalabels: {
// formatter: (value, context) => {
// var total = context.chart.data.datasets[0].data.reduce((acc, cur) =>
// acc + cur, 0);
// var percentage = ((value / total) * 100).toFixed(2) + '%';
// return percentage;
// },
// color: '#000', // Set the text color
// font: {
// weight: 'bold',
// size: 10
// },
// anchor: 'center', // Position the label in the center of the slice
// align: 'center' // Align the label to the middle of the slice
// },
tooltip: {
callbacks: {
label: function(context) {
var label = context.label || '';
var value = context.raw.toFixed(2);
var total = Object.values(pieEmissions).reduce((acc, cur) => acc +
cur, 0).toFixed(2);
var percentage = ((context.raw / total) * 100).toFixed(2);
return [
label + ': ' + value + ' Gg CO2 (' + percentage + '%)',
'Total: ' + total + ' Gg CO2'
];
}
}
},
legend: {
position: 'right',
labels: {
boxWidth: 20,
usePointStyle: true,
}
}
},
layout: {
padding: {
right: 40 // Adjust this value to create space above the chart to avoid overlap with y-axis labels
}
},
cutout: '50%',
responsive: true,
maintainAspectRatio: false
}
});
});
</script>
@endsection

View File

@ -1,9 +1,9 @@
<?php <?php
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use App\Http\Controllers\Dashboard\DashboardAdaptasiController; use App\Http\Controllers\DashboardAdaptasiController;
use App\Http\Controllers\Dashboard\DashboardInventoryController; use App\Http\Controllers\DashboardController;
use App\Http\Controllers\Dashboard\DashboardMitigasiController; use App\Http\Controllers\DashboardMitigasiController;
use App\Http\Controllers\Management\UserController; use App\Http\Controllers\Management\UserController;
use App\Http\Controllers\Management\UserSekolahController; use App\Http\Controllers\Management\UserSekolahController;
use App\Http\Controllers\Management\RoleController; use App\Http\Controllers\Management\RoleController;
@ -59,7 +59,7 @@ Route::get('dashboard',[HomeController::class,'index'])->name('index');
Route::name('dashboard.')->prefix('dashboard')->group(function () { Route::name('dashboard.')->prefix('dashboard')->group(function () {
Route::name('inventory.')->prefix('inventory')->group(function () { Route::name('inventory.')->prefix('inventory')->group(function () {
Route::resource('/',DashboardInventoryController::class); Route::resource('/',DashboardController::class);
}); });
Route::name('mitigasi.')->prefix('mitigasi')->group(function () { Route::name('mitigasi.')->prefix('mitigasi')->group(function () {
Route::resource('/',DashboardMitigasiController::class); Route::resource('/',DashboardMitigasiController::class);