feat: Menambahkan validasi file dan relasi model

main
marszayn 2025-03-10 13:59:31 +07:00
parent 6fabbe8708
commit cae368adc0
5 changed files with 1157 additions and 1 deletions

View File

@ -40,7 +40,7 @@ class PerusahaanRequest extends FormRequest
'JenisDokILId' => ['integer'],
'VerifikatorId' => ['required', 'integer'],
'IsPublish' => ['required', 'boolean'],
'ILDokumen' => ['string'],
'ILDokumen' => 'nullable|file|mimes:pdf|max:2048',
'Kawasan' => ['string'],
];
}

View File

@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Perusahaan extends Model
@ -92,7 +93,15 @@ class Perusahaan extends Model
return $this->belongsTo(JenisDokIL::class, 'JenisDokILId', 'JenisDokILId');
}
public function historyKegiatan()
{
return $this->belongsTo(HistoryKegiatan::class, 'HistoryKegiatanId', 'HistoryKegiatanId');
}
public function historyPerusahaan(): HasMany
{
return $this->hasMany(HistoryPerusahaan::class, 'PerusahaanId', 'PerusahaanId');
}
}

View File

@ -0,0 +1,808 @@
import React, { useEffect, useState } from "react";
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import { Head, Link, router, useForm } from "@inertiajs/react";
import Select from "react-select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
// Komponen shadcn/ui untuk date range (contoh)
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import { Button } from "@/components/ui/button";
import { ArrowLeft, CalendarIcon, Edit, Printer } from "lucide-react";
import { format } from "date-fns";
import { cn } from "@/lib/utils"; // biasanya helper classNames
import { DateRange } from "react-day-picker";
import { FileText } from "lucide-react";
import {
Perusahaan,
HistoryKegiatan,
HistoryPerusahaan as HistoryPerusahaanType,
} from "@/types/perusahaan";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useToast } from "@/hooks/use-toast";
type DetailHistoryPerusahaanProps = {
perusahaan: Perusahaan;
historyKegiatan: HistoryKegiatan[];
historyPerusahaan: HistoryPerusahaanType[]; // data existing
};
export default function DetailHistoryPerusahaan({
perusahaan,
historyKegiatan,
historyPerusahaan = [],
}: DetailHistoryPerusahaanProps) {
const { toast } = useToast();
useEffect(() => {
console.log("Isi Data History Perusahaan:", historyPerusahaan);
}, [historyPerusahaan]);
// --------------------
// 1. STATE FILTER
// --------------------
// Date range
const [dateRange, setDateRange] = useState<DateRange | undefined>({
from: undefined,
to: undefined,
});
// Kelompok Kegiatan
const [selectedKegiatan, setSelectedKegiatan] = useState<{
value: string;
label: string;
} | null>(null);
// State data terfilter
const [filteredData, setFilteredData] =
useState<HistoryPerusahaanType[]>(historyPerusahaan);
// Fungsi untuk memproses filter
function handleFilter() {
let filtered = [...historyPerusahaan];
// Filter rentang tanggal
if (dateRange?.from && dateRange?.to) {
const fromDay = format(dateRange.from, "yyyy-MM-dd");
const toDay = format(dateRange.to, "yyyy-MM-dd");
filtered = filtered.filter((item) => {
if (!item.TanggalHistory) return false;
const itemDay = format(
new Date(item.TanggalHistory),
"yyyy-MM-dd"
);
return itemDay >= fromDay && itemDay <= toDay;
});
}
// Filter Kelompok Kegiatan
if (selectedKegiatan) {
filtered = filtered.filter(
(item) =>
item.HistoryKegiatanId.toString() === selectedKegiatan.value
);
}
setFilteredData(filtered);
}
// Fungsi untuk mereset filter pencarian
function handleReset() {
setDateRange({ from: undefined, to: undefined });
setSelectedKegiatan(null);
setFilteredData(historyPerusahaan);
}
function handleBack() {
// Misal kembali ke halaman sebelumnya
window.history.back();
}
function handlePrint() {
// Contoh simpel: memanggil window.print()
window.print();
}
function handleExportPDF() {
// Contoh panggil route export PDF, atau link ke PDF
// Misal router.visit(route("pdf.export", { id: 123 }));
// Atau window.open("/path/to/pdf", "_blank");
alert("Export PDF belum diimplementasikan.");
}
// --------------------
// 2. STATE FORM TAMBAH HISTORY
// --------------------
const [showModal, setShowModal] = useState(false);
const { data, setData, post, processing, errors, reset } = useForm({
PerusahaanId: perusahaan.PerusahaanId,
NomorHistory: "",
TanggalHistory: "",
HistoryKegiatanId: "",
KeteranganHistory: "",
DokumenHistory: null as File | null,
});
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
post(route("admin.history_perusahaan.store", perusahaan.PerusahaanId), {
onSuccess: () => {
router.visit(window.location.href, { replace: true });
setShowModal(false);
reset();
toast({
title: "Sukses",
description: "History Perusahaan berhasil ditambahkan",
variant: "default",
});
},
onError: (errors) => {
toast({
title: "Error",
description: "Gagal menambahkan history perusahaan",
variant: "destructive",
});
},
});
}
// --------------------
// 3. STATE & FORM UNTUK EDIT HISTORY
// --------------------
const [showEditModal, setShowEditModal] = useState(false);
const [editingItem, setEditingItem] =
useState<HistoryPerusahaanType | null>(null);
const {
data: editData,
setData: setEditData,
post: postEdit,
processing: editProcessing,
errors: editErrors,
reset: resetEditForm,
} = useForm({
HistoryPerusahaanId: "",
PerusahaanId: perusahaan.PerusahaanId,
NomorHistory: "",
TanggalHistory: "",
HistoryKegiatanId: "",
KeteranganHistory: "",
DokumenHistory: null as File | null,
});
function handleEdit(item: HistoryPerusahaanType) {
setEditingItem(item);
setEditData("HistoryPerusahaanId", item.HistoryPerusahaanId);
setEditData("NomorHistory", item.NomorHistory);
setEditData("TanggalHistory", item.TanggalHistory);
setEditData("HistoryKegiatanId", item.HistoryKegiatanId.toString());
setEditData("KeteranganHistory", item.KeteranganHistory);
setShowEditModal(true);
}
function handleEditSubmit(e: React.FormEvent) {
e.preventDefault();
postEdit(
route(
"admin.history_perusahaan.update",
editData.HistoryPerusahaanId
),
{
onSuccess: () => {
router.visit(window.location.href, { replace: true });
setShowEditModal(false);
resetEditForm();
toast({
title: "Sukses",
description: "History Perusahaan berhasil diperbarui",
variant: "default",
});
},
onError: () => {
toast({
title: "Error",
description: "Gagal memperbarui history perusahaan",
variant: "destructive",
});
},
}
);
}
// --------------------
// 4. RENDER HALAMAN
// --------------------
return (
<AuthenticatedLayout header="Detail History Perusahaan">
<Head title="Detail History Perusahaan" />
<div className="container mx-auto p-4">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-lg font-semibold mb-2 text-gray-800">
History Perusahaan: {perusahaan.NamaPerusahaan}
</h2>
<p className="text-sm text-gray-600">
Nomor Induk: {perusahaan.NomorInduk || "-"}
</p>
</div>
<div className="flex gap-2">
<button
onClick={handleBack}
className="bg-blue-500 text-white px-2 py-1 rounded-md flex items-center gap-1 hover:bg-blue-600"
>
<ArrowLeft className="w-4 h-4" />
BACK
</button>
<button
onClick={handlePrint}
className="bg-blue-500 text-white px-2 py-1 rounded-md flex items-center gap-1 hover:bg-blue-600"
>
<Printer className="w-4 h-4" />
Print
</button>
<button
onClick={handleExportPDF}
className="bg-blue-500 text-white px-2 py-1 rounded-md flex items-center gap-1 hover:bg-blue-600"
>
<FileText className="w-4 h-4" />
Export PDF
</button>
</div>
</div>
{/* Bagian Filter */}
<div className="bg-white py-6 rounded-lg shadow-sm mb-6">
<Card className="shadow-md border-slate-200">
<CardHeader className="pb-3">
<CardTitle className="text-lg font-medium">
Pencarian
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Filter Rentang Tanggal */}
<div className="space-y-2">
<label className="text-sm font-medium">
Rentang Tanggal
</label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"w-full justify-start text-left font-normal",
!dateRange?.from &&
"text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{dateRange?.from
? dateRange.to
? `${format(
dateRange.from,
"dd/MM/yyyy"
)} - ${format(
dateRange.to,
"dd/MM/yyyy"
)}`
: format(
dateRange.from,
"dd/MM/yyyy"
)
: "Pilih Tanggal"}
</Button>
</PopoverTrigger>
<PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
initialFocus
mode="range"
defaultMonth={dateRange?.from}
selected={dateRange}
onSelect={setDateRange}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</div>
{/* Filter Kelompok Kegiatan - Using react-select */}
<div className="space-y-2">
<label className="text-sm font-medium">
Kelompok Kegiatan
</label>
<Select
className="basic-single"
classNamePrefix="select"
value={selectedKegiatan}
onChange={(option) =>
setSelectedKegiatan(option)
}
options={historyKegiatan.map((hk) => ({
value: hk.HistoryKegiatanId.toString(),
label: hk.NamaHistoryKegiatan,
}))}
isClearable
placeholder="-- Pilih --"
/>
</div>
</div>
{/* Tombol Cari */}
<div className="mt-4 flex gap-2">
<Button
onClick={handleFilter}
className="bg-green-600 hover:bg-green-700 text-white"
>
Cari
</Button>
<Button
onClick={handleReset}
className="bg-gray-600 hover:bg-gray-700 text-white"
>
Reset
</Button>
</div>
</CardContent>
</Card>
</div>
{/* Tombol Tambah History Perusahaan */}
<div className="mb-6">
<button
onClick={() => setShowModal(true)}
className="px-4 py-2 bg-green-600 text-white rounded-md"
>
Tambah History Perusahaan
</button>
</div>
{/* Tabel Data History Perusahaan (menggunakan filteredData) */}
<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 className="w-[50px] border-r border-l text-center text-white">
No.
</TableHead>
<TableHead className="border-r text-center text-white">
Nomor Surat
</TableHead>
<TableHead className="border-r text-center text-white">
Tanggal Surat
</TableHead>
<TableHead className="border-r text-center text-white">
Kelompok Kegiatan
</TableHead>
<TableHead className="border-r text-center text-white">
Keterangan
</TableHead>
<TableHead className="border-r text-center text-white">
Dokumen
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="border-b">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableRow
key={item.HistoryPerusahaanId}
className="border-b"
>
<TableCell className="font-medium text-center border-r border-l">
{index + 1}
</TableCell>
<TableCell className="text-center border-r">
{item.NomorHistory || "-"}
</TableCell>
<TableCell className="text-center border-r">
{item.TanggalHistory || "-"}
{/* {item.TanggalHistory
? new Date(
item.TanggalHistory
).toLocaleDateString(
"id-ID",
{
day: "2-digit",
month: "long",
year: "numeric",
}
)
: "-"} */}
</TableCell>
<TableCell className="text-center border-r">
{/* Anda bisa menampilkan data dari relasi, misal item.kegiatan?.NamaHistoryKegiatan */}
<div className="flex items-center justify-center">
<button
onClick={() =>
handleEdit(item)
}
className="px-2 py-1 bg-blue-100 font-normal hover:underline flex items-center gap-1 rounded-md"
>
<Edit className="h-3 w-3 text-blue-500" />
{item.history_kegiatan
?.NamaHistoryKegiatan ||
"-"}
</button>
</div>
</TableCell>
<TableCell className="text-center border-r">
{item.KeteranganHistory || "-"}
</TableCell>
<TableCell className="text-center border-r">
<div className="flex items-center justify-center">
{item.DokumenHistory && (
<a
href={`/storage/${item.DokumenHistory}`}
target="_blank"
className="px-2 py-1 bg-blue-100 font-normal hover:underline flex items-center gap-1 rounded-md"
>
<FileText className="h-3 w-3 text-blue-500" />
Lihat Dokumen
</a>
)}
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={6}
className="h-24 text-center"
>
Tidak ada data
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
{/* Modal Tambah History Perusahaan */}
{showModal && (
<div className="fixed inset-0 flex items-center justify-center z-50 bg-black/50">
<div className="bg-white p-6 rounded-md w-full max-w-md">
<h2 className="text-xl font-semibold mb-4">
Tambah History Perusahaan
</h2>
<form
onSubmit={handleSubmit}
encType="multipart/form-data"
>
{/* Nomor Surat */}
<div className="mb-4">
<label className="block font-medium mb-1">
Nomor Surat*
</label>
<input
type="text"
value={data.NomorHistory}
onChange={(e) =>
setData("NomorHistory", e.target.value)
}
className="border border-gray-300 rounded w-full p-2"
/>
{errors.NomorHistory && (
<div className="text-red-500 text-sm">
{errors.NomorHistory}
</div>
)}
</div>
{/* Tanggal Surat */}
<div className="mb-4">
<label className="block font-medium mb-1">
Tanggal Surat*
</label>
<input
type="date"
value={data.TanggalHistory}
onChange={(e) =>
setData(
"TanggalHistory",
e.target.value
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{errors.TanggalHistory && (
<div className="text-red-500 text-sm">
{errors.TanggalHistory}
</div>
)}
</div>
{/* Kelompok Kegiatan */}
<div className="mb-4">
<label className="block font-medium mb-1">
Kelompok Kegiatan*
</label>
<Select
options={historyKegiatan.map((hk) => ({
value: hk.HistoryKegiatanId.toString(),
label: hk.NamaHistoryKegiatan,
}))}
onChange={(selected) =>
setData(
"HistoryKegiatanId",
selected?.value?.toString() || ""
)
}
placeholder="-- Pilih --"
className="text-sm"
/>
{errors.HistoryKegiatanId && (
<div className="text-red-500 text-sm">
{errors.HistoryKegiatanId}
</div>
)}
</div>
{/* Keterangan */}
<div className="mb-4">
<label className="block font-medium mb-1">
Keterangan
</label>
<textarea
value={data.KeteranganHistory}
onChange={(e) =>
setData(
"KeteranganHistory",
e.target.value
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{errors.KeteranganHistory && (
<div className="text-red-500 text-sm">
{errors.KeteranganHistory}
</div>
)}
</div>
{/* Upload Dokumen */}
<div className="mb-4">
<label className="block font-medium mb-1">
Upload Dokumen*
</label>
<input
type="file"
onChange={(e) =>
setData(
"DokumenHistory",
e.target.files
? e.target.files[0]
: null
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{errors.DokumenHistory && (
<div className="text-red-500 text-sm">
{errors.DokumenHistory}
</div>
)}
</div>
{/* Tombol Aksi */}
<div className="flex justify-end mt-6">
<button
type="button"
onClick={() => {
setShowModal(false);
reset();
}}
className="px-4 py-2 bg-gray-300 rounded-md mr-2"
>
Batal
</button>
<button
type="submit"
disabled={processing}
className="px-4 py-2 bg-blue-600 text-white rounded-md"
>
Simpan
</button>
</div>
</form>
</div>
</div>
)}
{/* Modal Edit History Perusahaan */}
{showEditModal && (
<div className="fixed inset-0 flex items-center justify-center z-50 bg-black/50">
<div className="bg-white p-6 rounded-md w-full max-w-md">
<h2 className="text-xl font-semibold mb-4">
Edit History Perusahaan
</h2>
<form
onSubmit={handleEditSubmit}
encType="multipart/form-data"
>
<div className="mb-4">
<label className="block font-medium mb-1">
Nomor Surat*
</label>
<input
type="text"
value={editData.NomorHistory}
onChange={(e) =>
setEditData(
"NomorHistory",
e.target.value
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{editErrors.NomorHistory && (
<div className="text-red-500 text-sm">
{editErrors.NomorHistory}
</div>
)}
</div>
<div className="mb-4">
<label className="block font-medium mb-1">
Tanggal Surat*
</label>
<input
type="date"
value={editData.TanggalHistory}
onChange={(e) =>
setEditData(
"TanggalHistory",
e.target.value
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{editErrors.TanggalHistory && (
<div className="text-red-500 text-sm">
{editErrors.TanggalHistory}
</div>
)}
</div>
<div className="mb-4">
<label className="block font-medium mb-1">
Kelompok Kegiatan*
</label>
<Select
options={historyKegiatan.map((hk) => ({
value: hk.HistoryKegiatanId.toString(),
label: hk.NamaHistoryKegiatan,
}))}
value={
historyKegiatan.find(
(hk) =>
hk.HistoryKegiatanId.toString() ===
editData.HistoryKegiatanId
)
? {
value: editData.HistoryKegiatanId,
label:
historyKegiatan.find(
(hk) =>
hk.HistoryKegiatanId.toString() ===
editData.HistoryKegiatanId
)?.NamaHistoryKegiatan ||
"",
}
: null
}
onChange={(selected) =>
setEditData(
"HistoryKegiatanId",
selected?.value?.toString() || ""
)
}
placeholder="-- Pilih --"
className="text-sm"
/>
{editErrors.HistoryKegiatanId && (
<div className="text-red-500 text-sm">
{editErrors.HistoryKegiatanId}
</div>
)}
</div>
<div className="mb-4">
<label className="block font-medium mb-1">
Keterangan
</label>
<textarea
value={editData.KeteranganHistory}
onChange={(e) =>
setEditData(
"KeteranganHistory",
e.target.value
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{editErrors.KeteranganHistory && (
<div className="text-red-500 text-sm">
{editErrors.KeteranganHistory}
</div>
)}
</div>
<div className="mb-4">
<label className="block font-medium mb-1">
Upload Dokumen
</label>
<input
type="file"
onChange={(e) =>
setEditData(
"DokumenHistory",
e.target.files
? e.target.files[0]
: null
)
}
className="border border-gray-300 rounded w-full p-2"
/>
{editErrors.DokumenHistory && (
<div className="text-red-500 text-sm">
{editErrors.DokumenHistory}
</div>
)}
{/* Tombol Lihat File saat ini */}
{editingItem?.DokumenHistory && (
<Button
className="bg-green-100 mt-2"
type="button"
variant="outline"
size="sm"
onClick={() =>
window.open(
`/storage/${editingItem.DokumenHistory}`,
"_blank"
)
}
>
Lihat File saat ini
</Button>
)}
</div>
<div className="flex justify-end mt-6">
<button
type="button"
onClick={() => {
setShowEditModal(false);
resetEditForm();
}}
className="px-4 py-2 bg-gray-300 rounded-md mr-2"
>
Batal
</button>
<button
type="submit"
disabled={editProcessing}
className="px-4 py-2 bg-blue-600 text-white rounded-md"
>
Simpan
</button>
</div>
</form>
</div>
</div>
)}
</AuthenticatedLayout>
);
}

View File

@ -0,0 +1,301 @@
import { Input } from "@/components/ui/input";
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import {
HistoryKegiatan,
JenisKegiatan,
Kabupaten,
Perusahaan,
} from "@/types/perusahaan";
import { Head } from "@inertiajs/react";
import { Search } from "lucide-react";
import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { HistoryPerusahaanTable } from "@/components/HistoryPerusahaan/TableHistory";
import Select from "react-select"; // Import React-Select
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
type HistoryPerusahaanIndexProps = {
perusahaan?: Perusahaan[];
historyKegiatan?: HistoryKegiatan[];
kabupaten?: Kabupaten[];
};
export default function HistoryPerusahaanIndex({
perusahaan = [], // Default to empty array
historyKegiatan = [], // Default to empty array
kabupaten = [], // Default to empty array
}: HistoryPerusahaanIndexProps) {
// Define more precise types for select options
type SelectOption = {
value: string;
label: string;
kabupatenId?: string;
kabupatenName?: string;
};
// Define options first before using them
const historyKegiatanOptions: SelectOption[] = historyKegiatan.map(
(jk) => ({
value: jk.HistoryKegiatanId.toString(),
label: jk.NamaHistoryKegiatan,
})
);
const kabupatenOptions: SelectOption[] = kabupaten.map((k) => ({
value: k.KabupatenId.toString(),
label: k.NamaKabupaten,
}));
const companyOptions: SelectOption[] = perusahaan.map((c) => {
const kabupatenName =
c.kelurahan?.kecamatan?.kabupaten?.NamaKabupaten || "Unknown";
return {
value: c.PerusahaanId.toString(),
label: c.NamaPerusahaan,
kabupatenId:
c.kelurahan?.kecamatan?.kabupaten?.KabupatenId?.toString(),
kabupatenName: kabupatenName,
};
});
// State management with more explicit typing
const [selectedHistoryKegiatan, setSelectedHistoryKegiatan] =
useState<SelectOption | null>(null);
const [selectedKabupaten, setSelectedKabupaten] =
useState<SelectOption | null>(null);
const [company, setCompany] = useState<SelectOption | null>(
companyOptions.length > 0 ? companyOptions[0] : null
);
// Now initialize filteredCompanies after companyOptions is defined
const [filteredCompanies, setFilteredCompanies] = useState(companyOptions);
// Add state for number search
const [nomorPerusahaan, setNomorPerusahaan] = useState<string>("");
// Add state for filtered perusahaan data
const [filteredPerusahaan, setFilteredPerusahaan] =
useState<Perusahaan[]>(perusahaan);
// Update filtered companies when kabupaten selection changes
useEffect(() => {
if (selectedKabupaten) {
const filtered = companyOptions.filter(
(option) => option.kabupatenId === selectedKabupaten.value
);
setFilteredCompanies(filtered);
} else {
setFilteredCompanies(companyOptions);
}
}, [selectedKabupaten]);
// Explicit typing for onChange handlers
const handleCompanyChange = (selectedOption: SelectOption | null) => {
setCompany(selectedOption);
};
const handleNomorPerusahaanChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setNomorPerusahaan(e.target.value);
};
// Search function
const handleSearch = () => {
// Start with all perusahaan data
let results = [...perusahaan];
// Filter by nomor perusahaan if provided
if (nomorPerusahaan.trim() !== "") {
results = results.filter((p) =>
p.NomorInduk?.toLowerCase().includes(
nomorPerusahaan.toLowerCase()
)
);
}
// Filter by selected company if provided
if (company) {
results = results.filter(
(p) => p.PerusahaanId.toString() === company.value
);
}
// Filter by selected kabupaten if provided
if (selectedKabupaten) {
results = results.filter((p) => {
// Handle potential null/undefined values safely
const kabupatenId =
p.kelurahan?.kecamatan?.kabupaten?.KabupatenId;
return kabupatenId
? kabupatenId.toString() === selectedKabupaten.value
: false;
});
}
// Filter by selected history kegiatan if provided
if (selectedHistoryKegiatan) {
results = results.filter((p) => {
// Check if historyKegiatan exists and is an array
const historyKegiatanArray = p.historyKegiatan;
if (
!historyKegiatanArray ||
!Array.isArray(historyKegiatanArray)
) {
return false;
}
// Check if any historyKegiatan matches the selected one
return historyKegiatanArray.some((hk) => {
return (
hk &&
hk.HistoryKegiatanId &&
hk.HistoryKegiatanId.toString() ===
selectedHistoryKegiatan.value
);
});
});
}
// Update the filtered results
setFilteredPerusahaan(results);
};
// Custom styles for React-Select
const selectStyles = {
control: (base: any) => ({
...base,
minHeight: "38px",
borderRadius: "0.375rem",
borderColor: "#e2e8f0",
boxShadow: "none",
"&:hover": {
borderColor: "#cbd5e0",
},
}),
};
return (
<AuthenticatedLayout header={"History Perusahaan"}>
<Head title="History Perusahaan" />
<div className="container mx-auto p-4">
{/* Filter Section */}
<div className="bg-white py-6 rounded-lg shadow-sm mb-6">
<Card className="shadow-md border-slate-200">
<CardHeader className="pb-3">
<CardTitle className="text-lg font-medium">
Filter Pencarian
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Left Column */}
<div className="space-y-4">
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Nomor Perusahaan
</label>
<div className="col-span-2">
<Input
placeholder="Masukkan Nomor Perusahaan"
className="w-full"
value={nomorPerusahaan}
onChange={
handleNomorPerusahaanChange
}
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Perusahaan
</label>
<div className="col-span-2">
<Select
className="basic-single"
classNamePrefix="select"
value={company}
onChange={handleCompanyChange}
options={filteredCompanies}
isClearable={true}
isSearchable={true}
placeholder="Pilih Perusahaan"
noOptionsMessage={() =>
"Tidak ada data"
}
styles={selectStyles}
/>
</div>
</div>
</div>
{/* Right Column */}
<div className="space-y-4">
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Kabupaten/Kota
</label>
<div className="col-span-2">
<Select
className="basic-single"
classNamePrefix="select"
value={selectedKabupaten}
onChange={setSelectedKabupaten}
options={kabupatenOptions}
isClearable={true}
isSearchable={true}
placeholder="Pilih Kabupaten/Kota"
noOptionsMessage={() =>
"Tidak ada data"
}
styles={selectStyles}
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Kelompok Kegiatan
</label>
<div className="col-span-2">
<Select
className="basic-single"
classNamePrefix="select"
value={selectedHistoryKegiatan}
onChange={
setSelectedHistoryKegiatan
}
options={historyKegiatanOptions}
isClearable={true}
isSearchable={true}
placeholder="Pilih Kelompok Kegiatan"
noOptionsMessage={() =>
"Tidak ada data"
}
styles={selectStyles}
/>
</div>
</div>
<div className="flex justify-end mt-6">
<Button
className="bg-green-600 hover:bg-green-700 text-white"
onClick={handleSearch}
>
<Search className="w-4 h-4 mr-2" />
Cari
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Table Section - Now uses filteredPerusahaan */}
<HistoryPerusahaanTable data={filteredPerusahaan} />
</div>
</AuthenticatedLayout>
);
}

View File

@ -33,6 +33,44 @@ export interface Perusahaan {
JenisDokIL: {
NamaJenisDokIL: string;
};
Kabupaten?: string; // Make sure this field exists
}
export interface NamaPerusahaan {
PerusahaanId: number;
NamaPerusahaan: string;
}
export interface HistoryKegiatan {
HistoryKegiatanId: number;
NamaHistoryKegiatan: string;
}
export interface HistoryPerusahaan {
[x: string]: any;
PerusahaanId: number;
NamaPerusahaan: string;
HistoryKegiatanId: number;
NamaHistoryKegiatan: string;
TanggalHistory: string;
NomorHistory: string;
DokumenHistory: string;
}
export interface PerizinanLingkunganType {
[x: string]: any;
PerusahaanId: number;
NomorInduk: string;
NamaPerusahaan: string;
JenisKegiatanId: number;
JenisDokilId: number;
Alamat: string;
Telepon: string;
Fax: string;
Email: string;
ILNomor: string;
ILTanggal: string;
ILDokumen: string;
}
export interface HukumType {