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