laporan, excel, pdf

This commit is contained in:
Sveemaaa 2026-04-19 15:37:45 +07:00
parent bd18de6bcf
commit d2864db88a
8 changed files with 1053 additions and 13 deletions

View File

@ -0,0 +1,71 @@
<?php
namespace App\Exports;
use App\Models\PengukuranBalita;
use App\Models\PengukuranIbuHamil;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings; // <--- Cek ini
use Maatwebsite\Excel\Concerns\WithMapping;
class LaporanExport implements FromQuery, WithHeadings, WithMapping
{
protected $bulan, $tahun, $kategori;
public function __construct($bulan, $tahun, $kategori)
{
$this->bulan = $bulan;
$this->tahun = $tahun;
$this->kategori = $kategori;
}
public function query()
{
if ($this->kategori == 'balita') {
return PengukuranBalita::with('balita')
->whereMonth('tanggal', $this->bulan) // Harus pakai variabel ini!
->whereYear('tanggal', $this->tahun) // Harus pakai variabel ini!
->orderBy('tanggal', 'asc');
} else {
return PengukuranIbuHamil::with('ibuHamil')
->whereMonth('tanggal', $this->bulan)
->whereYear('tanggal', $this->tahun)
->orderBy('tanggal', 'asc');
}
}
public function headings(): array
{
if ($this->kategori == 'balita') {
return ["No", "Nama Balita", "Tanggal Ukur", "Usia (Bulan)", "BB (kg)", "TB (cm)", "ZS-TB/U", "Hasil/Status"];
}
return ["No", "Nama Ibu", "Tanggal Ukur", "Usia Hamil (Minggu)", "BB (kg)", "LILA (cm)", "IMT", "Status Gizi"];
}
public function map($item): array
{
static $no = 1;
if ($this->kategori == 'balita') {
return [
$no++,
$item->balita->nama ?? '-',
$item->tanggal,
$item->usia_saat_ukur,
$item->berat_badan,
$item->tinggi_badan,
$item->zs_tbu,
$item->hasil
];
}
return [
$no++,
$item->ibuHamil->nama ?? '-',
$item->tanggal,
$item->usia_kehamilan,
$item->berat_badan,
$item->lila,
$item->imt,
$item->status_gizi
];
}
}

View File

@ -3,26 +3,87 @@
namespace App\Http\Controllers;
use App\Models\PengukuranBalita;
use App\Models\PengukuranIbuHamil;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel; // Pastikan sudah install laravel-excel
use Barryvdh\DomPDF\PDF;
use App\Exports\LaporanExport; // Tambahkan ini
class LaporanController extends Controller
{
public function index(Request $request)
{
$query = PengukuranBalita::with('balita');
// 1. Ambil Parameter Filter (Default ke bulan & tahun sekarang)
$bulan = $request->get('bulan', date('n'));
$tahun = $request->get('tahun', date('Y'));
// Filter berdasarkan Status Stunting (Hasil AI)
if ($request->has('status') && $request->status != '') {
$query->where('hasil', $request->status);
// 2. Query Data Balita
$queryBalita = PengukuranBalita::with('balita')
->whereMonth('tanggal', $bulan)
->whereYear('tanggal', $tahun);
// 3. Query Data Ibu Hamil
$queryIbuHamil = PengukuranIbuHamil::with('ibuHamil')
->whereMonth('tanggal', $bulan)
->whereYear('tanggal', $tahun);
// 4. Hitung Statistik untuk Summary Cards
$stats = [
'total_balita' => $queryBalita->count(),
'total_ibu' => $queryIbuHamil->count(),
'stunting' => (clone $queryBalita)->whereIn('hasil', ['Stunting', 'Sangat Pendek', 'Pendek'])->count(),
'normal' => (clone $queryBalita)->where('hasil', 'Normal')->count(),
];
// 5. Eksekusi Data untuk Tabel
$laporanBalita = $queryBalita->latest()->get();
$laporanIbuHamil = $queryIbuHamil->latest()->get();
return view('laporan.index', compact('laporanBalita', 'laporanIbuHamil', 'stats', 'bulan', 'tahun'));
}
public function exportExcel(Request $request)
{
$bulan = $request->bulan;
$tahun = $request->tahun;
$kategori = $request->kategori;
// CEK: Apakah ada datanya?
if ($kategori == 'balita') {
$exists = PengukuranBalita::whereMonth('tanggal', $bulan)->whereYear('tanggal', $tahun)->exists();
} else {
$exists = PengukuranIbuHamil::whereMonth('tanggal', $bulan)->whereYear('tanggal', $tahun)->exists();
}
// Filter berdasarkan Bulan/Tahun (Optional)
if ($request->has('bulan') && $request->bulan != '') {
$query->whereMonth('tanggal', $request->bulan);
if (!$exists) {
return back()->with('error', 'Maat, tidak ada data pengukuran untuk periode yang dipilih.');
}
$laporan = $query->latest()->get();
return Excel::download(new LaporanExport($bulan, $tahun, $kategori), "Laporan_{$kategori}_{$bulan}_{$tahun}.xlsx");
}
return view('laporan.index', compact('laporan'));
public function exportPdf(Request $request)
{
$bulan = $request->bulan ?? date('n');
$tahun = $request->tahun ?? date('Y');
$kategori = $request->kategori;
if ($kategori == 'balita') {
$items = PengukuranBalita::with('balita')->whereMonth('tanggal', $bulan)->whereYear('tanggal', $tahun)->get();
$view = 'laporan.pdf_balita';
} else {
$items = PengukuranIbuHamil::with('ibuHamil')->whereMonth('tanggal', $bulan)->whereYear('tanggal', $tahun)->get();
$view = 'laporan.pdf_ibuhamil';
}
$data = [
'title' => 'LAPORAN BULANAN POSYANDU POSSEHAT',
'sub' => 'Kategori: ' . ucfirst($kategori) . " - Periode: $bulan/$tahun",
'date' => date('d/m/Y'),
'items' => $items
];
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView($view, $data);
return $pdf->download("Laporan_{$kategori}_{$bulan}_{$tahun}.pdf");
}
}

View File

@ -10,7 +10,8 @@
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8"
"laravel/tinker": "^2.8",
"maatwebsite/excel": "^3.1"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

592
project/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "50a9cf43c3c522104b200bfdbe2e0e54",
"content-hash": "081d5da149ebec75a83fc53f40468a8b",
"packages": [
{
"name": "barryvdh/laravel-dompdf",
@ -212,6 +212,162 @@
],
"time": "2023-12-11T17:09:12+00:00"
},
{
"name": "composer/pcre",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-11-12T16:29:46+00:00"
},
{
"name": "composer/semver",
"version": "3.4.4",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.4"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
}
],
"time": "2025-08-20T19:15:30+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@ -740,6 +896,67 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.19.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
},
"time": "2025-10-17T16:34:55+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.4.0",
@ -2120,6 +2337,271 @@
],
"time": "2024-09-21T08:32:55+00:00"
},
{
"name": "maatwebsite/excel",
"version": "3.1.68",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "1854739267d81d38eae7d8c623caf523f30f256b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/1854739267d81d38eae7d8c623caf523f30f256b",
"reference": "1854739267d81d38eae7d8c623caf523f30f256b",
"shasum": ""
},
"require": {
"composer/semver": "^3.3",
"ext-json": "*",
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0||^13.0",
"php": "^7.0||^8.0",
"phpoffice/phpspreadsheet": "^1.30.0",
"psr/simple-cache": "^1.0||^2.0||^3.0"
},
"require-dev": {
"laravel/scout": "^7.0||^8.0||^9.0||^10.0||^11.0",
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0||^11.0",
"predis/predis": "^1.1"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
},
"providers": [
"Maatwebsite\\Excel\\ExcelServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Maatwebsite\\Excel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Patrick Brouwers",
"email": "patrick@spartner.nl"
}
],
"description": "Supercharged Excel exports and imports in Laravel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel",
"php",
"phpspreadsheet"
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.68"
},
"funding": [
{
"url": "https://laravel-excel.com/commercial-support",
"type": "custom"
},
{
"url": "https://github.com/patrickbrouwers",
"type": "github"
}
],
"time": "2026-03-17T20:51:10+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "6187e9cc4493da94b9b63eb2315821552015fca9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/6187e9cc4493da94b9b63eb2315821552015fca9",
"reference": "6187e9cc4493da94b9b63eb2315821552015fca9",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.1"
},
"require-dev": {
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^10.0",
"vimeo/psalm": "^5.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.1"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"time": "2024-10-10T12:33:01+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "masterminds/html5",
"version": "2.10.0",
@ -2694,6 +3176,114 @@
],
"time": "2024-11-21T10:36:35+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.30.3",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "d28d4827f934469e7ca4de940ab0abd0788d1e65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/d28d4827f934469e7ca4de940ab0abd0788d1e65",
"reference": "d28d4827f934469e7ca4de940ab0abd0788d1e65",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": ">=7.4.0 <8.5.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"doctrine/instantiator": "^1.5",
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
},
{
"name": "Owen Leibman"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.3"
},
"time": "2026-04-10T03:47:16+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.5",

