refactor: Perbarui logika pengelolaan file terunggah

main
marszayn 2025-03-10 14:05:44 +07:00
parent f413f8e4e0
commit 64db7e7f8e
4 changed files with 857 additions and 10 deletions

View File

@ -70,7 +70,10 @@ class HukumController extends Controller
$data = $request->validated();
if ($request->hasFile('SanksiFile')) {
// Preserve existing SanksiFile if no new file is uploaded
if (!$request->hasFile('SanksiFile')) {
unset($data['SanksiFile']);
} else {
// Delete old file if exists
if ($hukum->SanksiFile && Storage::disk('public')->exists($hukum->SanksiFile)) {
Storage::disk('public')->delete($hukum->SanksiFile);
@ -81,7 +84,10 @@ class HukumController extends Controller
$data['SanksiFile'] = $path;
}
if ($request->hasFile('PenaatanFile')) {
// Preserve existing PenaatanFile if no new file is uploaded
if (!$request->hasFile('PenaatanFile')) {
unset($data['PenaatanFile']);
} else {
// Delete old file if exists
if ($hukum->PenaatanFile && Storage::disk('public')->exists($hukum->PenaatanFile)) {
Storage::disk('public')->delete($hukum->PenaatanFile);

View File

@ -10,7 +10,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Select from "react-select";
import { useState, useEffect } from "react";
import { useForm } from "@inertiajs/react";
import { router, useForm } from "@inertiajs/react";
import { useToast } from "@/hooks/use-toast";
import { HukumType } from "@/types/perusahaan";
@ -92,6 +92,7 @@ export function AddPenaatanModal({
data: formData,
forceFormData: true,
onSuccess: () => {
router.visit(window.location.href, { replace: true });
toast({
title: "Berhasil",
description: "Data Penaatan berhasil diperbarui",
@ -166,11 +167,12 @@ export function AddPenaatanModal({
/>
{editingData?.PenaatanFile && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>
{/* <span>
File saat ini:{" "}
{editingData.PenaatanFile}
</span>
</span> */}
<Button
className="bg-green-200"
type="button"
variant="outline"
size="sm"
@ -181,7 +183,7 @@ export function AddPenaatanModal({
)
}
>
Lihat File
Lihat File saat ini
</Button>
</div>
)}

View File

@ -10,7 +10,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Select from "react-select";
import { useState, useEffect } from "react";
import { useForm } from "@inertiajs/react";
import { router, useForm } from "@inertiajs/react";
import { useToast } from "@/hooks/use-toast";
import { HukumType } from "@/types/perusahaan";
@ -131,6 +131,7 @@ export function AddHukumModal({
data: formData,
forceFormData: true,
onSuccess: () => {
router.visit(window.location.href, { replace: true });
toast({
title: "Berhasil",
description: editingData
@ -222,10 +223,11 @@ export function AddHukumModal({
/>
{editingData?.SanksiFile && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>
{/* <span>
File saat ini: {editingData.SanksiFile}
</span>
</span> */}
<Button
className="bg-green-100"
type="button"
variant="outline"
size="sm"
@ -236,7 +238,7 @@ export function AddHukumModal({
)
}
>
Lihat File
Lihat File saat ini
</Button>
</div>
)}

View File

@ -0,0 +1,837 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Select from "react-select";
import { useState, useEffect } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import {
FileText,
Hotel,
LocateFixed,
MapPinned,
UsersRound,
VerifiedIcon,
} from "lucide-react";
import { Separator } from "../ui/separator";
import { Textarea } from "../ui/textarea";
import {
JenisDokIL,
JenisKegiatan,
Verifikator,
Kabupaten,
Kecamatan,
Kelurahan,
Perusahaan,
} from "@/types/perusahaan";
import { useForm } from "@inertiajs/react";
import { useToast } from "@/hooks/use-toast";
interface EditPerusahaanModalProps {
open: boolean;
onClose: () => void;
onSuccess: () => void;
jenisKegiatan: JenisKegiatan[];
jenisDokIL: JenisDokIL[];
verifikator: Verifikator[];
kabupaten: Kabupaten[];
kecamatan: Kecamatan[];
kelurahan: Kelurahan[];
perusahaan: Perusahaan | null;
}
export function EditPerusahaanModal({
open,
onClose,
onSuccess,
jenisKegiatan,
jenisDokIL,
verifikator,
kabupaten,
kecamatan,
kelurahan,
perusahaan,
}: EditPerusahaanModalProps) {
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const {
data,
setData,
post,
reset,
errors: formErrors,
} = useForm({
NomorInduk: "",
PerusahaanId: "",
JenisKegiatanId: "",
NamaPerusahaan: "",
Alamat: "",
KelurahanId: "",
KodePos: "",
Telepon: "",
Fax: "",
Email: "",
Lintang: "",
Bujur: "",
CPNama: "",
CPTelepon: "",
JenisDokILId: "",
VerifikatorId: "",
IsPublish: true,
ILDokumen: null as File | null,
ILNomor: "",
ILTanggal: "",
ReportLocked: true,
});
const [selectedKabupaten, setSelectedKabupaten] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedKecamatan, setSelectedKecamatan] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedKelurahan, setSelectedKelurahan] = useState<{
value: number;
label: string;
} | null>(null);
useEffect(() => {
if (perusahaan) {
setData({
NomorInduk: perusahaan.NomorInduk || "",
PerusahaanId: perusahaan.PerusahaanId.toString(),
JenisKegiatanId: perusahaan.JenisKegiatanId?.toString() || "",
NamaPerusahaan: perusahaan.NamaPerusahaan || "",
Alamat: perusahaan.Alamat || "",
KelurahanId: perusahaan.KelurahanId?.toString() || "",
KodePos: perusahaan.KodePos || "",
Telepon: perusahaan.Telepon || "",
Fax: perusahaan.Fax || "",
Email: perusahaan.Email || "",
Lintang: perusahaan.Lintang || "",
Bujur: perusahaan.Bujur || "",
CPNama: perusahaan.CPNama || "",
CPTelepon: perusahaan.CPTelepon || "",
JenisDokILId: perusahaan.JenisDokILId?.toString() || "",
VerifikatorId: perusahaan.VerifikatorId?.toString() || "",
IsPublish:
perusahaan.IsPublish !== undefined
? perusahaan.IsPublish
: true,
ILDokumen: null, // Files need to be uploaded again if needed
ILNomor: perusahaan.ILNomor || "",
ILTanggal: perusahaan.ILTanggal || "",
ReportLocked: perusahaan.ReportLocked || true,
});
// Set location dropdown selections
if (perusahaan.kelurahan && perusahaan.KelurahanId) {
const currentKelurahan = kelurahan.find(
(k) => k.KelurahanId === perusahaan.KelurahanId
);
if (currentKelurahan && currentKelurahan.KecamatanId) {
const currentKecamatan = kecamatan.find(
(k) => k.KecamatanId === currentKelurahan.KecamatanId
);
if (currentKecamatan && currentKecamatan.KabupatenId) {
// Set kabupaten
const kabupatenData = kabupaten.find(
(k) =>
k.KabupatenId === currentKecamatan.KabupatenId
);
if (kabupatenData) {
setSelectedKabupaten({
value: kabupatenData.KabupatenId,
label: kabupatenData.NamaKabupaten,
});
}
// Set kecamatan
setSelectedKecamatan({
value: currentKecamatan.KecamatanId,
label: currentKecamatan.NamaKecamatan,
});
// Set kelurahan
setSelectedKelurahan({
value: currentKelurahan.KelurahanId,
label: currentKelurahan.NamaKelurahan,
});
}
}
}
}
}, [perusahaan, kecamatan, kelurahan, kabupaten, setData]);
const jenisKegiatanOptions = jenisKegiatan.map((jk) => ({
value: jk.JenisKegiatanId,
label: jk.NamaJenisKegiatan,
}));
const jenisDokILOptions = jenisDokIL.map((jdi) => ({
value: jdi.JenisDokILId,
label: jdi.NamaJenisDokIL,
}));
const verifikatorOptions = verifikator.map((v) => ({
value: v.VerifikatorId,
label: v.NamaUnitKerja,
}));
const kabupatenOptions = kabupaten.map((k) => ({
value: k.KabupatenId,
label: k.NamaKabupaten,
}));
const kecamatanOptions = kecamatan
.filter(
(kec) =>
selectedKabupaten && kec.KabupatenId === selectedKabupaten.value
)
.map((kec) => ({
value: kec.KecamatanId,
label: kec.NamaKecamatan,
}));
const kelurahanOptions = kelurahan
.filter(
(kel) =>
selectedKecamatan && kel.KecamatanId === selectedKecamatan.value
)
.map((kel) => ({
value: kel.KelurahanId,
label: kel.NamaKelurahan,
}));
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!perusahaan) {
toast({
title: "Error",
description: "Data perusahaan tidak ditemukan",
variant: "destructive",
});
return;
}
setLoading(true);
const formData = new FormData();
// Add all form fields to FormData
Object.entries(data).forEach(([key, value]) => {
if (value !== null && key !== "ILDokumen") {
formData.append(key, value.toString());
}
});
// Add file only if a new one was selected
if (data.ILDokumen) {
formData.append("ILDokumen", data.ILDokumen);
}
post(`/admin/perusahaan/${perusahaan.PerusahaanId}`, {
data: formData,
forceFormData: true,
onSuccess: () => {
toast({
title: "Berhasil",
description: "Data perusahaan berhasil diperbarui",
variant: "default",
});
reset();
onSuccess();
onClose();
},
onError: (error) => {
console.error("Error:", error);
toast({
title: "Gagal",
description: "Terjadi kesalahan saat mengupdate perusahaan",
variant: "destructive",
});
},
onFinish: () => {
setLoading(false);
},
});
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Edit Data Perusahaan</DialogTitle>
<DialogDescription>
Perbarui informasi {perusahaan?.NamaPerusahaan || ""}{" "}
dalam sistem.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="flex flex-row gap-6">
{/* Sisi Kiri */}
<div className="w-1/2">
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<Hotel size={16} />
<h3 className="text-sm ">
Data Perusahaan
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="NomorInduk">
Nomor Induk
</Label>
<Input
id="NomorInduk"
value={data.NomorInduk}
onChange={(e) =>
setData({
...data,
NomorInduk: e.target.value,
})
}
/>
{formErrors.NomorInduk && (
<p className="text-red-500 text-sm">
{formErrors.NomorInduk}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="NamaPerusahaan">
Nama Perusahaan *
</Label>
<Input
id="NamaPerusahaan"
required
value={data.NamaPerusahaan}
onChange={(e) =>
setData({
...data,
NamaPerusahaan: e.target.value,
})
}
/>
{formErrors.NamaPerusahaan && (
<p className="text-red-500 text-sm">
{formErrors.NamaPerusahaan}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="JenisKegiatanId">
Jenis Kegiatan *
</Label>
<Select
id="JenisKegiatanId"
options={jenisKegiatanOptions}
required
placeholder="Pilih Jenis Kegiatan"
value={jenisKegiatanOptions.find(
(option) =>
option.value.toString() ===
data.JenisKegiatanId
)}
onChange={(option) =>
setData({
...data,
JenisKegiatanId:
option?.value?.toString() ||
"",
})
}
/>
{formErrors.JenisKegiatanId && (
<p className="text-red-500 text-sm">
{formErrors.JenisKegiatanId}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="VerifikatorId">
Admin *
</Label>
<Select
id="VerifikatorId"
options={verifikatorOptions}
required
placeholder="Pilih Admin"
value={verifikatorOptions.find(
(option) =>
option.value.toString() ===
data.VerifikatorId
)}
onChange={(option) =>
setData({
...data,
VerifikatorId:
option?.value?.toString() ||
"",
})
}
/>
{formErrors.VerifikatorId && (
<p className="text-red-500 text-sm">
{formErrors.VerifikatorId}
</p>
)}
</div>
<Separator />
{/* Kontak Person */}
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<UsersRound size={16} />
<h3 className="text-sm ">
Kontak Person
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="CPNama">Nama</Label>
<Input
id="CPNama"
value={data.CPNama}
onChange={(e) =>
setData({
...data,
CPNama: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="CPTelepon">
Telepon
</Label>
<Input
id="CPTelepon"
value={data.CPTelepon}
onChange={(e) =>
setData({
...data,
CPTelepon: e.target.value,
})
}
/>
</div>
</div>
<Separator className="space-y-4" />
{/* Dokumen Izin */}
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<FileText size={16} />
<h3 className="text-sm ">
Dokumen Izin
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="ILNomor">Nomor</Label>
<Input
id="ILNomor"
value={data.ILNomor}
onChange={(e) =>
setData({
...data,
ILNomor: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="ILTanggal">
Tanggal
</Label>
<Input
id="ILTanggal"
type="date"
value={
data.ILTanggal
? data.ILTanggal.toString().split(
"T"
)[0]
: ""
}
onChange={(e) =>
setData({
...data,
ILTanggal: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="JenisDokILId">
Jenis Dokumen
</Label>
<Select
id="JenisDokILId"
options={jenisDokILOptions}
placeholder="Pilih Jenis Dokumen"
value={jenisDokILOptions.find(
(option) =>
option.value.toString() ===
data.JenisDokILId
)}
onChange={(option) =>
setData({
...data,
JenisDokILId:
option?.value?.toString() ||
"",
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="ILDokumen">
Unggah Dokumen
</Label>
<Input
id="ILDokumen"
type="file"
accept=".pdf"
onChange={(e) =>
setData({
...data,
ILDokumen: e.target.files
? e.target.files[0]
: null,
})
}
/>
{perusahaan?.ILDokumen && (
// <div className="text-xs text-gray-600 mt-1">
// Dokumen saat ini:{" "}
// {perusahaan.ILDokumen}
// <a
// href={`/storage/${perusahaan.ILDokumen}`}
// target="_blank"
// rel="noopener noreferrer"
// className="ml-2 text-blue-600 hover:underline"
// >
// Lihat dokumen
// </a>
// </div>
<Button
className="bg-green-100"
type="button"
variant="outline"
size="sm"
onClick={() =>
window.open(
`/storage/${perusahaan.ILDokumen}`,
"_blank"
)
}
>
Lihat File saat ini
</Button>
)}
</div>
</div>
</div>
</div>
{/* Sisi Kanan */}
<div className="w-1/2">
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<MapPinned size={16} />
<h3 className="text-sm ">
Alamat Perusahaan
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="KabupatenId">
Kabupaten/Kota *
</Label>
<Select
id="KabupatenId"
options={kabupatenOptions}
value={selectedKabupaten}
onChange={(value) => {
setSelectedKabupaten(value);
setSelectedKecamatan(null);
setSelectedKelurahan(null);
setData({
...data,
KelurahanId: "",
});
}}
placeholder="Pilih Kabupaten/Kota"
isSearchable
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="KecamatanId">
Kecamatan *
</Label>
<Select
id="KecamatanId"
options={kecamatanOptions}
value={selectedKecamatan}
onChange={(value) => {
setSelectedKecamatan(value);
setSelectedKelurahan(null);
setData({
...data,
KelurahanId: "",
});
}}
placeholder="Pilih Kecamatan"
isSearchable
isDisabled={!selectedKabupaten}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="KelurahanId">
Kelurahan *
</Label>
<Select
id="KelurahanId"
options={kelurahanOptions}
value={selectedKelurahan}
onChange={(value) => {
setSelectedKelurahan(value);
setData({
...data,
KelurahanId:
value?.value?.toString() ||
"",
});
}}
placeholder="Pilih Kelurahan"
isSearchable
isDisabled={!selectedKecamatan}
required
/>
{formErrors.KelurahanId && (
<p className="text-red-500 text-sm">
{formErrors.KelurahanId}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="Alamat">Alamat</Label>
<Textarea
id="Alamat"
value={data.Alamat}
onChange={(e) =>
setData({
...data,
Alamat: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="KodePos">Kode Pos</Label>
<Input
id="KodePos"
value={data.KodePos}
onChange={(e) =>
setData({
...data,
KodePos: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Telepon">Telepon</Label>
<Input
id="Telepon"
value={data.Telepon}
onChange={(e) =>
setData({
...data,
Telepon: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Fax">Fax</Label>
<Input
id="Fax"
value={data.Fax}
onChange={(e) =>
setData({
...data,
Fax: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Email">Email *</Label>
<Input
id="Email"
type="email"
required
value={data.Email}
onChange={(e) =>
setData({
...data,
Email: e.target.value,
})
}
/>
{formErrors.Email && (
<p className="text-red-500 text-sm">
{formErrors.Email}
</p>
)}
</div>
<Separator className="space-y-4" />
{/* Kontak dan Koordinat */}
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<LocateFixed size={16} />
<h3 className="text-sm ">Koordinat</h3>
</div>
<div className="space-y-2">
<Label htmlFor="Lintang">Lintang</Label>
<Input
id="Lintang"
value={data.Lintang}
onChange={(e) =>
setData({
...data,
Lintang: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Bujur">Bujur</Label>
<Input
id="Bujur"
value={data.Bujur}
onChange={(e) =>
setData({
...data,
Bujur: e.target.value,
})
}
/>
</div>
</div>
<Separator className="space-y-4" />
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<VerifiedIcon size={16} />
<h3 className="text-sm">Status</h3>
</div>
<div className="flex space-x-4">
<div className="flex items-center space-x-2">
<Checkbox
id="IsPublishActive"
checked={data.IsPublish === true}
onCheckedChange={() =>
setData({
...data,
IsPublish: true,
})
}
/>
<Label htmlFor="IsPublishActive">
Aktif
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="IsPublishInactive"
checked={data.IsPublish === false}
onCheckedChange={() =>
setData({
...data,
IsPublish: false,
})
}
/>
<Label htmlFor="IsPublishInactive">
Non Aktif
</Label>
</div>
</div>
{/* <div className="mt-4">
<div className="flex items-center space-x-2">
<Checkbox
id="ReportLocked"
checked={data.ReportLocked === true}
onCheckedChange={(checked) =>
setData({
...data,
ReportLocked:
checked === true,
})
}
/>
<Label htmlFor="ReportLocked">
Kunci Laporan
</Label>
</div>
</div> */}
</div>
</div>
</div>
<Separator className="space-y-4" />
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={onClose}
disabled={loading}
>
Batal
</Button>
<Button type="submit" disabled={loading}>
{loading ? "Menyimpan..." : "Simpan Perubahan"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}