From 8c5285b98842131bf69d2eb233ad07dc946b52b1 Mon Sep 17 00:00:00 2001 From: "akbarguci.pkl" Date: Tue, 19 May 2026 09:49:54 +0700 Subject: [PATCH] first commit --- src/components/dashboard/Hero.tsx | 4 +- src/components/manajemenUsers/UserFilter.tsx | 4 +- src/components/manajemenUsers/UserHero.tsx | 230 ++++++++++-- src/components/manajemenUsers/UserTable.tsx | 5 +- src/components/masterData/BarangHero.tsx | 7 +- src/components/masterData/EditBarangModal.tsx | 2 +- src/pages/Laporan.tsx | 7 +- src/pages/Settings.tsx | 4 +- src/pages/TransaksiBAST.tsx | 7 +- src/routes/ManajementUsers.tsx | 3 +- src/routes/MasterData.tsx | 342 ++++++++++++++---- 11 files changed, 497 insertions(+), 118 deletions(-) diff --git a/src/components/dashboard/Hero.tsx b/src/components/dashboard/Hero.tsx index fda999a..55b6f40 100644 --- a/src/components/dashboard/Hero.tsx +++ b/src/components/dashboard/Hero.tsx @@ -1,6 +1,8 @@ import { useState, useEffect } from 'react'; import axios from 'axios'; +import { MdOutlineWavingHand } from "react-icons/md"; + interface DashboardStats { permintaanHariIni: number; menungguPersetujuan: number; @@ -80,7 +82,7 @@ export default function Hero() {

- Selamat datang kembali 👋 + Selamat datang kembali

diff --git a/src/components/manajemenUsers/UserFilter.tsx b/src/components/manajemenUsers/UserFilter.tsx index 43f8560..a3a5b00 100644 --- a/src/components/manajemenUsers/UserFilter.tsx +++ b/src/components/manajemenUsers/UserFilter.tsx @@ -1,5 +1,7 @@ import type { Dispatch, SetStateAction } from "react"; +import { IoIosSearch } from "react-icons/io"; + interface FilterProps { kataKunci: string; setKataKunci: Dispatch>; @@ -9,7 +11,7 @@ export default function UsersFilter({ kataKunci, setKataKunci }: FilterProps) { return (
- 🔍 + void; } -export default function UsersHero({ onUserDitambahkan }: HeroProps) { - const [isModalOpen, setIsModalOpen] = useState(false); +export default function UsersHero({ + onUserDitambahkan, +}: HeroProps) { + + const [isModalOpen, setIsModalOpen] = + useState(false); + const [formData, setFormData] = useState({ nama_lengkap: "", username: "", @@ -15,144 +23,300 @@ export default function UsersHero({ onUserDitambahkan }: HeroProps) { email: "", 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(); + setLoading(true); 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) { + alert(respon.data.message); + setIsModalOpen(false); - // Reset Form - setFormData({ nama_lengkap: "", username: "", password: "", role: "User", email: "", lembaga: "" }); - // Refresh tabel di halaman utama otomatis + + // RESET FORM + setFormData({ + nama_lengkap: "", + username: "", + password: "", + role: "User", + email: "", + lembaga: "", + }); + + // REFRESH TABEL onUserDitambahkan(); } + } catch (error: any) { + console.error(error); - alert(error.response?.data?.message || "Gagal menambah user baru!"); + + alert( + error.response?.data?.message || + "Gagal menambah user baru!" + ); + } finally { + setLoading(false); } }; return (
+ + {/* KIRI */}
-

Manajemen Pengguna

-

Kelola hak akses data, penyerah, penerima, dan petugas BAST CIFO.

+ +

+ Manajemen Pengguna +

+ +

+ Kelola hak akses data, + penyerah, penerima, + dan petugas BAST CIFO. +

+
+ {/* BUTTON TAMBAH USER */} - {/* MODAL POP UP FORM TAMBAH USER */} + {/* MODAL */} {isModalOpen && ( +
+
+ + {/* HEADER MODAL */}

- 👤 Registrasi Pengguna Baru + + + + Registrasi Pengguna Baru +

- -
+ + {/* FORM */} + + + {/* NAMA */}
- + + + 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" placeholder="Contoh: Bu Sari" /> +
+ {/* USERNAME & PASSWORD */}
+
- + + + 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" placeholder="sari123" /> +
+
- + + + 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" placeholder="******" /> +
+
+ {/* ROLE */}
- + + + +
+ {/* EMAIL */}
- + + + 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" placeholder="sari@cifo.co.id" /> +
+ {/* LEMBAGA */}
- + + + 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" placeholder="SMKN 4 Bandung / PT CIFO" /> +
+ {/* BUTTON */}
+ + +
+
+
+
+ )} +
); } \ No newline at end of file diff --git a/src/components/manajemenUsers/UserTable.tsx b/src/components/manajemenUsers/UserTable.tsx index eb894c6..265ef08 100644 --- a/src/components/manajemenUsers/UserTable.tsx +++ b/src/components/manajemenUsers/UserTable.tsx @@ -4,6 +4,7 @@ import type { UserData } from "../../routes/ManajementUsers"; import { FaEdit } from "react-icons/fa"; import { MdDelete } from "react-icons/md"; +import { MdEmail } from "react-icons/md"; interface TableProps { dataUsers: UserData[]; @@ -142,7 +143,7 @@ export default function UsersTable({ colSpan={7} 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! @@ -245,7 +246,7 @@ export default function UsersTable({

- ✏️ Sunting Data Pengguna + Sunting Data Pengguna

+
+

Kelola Barang Sekolah

- 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.

+
); } \ No newline at end of file diff --git a/src/components/masterData/EditBarangModal.tsx b/src/components/masterData/EditBarangModal.tsx index ee6bb04..d85baea 100644 --- a/src/components/masterData/EditBarangModal.tsx +++ b/src/components/masterData/EditBarangModal.tsx @@ -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); if (res.data.success || res.status === 200) { - alert("Perubahan Inventaris Berhasil Diperbarui! ✏️"); + alert("Perubahan Inventaris Berhasil Diperbarui! "); onRefresh(); onClose(); } diff --git a/src/pages/Laporan.tsx b/src/pages/Laporan.tsx index ee0d7e1..a3a77e0 100644 --- a/src/pages/Laporan.tsx +++ b/src/pages/Laporan.tsx @@ -15,6 +15,9 @@ import axios from "axios"; import jsPDF from "jspdf"; import autoTable from "jspdf-autotable"; +import { FaHourglassEnd } from "react-icons/fa"; +import { FaBox } from "react-icons/fa"; + export default function Laporan() { /* ======================================================= @@ -139,7 +142,7 @@ export default function Laporan() { {/* RENDER KONDISIONAL KARTU LAPORAN */} {loading ? (
- Sedang menarik data rekapan laporan dari server... ⏳ + Sedang menarik data rekapan laporan dari server...
) : dataLaporan && dataLaporan.length > 0 ? (
@@ -212,7 +215,7 @@ export default function Laporan() {
) : (
- Tidak ditemukan rekapan laporan transaksi barang, Bang! 📦 + Tidak ditemukan rekapan laporan transaksi barang, Bang!
)}
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index c820dca..8b29777 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,3 +1,5 @@ +import { IoIosSettings } from "react-icons/io"; + export default function Settings() { const settingsMenu = [ "Profil Toko", @@ -65,7 +67,7 @@ export default function Settings() { text-2xl x "> - ⚙️ +
diff --git a/src/pages/TransaksiBAST.tsx b/src/pages/TransaksiBAST.tsx index 5d8f59b..f021a82 100644 --- a/src/pages/TransaksiBAST.tsx +++ b/src/pages/TransaksiBAST.tsx @@ -16,6 +16,9 @@ import axios from "axios"; import jsPDF from "jspdf"; import autoTable from "jspdf-autotable"; +import { FaBox } from "react-icons/fa"; +import { FaHourglassEnd } from "react-icons/fa"; + export default function TransaksiBAST() { /* ======================================================= @@ -279,7 +282,7 @@ export default function TransaksiBAST() { {loading ? (
- Sedang menyinkronkan data dokumen BAST dari server CIFO... ⏳ + Sedang menyinkronkan data dokumen BAST dari server CIFO...
) : dataBAST && dataBAST.length > 0 ? ( @@ -477,7 +480,7 @@ export default function TransaksiBAST() { ) : (
- Belum ada riwayat dokumen transaksi BAST terdaftar, Bang! 📦 + Belum ada riwayat dokumen transaksi BAST terdaftar, Bang!
)} diff --git a/src/routes/ManajementUsers.tsx b/src/routes/ManajementUsers.tsx index 3de79d1..db4cdb3 100644 --- a/src/routes/ManajementUsers.tsx +++ b/src/routes/ManajementUsers.tsx @@ -5,6 +5,7 @@ import axios from "axios"; import UsersHero from "../components/manajemenUsers/UserHero"; import UsersFilter from "../components/manajemenUsers/UserFilter"; import UsersTable from "../components/manajemenUsers/UserTable"; +import { FaHourglassEnd } from "react-icons/fa"; // Definisikan tipe data User sesuai kolom database CIFO Abang export interface UserData { @@ -83,7 +84,7 @@ export default function ManajemenUsers() { {loading ? (

- Sabar Bang, lagi narik data dari DB CIFO... ⏳ + Sabar Bang, lagi narik data dari DB CIFO...

) : ( diff --git a/src/routes/MasterData.tsx b/src/routes/MasterData.tsx index 4dec791..41ce602 100644 --- a/src/routes/MasterData.tsx +++ b/src/routes/MasterData.tsx @@ -1,9 +1,14 @@ // src/routes/MasterData.tsx + import { useState, useEffect } from "react"; 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 -import BarangHero from "../components/masterData/BarangHero"; import EditBarangModal from "../components/masterData/EditBarangModal"; import TambahBarang from "../components/masterData/TambahBarang"; @@ -13,38 +18,69 @@ export interface BarangType { kode_barang: string; nama_barang: string; stok: number; - status_barang: string; // 'Baik' / 'Rusak' - qr_image: string; // Base64 string dari backend Express + status_barang: string; + qr_image: string; foto_barang: string | null; nama_kategori?: string; nama_lokasi?: string; } export default function MasterData() { - const [selectedKategori, setSelectedKategori] = useState("Semua"); - const [selectedBarang, setSelectedBarang] = useState(null); - const [showTambah, setShowTambah] = useState(false); - const [barangList, setBarangList] = useState([]); - const [loading, setLoading] = useState(true); - // 1. FUNGSI READ: Mengambil data barang real-time dari DB backend Express + const [selectedKategori, setSelectedKategori] = + useState("Semua"); + + const [selectedBarang, setSelectedBarang] = + useState(null); + + const [showTambah, setShowTambah] = + useState(false); + + const [barangList, setBarangList] = + useState([]); + + const [loading, setLoading] = + useState(true); + + // LOAD DATA BARANG 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: [...] } - if (respon.data && respon.data.success && Array.isArray(respon.data.data)) { + + 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); + } else if (Array.isArray(respon.data)) { + setBarangList(respon.data); + } else { + setBarangList([]); } + } catch (error) { - console.error("Gagal memuat data dari API:", error); + + console.error( + "Gagal memuat data dari API:", + error + ); + setBarangList([]); + } finally { + setLoading(false); } }; @@ -53,40 +89,87 @@ export default function MasterData() { muatDataBarang(); }, []); - // 2. FUNGSI FILTER KATEGORI SECARA DINAMIS - const filteredBarang = barangList.filter((barang) => { - if (selectedKategori === "Semua") return true; - return barang.nama_kategori?.toLowerCase() === selectedKategori.toLowerCase(); - }); + // FILTER KATEGORI + const filteredBarang = + barangList.filter((barang) => { - // 3. TRIGGER SETELAH BERHASIL SAVE / EDIT / TAMBAH + if (selectedKategori === "Semua") + return true; + + return ( + barang.nama_kategori?.toLowerCase() === + selectedKategori.toLowerCase() + ); + }); + + // REFRESH const handleSaveSuccess = () => { - muatDataBarang(); // Refresh list barang dari DB - setSelectedBarang(null); // Tutup Modal Edit - setShowTambah(false); // Tutup Modal Tambah + + muatDataBarang(); + + setSelectedBarang(null); + + setShowTambah(false); }; return (
- {/* HEADER HERO CARD */} -
-
- + + {/* HEADER HERO */} +
+ +
+ + {/* TEXT HERO */} +
+ +

+ Kelola Barang Sekolah +

+ +

+ Pantau sirkulasi stok, kodifikasi QR-Code otomatis, + kondisi fisik, dan lokasi penempatan aset inventaris CIFO. +

+ +
+ + {/* BUTTON TAMBAH */} + +
- +
- {/* FILTER KATEGORI BARANG */} + {/* FILTER */}
- {["Semua", "Elektronik", "Buku dan Modul", "Alat Tulis"].map((kat) => ( + + {[ + "Semua", + "Elektronik", + "Buku dan Modul", + "Alat Tulis", + ].map((kat) => ( + + ))} +
- {/* GRID DAFTAR INVENTARIS UTAMA */} + {/* GRID */} {loading ? ( +
- Narik data & QR Code dari server CIFO... ⏳ + Narik data & QR Code dari server CIFO...
+ ) : ( +
+ {filteredBarang.length === 0 ? ( +
- Belum ada aset terdaftar di kategori ini, Bang! 📦 + Belum ada aset terdaftar di kategori ini, Bang!
+ ) : ( + filteredBarang.map((barang) => ( -
+
- {/* BOX FRAME IMAGE DENGAN PROTESKSI ANTI IMAGE PECAH */} + + {/* IMAGE */}
+ {barang.foto_barang ? ( - {barang.nama_barang} { - const target = e.target as HTMLImageElement; - target.style.display = 'none'; // Sembunyikan gambar yang rusak - const parent = target.parentElement; + + const target = + e.target as HTMLImageElement; + + target.style.display = + "none"; + + const parent = + target.parentElement; + if (parent) { - const fallback = document.createElement('div'); - fallback.className = 'flex flex-col items-center justify-center text-gray-400'; - fallback.innerHTML = '📦No Image'; - parent.appendChild(fallback); + + const fallback = + document.createElement( + "div" + ); + + fallback.className = + "flex flex-col items-center justify-center text-gray-400"; + + fallback.innerHTML = + 'No Image'; + + parent.appendChild( + fallback + ); } }} /> + ) : ( +
- 📦 - No Image + + + + + + + No Image + +
+ )} - + + {/* STATUS */} + {barang.status_barang} +
+ {/* KODE */} {barang.kode_barang} -

{barang.nama_barang}

- + + {/* NAMA */} +

+ {barang.nama_barang} +

+ + {/* DETAIL */}
-
Kategori: {barang.nama_kategori || '-'}
-
Lokasi: {barang.nama_lokasi || '-'}
+ +
+ Kategori: + + {barang.nama_kategori || "-"} + +
+ +
+ Lokasi: + + {barang.nama_lokasi || "-"} + +
+
+
- {/* BOTTOM ACTION & BARCODE PREVIEW CARD */} + {/* FOOTER */}
+ + {/* QR */} {barang.qr_image && ( - QR + + QR + )} + + {/* STOK */}
- Stok - {barang.stok} Unit + + + Stok + + + + {barang.stok} Unit + +
+ + {/* BUTTON EDIT */} +
+
+ )) )} +
+ )} - {/* RENDER MODAL EDIT BARANG (Props disinkronkan dengan EditBarangModal.tsx baru) */} + {/* MODAL EDIT */} {selectedBarang && ( + setSelectedBarang(null)} + onClose={() => + setSelectedBarang(null) + } onRefresh={handleSaveSuccess} /> + )} - {/* RENDER MODAL TAMBAH BARANG (Props disinkronkan dengan TambahBarang.tsx baru) */} + {/* MODAL TAMBAH */} {showTambah && ( + setShowTambah(false)} + onClose={() => + setShowTambah(false) + } onRefresh={handleSaveSuccess} /> + )} +
); } \ No newline at end of file