View File

@ -0,0 +1,261 @@
@extends('layouts.dashboard')
@section('title', 'Laporan Rekapitulasi')
@section('content')
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<div class="pagetitle mb-4">
<h1 style="color: #FF782D; font-weight: 700;">Laporan PosSehat</h1>
<p class="text-muted">Rekapitulasi data bulanan dan tahunan PosSehat.</p>
</div>
<div class="card shadow-sm border-0 mb-4" style="border-radius: 20px;">
<div class="card-body p-4">
<form action="{{ route('laporan.index') }}" method="GET" class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label fw-semibold small">Bulan</label>
<select name="bulan" class="form-select custom-input">
@foreach(range(1, 12) as $m)
<option value="{{ $m }}" {{ request('bulan', date('n')) == $m ? 'selected' : '' }}>
{{ date('F', mktime(0, 0, 0, $m, 1)) }}
</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-semibold small">Tahun</label>
<select name="tahun" class="form-select custom-input">
@foreach(range(date('Y')-2, date('Y')) as $y)
<option value="{{ $y }}" {{ request('tahun', date('Y')) == $y ? 'selected' : '' }}>{{ $y }}</option>
@endforeach
</select>
</div>
<div class="col-md-6 text-md-end">
<div class="btn-group shadow-sm rounded-pill overflow-hidden">
<button type="submit" class="btn btn-orange px-4"><i class="bi bi-filter me-1"></i> Filter</button>
<a id="exportExcelBtn" class="btn btn-success px-3" style="cursor: pointer;"><i class="bi bi-file-earmark-excel"></i> Excel</a>
<a id="exportPdfBtn" class="btn btn-danger px-3" style="cursor: pointer;"><i class="bi bi-file-earmark-pdf"></i> PDF</a>
</div>
</div>
</form>
</div>
</div>
{{-- <div class="row mb-4">
<div class="col-md-6 col-lg-3">
<div class="card border-0 shadow-sm p-3 card-stat">
<div class="d-flex align-items-center">
<div class="icon-stat bg-orange-light text-orange me-3"><i class="bi bi-people fs-4"></i></div>
<div><small class="text-muted d-block">Total Balita</small><h4 class="fw-bold mb-0">124</h4></div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card border-0 shadow-sm p-3 card-stat">
<div class="d-flex align-items-center">
<div class="icon-stat bg-pink-light text-danger me-3"><i class="bi bi-heart-pulse fs-4"></i></div>
<div><small class="text-muted d-block">Ibu Hamil</small><h4 class="fw-bold mb-0">45</h4></div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card border-0 shadow-sm p-3 card-stat" style="border-bottom: 3px solid #dc3545;">
<div class="d-flex align-items-center">
<div class="icon-stat bg-danger-soft text-danger me-3"><i class="bi bi-exclamation-circle fs-4"></i></div>
<div><small class="text-muted d-block">Indikasi Stunting</small><h4 class="fw-bold mb-0 text-danger">8</h4></div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card border-0 shadow-sm p-3 card-stat" style="border-bottom: 3px solid #28a745;">
<div class="d-flex align-items-center">
<div class="icon-stat bg-success-soft text-success me-3"><i class="bi bi-shield-check fs-4"></i></div>
<div><small class="text-muted d-block">Gizi Normal</small><h4 class="fw-bold mb-0 text-success">116</h4></div>
</div>
</div>
</div>
</div> --}}
<div class="card shadow-sm border-0" style="border-radius: 20px;">
<div class="card-body p-4">
<ul class="nav nav-pills mb-4 gap-2 bg-light p-2 rounded-pill" id="pills-tab" role="tablist" style="width: fit-content;">
<li class="nav-item">
<button class="nav-link active rounded-pill px-4" id="pills-balita-tab" data-bs-toggle="pill" data-bs-target="#pills-balita">Data Balita</button>
</li>
<li class="nav-item">
<button class="nav-link rounded-pill px-4" id="pills-ibu-tab" data-bs-toggle="pill" data-bs-target="#pills-ibu">Data Ibu Hamil</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-balita" role="tabpanel">
<h6 class="fw-bold mb-3">Rekapitulasi Pengukuran Balita</h6>
<div class="table-responsive">
<table class="table table-hover align-middle custom-table">
<thead>
<tr>
<th>No</th>
<th>NIK</th>
<th>Nama & Usia Balita</th>
<th>BB/TB</th>
<th>Z-Score TB/U</th>
<th>Status Gizi</th>
<th>Tanggal</th>
</tr>
</thead>
<tbody id="balita-table">
@forelse ($laporanBalita as $index => $item)
<tr>
<td class="fw-bold text-muted">{{ $index + 1 }}</td>
<td>{{ $item->balita->nik ?? '-' }}</td>
<td>
<div class="fw-bold text-dark">{{ $item->balita->nama ?? '-' }}</div>
<small class="text-muted">{{ $item->usia_saat_ukur }} Bulan</small>
</td>
<td>
<span class="badge bg-blue-soft text-primary">BB: {{ $item->berat_badan }}kg</span><br>
<span class="badge bg-green-soft text-success">TB: {{ $item->tinggi_badan }}cm</span>
</td>
<td><span class="badge bg-light text-dark">{{ $item->zs_tbu }}</span></td>
<td>
@php
$statusClass = match(strtolower($item->hasil)) {
'normal' => 'bg-success',
'pendek', 'sangat pendek' => 'bg-danger',
default => 'bg-warning'
};
@endphp
<span class="badge {{ $statusClass }} px-3 py-2" style="border-radius: 8px;">
{{ strtoupper($item->hasil) }}
</span>
</td>
<td>
<div class="small fw-semibold">{{ \Carbon\Carbon::parse($item->tanggal)->translatedFormat('d M Y') }}</div>
<small class="text-muted">{{ $item->petugas->nama ?? '-' }}</small>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center py-5 text-muted">Data pengukuran balita bulan ini kosong.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div> <div class="tab-pane fade" id="pills-ibu" role="tabpanel">
<h6 class="fw-bold mb-3">Rekapitulasi Pengukuran Ibu Hamil</h6>
<div class="table-responsive">
<table class="table table-hover align-middle custom-table">
<thead>
<tr>
<th style="width: 5%">No</th>
<th>Identitas Ibu</th>
<th>Usia Hamil</th>
<th>Fisik (BB/TB/LILA)</th>
<th class="text-center">IMT & Status</th>
<th>Tanggal & Petugas</th>
</tr>
</thead>
<tbody id="ibuhamil-table">
@forelse ($laporanIbuHamil as $index => $item)
<tr>
<td class="fw-bold text-muted">{{ $index + 1 }}</td>
<td>
<div class="fw-bold text-dark">{{ $item->ibuHamil->nama ?? '-' }}</div>
<small class="text-muted">{{ \Carbon\Carbon::parse($item->ibuHamil->tgl_lahir)->age }} Tahun</small>
</td>
<td><div class="text-dark-bold">{{ $item->usia_kehamilan ?? '0' }} Minggu</div></td>
<td>
<div class="small">
<span class="badge bg-blue-soft text-primary mb-1">BB: {{ $item->berat_badan }}kg</span>
<span class="badge bg-green-soft text-success mb-1">TB: {{ $item->tinggi_badan }}cm</span><br>
<span class="badge bg-purple-soft text-purple">LILA: {{ $item->lila ?? '-' }} cm</span>
</div>
</td>
<td class="text-center">
<div class="fw-bold text-dark mb-1">IMT: {{ $item->imt ?? '0' }}</div>
@php
$statusIbu = match(strtolower($item->status_gizi ?? '')) {
'gizi kurang' => 'bg-danger',
'gizi normal', 'normal' => 'bg-success',
'gizi lebih', 'obesitas' => 'bg-warning text-dark',
default => 'bg-secondary'
};
@endphp
<span class="badge {{ $statusIbu }} px-2 py-1" style="font-size: 10px;">
{{ strtoupper($item->status_gizi ?? 'TIDAK ADA DATA') }}
</span>
</td>
<td>
<div class="small fw-semibold">{{ \Carbon\Carbon::parse($item->tanggal)->translatedFormat('d M Y') }}</div>
<small class="text-muted">{{ $item->petugas->nama ?? '-' }}</small>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-5 text-muted">Data pengukuran ibu hamil bulan ini kosong.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div> </div> </div>
</div>
<style>
.card-stat { border-radius: 15px; transition: transform 0.3s; }
.card-stat:hover { transform: translateY(-5px); }
.icon-stat { width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; border-radius: 12px; }
.bg-danger-soft { background-color: #fff5f5; }
.bg-success-soft { background-color: #f0fff4; }
.btn-orange { background-color: #FF782D; color: white; border: none; }
.nav-pills .nav-link.active { background-color: #FF782D !important; }
.custom-input { border-radius: 10px !important; }
.custom-table thead th { font-size: 11px; text-transform: uppercase; color: #888; letter-spacing: 0.5px; border-top: none; }
.bg-purple-soft { background-color: #f3e8ff !important; border: 1px solid #d8b4fe; }
.text-purple { color: #6b21a8 !important; font-weight: 600; }
.text-dark-bold { color: #2c3e50 !important; font-weight: 700; }
.bg-blue-soft { background-color: #e7f3ff; }
.bg-green-soft { background-color: #f0fff4; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
let kategoriAktif = 'balita';
// Update kategori saat tab diklik
const tabButtons = document.querySelectorAll('button[data-bs-toggle="pill"]');
tabButtons.forEach(button => {
button.addEventListener('shown.bs.tab', function (e) {
kategoriAktif = e.target.id === 'pills-balita-tab' ? 'balita' : 'ibu_hamil';
});
});
// Interceptor Download
const handleDownload = (e, type) => {
e.preventDefault();
const bulan = document.querySelector('select[name="bulan"]').value;
const tahun = document.querySelector('select[name="tahun"]').value;
const tableId = kategoriAktif === 'balita' ? 'balita-table' : 'ibuhamil-table';
const tableContent = document.getElementById(tableId).innerText;
if (tableContent.includes('kosong') || tableContent.includes('Data tidak ditemukan')) {
Swal.fire({
icon: 'warning',
title: 'Data Kosong!',
text: `Maaf, tidak ada data ${kategoriAktif.replace('_', ' ')} untuk diunduh.`,
confirmButtonColor: '#FF782D',
});
} else {
const route = type === 'excel' ? '/laporan/excel' : '/laporan/pdf';
window.location.href = `${route}?kategori=${kategoriAktif}&bulan=${bulan}&tahun=${tahun}`;
}
};
document.getElementById('exportExcelBtn').addEventListener('click', (e) => handleDownload(e, 'excel'));
document.getElementById('exportPdfBtn').addEventListener('click', (e) => handleDownload(e, 'pdf'));
});
</script>
@endsection

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Laporan Balita</title>
<style>
body { font-family: sans-serif; font-size: 11pt; color: #333; }
.header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid #444; padding-bottom: 10px; }
.header h2 { margin: 0; text-transform: uppercase; color: #FF782D; }
.header p { margin: 5px 0; font-size: 10pt; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #999; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; font-size: 10pt; text-transform: uppercase; }
.status-badge { font-weight: bold; }
.footer { margin-top: 30px; text-align: right; font-size: 10pt; }
</style>
</head>
<body>
<div class="header">
<h2>{{ $title }}</h2>
<p>{{ $sub }}</p>
<p>Dicetak pada: {{ $date }}</p>
</div>
<table>
<thead>
<tr>
<th width="5%">No</th>
<th>Nama Balita</th>
<th>Usia</th>
<th>BB (kg)</th>
<th>TB (cm)</th>
<th>Z-Score TB/U</th>
<th>Hasil Stunting</th>
</tr>
</thead>
<tbody>
@foreach($items as $index => $item)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $item->balita->nama }}</td>
<td>{{ $item->usia_saat_ukur }} Bulan</td>
<td>{{ $item->berat_badan }}</td>
<td>{{ $item->tinggi_badan }}</td>
<td>{{ $item->zs_tbu }}</td>
<td class="status-badge">{{ strtoupper($item->hasil) }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@ -82,7 +82,12 @@
Route::post('/cetak-balita/{id}', [PengukuranBalitaController::class, 'cetakPdf']);
Route::middleware(['auth','role:admin'])->group(function () {
Route::get('/laporan', [LaporanController::class, 'index'])->name('laporan');
Route::middleware(['auth', 'role:admin'])->group(function () {
// Halaman Utama Laporan
Route::get('/laporan', [LaporanController::class, 'index'])->name('laporan.index');
// Route untuk Export (Pastikan name-nya sesuai dengan yang dipanggil di View/JS)
Route::get('/laporan/excel', [LaporanController::class, 'exportExcel'])->name('laporan.excel');
Route::get('/laporan/pdf', [LaporanController::class, 'exportPdf'])->name('laporan.pdf');
});