first commit
This commit is contained in:
parent
03229101d5
commit
8c5285b988
|
|
@ -1,6 +1,8 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { MdOutlineWavingHand } from "react-icons/md";
|
||||||
|
|
||||||
interface DashboardStats {
|
interface DashboardStats {
|
||||||
permintaanHariIni: number;
|
permintaanHariIni: number;
|
||||||
menungguPersetujuan: number;
|
menungguPersetujuan: number;
|
||||||
|
|
@ -80,7 +82,7 @@ export default function Hero() {
|
||||||
<div className="flex justify-between items-start mb-8">
|
<div className="flex justify-between items-start mb-8">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500 text-lg">
|
<p className="text-gray-500 text-lg">
|
||||||
Selamat datang kembali 👋
|
Selamat datang kembali <MdOutlineWavingHand />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 className="text-5xl font-extrabold text-blue-700 mt-2">
|
<h1 className="text-5xl font-extrabold text-blue-700 mt-2">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
import { IoIosSearch } from "react-icons/io";
|
||||||
|
|
||||||
interface FilterProps {
|
interface FilterProps {
|
||||||
kataKunci: string;
|
kataKunci: string;
|
||||||
setKataKunci: Dispatch<SetStateAction<string>>;
|
setKataKunci: Dispatch<SetStateAction<string>>;
|
||||||
|
|
@ -9,7 +11,7 @@ export default function UsersFilter({ kataKunci, setKataKunci }: FilterProps) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-2xl p-4 shadow-sm border border-blue-100 mb-6 flex items-center justify-between gap-4">
|
<div className="bg-white rounded-2xl p-4 shadow-sm border border-blue-100 mb-6 flex items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-3 bg-gray-50 rounded-xl px-4 py-2.5 border border-gray-200 w-full max-w-md">
|
<div className="flex items-center gap-3 bg-gray-50 rounded-xl px-4 py-2.5 border border-gray-200 w-full max-w-md">
|
||||||
<span className="text-gray-400 text-lg">🔍</span>
|
<span className="text-gray-400 text-lg"><IoIosSearch /></span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={kataKunci}
|
value={kataKunci}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { CiCirclePlus } from "react-icons/ci";
|
||||||
|
import { FaUser } from "react-icons/fa";
|
||||||
|
|
||||||
interface HeroProps {
|
interface HeroProps {
|
||||||
onUserDitambahkan: () => void;
|
onUserDitambahkan: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UsersHero({ onUserDitambahkan }: HeroProps) {
|
export default function UsersHero({
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
onUserDitambahkan,
|
||||||
|
}: HeroProps) {
|
||||||
|
|
||||||
|
const [isModalOpen, setIsModalOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
nama_lengkap: "",
|
nama_lengkap: "",
|
||||||
username: "",
|
username: "",
|
||||||
|
|
@ -15,144 +23,300 @@ export default function UsersHero({ onUserDitambahkan }: HeroProps) {
|
||||||
email: "",
|
email: "",
|
||||||
lembaga: "",
|
lembaga: "",
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const [loading, setLoading] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
// SUBMIT TAMBAH USER
|
||||||
|
const handleSubmit = async (
|
||||||
|
e: React.FormEvent
|
||||||
|
) => {
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const respon = await axios.post("http://localhost:5000/api/register", formData);
|
|
||||||
|
const respon = await axios.post(
|
||||||
|
"http://localhost:5000/api/register",
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
if (respon.data.success) {
|
if (respon.data.success) {
|
||||||
|
|
||||||
alert(respon.data.message);
|
alert(respon.data.message);
|
||||||
|
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
// Reset Form
|
|
||||||
setFormData({ nama_lengkap: "", username: "", password: "", role: "User", email: "", lembaga: "" });
|
// RESET FORM
|
||||||
// Refresh tabel di halaman utama otomatis
|
setFormData({
|
||||||
|
nama_lengkap: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
role: "User",
|
||||||
|
email: "",
|
||||||
|
lembaga: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// REFRESH TABEL
|
||||||
onUserDitambahkan();
|
onUserDitambahkan();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
alert(error.response?.data?.message || "Gagal menambah user baru!");
|
|
||||||
|
alert(
|
||||||
|
error.response?.data?.message ||
|
||||||
|
"Gagal menambah user baru!"
|
||||||
|
);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-r from-blue-600 to-blue-700 rounded-3xl p-8 shadow-lg text-white mb-8 flex justify-between items-center">
|
<div className="bg-gradient-to-r from-blue-600 to-blue-700 rounded-3xl p-8 shadow-lg text-white mb-8 flex justify-between items-center">
|
||||||
|
|
||||||
|
{/* KIRI */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-extrabold tracking-tight">Manajemen Pengguna</h1>
|
|
||||||
<p className="text-blue-100 mt-2 text-lg">Kelola hak akses data, penyerah, penerima, dan petugas BAST CIFO.</p>
|
<h1 className="text-4xl font-extrabold tracking-tight">
|
||||||
|
Manajemen Pengguna
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-blue-100 mt-2 text-lg">
|
||||||
|
Kelola hak akses data,
|
||||||
|
penyerah, penerima,
|
||||||
|
dan petugas BAST CIFO.
|
||||||
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* BUTTON TAMBAH USER */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsModalOpen(true)}
|
onClick={() =>
|
||||||
|
setIsModalOpen(true)
|
||||||
|
}
|
||||||
className="bg-white text-blue-700 font-bold px-6 py-3 rounded-2xl shadow hover:bg-blue-50 transition-all duration-200 flex items-center gap-2 transform hover:scale-105"
|
className="bg-white text-blue-700 font-bold px-6 py-3 rounded-2xl shadow hover:bg-blue-50 transition-all duration-200 flex items-center gap-2 transform hover:scale-105"
|
||||||
>
|
>
|
||||||
➕ Tambah User Baru
|
|
||||||
|
<CiCirclePlus size={24} />
|
||||||
|
|
||||||
|
Tambah User Baru
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* MODAL POP UP FORM TAMBAH USER */}
|
{/* MODAL */}
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|
||||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex justify-center items-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex justify-center items-center z-50 p-4">
|
||||||
|
|
||||||
<div className="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-gray-100 text-gray-800 animate-in fade-in zoom-in-95 duration-200">
|
<div className="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-gray-100 text-gray-800 animate-in fade-in zoom-in-95 duration-200">
|
||||||
|
|
||||||
|
{/* HEADER MODAL */}
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center gap-2 text-blue-700">
|
<h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center gap-2 text-blue-700">
|
||||||
👤 Registrasi Pengguna Baru
|
|
||||||
|
<FaUser size={20} />
|
||||||
|
|
||||||
|
Registrasi Pengguna Baru
|
||||||
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
{/* FORM */}
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* NAMA */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-1">Nama Lengkap *</label>
|
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Nama Lengkap *
|
||||||
|
</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
value={formData.nama_lengkap}
|
value={formData.nama_lengkap}
|
||||||
onChange={(e) => setFormData({ ...formData, nama_lengkap: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
nama_lengkap:
|
||||||
|
e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
||||||
placeholder="Contoh: Bu Sari"
|
placeholder="Contoh: Bu Sari"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* USERNAME & PASSWORD */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-1">Username *</label>
|
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Username *
|
||||||
|
</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
value={formData.username}
|
value={formData.username}
|
||||||
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
username:
|
||||||
|
e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
||||||
placeholder="sari123"
|
placeholder="sari123"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-1">Password *</label>
|
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Password *
|
||||||
|
</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
required
|
required
|
||||||
value={formData.password}
|
value={formData.password}
|
||||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
password:
|
||||||
|
e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
||||||
placeholder="******"
|
placeholder="******"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ROLE */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-1">Role / Hak Akses</label>
|
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Role / Hak Akses
|
||||||
|
</label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={formData.role}
|
value={formData.role}
|
||||||
onChange={(e) => setFormData({ ...formData, role: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
role: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition bg-white"
|
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition bg-white"
|
||||||
>
|
>
|
||||||
<option value="Admin">Admin</option>
|
|
||||||
<option value="User">User</option>
|
<option value="Admin">
|
||||||
|
Admin
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="User">
|
||||||
|
User
|
||||||
|
</option>
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* EMAIL */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-1">Email (Opsional)</label>
|
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Email (Opsional)
|
||||||
|
</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
email:
|
||||||
|
e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
||||||
placeholder="sari@cifo.co.id"
|
placeholder="sari@cifo.co.id"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* LEMBAGA */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-1">Instansi / Lembaga (Opsional)</label>
|
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-1">
|
||||||
|
Instansi / Lembaga
|
||||||
|
(Opsional)
|
||||||
|
</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.lembaga}
|
value={formData.lembaga}
|
||||||
onChange={(e) => setFormData({ ...formData, lembaga: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
lembaga:
|
||||||
|
e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition"
|
||||||
placeholder="SMKN 4 Bandung / PT CIFO"
|
placeholder="SMKN 4 Bandung / PT CIFO"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* BUTTON */}
|
||||||
<div className="flex justify-end gap-3 pt-4 border-t border-gray-100 mt-6">
|
<div className="flex justify-end gap-3 pt-4 border-t border-gray-100 mt-6">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsModalOpen(false)}
|
onClick={() =>
|
||||||
|
setIsModalOpen(false)
|
||||||
|
}
|
||||||
className="px-5 py-2.5 rounded-xl border border-gray-300 font-semibold text-gray-600 hover:bg-gray-50 transition"
|
className="px-5 py-2.5 rounded-xl border border-gray-300 font-semibold text-gray-600 hover:bg-gray-50 transition"
|
||||||
>
|
>
|
||||||
Batal
|
Batal
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="px-5 py-2.5 rounded-xl bg-blue-600 font-semibold text-white hover:bg-blue-700 shadow transition disabled:opacity-50"
|
className="px-5 py-2.5 rounded-xl bg-blue-600 font-semibold text-white hover:bg-blue-700 shadow transition disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{loading ? "Menyimpan..." : "Simpan Pengguna"}
|
{loading
|
||||||
|
? "Menyimpan..."
|
||||||
|
: "Simpan Pengguna"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import type { UserData } from "../../routes/ManajementUsers";
|
||||||
|
|
||||||
import { FaEdit } from "react-icons/fa";
|
import { FaEdit } from "react-icons/fa";
|
||||||
import { MdDelete } from "react-icons/md";
|
import { MdDelete } from "react-icons/md";
|
||||||
|
import { MdEmail } from "react-icons/md";
|
||||||
|
|
||||||
interface TableProps {
|
interface TableProps {
|
||||||
dataUsers: UserData[];
|
dataUsers: UserData[];
|
||||||
|
|
@ -142,7 +143,7 @@ export default function UsersTable({
|
||||||
colSpan={7}
|
colSpan={7}
|
||||||
className="p-8 text-center text-gray-400 font-medium"
|
className="p-8 text-center text-gray-400 font-medium"
|
||||||
>
|
>
|
||||||
Belum ada data pengguna terdaftar atau kata kunci salah, Bang! 📭
|
Belum ada data pengguna terdaftar atau kata kunci salah, Bang! <MdEmail />
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -245,7 +246,7 @@ export default function UsersTable({
|
||||||
<div className="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-gray-100 text-gray-800 animate-in fade-in zoom-in-95 duration-200">
|
<div className="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-gray-100 text-gray-800 animate-in fade-in zoom-in-95 duration-200">
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center gap-2 text-amber-600">
|
<h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center gap-2 text-amber-600">
|
||||||
✏️ Sunting Data Pengguna
|
<FaEdit /> Sunting Data Pengguna
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
export default function BarangHero() {
|
export default function BarangHero() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-gradient-to-r from-black via-blue-900 to-blue-600 rounded-3xl p-8 text-white">
|
<div className="bg-gradient-to-r from-black via-blue-900 to-blue-600 rounded-3xl p-8 text-white text-right">
|
||||||
|
|
||||||
<h1 className="text-4xl font-extrabold tracking-tight mb-2">
|
<h1 className="text-4xl font-extrabold tracking-tight mb-2">
|
||||||
Kelola Barang Sekolah
|
Kelola Barang Sekolah
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-blue-100 text-sm md:text-base">
|
<p className="text-blue-100 text-sm md:text-base">
|
||||||
Pantau sirkulasi stok, kodifikasi QR-Code otomatis, kondisi fisik, dan lokasi penempatan aset inventaris CIFO.
|
Pantau sirkulasi stok, kodifikasi QR-Code otomatis, kondisi fisik,
|
||||||
|
dan lokasi penempatan aset inventaris CIFO.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ export default function EditBarangModal({ itemSelected, onClose, onRefresh }: Ed
|
||||||
const res = await axios.put(`http://localhost:5000/api/barang/${itemSelected.barang_id}`, dataUpdate);
|
const res = await axios.put(`http://localhost:5000/api/barang/${itemSelected.barang_id}`, dataUpdate);
|
||||||
|
|
||||||
if (res.data.success || res.status === 200) {
|
if (res.data.success || res.status === 200) {
|
||||||
alert("Perubahan Inventaris Berhasil Diperbarui! ✏️");
|
alert("Perubahan Inventaris Berhasil Diperbarui! <FaEdit />");
|
||||||
onRefresh();
|
onRefresh();
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ import axios from "axios";
|
||||||
import jsPDF from "jspdf";
|
import jsPDF from "jspdf";
|
||||||
import autoTable from "jspdf-autotable";
|
import autoTable from "jspdf-autotable";
|
||||||
|
|
||||||
|
import { FaHourglassEnd } from "react-icons/fa";
|
||||||
|
import { FaBox } from "react-icons/fa";
|
||||||
|
|
||||||
export default function Laporan() {
|
export default function Laporan() {
|
||||||
|
|
||||||
/* =======================================================
|
/* =======================================================
|
||||||
|
|
@ -139,7 +142,7 @@ export default function Laporan() {
|
||||||
{/* RENDER KONDISIONAL KARTU LAPORAN */}
|
{/* RENDER KONDISIONAL KARTU LAPORAN */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow animate-pulse">
|
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow animate-pulse">
|
||||||
Sedang menarik data rekapan laporan dari server... ⏳
|
Sedang menarik data rekapan laporan dari server... <FaHourglassEnd />
|
||||||
</div>
|
</div>
|
||||||
) : dataLaporan && dataLaporan.length > 0 ? (
|
) : dataLaporan && dataLaporan.length > 0 ? (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
|
|
@ -212,7 +215,7 @@ export default function Laporan() {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl border border-gray-200 dark:border-gray-700 text-gray-400 font-medium">
|
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl border border-gray-200 dark:border-gray-700 text-gray-400 font-medium">
|
||||||
Tidak ditemukan rekapan laporan transaksi barang, Bang! 📦
|
Tidak ditemukan rekapan laporan transaksi barang, Bang! <FaBox />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { IoIosSettings } from "react-icons/io";
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const settingsMenu = [
|
const settingsMenu = [
|
||||||
"Profil Toko",
|
"Profil Toko",
|
||||||
|
|
@ -65,7 +67,7 @@ export default function Settings() {
|
||||||
text-2xl
|
text-2xl
|
||||||
x
|
x
|
||||||
">
|
">
|
||||||
⚙️
|
<IoIosSettings />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ import axios from "axios";
|
||||||
import jsPDF from "jspdf";
|
import jsPDF from "jspdf";
|
||||||
import autoTable from "jspdf-autotable";
|
import autoTable from "jspdf-autotable";
|
||||||
|
|
||||||
|
import { FaBox } from "react-icons/fa";
|
||||||
|
import { FaHourglassEnd } from "react-icons/fa";
|
||||||
|
|
||||||
export default function TransaksiBAST() {
|
export default function TransaksiBAST() {
|
||||||
|
|
||||||
/* =======================================================
|
/* =======================================================
|
||||||
|
|
@ -279,7 +282,7 @@ export default function TransaksiBAST() {
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|
||||||
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow-md animate-pulse">
|
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow-md animate-pulse">
|
||||||
Sedang menyinkronkan data dokumen BAST dari server CIFO... ⏳
|
Sedang menyinkronkan data dokumen BAST dari server CIFO... <FaHourglassEnd />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
) : dataBAST && dataBAST.length > 0 ? (
|
) : dataBAST && dataBAST.length > 0 ? (
|
||||||
|
|
@ -477,7 +480,7 @@ export default function TransaksiBAST() {
|
||||||
) : (
|
) : (
|
||||||
|
|
||||||
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl border border-gray-200 dark:border-gray-700 text-gray-400 font-medium">
|
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl border border-gray-200 dark:border-gray-700 text-gray-400 font-medium">
|
||||||
Belum ada riwayat dokumen transaksi BAST terdaftar, Bang! 📦
|
Belum ada riwayat dokumen transaksi BAST terdaftar, Bang! <FaBox />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import axios from "axios";
|
||||||
import UsersHero from "../components/manajemenUsers/UserHero";
|
import UsersHero from "../components/manajemenUsers/UserHero";
|
||||||
import UsersFilter from "../components/manajemenUsers/UserFilter";
|
import UsersFilter from "../components/manajemenUsers/UserFilter";
|
||||||
import UsersTable from "../components/manajemenUsers/UserTable";
|
import UsersTable from "../components/manajemenUsers/UserTable";
|
||||||
|
import { FaHourglassEnd } from "react-icons/fa";
|
||||||
|
|
||||||
// Definisikan tipe data User sesuai kolom database CIFO Abang
|
// Definisikan tipe data User sesuai kolom database CIFO Abang
|
||||||
export interface UserData {
|
export interface UserData {
|
||||||
|
|
@ -83,7 +84,7 @@ export default function ManajemenUsers() {
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex justify-center items-center p-10 bg-white rounded-2xl shadow-sm border border-blue-100">
|
<div className="flex justify-center items-center p-10 bg-white rounded-2xl shadow-sm border border-blue-100">
|
||||||
<p className="text-blue-600 font-semibold animate-pulse">
|
<p className="text-blue-600 font-semibold animate-pulse">
|
||||||
Sabar Bang, lagi narik data dari DB CIFO... ⏳
|
Sabar Bang, lagi narik data dari DB CIFO... <FaHourglassEnd />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
// src/routes/MasterData.tsx
|
// src/routes/MasterData.tsx
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { CiCirclePlus } from "react-icons/ci";
|
||||||
|
import { FaEdit } from "react-icons/fa";
|
||||||
|
import { FaBox } from "react-icons/fa";
|
||||||
|
import { FaHourglassEnd } from "react-icons/fa";
|
||||||
|
|
||||||
// COMPONENTS
|
// COMPONENTS
|
||||||
import BarangHero from "../components/masterData/BarangHero";
|
|
||||||
import EditBarangModal from "../components/masterData/EditBarangModal";
|
import EditBarangModal from "../components/masterData/EditBarangModal";
|
||||||
import TambahBarang from "../components/masterData/TambahBarang";
|
import TambahBarang from "../components/masterData/TambahBarang";
|
||||||
|
|
||||||
|
|
@ -13,38 +18,69 @@ export interface BarangType {
|
||||||
kode_barang: string;
|
kode_barang: string;
|
||||||
nama_barang: string;
|
nama_barang: string;
|
||||||
stok: number;
|
stok: number;
|
||||||
status_barang: string; // 'Baik' / 'Rusak'
|
status_barang: string;
|
||||||
qr_image: string; // Base64 string dari backend Express
|
qr_image: string;
|
||||||
foto_barang: string | null;
|
foto_barang: string | null;
|
||||||
nama_kategori?: string;
|
nama_kategori?: string;
|
||||||
nama_lokasi?: string;
|
nama_lokasi?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MasterData() {
|
export default function MasterData() {
|
||||||
const [selectedKategori, setSelectedKategori] = useState("Semua");
|
|
||||||
const [selectedBarang, setSelectedBarang] = useState<BarangType | null>(null);
|
|
||||||
const [showTambah, setShowTambah] = useState(false);
|
|
||||||
const [barangList, setBarangList] = useState<BarangType[]>([]);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
|
|
||||||
// 1. FUNGSI READ: Mengambil data barang real-time dari DB backend Express
|
const [selectedKategori, setSelectedKategori] =
|
||||||
|
useState("Semua");
|
||||||
|
|
||||||
|
const [selectedBarang, setSelectedBarang] =
|
||||||
|
useState<BarangType | null>(null);
|
||||||
|
|
||||||
|
const [showTambah, setShowTambah] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const [barangList, setBarangList] =
|
||||||
|
useState<BarangType[]>([]);
|
||||||
|
|
||||||
|
const [loading, setLoading] =
|
||||||
|
useState<boolean>(true);
|
||||||
|
|
||||||
|
// LOAD DATA BARANG
|
||||||
const muatDataBarang = async () => {
|
const muatDataBarang = async () => {
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const respon = await axios.get("http://localhost:5000/api/barang");
|
|
||||||
|
|
||||||
// Pengaman jika struktur response backend dibungkus { success: true, data: [...] }
|
setLoading(true);
|
||||||
if (respon.data && respon.data.success && Array.isArray(respon.data.data)) {
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const respon = await axios.get(
|
||||||
|
"http://localhost:5000/api/barang"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
respon.data &&
|
||||||
|
respon.data.success &&
|
||||||
|
Array.isArray(respon.data.data)
|
||||||
|
) {
|
||||||
|
|
||||||
setBarangList(respon.data.data);
|
setBarangList(respon.data.data);
|
||||||
|
|
||||||
} else if (Array.isArray(respon.data)) {
|
} else if (Array.isArray(respon.data)) {
|
||||||
|
|
||||||
setBarangList(respon.data);
|
setBarangList(respon.data);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
setBarangList([]);
|
setBarangList([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal memuat data dari API:", error);
|
|
||||||
|
console.error(
|
||||||
|
"Gagal memuat data dari API:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
setBarangList([]);
|
setBarangList([]);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -53,40 +89,87 @@ export default function MasterData() {
|
||||||
muatDataBarang();
|
muatDataBarang();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 2. FUNGSI FILTER KATEGORI SECARA DINAMIS
|
// FILTER KATEGORI
|
||||||
const filteredBarang = barangList.filter((barang) => {
|
const filteredBarang =
|
||||||
if (selectedKategori === "Semua") return true;
|
barangList.filter((barang) => {
|
||||||
return barang.nama_kategori?.toLowerCase() === selectedKategori.toLowerCase();
|
|
||||||
|
if (selectedKategori === "Semua")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
barang.nama_kategori?.toLowerCase() ===
|
||||||
|
selectedKategori.toLowerCase()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. TRIGGER SETELAH BERHASIL SAVE / EDIT / TAMBAH
|
// REFRESH
|
||||||
const handleSaveSuccess = () => {
|
const handleSaveSuccess = () => {
|
||||||
muatDataBarang(); // Refresh list barang dari DB
|
|
||||||
setSelectedBarang(null); // Tutup Modal Edit
|
muatDataBarang();
|
||||||
setShowTambah(false); // Tutup Modal Tambah
|
|
||||||
|
setSelectedBarang(null);
|
||||||
|
|
||||||
|
setShowTambah(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 bg-gray-100 min-h-screen text-gray-800">
|
<div className="p-8 bg-gray-100 min-h-screen text-gray-800">
|
||||||
{/* HEADER HERO CARD */}
|
|
||||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
|
{/* HEADER HERO */}
|
||||||
<div className="flex-1 w-full">
|
<div className="mb-6">
|
||||||
<BarangHero />
|
|
||||||
</div>
|
<div className="bg-gradient-to-r from-blue-600 to-blue-700 rounded-3xl p-8 text-white flex flex-col lg:flex-row justify-between items-center gap-6 shadow-lg">
|
||||||
<button
|
|
||||||
onClick={() => setShowTambah(true)}
|
{/* TEXT HERO */}
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white font-bold px-6 py-4 rounded-2xl shadow-lg transition duration-200 whitespace-nowrap"
|
<div className="flex-1">
|
||||||
>
|
|
||||||
➕ Tambah Barang UKK
|
<h1 className="text-4xl font-extrabold tracking-tight mb-2">
|
||||||
</button>
|
Kelola Barang Sekolah
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-blue-100 text-sm md:text-base max-w-3xl">
|
||||||
|
Pantau sirkulasi stok, kodifikasi QR-Code otomatis,
|
||||||
|
kondisi fisik, dan lokasi penempatan aset inventaris CIFO.
|
||||||
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* FILTER KATEGORI BARANG */}
|
{/* BUTTON TAMBAH */}
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setShowTambah(true)
|
||||||
|
}
|
||||||
|
className="bg-white hover:bg-gray-100 text-blue-700 font-bold px-7 py-4 rounded-2xl shadow-md transition duration-200 flex items-center gap-3 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
|
||||||
|
<CiCirclePlus
|
||||||
|
size={28}
|
||||||
|
className="text-blue-600"
|
||||||
|
/>
|
||||||
|
|
||||||
|
Tambah Barang UKK
|
||||||
|
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* FILTER */}
|
||||||
<div className="flex gap-2 mb-6 overflow-x-auto pb-2">
|
<div className="flex gap-2 mb-6 overflow-x-auto pb-2">
|
||||||
{["Semua", "Elektronik", "Buku dan Modul", "Alat Tulis"].map((kat) => (
|
|
||||||
|
{[
|
||||||
|
"Semua",
|
||||||
|
"Elektronik",
|
||||||
|
"Buku dan Modul",
|
||||||
|
"Alat Tulis",
|
||||||
|
].map((kat) => (
|
||||||
|
|
||||||
<button
|
<button
|
||||||
key={kat}
|
key={kat}
|
||||||
onClick={() => setSelectedKategori(kat)}
|
onClick={() =>
|
||||||
|
setSelectedKategori(kat)
|
||||||
|
}
|
||||||
className={`px-5 py-2.5 rounded-xl font-semibold transition text-sm ${
|
className={`px-5 py-2.5 rounded-xl font-semibold transition text-sm ${
|
||||||
selectedKategori === kat
|
selectedKategori === kat
|
||||||
? "bg-blue-600 text-white shadow-sm"
|
? "bg-blue-600 text-white shadow-sm"
|
||||||
|
|
@ -95,108 +178,223 @@ export default function MasterData() {
|
||||||
>
|
>
|
||||||
{kat}
|
{kat}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* GRID DAFTAR INVENTARIS UTAMA */}
|
{/* GRID */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|
||||||
<div className="p-12 text-center bg-white rounded-3xl border border-gray-200 font-medium text-blue-600 animate-pulse">
|
<div className="p-12 text-center bg-white rounded-3xl border border-gray-200 font-medium text-blue-600 animate-pulse">
|
||||||
Narik data & QR Code dari server CIFO... ⏳
|
Narik data & QR Code dari server CIFO... <FaHourglassEnd />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||||
|
|
||||||
{filteredBarang.length === 0 ? (
|
{filteredBarang.length === 0 ? (
|
||||||
|
|
||||||
<div className="col-span-full text-center py-12 bg-white rounded-3xl border border-gray-200 text-gray-400 font-medium">
|
<div className="col-span-full text-center py-12 bg-white rounded-3xl border border-gray-200 text-gray-400 font-medium">
|
||||||
Belum ada aset terdaftar di kategori ini, Bang! 📦
|
Belum ada aset terdaftar di kategori ini, Bang! <FaBox />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
|
|
||||||
filteredBarang.map((barang) => (
|
filteredBarang.map((barang) => (
|
||||||
|
|
||||||
<div
|
<div
|
||||||
key={barang.barang_id}
|
key={barang.barang_id}
|
||||||
className="bg-white rounded-3xl p-5 shadow-sm border border-gray-200 flex flex-col justify-between hover:shadow-md transition"
|
className="bg-white rounded-3xl p-5 shadow-sm border border-gray-200 flex flex-col justify-between hover:shadow-md transition"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{/* BOX FRAME IMAGE DENGAN PROTESKSI ANTI IMAGE PECAH */}
|
|
||||||
|
{/* IMAGE */}
|
||||||
<div className="w-full h-40 bg-gray-50 rounded-2xl overflow-hidden mb-4 border border-gray-100 flex items-center justify-center relative">
|
<div className="w-full h-40 bg-gray-50 rounded-2xl overflow-hidden mb-4 border border-gray-100 flex items-center justify-center relative">
|
||||||
|
|
||||||
{barang.foto_barang ? (
|
{barang.foto_barang ? (
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={barang.foto_barang.startsWith('http') ? barang.foto_barang : `http://localhost:5000/${barang.foto_barang}`}
|
src={
|
||||||
|
barang.foto_barang.startsWith(
|
||||||
|
"http"
|
||||||
|
)
|
||||||
|
? barang.foto_barang
|
||||||
|
: `http://localhost:5000/${barang.foto_barang}`
|
||||||
|
}
|
||||||
alt={barang.nama_barang}
|
alt={barang.nama_barang}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
const target = e.target as HTMLImageElement;
|
|
||||||
target.style.display = 'none'; // Sembunyikan gambar yang rusak
|
const target =
|
||||||
const parent = target.parentElement;
|
e.target as HTMLImageElement;
|
||||||
|
|
||||||
|
target.style.display =
|
||||||
|
"none";
|
||||||
|
|
||||||
|
const parent =
|
||||||
|
target.parentElement;
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const fallback = document.createElement('div');
|
|
||||||
fallback.className = 'flex flex-col items-center justify-center text-gray-400';
|
const fallback =
|
||||||
fallback.innerHTML = '<span class="text-4xl mb-1">📦</span><span class="text-xs font-medium">No Image</span>';
|
document.createElement(
|
||||||
parent.appendChild(fallback);
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
fallback.className =
|
||||||
|
"flex flex-col items-center justify-center text-gray-400";
|
||||||
|
|
||||||
|
fallback.innerHTML =
|
||||||
|
'<span class="text-4xl mb-1"><FaBox /></span><span class="text-xs font-medium">No Image</span>';
|
||||||
|
|
||||||
|
parent.appendChild(
|
||||||
|
fallback
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
|
|
||||||
<div className="flex flex-col items-center justify-center text-gray-400">
|
<div className="flex flex-col items-center justify-center text-gray-400">
|
||||||
<span className="text-4xl mb-1">📦</span>
|
|
||||||
<span className="text-xs font-medium">No Image</span>
|
<span className="text-4xl mb-1">
|
||||||
</div>
|
<FaBox />
|
||||||
)}
|
|
||||||
<span className={`absolute top-3 right-3 text-xs font-bold px-2.5 py-1 rounded-full ${
|
|
||||||
barang.status_barang === 'Baik' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
|
||||||
}`}>
|
|
||||||
{barang.status_barang}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span className="text-xs font-medium">
|
||||||
|
No Image
|
||||||
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* STATUS */}
|
||||||
|
<span
|
||||||
|
className={`absolute top-3 right-3 text-xs font-bold px-2.5 py-1 rounded-full ${
|
||||||
|
barang.status_barang ===
|
||||||
|
"Baik"
|
||||||
|
? "bg-green-100 text-green-700"
|
||||||
|
: "bg-red-100 text-red-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{barang.status_barang}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* KODE */}
|
||||||
<span className="text-xs font-mono font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded">
|
<span className="text-xs font-mono font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded">
|
||||||
{barang.kode_barang}
|
{barang.kode_barang}
|
||||||
</span>
|
</span>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mt-1.5 line-clamp-1">{barang.nama_barang}</h3>
|
|
||||||
|
|
||||||
|
{/* NAMA */}
|
||||||
|
<h3 className="text-lg font-bold text-gray-900 mt-1.5 line-clamp-1">
|
||||||
|
{barang.nama_barang}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* DETAIL */}
|
||||||
<div className="grid grid-cols-2 gap-2 mt-3 text-xs text-gray-500">
|
<div className="grid grid-cols-2 gap-2 mt-3 text-xs text-gray-500">
|
||||||
<div>Kategori: <strong className="text-gray-700 block">{barang.nama_kategori || '-'}</strong></div>
|
|
||||||
<div>Lokasi: <strong className="text-gray-700 block">{barang.nama_lokasi || '-'}</strong></div>
|
<div>
|
||||||
</div>
|
Kategori:
|
||||||
|
<strong className="text-gray-700 block">
|
||||||
|
{barang.nama_kategori || "-"}
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* BOTTOM ACTION & BARCODE PREVIEW CARD */}
|
<div>
|
||||||
|
Lokasi:
|
||||||
|
<strong className="text-gray-700 block">
|
||||||
|
{barang.nama_lokasi || "-"}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
<div className="mt-5 pt-4 border-t border-gray-100 flex items-center justify-between gap-4">
|
<div className="mt-5 pt-4 border-t border-gray-100 flex items-center justify-between gap-4">
|
||||||
|
|
||||||
|
{/* QR */}
|
||||||
{barang.qr_image && (
|
{barang.qr_image && (
|
||||||
<img src={barang.qr_image} alt="QR" className="w-12 h-12 rounded-lg border p-0.5 bg-white shadow-sm" />
|
|
||||||
|
<img
|
||||||
|
src={barang.qr_image}
|
||||||
|
alt="QR"
|
||||||
|
className="w-12 h-12 rounded-lg border p-0.5 bg-white shadow-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* STOK */}
|
||||||
<div className="text-right flex-1">
|
<div className="text-right flex-1">
|
||||||
<span className="text-xs text-gray-400 block">Stok</span>
|
|
||||||
<span className="text-base font-extrabold text-gray-800">{barang.stok} Unit</span>
|
<span className="text-xs text-gray-400 block">
|
||||||
|
Stok
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-base font-extrabold text-gray-800">
|
||||||
|
{barang.stok} Unit
|
||||||
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* BUTTON EDIT */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedBarang(barang)}
|
onClick={() =>
|
||||||
className="bg-gray-50 hover:bg-blue-50 hover:text-blue-600 text-gray-500 p-2.5 rounded-xl border border-gray-200 transition"
|
setSelectedBarang(
|
||||||
|
barang
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="bg-gray-50 hover:bg-blue-50 hover:text-blue-600 text-gray-500 p-3 rounded-xl border border-gray-200 transition"
|
||||||
>
|
>
|
||||||
✏️
|
|
||||||
|
<FaEdit size={16} />
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* RENDER MODAL EDIT BARANG (Props disinkronkan dengan EditBarangModal.tsx baru) */}
|
{/* MODAL EDIT */}
|
||||||
{selectedBarang && (
|
{selectedBarang && (
|
||||||
|
|
||||||
<EditBarangModal
|
<EditBarangModal
|
||||||
itemSelected={selectedBarang}
|
itemSelected={selectedBarang}
|
||||||
onClose={() => setSelectedBarang(null)}
|
onClose={() =>
|
||||||
|
setSelectedBarang(null)
|
||||||
|
}
|
||||||
onRefresh={handleSaveSuccess}
|
onRefresh={handleSaveSuccess}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* RENDER MODAL TAMBAH BARANG (Props disinkronkan dengan TambahBarang.tsx baru) */}
|
{/* MODAL TAMBAH */}
|
||||||
{showTambah && (
|
{showTambah && (
|
||||||
|
|
||||||
<TambahBarang
|
<TambahBarang
|
||||||
onClose={() => setShowTambah(false)}
|
onClose={() =>
|
||||||
|
setShowTambah(false)
|
||||||
|
}
|
||||||
onRefresh={handleSaveSuccess}
|
onRefresh={handleSaveSuccess}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue