feat: Menambahkan validasi file dan relasi model
parent
6fabbe8708
commit
cae368adc0
|
@ -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'],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue