first commit

This commit is contained in:
akbarguci.pkl 2026-05-19 16:16:32 +07:00
parent 8c5285b988
commit 527ad45ab8
1 changed files with 191 additions and 115 deletions

View File

@ -1,4 +1,5 @@
// src/components/masterData/TambahBarang.tsx // src/components/masterData/TambahBarang.tsx
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import axios from "axios"; import axios from "axios";
import { PlusCircle, Image } from "lucide-react"; import { PlusCircle, Image } from "lucide-react";
@ -12,14 +13,42 @@ export default function TambahBarang({
onClose, onClose,
onRefresh, onRefresh,
}: TambahBarangProps) { }: TambahBarangProps) {
// STATE INPUT FORM // STATE INPUT FORM
const [kodeBarang, setKodeBarang] = useState(""); const [kodeBarang, setKodeBarang] = useState("");
const [namaBarang, setNamaBarang] = useState(""); const [namaBarang, setNamaBarang] = useState("");
// BISA PILIH / KETIK SENDIRI
const [kategoriSelected, setKategoriSelected] = useState(""); const [kategoriSelected, setKategoriSelected] = useState("");
const [lokasiSelected, setLokasiSelected] = useState(""); const [lokasiSelected, setLokasiSelected] = useState("");
const [kondisiFisik, setKondisiFisik] = useState(""); const [kondisiFisik, setKondisiFisik] = useState("");
const [stokAwal, setStokAwal] = useState(""); const [stokAwal, setStokAwal] = useState("");
// LIST DINAMIS
const [kategoriList, setKategoriList] = useState<string[]>([
"Elektronik",
"Alat Tulis",
"Kebersihan",
"Mebel dan Furnitur",
"Perangkat Jaringan",
"Peralatan Operasional",
"Peralatan Bengkel",
"Buku dan Modul",
]);
const [lokasiList, setLokasiList] = useState<string[]>([
"Gudang Pusat",
"Lab PPLG",
"Lab TKJ",
"X PPLG",
"XI PPLG",
"XII PPLG",
"X TKJ",
"XI TKJ",
"XII TKJ",
]);
// FILE FOTO // FILE FOTO
const [fotoBarang, setFotoBarang] = useState<File | null>(null); const [fotoBarang, setFotoBarang] = useState<File | null>(null);
@ -34,37 +63,34 @@ export default function TambahBarang({
// LOGIKA KODE OTOMATIS // LOGIKA KODE OTOMATIS
useEffect(() => { useEffect(() => {
if (kategoriSelected && lokasiSelected) { if (kategoriSelected && lokasiSelected) {
const inisialKategori: { [key: string]: string } = {
Elektronik: "ELK", // AMBIL INISIAL OTOMATIS
"Alat Tulis": "ATK", const ambilInisial = (text: string) => {
Kebersihan: "KBR", const kata = text
"Mebel dan Furnitur": "MUB", .replace(/[^a-zA-Z0-9 ]/g, "")
"Perangkat Jaringan": "JRG", .toUpperCase()
"Peralatan Operasional": "OPR", .split(" ")
"Peralatan Bengkel": "PBG", .filter(Boolean);
"Buku dan Modul": "BKM",
if (kata.length >= 2) {
return (
kata[0].substring(0, 2) +
kata[1].substring(0, 1)
);
}
return kata[0]?.substring(0, 3) || "BRG";
}; };
const inisialLokasi: { [key: string]: string } = { const kat = ambilInisial(kategoriSelected);
"Gudang Pusat": "GUD", const lok = ambilInisial(lokasiSelected);
"Lab PPLG": "LAB-PPLG",
"Lab TKJ": "LAB-TKJ",
"X PPLG": "KLS-X-PPLG",
"XI PPLG": "KLS-XI-PPLG",
"XII PPLG": "KLS-XII-PPLG",
"X TKJ": "KLS-X-TKJ",
"XI TKJ": "KLS-XI-TKJ",
"XII TKJ": "KLS-XII-TKJ",
};
const kat = inisialKategori[kategoriSelected] || "BRG";
const lok = inisialLokasi[lokasiSelected] || "GUD";
const nomorUrut = String( const nomorUrut = String(
Math.floor(100 + Math.random() * 900) Math.floor(100 + Math.random() * 900)
); );
setKodeBarang(`${kat}/${lok}/${nomorUrut}`); setKodeBarang(`${kat}/${lok}/${nomorUrut}`);
} else { } else {
setKodeBarang(""); setKodeBarang("");
} }
@ -74,7 +100,9 @@ export default function TambahBarang({
const handleFileChange = ( const handleFileChange = (
e: React.ChangeEvent<HTMLInputElement> e: React.ChangeEvent<HTMLInputElement>
) => { ) => {
if (e.target.files && e.target.files[0]) { if (e.target.files && e.target.files[0]) {
const file = e.target.files[0]; const file = e.target.files[0];
setFotoBarang(file); setFotoBarang(file);
@ -83,7 +111,7 @@ export default function TambahBarang({
setPreviewUrl(localPreview); setPreviewUrl(localPreview);
// RESET LINK KALAU PAKAI FILE // RESET LINK
setFotoLink(""); setFotoLink("");
} }
}; };
@ -92,25 +120,27 @@ export default function TambahBarang({
const handleLinkChange = ( const handleLinkChange = (
e: React.ChangeEvent<HTMLInputElement> e: React.ChangeEvent<HTMLInputElement>
) => { ) => {
const value = e.target.value; const value = e.target.value;
setFotoLink(value); setFotoLink(value);
// PREVIEW DARI LINK // PREVIEW LINK
setPreviewUrl(value); setPreviewUrl(value);
// RESET FILE KALAU PAKAI LINK // RESET FILE
setFotoBarang(null); setFotoBarang(null);
}; };
const handleSimpanKeDB = async ( const handleSimpanKeDB = async (
e: React.FormEvent e: React.FormEvent
) => { ) => {
e.preventDefault(); e.preventDefault();
if (!kodeBarang) { if (!kodeBarang) {
alert( alert(
"Pilih Kategori dan Lokasi dulu agar kode barang tercipta otomatis!" "Pilih / ketik kategori dan lokasi dulu!"
); );
return; return;
} }
@ -121,23 +151,33 @@ export default function TambahBarang({
} }
if (!kondisiFisik) { if (!kondisiFisik) {
alert("Silakan pilih Kondisi Fisik dulu!"); alert("Silakan pilih kondisi fisik!");
return; return;
} }
if (!stokAwal) { if (!stokAwal) {
alert("Stok awal jangan dikosongkan!"); alert("Stok awal jangan kosong!");
return; return;
} }
// VALIDASI FOTO // VALIDASI FOTO
if (!fotoBarang && !fotoLink) { if (!fotoBarang && !fotoLink) {
alert( alert(
"Foto barang wajib dipilih atau menggunakan link online!" "Foto barang wajib dipilih atau pakai link!"
); );
return; return;
} }
const formData = new FormData();
formData.append("kode_barang", kodeBarang);
formData.append(
"nama_barang",
namaBarang.trim()
);
// WAJIB BACKEND LAMA
const kategoriMapping: { [key: string]: number } = { const kategoriMapping: { [key: string]: number } = {
Elektronik: 1, Elektronik: 1,
"Alat Tulis": 7, "Alat Tulis": 7,
@ -161,19 +201,33 @@ export default function TambahBarang({
"XII TKJ": 338, "XII TKJ": 338,
}; };
const formData = new FormData(); // KALAU INPUT BARU BELUM ADA
const kategoriId =
kategoriMapping[kategoriSelected] || 999;
formData.append("kode_barang", kodeBarang); const lokasiId =
formData.append("nama_barang", namaBarang.trim()); lokasiMapping[lokasiSelected] || 999;
// BACKEND LAMA
formData.append( formData.append(
"kategori_id", "kategori_id",
String(kategoriMapping[kategoriSelected] || 1) String(kategoriId)
); );
formData.append( formData.append(
"lokasi_id", "lokasi_id",
String(lokasiMapping[lokasiSelected] || 1) String(lokasiId)
);
// TAMBAHAN BARU
formData.append(
"nama_kategori",
kategoriSelected
);
formData.append(
"nama_lokasi",
lokasiSelected
); );
formData.append( formData.append(
@ -183,19 +237,29 @@ export default function TambahBarang({
: "Rusak" : "Rusak"
); );
formData.append("stok", stokAwal); formData.append(
"stok",
stokAwal
);
// FILE FOTO // FILE FOTO
if (fotoBarang) { if (fotoBarang) {
formData.append("foto_barang", fotoBarang); formData.append(
"foto_barang",
fotoBarang
);
} }
// LINK FOTO // LINK FOTO
if (fotoLink) { if (fotoLink) {
formData.append("foto_link", fotoLink); formData.append(
"foto_link",
fotoLink
);
} }
try { try {
setLoading(true); setLoading(true);
const res = await axios.post( const res = await axios.post(
@ -214,6 +278,29 @@ export default function TambahBarang({
res.status === 200 || res.status === 200 ||
res.status === 201 res.status === 201
) { ) {
// AUTO TAMBAH KE LIST KATEGORI
if (
kategoriSelected &&
!kategoriList.includes(kategoriSelected)
) {
setKategoriList((prev) => [
...prev,
kategoriSelected,
]);
}
// AUTO TAMBAH KE LIST LOKASI
if (
lokasiSelected &&
!lokasiList.includes(lokasiSelected)
) {
setLokasiList((prev) => [
...prev,
lokasiSelected,
]);
}
alert( alert(
`Sukses! Barang terdaftar dengan kode ${kodeBarang}` `Sukses! Barang terdaftar dengan kode ${kodeBarang}`
); );
@ -231,23 +318,30 @@ export default function TambahBarang({
if (onRefresh) onRefresh(); if (onRefresh) onRefresh();
if (onClose) onClose(); if (onClose) onClose();
} }
} catch (err: any) { } catch (err: any) {
console.error(err); console.error(err);
alert( alert(
err.response?.data?.message || err.response?.data?.message ||
"Gagal menyimpan barang!" "Gagal menyimpan barang!"
); );
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
<div className="p-6 bg-white dark:bg-gray-800 rounded-3xl shadow-2xl max-w-2xl w-full mx-auto text-black dark:text-white max-h-[90vh] overflow-y-auto"> <div className="p-6 bg-white dark:bg-gray-800 rounded-3xl shadow-2xl max-w-2xl w-full mx-auto text-black dark:text-white max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-6 border-b pb-3 border-gray-100 dark:border-gray-700"> <div className="flex items-center justify-between mb-6 border-b pb-3 border-gray-100 dark:border-gray-700">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white"> <h2 className="text-2xl font-bold text-gray-900 dark:text-white">
Input Inventaris Baru Input Inventaris Baru
</h2> </h2>
@ -260,20 +354,25 @@ export default function TambahBarang({
✕ ✕
</button> </button>
)} )}
</div> </div>
<form <form
onSubmit={handleSimpanKeDB} onSubmit={handleSimpanKeDB}
className="space-y-4" className="space-y-4"
> >
{/* FOTO */} {/* FOTO */}
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Foto Barang Foto Barang
</label> </label>
<div className="flex items-center gap-4 p-3 border border-dashed border-gray-300 dark:border-gray-600 rounded-xl bg-gray-50 dark:bg-gray-700"> <div className="flex items-center gap-4 p-3 border border-dashed border-gray-300 dark:border-gray-600 rounded-xl bg-gray-50 dark:bg-gray-700">
<label className="flex items-center gap-2 bg-blue-50 hover:bg-blue-100 text-blue-600 px-4 py-2 rounded-xl text-sm font-semibold cursor-pointer border border-blue-200 transition"> <label className="flex items-center gap-2 bg-blue-50 hover:bg-blue-100 text-blue-600 px-4 py-2 rounded-xl text-sm font-semibold cursor-pointer border border-blue-200 transition">
<Image size={16} /> <Image size={16} />
Pilih File Pilih File
@ -284,6 +383,7 @@ export default function TambahBarang({
onChange={handleFileChange} onChange={handleFileChange}
className="hidden" className="hidden"
/> />
</label> </label>
<div className="text-xs text-gray-400"> <div className="text-xs text-gray-400">
@ -291,10 +391,12 @@ export default function TambahBarang({
? fotoBarang.name ? fotoBarang.name
: "Belum ada file terpilih"} : "Belum ada file terpilih"}
</div> </div>
</div> </div>
{/* INPUT LINK FOTO */} {/* INPUT LINK FOTO */}
<div className="mt-3"> <div className="mt-3">
<input <input
type="text" type="text"
placeholder="Atau masukkan link foto online..." placeholder="Atau masukkan link foto online..."
@ -302,134 +404,92 @@ export default function TambahBarang({
onChange={handleLinkChange} onChange={handleLinkChange}
className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500" className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500"
/> />
</div> </div>
{/* PREVIEW */} {/* PREVIEW */}
{previewUrl && ( {previewUrl && (
<div className="mt-3 w-32 h-32 rounded-xl overflow-hidden border border-gray-200 shadow-sm"> <div className="mt-3 w-32 h-32 rounded-xl overflow-hidden border border-gray-200 shadow-sm">
<img <img
src={previewUrl} src={previewUrl}
alt="Preview" alt="Preview"
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </div>
)} )}
</div> </div>
{/* KATEGORI & LOKASI */} {/* KATEGORI & LOKASI */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* KATEGORI */}
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Kategori Kategori
</label> </label>
<select <input
list="kategori-list"
value={kategoriSelected} value={kategoriSelected}
onChange={(e) => onChange={(e) =>
setKategoriSelected( setKategoriSelected(
e.target.value e.target.value
) )
} }
placeholder="Pilih atau ketik kategori..."
className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500 bg-white text-black" className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500 bg-white text-black"
> />
<option value="">
-- Pilih Kategori --
</option>
<option value="Elektronik"> <datalist id="kategori-list">
Elektronik {kategoriList.map((kategori, index) => (
</option> <option
key={index}
value={kategori}
/>
))}
</datalist>
<option value="Alat Tulis">
Alat Tulis
</option>
<option value="Kebersihan">
Kebersihan
</option>
<option value="Mebel dan Furnitur">
Mebel dan Furnitur
</option>
<option value="Perangkat Jaringan">
Perangkat Jaringan
</option>
<option value="Peralatan Operasional">
Peralatan Operasional
</option>
<option value="Peralatan Bengkel">
Peralatan Bengkel
</option>
<option value="Buku dan Modul">
Buku dan Modul
</option>
</select>
</div> </div>
{/* LOKASI */}
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Lokasi Gudang / Ruang Lokasi Gudang / Ruang
</label> </label>
<select <input
list="lokasi-list"
value={lokasiSelected} value={lokasiSelected}
onChange={(e) => onChange={(e) =>
setLokasiSelected( setLokasiSelected(
e.target.value e.target.value
) )
} }
placeholder="Pilih atau ketik lokasi..."
className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500 bg-white text-black" className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500 bg-white text-black"
> />
<option value="">
-- Pilih Lokasi --
</option>
<option value="Gudang Pusat"> <datalist id="lokasi-list">
Gudang Pusat {lokasiList.map((lokasi, index) => (
</option> <option
key={index}
value={lokasi}
/>
))}
</datalist>
<option value="Lab PPLG">
Lab PPLG
</option>
<option value="Lab TKJ">
Lab TKJ
</option>
<option value="X PPLG">
X PPLG
</option>
<option value="XI PPLG">
XI PPLG
</option>
<option value="XII PPLG">
XII PPLG
</option>
<option value="X TKJ">
X TKJ
</option>
<option value="XI TKJ">
XI TKJ
</option>
<option value="XII TKJ">
XII TKJ
</option>
</select>
</div> </div>
</div> </div>
{/* KODE */} {/* KODE */}
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Kode Barang Kode Barang
</label> </label>
@ -442,10 +502,12 @@ export default function TambahBarang({
value={kodeBarang} value={kodeBarang}
className="w-full p-3 rounded-xl border border-gray-200 bg-gray-100 dark:bg-gray-900 font-mono font-bold text-blue-600 outline-none cursor-not-allowed" className="w-full p-3 rounded-xl border border-gray-200 bg-gray-100 dark:bg-gray-900 font-mono font-bold text-blue-600 outline-none cursor-not-allowed"
/> />
</div> </div>
{/* NAMA */} {/* NAMA */}
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Nama Barang Nama Barang
</label> </label>
@ -461,12 +523,14 @@ export default function TambahBarang({
} }
className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500" className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500"
/> />
</div> </div>
{/* KONDISI & STOK */} {/* KONDISI & STOK */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Kondisi Fisik Kondisi Fisik
</label> </label>
@ -480,6 +544,7 @@ export default function TambahBarang({
} }
className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500 bg-white text-black" className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500 bg-white text-black"
> >
<option value=""> <option value="">
-- Pilih Kondisi -- -- Pilih Kondisi --
</option> </option>
@ -491,10 +556,13 @@ export default function TambahBarang({
<option value="Rusak"> <option value="Rusak">
Rusak Rusak
</option> </option>
</select> </select>
</div> </div>
<div> <div>
<label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200"> <label className="block text-sm font-semibold mb-1 text-gray-700 dark:text-gray-200">
Stok Awal Stok Awal
</label> </label>
@ -510,7 +578,9 @@ export default function TambahBarang({
} }
className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500" className="w-full p-3 rounded-xl border border-gray-300 dark:border-gray-600 dark:bg-gray-700 outline-none focus:border-blue-500"
/> />
</div> </div>
</div> </div>
{/* BUTTON */} {/* BUTTON */}
@ -531,15 +601,21 @@ export default function TambahBarang({
disabled={loading} disabled={loading}
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl font-semibold shadow transition disabled:opacity-50" className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl font-semibold shadow transition disabled:opacity-50"
> >
<PlusCircle size={18} /> <PlusCircle size={18} />
{loading {loading
? "Menyimpan..." ? "Menyimpan..."
: "Simpan ke DB"} : "Simpan ke DB"}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
); );
} }