659 lines
31 KiB
TypeScript
659 lines
31 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import AuthenticatedLayout from "@/layouts/authenticated-layout";
|
|
import { Head, Link } from "@inertiajs/react";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Search, FileText, BadgeCheck } from "lucide-react";
|
|
import Select from "react-select";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
// -------------------------------------------------
|
|
// Tipe data (opsional, jika Anda pakai TypeScript)
|
|
interface Perusahaan {
|
|
PerusahaanId: number;
|
|
NamaPerusahaan: string;
|
|
}
|
|
|
|
interface PeriodePelaporan {
|
|
PeriodePelaporanId: number;
|
|
Nama: string; // misal: "Triwulan 1", "Semester 1", "Triwulan 2", dsb.
|
|
}
|
|
|
|
interface Pelaporan {
|
|
PelaporanId: number;
|
|
PerusahaanId: number;
|
|
PeriodePelaporanId: number;
|
|
Tahun: number;
|
|
// Contoh field
|
|
SKL?: number;
|
|
SPL?: number;
|
|
SKL_IL?: number;
|
|
SKL_AL?: number;
|
|
SKL_LB3?: number;
|
|
SKL_SB?: number;
|
|
SKL_BS?: number;
|
|
SKL_STB?: number;
|
|
SKL_LP?: number;
|
|
SKL_KDM?: number;
|
|
// relasi
|
|
perusahaan?: Perusahaan;
|
|
periodePelaporan?: PeriodePelaporan;
|
|
}
|
|
|
|
interface PelaporanIndexProps {
|
|
companies: Perusahaan[];
|
|
periodes: PeriodePelaporan[];
|
|
pelaporan: Pelaporan[];
|
|
}
|
|
// -------------------------------------------------
|
|
|
|
export default function PelaporanIndex({
|
|
companies,
|
|
periodes,
|
|
pelaporan,
|
|
}: PelaporanIndexProps) {
|
|
// -------------------------------------------------
|
|
// 1. Buat options dari data companies & periodes
|
|
const companyOptions = companies.map((c) => ({
|
|
value: c.PerusahaanId.toString(),
|
|
label: c.NamaPerusahaan,
|
|
}));
|
|
|
|
const periodeOptions = periodes.map((p) => ({
|
|
value: p.PeriodePelaporanId.toString(),
|
|
label: p.Nama,
|
|
}));
|
|
|
|
// -------------------------------------------------
|
|
// 2. State filter
|
|
const [year, setYear] = useState<string>("2025");
|
|
|
|
// Secara default, pilih perusahaan pertama & periode pertama (jika ada)
|
|
const [company, setCompany] = useState<{
|
|
value: string;
|
|
label: string;
|
|
} | null>(companyOptions.length > 0 ? companyOptions[0] : null);
|
|
|
|
const [selectedPeriode, setSelectedPeriode] = useState<{
|
|
value: string;
|
|
label: string;
|
|
} | null>(periodeOptions.length > 0 ? periodeOptions[0] : null);
|
|
|
|
// Data terfilter
|
|
const [filteredData, setFilteredData] = useState<Pelaporan[]>([]);
|
|
|
|
// -------------------------------------------------
|
|
// 3. Fungsi bantu: cari label periode, dsb.
|
|
function getPeriodeLabel(id: number) {
|
|
const found = periodes.find((p) => p.PeriodePelaporanId === id);
|
|
return found ? found.Nama : "-";
|
|
}
|
|
|
|
// Apakah “Triwulan 1/3” atau “Semester 1/3”?
|
|
// => cek label mengandung "1" atau "3"
|
|
function isOneOrThree(label: string) {
|
|
return label.includes("Triwulan 1") || label.includes("Triwulan 3");
|
|
}
|
|
|
|
// -------------------------------------------------
|
|
// 4. Fungsi warna & nilai
|
|
// - Jika data null => merah (belum diisi)
|
|
// - Jika data 0 => abu-abu
|
|
// - Jika data > 0 => hijau
|
|
// - Triwulan 1/3 => kolom IL, SB, BS, STB, KDM => blank
|
|
function getCellDisplay(
|
|
val: number | undefined,
|
|
hidden: boolean
|
|
): { text: string; style: string } {
|
|
if (hidden) {
|
|
// Kolom disembunyikan (kosong) => merah (belum diisi)
|
|
return { text: "", style: "bg-red-100 text-red-700" };
|
|
}
|
|
|
|
if (val === undefined || val === null) {
|
|
// Belum ada data => merah
|
|
return { text: "", style: "bg-red-100 text-red-700" };
|
|
}
|
|
if (val === 0) {
|
|
// Data 0 => abu-abu
|
|
return { text: "0.00", style: "bg-gray-100 text-gray-700" };
|
|
}
|
|
// Data > 0 => hijau
|
|
return { text: val.toFixed(2), style: "bg-green-100 text-green-700" };
|
|
}
|
|
|
|
// -------------------------------------------------
|
|
// 5. Fungsi Filter
|
|
const handleFilter = () => {
|
|
let temp = [...pelaporan];
|
|
|
|
// Filter Perusahaan
|
|
if (company) {
|
|
temp = temp.filter(
|
|
(item) => item.PerusahaanId.toString() === company.value
|
|
);
|
|
}
|
|
// Filter Tahun
|
|
if (year) {
|
|
temp = temp.filter((item) => item.Tahun.toString() === year);
|
|
}
|
|
// Filter Periode
|
|
if (selectedPeriode) {
|
|
temp = temp.filter(
|
|
(item) =>
|
|
item.PeriodePelaporanId.toString() === selectedPeriode.value
|
|
);
|
|
}
|
|
|
|
// Jika tidak ada data tapi filter sudah diisi => buat dummy row
|
|
if (
|
|
temp.length === 0 &&
|
|
company !== null &&
|
|
selectedPeriode !== null &&
|
|
year !== ""
|
|
) {
|
|
const dummy: Pelaporan = {
|
|
PelaporanId: 0, // menandakan dummy
|
|
PerusahaanId: parseInt(company.value, 10),
|
|
PeriodePelaporanId: parseInt(selectedPeriode.value, 10),
|
|
Tahun: parseInt(year, 10),
|
|
SKL: 0,
|
|
SPL: 0,
|
|
SKL_IL: 0,
|
|
SKL_AL: 0,
|
|
SKL_LB3: 0,
|
|
SKL_SB: 0,
|
|
SKL_BS: 0,
|
|
SKL_STB: 0,
|
|
SKL_LP: 0,
|
|
SKL_KDM: 0,
|
|
perusahaan: {
|
|
PerusahaanId: parseInt(company.value, 10),
|
|
NamaPerusahaan: company.label,
|
|
},
|
|
periodePelaporan: {
|
|
PeriodePelaporanId: parseInt(selectedPeriode.value, 10),
|
|
Nama: selectedPeriode.label,
|
|
},
|
|
};
|
|
temp = [dummy];
|
|
}
|
|
|
|
setFilteredData(temp);
|
|
};
|
|
|
|
// Agar default langsung memfilter
|
|
React.useEffect(() => {
|
|
handleFilter();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
// -------------------------------------------------
|
|
// 6. Fungsi reset filter
|
|
const handleReset = () => {
|
|
setYear("2025");
|
|
setCompany(companyOptions.length > 0 ? companyOptions[0] : null);
|
|
setSelectedPeriode(
|
|
periodeOptions.length > 0 ? periodeOptions[0] : null
|
|
);
|
|
// Re-filter
|
|
setTimeout(() => handleFilter(), 0);
|
|
};
|
|
|
|
// -------------------------------------------------
|
|
return (
|
|
<AuthenticatedLayout header={"Pelaporan SKL"}>
|
|
<Head title="Pelaporan SKL" />
|
|
|
|
<div className="p-8">
|
|
{/* Filter Section */}
|
|
<div className="bg-white rounded-lg shadow-sm mb-6">
|
|
<Card className="shadow-md border-slate-200 justify-between">
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-lg font-medium">
|
|
Pelaporan SKL
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
|
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center w-full">
|
|
{/* Select Perusahaan */}
|
|
<Select
|
|
options={companyOptions}
|
|
value={company}
|
|
onChange={setCompany}
|
|
placeholder="Pilih Perusahaan"
|
|
isSearchable
|
|
className="w-full sm:w-[300px]"
|
|
/>
|
|
|
|
<div className="flex gap-4 w-full sm:w-auto">
|
|
<Input
|
|
type="number"
|
|
value={year}
|
|
onChange={(e) =>
|
|
setYear(e.target.value)
|
|
}
|
|
className="w-full sm:w-24"
|
|
/>
|
|
|
|
<Select
|
|
options={periodeOptions}
|
|
value={selectedPeriode}
|
|
onChange={setSelectedPeriode}
|
|
placeholder="Pilih Periode"
|
|
className="w-full sm:w-[150px]"
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
className="w-full sm:w-auto"
|
|
onClick={handleFilter}
|
|
>
|
|
<Search className="w-4 h-4 mr-2" />
|
|
Cari
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
className="w-full sm:w-auto"
|
|
onClick={handleReset}
|
|
>
|
|
Reset
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Table Section */}
|
|
<div className="w-full overflow-x-auto">
|
|
<Table className="min-w-[1000px] border-collapse">
|
|
<TableHeader>
|
|
<TableRow className="border-b border-t bg-green-800">
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="w-[50px] border-r border-l text-white"
|
|
>
|
|
No.
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
Nama Perusahaan
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
Tahun
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
Periode Pelaporan
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
SKL (%)
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
SPBM (%)
|
|
</TableHead>
|
|
<TableHead
|
|
colSpan={8}
|
|
className="border-r text-center text-white"
|
|
>
|
|
Hasil (Nilai SKL)
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
STT
|
|
</TableHead>
|
|
<TableHead
|
|
rowSpan={2}
|
|
className="border-r text-center text-white"
|
|
>
|
|
SE
|
|
</TableHead>
|
|
</TableRow>
|
|
<TableRow className="border-b bg-green-600">
|
|
<TableHead className="text-center border-r text-white">
|
|
IL
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
AL
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
LB3
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
SB
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
BS
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
STB
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
LP
|
|
</TableHead>
|
|
<TableHead className="text-center border-r text-white">
|
|
KDM
|
|
</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody className="border-b">
|
|
{filteredData.length > 0 ? (
|
|
filteredData.map((item, index) => {
|
|
// Ambil label periode
|
|
const labelPeriode = getPeriodeLabel(
|
|
item?.PeriodePelaporanId
|
|
);
|
|
// Apakah triwulan/semester 1/3?
|
|
const hidden = isOneOrThree(labelPeriode);
|
|
|
|
// Dapatkan style + text untuk IL
|
|
const IL = getCellDisplay(
|
|
item?.SKL_IL,
|
|
hidden
|
|
);
|
|
const AL = getCellDisplay(
|
|
item.SKL_AL,
|
|
false
|
|
); // AL selalu tampil
|
|
const LB3 = getCellDisplay(
|
|
item.SKL_LB3,
|
|
false
|
|
);
|
|
const SB = getCellDisplay(
|
|
item.SKL_SB,
|
|
hidden
|
|
);
|
|
const BS = getCellDisplay(
|
|
item.SKL_BS,
|
|
hidden
|
|
);
|
|
const STB = getCellDisplay(
|
|
item.SKL_STB,
|
|
hidden
|
|
);
|
|
const LP = getCellDisplay(
|
|
item.SKL_LP,
|
|
false
|
|
);
|
|
const KDM = getCellDisplay(
|
|
item.SKL_KDM,
|
|
hidden
|
|
);
|
|
|
|
// Dapatkan style + text untuk SKL, SPL
|
|
const skl = getCellDisplay(item.SKL, false);
|
|
const spl = getCellDisplay(item.SPL, false);
|
|
|
|
return (
|
|
<TableRow
|
|
key={item.PelaporanId}
|
|
className="border-b"
|
|
>
|
|
<TableCell className="text-center border-r hover:underline border-l">
|
|
{index + 1}
|
|
</TableCell>
|
|
<TableCell className="text-center border-r hover:underline">
|
|
{item.perusahaan
|
|
?.NamaPerusahaan ?? "-"}
|
|
</TableCell>
|
|
<TableCell className="text-center border-r hover:underline">
|
|
{item.Tahun}
|
|
</TableCell>
|
|
<TableCell className="text-center border-r hover:underline">
|
|
{labelPeriode}
|
|
</TableCell>
|
|
{/* SKL & SPBM */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${skl.style}`}
|
|
>
|
|
{skl.text}
|
|
</TableCell>
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${spl.style}`}
|
|
>
|
|
{spl.text}
|
|
</TableCell>
|
|
|
|
{/* IL */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${IL.style}`}
|
|
>
|
|
{IL.text}
|
|
</TableCell>
|
|
{/* AL */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${AL.style}`}
|
|
>
|
|
{item.PerusahaanId > 0 ? (
|
|
<Link
|
|
href={route(
|
|
"admin.pelaporanAL.index",
|
|
{
|
|
id: btoa(
|
|
`perusahaan-${item.PerusahaanId}`
|
|
),
|
|
}
|
|
)}
|
|
>
|
|
{AL.text}
|
|
</Link>
|
|
) : (
|
|
AL.text
|
|
)}
|
|
</TableCell>
|
|
{/* LB3 */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${LB3.style}`}
|
|
>
|
|
{LB3.text}
|
|
</TableCell>
|
|
{/* SB */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${SB.style}`}
|
|
>
|
|
{SB.text}
|
|
</TableCell>
|
|
{/* BS */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${BS.style}`}
|
|
>
|
|
{BS.text}
|
|
</TableCell>
|
|
{/* STB */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${STB.style}`}
|
|
>
|
|
{STB.text}
|
|
</TableCell>
|
|
{/* LP */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${LP.style}`}
|
|
>
|
|
{LP.text}
|
|
</TableCell>
|
|
{/* KDM */}
|
|
<TableCell
|
|
className={`text-center border-r hover:underline ${KDM.style}`}
|
|
>
|
|
{KDM.text}
|
|
</TableCell>
|
|
|
|
{/* STT */}
|
|
<TableCell className="text-center border-r">
|
|
<FileText className="w-4 h-4" />
|
|
</TableCell>
|
|
{/* SE */}
|
|
<TableCell className="text-center border-r">
|
|
<FileText className="w-4 h-4" />
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})
|
|
) : (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={16}
|
|
className="text-center py-4"
|
|
>
|
|
Tidak ada data
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
|
|
{/* Legend Section */}
|
|
<div className="mt-6 flex flex-col md:grid md:grid-cols-2 gap-6">
|
|
{/* Keterangan Section */}
|
|
<div className="bg-white rounded-lg shadow-sm border p-4 md:p-6">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<FileText className="w-5 h-5 text-green-600" />
|
|
<h3 className="font-semibold text-lg">
|
|
Keterangan
|
|
</h3>
|
|
</div>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
SKL : Status Ketaatan Lingkungan
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
SPBM : Status Pemenuhan Baku Mutu
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
IL : Ijin Lingkungan
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">AL : Air Limbah</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">LB3 : Limbah B3</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
SB : Sumber Bergerak
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
BS : Kebisingan & Udara Ambien
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
STB : Sumber Tidak Bergerak
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">LP : Limbah Padat</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
KDM : Kawasan Dilarang Merokok
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 pt-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
STT : Surat Tanda Terima
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 pt-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="min-w-[8px] w-2 h-2 rounded-full bg-green-500"></div>
|
|
<p className="text-sm">
|
|
SE : Surat Evaluasi
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Data Section */}
|
|
<div className="bg-white rounded-lg shadow-sm border p-4 md:p-6 mt-4 md:mt-0">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<BadgeCheck className="w-5 h-5 text-green-600" />
|
|
<h3 className="font-semibold text-lg">
|
|
Status Data
|
|
</h3>
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-3 p-2 rounded-md bg-red-100">
|
|
<div className="min-w-[12px] w-3 h-3 rounded-full bg-red-500"></div>
|
|
<p className="text-sm text-red-700">
|
|
Data belum diisi
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-3 p-2 rounded-md bg-gray-100">
|
|
<div className="min-w-[12px] w-3 h-3 rounded-full bg-gray-500"></div>
|
|
<p className="text-sm text-gray-700">
|
|
Data telah diisi dan belum diverifikasi
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-3 p-2 rounded-md bg-orange-100">
|
|
<div className="min-w-[12px] w-3 h-3 rounded-full bg-orange-500"></div>
|
|
<p className="text-sm text-orange-700">
|
|
Data telah siap untuk diverifikasi
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-3 p-2 rounded-md bg-green-100">
|
|
<div className="min-w-[12px] w-3 h-3 rounded-full bg-green-500"></div>
|
|
<p className="text-sm text-green-700">
|
|
Data telah diisi dan diverifikasi
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AuthenticatedLayout>
|
|
);
|
|
}
|