417 lines
19 KiB
TypeScript
417 lines
19 KiB
TypeScript
import React from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Tooltip,
|
|
TooltipTrigger,
|
|
TooltipContent,
|
|
} from "@/components/ui/tooltip";
|
|
import { CircleHelp } from "lucide-react";
|
|
import {
|
|
Select,
|
|
SelectTrigger,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
|
|
interface KomponenData {
|
|
id: number;
|
|
nama: string;
|
|
kode: string;
|
|
nilai: number;
|
|
hasil?: {
|
|
id: number;
|
|
nama: string;
|
|
nilai: number;
|
|
}[];
|
|
nama_refhasil?: string;
|
|
verifikasi?: boolean;
|
|
keterangan?: string;
|
|
lampiran?: boolean;
|
|
}
|
|
|
|
interface JenisData {
|
|
nama: string;
|
|
komponen: Record<string, KomponenData>;
|
|
}
|
|
|
|
interface ALTableProps {
|
|
dataTable: Record<string, JenisData>;
|
|
nilaiKomponen: Record<string, any>;
|
|
nilaiSyaratTeknis: Record<string, number>;
|
|
nilai_A6: number;
|
|
nilai_A7: number;
|
|
idmcpelaporan: number;
|
|
idrefpelaporan: number;
|
|
rowspan_1: number;
|
|
spl: number;
|
|
spl_2: number;
|
|
editable: boolean;
|
|
onUpdateNilai: (kode: string, nilai: number) => void;
|
|
onOpenLampiranModal: (kode: string, idrefkomponen: number) => void;
|
|
onOpenSplModal: () => void;
|
|
}
|
|
|
|
const ALTable: React.FC<ALTableProps> = ({
|
|
dataTable,
|
|
nilaiKomponen,
|
|
nilaiSyaratTeknis,
|
|
nilai_A6,
|
|
nilai_A7,
|
|
idmcpelaporan,
|
|
idrefpelaporan,
|
|
rowspan_1,
|
|
spl,
|
|
spl_2,
|
|
editable,
|
|
onUpdateNilai,
|
|
onOpenLampiranModal,
|
|
onOpenSplModal,
|
|
}) => {
|
|
const [sklNilai, setSklNilai] = React.useState<number>(0);
|
|
const komponenHide = ["A1", "A2", "A3", "A4", "A5"];
|
|
const daftarNilai = React.useRef<Record<string, number>>({});
|
|
|
|
React.useEffect(() => {
|
|
// Initialize daftarNilai
|
|
const nilai: Record<string, number> = {};
|
|
Object.entries(dataTable).forEach(([_noJenis, jenis]) => {
|
|
Object.entries(jenis.komponen).forEach(
|
|
([kodeKomponen, komponen]) => {
|
|
if (kodeKomponen !== "SK") {
|
|
if (komponenHide.includes(kodeKomponen)) {
|
|
nilai[kodeKomponen.toLowerCase()] = parseFloat(
|
|
nilaiSyaratTeknis[kodeKomponen].toFixed(2)
|
|
);
|
|
} else if (kodeKomponen === "A6") {
|
|
nilai[kodeKomponen.toLowerCase()] = parseFloat(
|
|
nilai_A6.toFixed(2)
|
|
);
|
|
} else if (kodeKomponen === "A7") {
|
|
nilai[kodeKomponen.toLowerCase()] = parseFloat(
|
|
nilai_A7.toFixed(2)
|
|
);
|
|
} else {
|
|
const val = nilaiKomponen[kodeKomponen];
|
|
nilai[kodeKomponen.toLowerCase()] = val
|
|
? parseFloat(val.nilai)
|
|
: 0;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
});
|
|
daftarNilai.current = nilai;
|
|
calculateSklNilai();
|
|
}, [dataTable, nilaiKomponen, nilaiSyaratTeknis, nilai_A6, nilai_A7]);
|
|
|
|
const calculateSklNilai = () => {
|
|
const kelompok1 = ["a1", "a2", "a3", "a4"];
|
|
const kelompok2 = ["a5"];
|
|
const kelompok3 = ["a6", "a7", "a8", "a9", "a10"];
|
|
let total1 = 0;
|
|
let total2 = 0;
|
|
let total3 = 0;
|
|
|
|
for (const [key, value] of Object.entries(daftarNilai.current)) {
|
|
if (typeof value === "number") {
|
|
if (kelompok1.includes(key)) {
|
|
total1 += value;
|
|
} else if (kelompok2.includes(key)) {
|
|
total2 += value;
|
|
} else if (kelompok3.includes(key)) {
|
|
total3 += value;
|
|
}
|
|
}
|
|
}
|
|
|
|
const newSklNilai =
|
|
(15 * (total1 / kelompok1.length)) / 100 +
|
|
(15 * (total2 / kelompok2.length)) / 100 +
|
|
(70 * (total3 / kelompok3.length)) / 100;
|
|
|
|
setSklNilai(parseFloat(newSklNilai.toFixed(2)));
|
|
};
|
|
|
|
const handleHasilChange = (komponen: KomponenData, hasilId: number) => {
|
|
if (!editable) return;
|
|
|
|
const hasil = komponen.hasil?.find(
|
|
(h) => h.id === parseInt(hasilId.toString())
|
|
);
|
|
if (hasil) {
|
|
daftarNilai.current[komponen.kode.toLowerCase()] = hasil.nilai;
|
|
onUpdateNilai(komponen.kode, hasil.nilai);
|
|
calculateSklNilai();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<table className="w-full border-collapse text-sm">
|
|
<thead>
|
|
<tr className="bg-gray-100">
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
No
|
|
</th>
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
Komponen
|
|
</th>
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
Hasil
|
|
</th>
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
Nilai
|
|
</th>
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
Lampiran
|
|
</th>
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
Verifikasi
|
|
</th>
|
|
<th className="border border-gray-300 p-2 text-center">
|
|
Keterangan
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td
|
|
rowSpan={rowspan_1}
|
|
className="border border-gray-300 p-2 text-center"
|
|
>
|
|
<strong>I</strong>
|
|
</td>
|
|
<td colSpan={2} className="border border-gray-300 p-2">
|
|
<strong>
|
|
Nilai tingkat ketaatan pengelolaan air limbah (SKL
|
|
<sub>AL</sub>) (%)
|
|
</strong>
|
|
<input
|
|
type="hidden"
|
|
name="skl-nilai"
|
|
id="skl"
|
|
value={sklNilai}
|
|
/>
|
|
</td>
|
|
<td
|
|
id="skl-nilai"
|
|
className="border border-gray-300 p-2 text-right font-bold"
|
|
>
|
|
{sklNilai}
|
|
</td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
</tr>
|
|
|
|
{Object.entries(dataTable).map(([noJenis, jenis]) => (
|
|
<React.Fragment key={noJenis}>
|
|
{noJenis && (
|
|
<tr>
|
|
<td
|
|
colSpan={2}
|
|
className="border border-gray-300 p-2"
|
|
>
|
|
{noJenis}. {jenis.nama}
|
|
</td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
</tr>
|
|
)}
|
|
|
|
{Object.entries(jenis.komponen).map(
|
|
([kodeKomponen, komponen]) =>
|
|
kodeKomponen !== "SK" && (
|
|
<tr key={kodeKomponen}>
|
|
<td className="border border-gray-300 p-2 pl-[3%]">
|
|
{kodeKomponen}. {komponen.nama}
|
|
</td>
|
|
|
|
{/* Kolom Hasil */}
|
|
<td className="border border-gray-300 p-2">
|
|
{komponen.hasil &&
|
|
!komponenHide.includes(
|
|
kodeKomponen
|
|
) ? (
|
|
<Select
|
|
value={
|
|
nilaiKomponen[
|
|
kodeKomponen
|
|
]?.nilai?.toString() ||
|
|
"0"
|
|
}
|
|
disabled={
|
|
!editable ||
|
|
daftarNilai.current[
|
|
kodeKomponen.toLowerCase()
|
|
] === undefined
|
|
}
|
|
onValueChange={(value) => {
|
|
const selectedHasil =
|
|
komponen.hasil?.find(
|
|
(h) =>
|
|
h.nilai.toString() ===
|
|
value
|
|
);
|
|
if (selectedHasil) {
|
|
handleHasilChange(
|
|
komponen,
|
|
selectedHasil.id
|
|
);
|
|
}
|
|
}}
|
|
>
|
|
<SelectTrigger
|
|
className="w-full"
|
|
data-komponen={kodeKomponen.toLowerCase()}
|
|
>
|
|
<SelectValue placeholder="Pilih hasil" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{komponen.hasil.map(
|
|
(hasil) => (
|
|
<SelectItem
|
|
key={
|
|
hasil.id
|
|
}
|
|
value={hasil.nilai.toString()}
|
|
data-nilai={
|
|
hasil.nilai
|
|
}
|
|
>
|
|
{hasil.nama}
|
|
</SelectItem>
|
|
)
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
) : null}
|
|
</td>
|
|
|
|
{/* Kolom Nilai */}
|
|
<td
|
|
className="border border-gray-300 p-2 text-right nilai-komponen"
|
|
id={`${kodeKomponen.toLowerCase()}-nilai`}
|
|
data-komponen={kodeKomponen.toLowerCase()}
|
|
>
|
|
{komponenHide.includes(kodeKomponen)
|
|
? parseFloat(
|
|
(
|
|
nilaiSyaratTeknis[
|
|
kodeKomponen
|
|
] || 0
|
|
).toFixed(2)
|
|
)
|
|
: kodeKomponen === "A6"
|
|
? parseFloat(
|
|
nilai_A6.toFixed(2)
|
|
)
|
|
: kodeKomponen === "A7"
|
|
? parseFloat(
|
|
nilai_A7.toFixed(2)
|
|
)
|
|
: nilaiKomponen[kodeKomponen]
|
|
? nilaiKomponen[kodeKomponen]
|
|
.nilai
|
|
: 0}
|
|
</td>
|
|
|
|
{/* Kolom Lampiran */}
|
|
<td className="border border-gray-300 p-2 text-center">
|
|
{komponen.lampiran &&
|
|
!komponenHide.includes(
|
|
kodeKomponen
|
|
) && (
|
|
<button
|
|
onClick={() =>
|
|
onOpenLampiranModal(
|
|
kodeKomponen,
|
|
komponen.id
|
|
)
|
|
}
|
|
className="text-blue-600 hover:text-blue-800 hover:underline"
|
|
disabled={!editable}
|
|
>
|
|
{kodeKomponen === "A6"
|
|
? "Data..."
|
|
: "Lampiran"}
|
|
</button>
|
|
)}
|
|
</td>
|
|
|
|
{/* Kolom Verifikasi */}
|
|
<td
|
|
className="border border-gray-300 p-2 verifikasi"
|
|
data-komponen={kodeKomponen}
|
|
>
|
|
{!komponenHide.includes(
|
|
kodeKomponen
|
|
) &&
|
|
nilaiKomponen[kodeKomponen]
|
|
?.verifikasi !==
|
|
undefined &&
|
|
(nilaiKomponen[kodeKomponen]
|
|
?.verifikasi ? (
|
|
<span className="text-green-600">
|
|
Approved
|
|
</span>
|
|
) : (
|
|
<span className="text-red-600">
|
|
Not Approved
|
|
</span>
|
|
))}
|
|
</td>
|
|
|
|
{/* Kolom Keterangan */}
|
|
<td className="border border-gray-300 p-2">
|
|
{!komponenHide.includes(
|
|
kodeKomponen
|
|
) &&
|
|
nilaiKomponen[kodeKomponen]
|
|
?.keterangan}
|
|
</td>
|
|
</tr>
|
|
)
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
|
|
<tr>
|
|
<td className="border border-gray-300 p-2 text-center">
|
|
<strong>II</strong>
|
|
</td>
|
|
<td colSpan={2} className="border border-gray-300 p-2">
|
|
<strong>
|
|
Nilai tingkat pemenuhan baku mutu air limbah{" "}
|
|
<sup>*</sup> (SPBM<sub>AL</sub>) (%)
|
|
</strong>
|
|
</td>
|
|
<td
|
|
id="nilai-spl"
|
|
data-spl2={spl_2}
|
|
className="border border-gray-300 p-2 text-right font-bold"
|
|
>
|
|
{parseFloat(spl.toString())}
|
|
</td>
|
|
<td className="border border-gray-300 p-2 text-center">
|
|
<button
|
|
onClick={onOpenSplModal}
|
|
className="text-blue-600 hover:text-blue-800 hover:underline spl-link-modal"
|
|
data-id={idmcpelaporan}
|
|
disabled={!editable}
|
|
>
|
|
Data...
|
|
</button>
|
|
</td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
<td className="border border-gray-300 p-2"></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
);
|
|
};
|
|
|
|
export default ALTable;
|