diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 0a998e2f..261cf123 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -324,8 +324,8 @@ model PegawaiPPID {
model StrukturOrganisasiPPID {
id String @id @default(uuid())
posisiOrganisasiId String @db.VarChar(50)
- pegawaiId String
- hubunganOrganisasiId String
+ pegawaiId String
+ hubunganOrganisasiId String
posisiOrganisasi PosisiOrganisasiPPID @relation(fields: [posisiOrganisasiId], references: [id])
pegawai PegawaiPPID @relation(fields: [pegawaiId], references: [id])
createdAt DateTime @default(now())
@@ -1450,8 +1450,8 @@ model PegawaiBumDes {
model StrukturOrganisasiBumDes {
id String @id @default(uuid())
posisiOrganisasiId String @db.VarChar(50)
- pegawaiId String
- hubunganOrganisasiId String
+ pegawaiId String
+ hubunganOrganisasiId String
posisiOrganisasi PosisiOrganisasiBumDes @relation(fields: [posisiOrganisasiId], references: [id])
pegawai PegawaiBumDes @relation(fields: [pegawaiId], references: [id])
createdAt DateTime @default(now())
@@ -1820,7 +1820,7 @@ model KategoriKegiatan {
isActive Boolean @default(true)
KegiatanDesa KegiatanDesa[]
}
-
+
// ========================================= EDUKASI LINGKUNGAN ========================================= //
model TujuanEdukasiLingkungan {
id String @id @default(cuid())
@@ -2087,6 +2087,9 @@ model DataPerpustakaan {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
+
+ // relasi baru ke peminjaman
+ peminjamanBuku PeminjamanBuku[]
}
model KategoriBuku {
@@ -2099,6 +2102,31 @@ model KategoriBuku {
DataPerpustakaan DataPerpustakaan[]
}
+model PeminjamanBuku {
+ id String @id @default(cuid())
+ nama String
+ noTelp String
+ alamat String
+ bukuId String
+ tanggalPinjam DateTime @default(now())
+ batasKembali DateTime // tenggat waktu pengembalian
+ tanggalKembali DateTime? // diisi saat dikembalikan
+ status StatusPeminjaman @default(Dipinjam)
+ catatan String? // opsional, misal: kondisi buku
+ buku DataPerpustakaan @relation(fields: [bukuId], references: [id])
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime @default(now())
+ isActive Boolean @default(true)
+}
+
+enum StatusPeminjaman {
+ Dipinjam
+ Dikembalikan
+ Terlambat
+ Dibatalkan
+}
+
// ========================================= USER ========================================= //
model User {
diff --git a/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts b/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts
index da6411e9..6fee556c 100644
--- a/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts
+++ b/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts
@@ -514,9 +514,312 @@ const kategoriBuku = proxy({
},
});
+const templatePeminjamanBuku = z.object({
+ nama: z.string().min(1, "Nama harus diisi"),
+ noTelp: z.string().min(1, "No Telp harus diisi"),
+ alamat: z.string().min(1, "Alamat harus diisi"),
+ bukuId: z.string().min(1, "Buku ID harus diisi"),
+ tanggalPinjam: z.string().min(1, "Tanggal Pinjam harus diisi"),
+ batasKembali: z.string().min(1, "Batas Kembali harus diisi"),
+ tanggalKembali: z.string().min(1, "Tanggal Kembali harus diisi"),
+ catatan: z.string().min(1, "Catatan harus diisi")
+});
+
+const defaultPeminjamanBuku = {
+ nama: "",
+ noTelp: "",
+ alamat: "",
+ bukuId: "",
+ tanggalPinjam: "",
+ batasKembali: "",
+ tanggalKembali: "",
+ catatan: ""
+};
+
+interface FormEditData {
+ nama: string;
+ noTelp: string;
+ alamat: string;
+ bukuId: string;
+ buku?: {
+ id: string;
+ judul: string;
+ };
+ tanggalPinjam: string;
+ batasKembali: string;
+ tanggalKembali: string;
+ catatan: string;
+ status: 'Dipinjam' | 'Dikembalikan' | 'Terlambat' | 'Dibatalkan';
+}
+
+const editForm: FormEditData = {
+ nama: "",
+ noTelp: "",
+ alamat: "",
+ bukuId: "",
+ tanggalPinjam: "",
+ batasKembali: "",
+ tanggalKembali: "",
+ catatan: "",
+ status: "Dipinjam"
+}
+
+const peminjamanBuku = proxy({
+ create: {
+ form: { ...defaultPeminjamanBuku },
+ loading: false,
+ async create() {
+ const cek = templatePeminjamanBuku.safeParse(peminjamanBuku.create.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ return toast.error(err);
+ }
+
+ try {
+ peminjamanBuku.create.loading = true;
+ const res =
+ await ApiFetch.api.pendidikan.perpustakaandigital.peminjamanbuku[
+ "create"
+ ].post(peminjamanBuku.create.form);
+ if (res.status === 200) {
+ peminjamanBuku.findMany.load();
+ return toast.success("Data Peminjaman Buku Berhasil Dibuat");
+ }
+ console.log(res);
+ return toast.error("failed create");
+ } catch (error) {
+ console.log(error);
+ return toast.error("failed create");
+ } finally {
+ peminjamanBuku.create.loading = false;
+ }
+ },
+ },
+ findMany: {
+ data: [] as Prisma.PeminjamanBukuGetPayload<{
+ include: {
+ buku: true;
+ };
+ }>[],
+ page: 1,
+ totalPages: 1,
+ loading: false,
+ search: "",
+ load: async (page = 1, limit = 10, search = "") => {
+ peminjamanBuku.findMany.loading = true; // ✅ Akses langsung via nama path
+ peminjamanBuku.findMany.page = page;
+ peminjamanBuku.findMany.search = search;
+
+ try {
+ const query: any = { page, limit };
+ if (search) query.search = search;
+
+ const res = await ApiFetch.api.pendidikan.perpustakaandigital.peminjamanbuku["findMany"].get({ query });
+
+ if (res.status === 200 && res.data?.success) {
+ peminjamanBuku.findMany.data = res.data.data ?? [];
+ peminjamanBuku.findMany.totalPages = res.data.totalPages ?? 1;
+ } else {
+ peminjamanBuku.findMany.data = [];
+ peminjamanBuku.findMany.totalPages = 1;
+ }
+ } catch (err) {
+ console.error("Gagal fetch data peminjaman buku paginated:", err);
+ peminjamanBuku.findMany.data = [];
+ peminjamanBuku.findMany.totalPages = 1;
+ } finally {
+ peminjamanBuku.findMany.loading = false;
+ }
+ },
+ },
+ findUnique: {
+ data: null as Prisma.PeminjamanBukuGetPayload<{
+ include: {
+ buku: true;
+ };
+ }> | null,
+ loading: false,
+ async load(id: string) {
+ try {
+ const res = await fetch(
+ `/api/pendidikan/perpustakaandigital/peminjamanbuku/${id}`
+ );
+ if (res.ok) {
+ const data = await res.json();
+ peminjamanBuku.findUnique.data = data.data ?? null;
+ } else {
+ console.error("Failed to fetch data", res.status, res.statusText);
+ peminjamanBuku.findUnique.data = null;
+ }
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ peminjamanBuku.findUnique.data = null;
+ }
+ },
+ },
+ delete: {
+ loading: false,
+ async delete(id: string) {
+ if (!id) return toast.warn("ID tidak valid");
+
+ try {
+ peminjamanBuku.delete.loading = true;
+
+ const response = await fetch(
+ `/api/pendidikan/perpustakaandigital/peminjamanbuku/del/${id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ const result = await response.json();
+
+ if (response.ok && result?.success) {
+ toast.success(
+ result.message || "Data Peminjaman Buku berhasil dihapus"
+ );
+ await peminjamanBuku.findMany.load(); // refresh list
+ } else {
+ toast.error(result?.message || "Gagal menghapus Data Peminjaman Buku");
+ }
+ } catch (error) {
+ console.error("Gagal delete:", error);
+ toast.error("Terjadi kesalahan saat menghapus Data Peminjaman Buku");
+ } finally {
+ peminjamanBuku.delete.loading = false;
+ }
+ },
+ },
+ update: {
+ id: "",
+ form: { ...editForm },
+ loading: false,
+ async load(id: string) {
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+
+ try {
+ const response = await fetch(
+ `/api/pendidikan/perpustakaandigital/peminjamanbuku/${id}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+
+ if (result?.success) {
+ const data = result.data;
+ this.id = data.id;
+ this.form = {
+ nama: data.nama,
+ noTelp: data.noTelp,
+ alamat: data.alamat,
+ bukuId: data.bukuId,
+ tanggalPinjam: data.tanggalPinjam,
+ batasKembali: data.batasKembali,
+ tanggalKembali: data.tanggalKembali,
+ catatan: data.catatan,
+ status: data.status
+ };
+ return data; // Return the loaded data
+ } else {
+ throw new Error(result?.message || "Gagal memuat data");
+ }
+ } catch (error) {
+ console.error("Error loading peminjaman buku:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
+ }
+ },
+ async update() {
+ const cek = templatePeminjamanBuku.safeParse(peminjamanBuku.update.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ toast.error(err);
+ return false;
+ }
+
+ try {
+ peminjamanBuku.update.loading = true;
+
+ const response = await fetch(
+ `/api/pendidikan/perpustakaandigital/peminjamanbuku/${this.id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ nama: this.form.nama,
+ noTelp: this.form.noTelp,
+ alamat: this.form.alamat,
+ bukuId: this.form.bukuId,
+ tanggalPinjam: this.form.tanggalPinjam,
+ batasKembali: this.form.batasKembali,
+ tanggalKembali: this.form.tanggalKembali,
+ catatan: this.form.catatan,
+ status: this.form.status
+ }),
+ }
+ );
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ throw new Error(
+ errorData.message || `HTTP error! status: ${response.status}`
+ );
+ }
+
+ const result = await response.json();
+
+ if (result.success) {
+ toast.success("Berhasil update data peminjaman buku");
+ await peminjamanBuku.findMany.load(); // refresh list
+ return true;
+ } else {
+ throw new Error(result.message || "Gagal update data peminjaman buku");
+ }
+ } catch (error) {
+ console.error("Error updating data peminjaman buku:", error);
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Terjadi kesalahan saat update data peminjaman buku"
+ );
+ return false;
+ } finally {
+ peminjamanBuku.update.loading = false;
+ }
+ },
+ reset() {
+ peminjamanBuku.update.id = "";
+ peminjamanBuku.update.form = { ...editForm };
+ },
+ },
+})
+
const perpustakaanDigitalState = proxy({
dataPerpustakaan,
kategoriBuku,
+ peminjamanBuku,
});
export default perpustakaanDigitalState;
diff --git a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx
index b3ead298..40848d2f 100644
--- a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx
+++ b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx
@@ -4,7 +4,7 @@ import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
-import { IconBook2, IconCategory } from '@tabler/icons-react';
+import { IconBook2, IconCategory, IconUser } from '@tabler/icons-react';
function LayoutTabs({ children }: { children: React.ReactNode }) {
const router = useRouter();
@@ -25,6 +25,13 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
icon: ,
tooltip: "Atur kategori untuk buku digital",
},
+ {
+ label: "Peminjam",
+ value: "peminjam",
+ href: "/admin/pendidikan/perpustakaan-digital/peminjam",
+ icon: ,
+ tooltip: "Data Peminjam Buku",
+ },
];
const currentTab = tabs.find(tab => tab.href === pathname);
diff --git a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/[id]/edit/page.tsx b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/[id]/edit/page.tsx
new file mode 100644
index 00000000..20acc5ec
--- /dev/null
+++ b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/[id]/edit/page.tsx
@@ -0,0 +1,245 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+'use client'
+import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
+import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
+
+import colors from '@/con/colors';
+import {
+ Box,
+ Button,
+ Group,
+ Paper,
+ Select,
+ Stack,
+ Text,
+ TextInput,
+ Title,
+ Tooltip,
+} from '@mantine/core';
+import { DateInput } from '@mantine/dates';
+import { IconArrowBack } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import { toast } from 'react-toastify';
+import { useProxy } from 'valtio/utils';
+
+export type Status = "Dipinjam" | "Dikembalikan" | "Terlambat" | "Dibatalkan";
+
+function EditPeminjam() {
+ const stateEdit = useProxy(perpustakaanDigitalState.peminjamanBuku);
+ const router = useRouter();
+ const params = useParams();
+
+ const [formData, setFormData] = useState<{
+ nama: string;
+ noTelp: string;
+ alamat: string;
+ bukuId: string;
+ buku?: {
+ id: string;
+ judul: string;
+ };
+ tanggalPinjam: string;
+ batasKembali: string;
+ tanggalKembali: string;
+ status: Status;
+ catatan: string;
+ }>({
+ nama: '',
+ noTelp: '',
+ alamat: '',
+ bukuId: '',
+ tanggalPinjam: '',
+ batasKembali: '',
+ tanggalKembali: '',
+ status: 'Dipinjam', // Default status
+ catatan: '',
+ });
+
+ useEffect(() => {
+ const loadPeminjam = async () => {
+ const id = params?.id as string;
+ if (!id) return;
+
+ try {
+ const data = await stateEdit.update.load(id);
+ if (data) {
+ setFormData((prev) => ({
+ ...prev,
+ nama: data.nama ?? prev.nama,
+ noTelp: data.noTelp ?? prev.noTelp,
+ alamat: data.alamat ?? prev.alamat,
+ bukuId: data.bukuId ?? prev.bukuId,
+ buku: data.buku ? {
+ id: data.buku.id,
+ judul: data.buku.judul
+ } : undefined,
+ tanggalPinjam: data.tanggalPinjam ?? prev.tanggalPinjam,
+ batasKembali: data.batasKembali ?? prev.batasKembali,
+ tanggalKembali: data.tanggalKembali ?? prev.tanggalKembali,
+ status: (data.status as Status) ?? prev.status,
+ catatan: data.catatan ?? prev.catatan,
+ }));
+ }
+ } catch (error) {
+ console.error("Error loading peminjam:", error);
+ toast.error("Gagal mengambil data peminjam");
+ }
+ };
+
+ loadPeminjam();
+ }, [params?.id]);
+
+
+ const handleChange = (field: string, value: string | Status) => {
+ setFormData((prev) => ({ ...prev, [field]: value }));
+ };
+
+ const handleSubmit = async () => {
+ try {
+ stateEdit.update.form = {
+ ...stateEdit.update.form,
+ ...formData,
+ };
+
+ await stateEdit.update.update();
+ toast.success("Peminjam berhasil diperbarui!");
+ router.push("/admin/pendidikan/perpustakaan-digital/peminjam");
+ } catch (error) {
+ console.error("Error updating peminjam:", error);
+ toast.error("Terjadi kesalahan saat memperbarui peminjam");
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ Edit Peminjam Buku
+
+
+
+ {/* Card */}
+
+
+ handleChange('nama', e.target.value)}
+ label={Nama Peminjam}
+ placeholder="Masukkan nama peminjam"
+ required
+ />
+
+ handleChange('noTelp', e.target.value)}
+ label={No Telp Peminjam}
+ placeholder="Masukkan no telp peminjam"
+ required
+ />
+
+ handleChange('alamat', e.target.value)}
+ label={Alamat Peminjam}
+ placeholder="Masukkan alamat peminjam"
+ required
+ />
+
+ Buku}
+ placeholder="Buku"
+ required
+ readOnly
+ />
+
+ {
+ perpustakaanDigitalState.peminjamanBuku.update.form.tanggalPinjam =
+ date ? new Date(date).toISOString() : '';
+ }}
+ label={Tanggal Pinjam}
+ placeholder="Masukkan tanggal pinjam"
+ required
+ />
+
+ {
+ perpustakaanDigitalState.peminjamanBuku.update.form.tanggalKembali =
+ date ? new Date(date).toISOString() : '';
+ }}
+ label={Tanggal Kembali}
+ placeholder="Masukkan tanggal kembali"
+ required
+ />
+
+ {
+ perpustakaanDigitalState.peminjamanBuku.update.form.batasKembali =
+ date ? new Date(date).toISOString() : '';
+ }}
+ label={Batas Kembali}
+ placeholder="Masukkan batas kembali"
+ required
+ />
+
+
+
+
+ );
+}
+
+export default EditPeminjam;
diff --git a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/[id]/page.tsx
new file mode 100644
index 00000000..ed599d33
--- /dev/null
+++ b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/[id]/page.tsx
@@ -0,0 +1,237 @@
+'use client';
+import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
+import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
+import colors from '@/con/colors';
+import {
+ Badge,
+ Box,
+ Button,
+ Group,
+ Paper,
+ Skeleton,
+ Stack,
+ Text,
+ Tooltip
+} from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import { useState } from 'react';
+import { useProxy } from 'valtio/utils';
+
+
+function DetailDataPeminjaman() {
+ const stateDetail = useProxy(perpustakaanDigitalState.peminjamanBuku);
+ const [modalHapus, setModalHapus] = useState(false);
+ const [selectedId, setSelectedId] = useState(null);
+ const params = useParams();
+ const router = useRouter();
+
+ useShallowEffect(() => {
+ stateDetail.findUnique.load(params?.id as string);
+ }, []);
+
+ const data = stateDetail.findUnique.data;
+
+ const handleHapus = () => {
+ if (selectedId) {
+ stateDetail.delete.delete(selectedId);
+ setModalHapus(false);
+ setSelectedId(null);
+ router.push('/admin/pendidikan/perpustakaan-digital/peminjam');
+ }
+ };
+
+ const renderStatusBadge = (status: string) => {
+ const normalized = status?.toUpperCase();
+
+ switch (normalized) {
+ case 'DIPINJAM':
+ return Dipinjam;
+ case 'DIKEMBALIKAN':
+ return Dikembalikan;
+ case 'TERLAMBAT':
+ return Terlambat;
+ case 'DIBATALKAN':
+ return Dibatalkan;
+ default:
+ return Tidak diketahui;
+ }
+ };
+
+ if (!data) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ Detail Data Peminjam Buku
+
+
+
+
+
+
+ Nama
+
+
+ {data.nama || '-'}
+
+
+
+
+
+ No Telp
+
+
+ {data.noTelp || '-'}
+
+
+
+ {/* 🔹 Editable Status */}
+
+
+
+
+ Status
+
+ {renderStatusBadge(data.status || '')}
+
+
+
+
+
+
+ Alamat
+
+
+ {data.alamat || '-'}
+
+
+
+
+
+ Buku
+
+
+ {data.buku?.judul || '-'}
+
+
+
+
+
+ Tanggal Pinjam
+
+
+ {data.tanggalPinjam
+ ? new Date(data.tanggalPinjam).toLocaleDateString('id-ID')
+ : '-'}
+
+
+
+
+
+ Tanggal Kembali
+
+
+ {data.tanggalKembali
+ ? new Date(data.tanggalKembali).toLocaleDateString('id-ID')
+ : '-'}
+
+
+
+
+
+ Batas Kembali
+
+
+ {data.batasKembali
+ ? new Date(data.batasKembali).toLocaleDateString('id-ID')
+ : '-'}
+
+
+
+
+
+ Catatan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setModalHapus(false)}
+ onConfirm={handleHapus}
+ text="Apakah Anda yakin ingin menghapus data ini?"
+ />
+
+ );
+}
+
+export default DetailDataPeminjaman;
diff --git a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/page.tsx b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/page.tsx
new file mode 100644
index 00000000..89d8505a
--- /dev/null
+++ b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/peminjam/page.tsx
@@ -0,0 +1,157 @@
+'use client';
+import colors from '@/con/colors';
+import {
+ Badge,
+ Box,
+ Button,
+ Center,
+ Pagination,
+ Paper,
+ Skeleton,
+ Stack,
+ Table,
+ TableTbody,
+ TableTd,
+ TableTh,
+ TableThead,
+ TableTr,
+ Text,
+ Title,
+} from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
+import { useRouter } from 'next/navigation';
+import { useState } from 'react';
+import { useProxy } from 'valtio/utils';
+import HeaderSearch from '../../../_com/header';
+import perpustakaanDigitalState from '../../../_state/pendidikan/perpustakaan-digital';
+
+function PeminjamBuku() {
+ const [search, setSearch] = useState('');
+ return (
+
+ }
+ value={search}
+ onChange={(e) => setSearch(e.currentTarget.value)}
+ />
+
+
+ );
+}
+
+function ListPeminjamBuku({ search }: { search: string }) {
+ const statePeminjam = useProxy(perpustakaanDigitalState.peminjamanBuku);
+ const router = useRouter();
+
+ const { data, page, totalPages, loading, load } = statePeminjam.findMany;
+
+ useShallowEffect(() => {
+ load(page, 10, search);
+ }, [page, search]);
+
+ const filteredData = data || [];
+
+ // 🔹 Fungsi helper untuk badge status
+ const renderStatusBadge = (status: string) => {
+ const normalized = status?.toUpperCase();
+
+ switch (normalized) {
+ case 'DIPINJAM':
+ return Dipinjam;
+ case 'DIKEMBALIKAN':
+ return Dikembalikan;
+ case 'TERLAMBAT':
+ return Terlambat;
+ case 'DIBATALKAN':
+ return Dibatalkan;
+ default:
+ return Tidak diketahui;
+ }
+ };
+
+ if (loading || !data) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ Daftar Peminjam Buku
+
+
+
+
+ No
+ Nama Peminjam
+ Status
+ Detail
+
+
+
+ {filteredData.length > 0 ? (
+ filteredData.map((item, index) => (
+
+ {index + 1}
+
+ {item.nama}
+
+
+ {renderStatusBadge(item.status)}
+
+
+
+
+
+ ))
+ ) : (
+
+
+
+
+ Tidak ada data Peminjam buku yang cocok
+
+
+
+
+ )}
+
+
+
+
+
+ {totalPages > 1 && (
+
+ {
+ load(newPage, 10);
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }}
+ total={totalPages}
+ mt="md"
+ mb="md"
+ color="blue"
+ radius="md"
+ />
+
+ )}
+
+ );
+}
+
+export default PeminjamBuku;
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/info-sekolah-paud/lembaga/findMany.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/info-sekolah-paud/lembaga/findMany.ts
index d4b446b6..6eee2e52 100644
--- a/src/app/api/[[...slugs]]/_lib/pendidikan/info-sekolah-paud/lembaga/findMany.ts
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/info-sekolah-paud/lembaga/findMany.ts
@@ -66,9 +66,6 @@ async function lembagaPendidikanFindMany(context: Context) {
})
]);
- console.log('Fetched data count:', data.length);
- console.log('Total count:', total);
-
return {
success: true,
message: "Success fetch lembaga pendidikan with pagination",
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/index.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/index.ts
index c2abe457..dfe063e3 100644
--- a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/index.ts
@@ -1,12 +1,14 @@
import Elysia from "elysia";
import DataPerpustakaan from "./data-perpustakaan";
import KategoriBuku from "./kategori-buku";
+import PeminjamanBuku from "./peminjaman";
const PerpustakaanDigital = new Elysia({
prefix: "/perpustakaandigital",
tags: ["Pendidikan / Perpustakaan Digital"],
})
.use(DataPerpustakaan)
- .use(KategoriBuku);
+ .use(KategoriBuku)
+ .use(PeminjamanBuku);
export default PerpustakaanDigital;
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/create.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/create.ts
new file mode 100644
index 00000000..82dda9e2
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/create.ts
@@ -0,0 +1,40 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+type FormCreate = {
+ nama: string;
+ noTelp: string;
+ alamat: string;
+ bukuId: string;
+ tanggalPinjam: string;
+ batasKembali: string;
+ tanggalKembali?: string;
+ catatan?: string;
+}
+
+export default async function peminjamanBukuCreate(context: Context) {
+ const body = (await context.body) as FormCreate;
+
+ try {
+ const result = await prisma.peminjamanBuku.create({
+ data: {
+ nama: body.nama,
+ noTelp: body.noTelp,
+ alamat: body.alamat,
+ bukuId: body.bukuId,
+ tanggalPinjam: body.tanggalPinjam,
+ batasKembali: body.batasKembali,
+ tanggalKembali: body.tanggalKembali,
+ catatan: body.catatan,
+ },
+ });
+ return {
+ success: true,
+ message: "Berhasil membuat peminjaman buku",
+ data: result,
+ };
+ } catch (error) {
+ console.error("Error creating peminjaman buku:", error);
+ throw new Error("Gagal membuat peminjaman buku: " + (error as Error).message);
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/del.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/del.ts
new file mode 100644
index 00000000..4b38efd7
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/del.ts
@@ -0,0 +1,16 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+export default async function peminjamanBukuDelete(context: Context) {
+ const id = context.params.id as string;
+
+ await prisma.peminjamanBuku.delete({
+ where: { id },
+ });
+
+ return {
+ status: 200,
+ success: true,
+ message: "Success delete peminjaman buku",
+ };
+}
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/findMany.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/findMany.ts
new file mode 100644
index 00000000..1de38808
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/findMany.ts
@@ -0,0 +1,71 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+async function peminjamanBukuFindMany(context: Context) {
+ // Ambil parameter dari query
+ const page = Number(context.query.page) || 1;
+ const limit = Number(context.query.limit) || 10;
+ const search = (context.query.search as string) || '';
+ const skip = (page - 1) * limit;
+
+ // Buat where clause
+ const where: any = {};
+
+ // Tambahkan pencarian (jika ada)
+ if (search) {
+ where.OR = [
+ { catatan: { contains: search, mode: 'insensitive' } },
+ { nama: { contains: search, mode: 'insensitive' } },
+ { buku: { judul: { contains: search, mode: 'insensitive' } } },
+ { noTelp: { contains: search, mode: 'insensitive' } },
+ { alamat: { contains: search, mode: 'insensitive' } },
+ ];
+ }
+
+ try {
+ // Ambil data dan total count secara paralel
+ const [data, total] = await Promise.all([
+ prisma.peminjamanBuku.findMany({
+ where,
+ include: {
+ buku: {
+ select: {
+ id: true,
+ judul: true,
+ kategoriId: true,
+ createdAt: true,
+ updatedAt: true,
+ deletedAt: true,
+ isActive: true,
+ imageId: true,
+ deskripsi: true
+ }
+ },
+ },
+ skip,
+ take: limit,
+ orderBy: { tanggalPinjam: 'desc' },
+ }),
+ prisma.peminjamanBuku.count({ where }),
+ ]);
+
+ return {
+ success: true,
+ message: "Berhasil ambil data peminjaman buku dengan pagination",
+ data,
+ page,
+ limit,
+ total,
+ totalPages: Math.ceil(total / limit),
+ };
+ } catch (e) {
+ console.error("Error di peminjamanBukuFindMany:", e);
+ return {
+ success: false,
+ message: "Gagal mengambil data peminjaman buku",
+ };
+ }
+}
+
+export default peminjamanBukuFindMany;
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/findUnique.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/findUnique.ts
new file mode 100644
index 00000000..971ac0ff
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/findUnique.ts
@@ -0,0 +1,49 @@
+import prisma from "@/lib/prisma";
+
+export default async function peminjamanBukuFindUnique(request: Request) {
+ const url = new URL(request.url);
+ const pathSegments = url.pathname.split('/');
+ const id = pathSegments[pathSegments.length - 1];
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID is required",
+ }
+ }
+
+ try {
+ if (typeof id !== 'string') {
+ return {
+ success: false,
+ message: "ID is required",
+ }
+ }
+
+ const data = await prisma.peminjamanBuku.findUnique({
+ where: { id },
+ include: {
+ buku: true,
+ },
+ });
+
+ if (!data) {
+ return {
+ success: false,
+ message: "Data not found",
+ }
+ }
+
+ return {
+ success: true,
+ message: "Success get data peminjaman buku",
+ data,
+ }
+ } catch (error) {
+ console.error("Find by ID error:", error);
+ return {
+ success: false,
+ message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/index.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/index.ts
new file mode 100644
index 00000000..28b0d753
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/index.ts
@@ -0,0 +1,48 @@
+import Elysia, { t } from "elysia";
+import peminjamanBukuCreate from "./create";
+import peminjamanBukuDelete from "./del";
+import peminjamanBukuFindMany from "./findMany";
+import peminjamanBukuFindUnique from "./findUnique";
+import peminjamanBukuUpdate from "./updt";
+
+const PeminjamanBuku = new Elysia({
+ prefix: "/peminjamanbuku",
+ tags: ["Pendidikan / Perpustakaan Digital / Peminjaman Buku"],
+})
+
+ .post("/create", peminjamanBukuCreate, {
+ body: t.Object({
+ nama: t.String(),
+ noTelp: t.String(),
+ alamat: t.String(),
+ bukuId: t.String(),
+ tanggalPinjam: t.String(),
+ batasKembali: t.String(),
+ tanggalKembali: t.String(),
+ catatan: t.String()
+ }),
+ })
+
+ .get("/findMany", peminjamanBukuFindMany)
+ .get("/:id", async (context) => {
+ const response = await peminjamanBukuFindUnique(
+ new Request(context.request)
+ );
+ return response;
+ })
+ .put("/:id", peminjamanBukuUpdate, {
+ body: t.Object({
+ nama: t.String(),
+ noTelp: t.String(),
+ alamat: t.String(),
+ bukuId: t.String(),
+ tanggalPinjam: t.String(),
+ batasKembali: t.String(),
+ tanggalKembali: t.String(),
+ catatan: t.String(),
+ status: t.String()
+ }),
+ })
+ .delete("/del/:id", peminjamanBukuDelete);
+
+export default PeminjamanBuku;
diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/updt.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/updt.ts
new file mode 100644
index 00000000..0954d054
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/pendidikan/perpustakaan-digital/peminjaman/updt.ts
@@ -0,0 +1,49 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+type FormUpdate = {
+ nama: string;
+ noTelp: string;
+ alamat: string;
+ bukuId: string;
+ tanggalPinjam: string;
+ batasKembali: string;
+ tanggalKembali?: string;
+ catatan?: string;
+ status?: 'Dipinjam' | 'Dikembalikan' | 'Terlambat' | 'Dibatalkan';
+};
+
+export default async function dataPerpustakaanUpdate(context: Context) {
+ const body = (await context.body) as FormUpdate;
+ const id = context.params.id as string;
+
+ try {
+ const result = await prisma.peminjamanBuku.update({
+ where: { id },
+ data: {
+ nama: body.nama,
+ noTelp: body.noTelp,
+ alamat: body.alamat,
+ bukuId: body.bukuId,
+ tanggalPinjam: body.tanggalPinjam,
+ batasKembali: body.batasKembali,
+ tanggalKembali: body.tanggalKembali,
+ catatan: body.catatan,
+ status: body.status,
+ },
+ include: {
+ buku: true,
+ }
+ });
+ return {
+ success: true,
+ message: "Berhasil mengupdate data peminjaman buku",
+ data: result,
+ };
+ } catch (error) {
+ console.error("Error updating data peminjaman buku:", error);
+ throw new Error(
+ "Gagal mengupdate data peminjaman buku: " + (error as Error).message
+ );
+ }
+}
diff --git a/src/app/darmasaba/(pages)/pendidikan/info-sekolah/_lib/layoutTabs.tsx b/src/app/darmasaba/(pages)/pendidikan/info-sekolah/_lib/layoutTabs.tsx
index cf547483..146953cc 100644
--- a/src/app/darmasaba/(pages)/pendidikan/info-sekolah/_lib/layoutTabs.tsx
+++ b/src/app/darmasaba/(pages)/pendidikan/info-sekolah/_lib/layoutTabs.tsx
@@ -1,5 +1,101 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+// 'use client'
+// import colors from '@/con/colors';
+// import {
+// ActionIcon,
+// Box,
+// Button,
+// Container,
+// Group,
+// Paper,
+// Stack,
+// Text,
+// VisuallyHidden
+// } from '@mantine/core';
+// import { IconArrowLeft } from '@tabler/icons-react';
+// import { useRouter, useSearchParams } from 'next/navigation';
+// import React, { useState } from 'react';
+
+// type LayoutSekolahProps = {
+// title?: string;
+// jenjangPendidikanList?: string[];
+// children: React.ReactNode;
+// };
+
+// export default function LayoutSekolah({
+// title = 'Cari Informasi Sekolah',
+// jenjangPendidikanList = ['Semua', 'TK', 'SD', 'SMP', 'SMA'],
+// children,
+// }: LayoutSekolahProps) {
+// const router = useRouter();
+// const searchParams = useSearchParams();
+// const initialJenjangPendidikan = searchParams.get('jenjangPendidikan') || 'Semua';
+
+// const [jenjangPendidikanAktif, setJenjangPendidikanAktif] = useState(initialJenjangPendidikan);
+
+// // Cleanup timeout
+
+
+// // Handle jenjang pendidikan click
+// const handleJenjangPendidikanChange = (k: string) => {
+// // arahkan langsung ke route jenjang pendidikan
+// if (k.toLowerCase() === 'semua') {
+// setJenjangPendidikanAktif(k);
+// router.push(`/darmasaba/pendidikan/info-sekolah/semua`);
+// } else {
+// setJenjangPendidikanAktif(k);
+// router.push(`/darmasaba/pendidikan/info-sekolah/${encodeURIComponent(k.toLowerCase())}`);
+// }
+// };
+
+
+// return (
+//
+//
+//
+// {/* Back Button */}
+// window.history.back()} variant="light" radius="md" size="lg">
+//
+// Kembali
+//
+
+// {/* Search & Filter */}
+//
+//
+// {title}
+
+//
+// Temukan data lengkap mengenai lembaga pendidikan, jumlah siswa terdaftar, dan tenaga pengajar berdasarkan jenjang pendidikan yang tersedia (TK, SD, SMP, SMA). Gunakan tombol di bawah untuk melihat detail sesuai kebutuhanmu.
+//
+//
+// {jenjangPendidikanList.map((k) => {
+// const aktif = k === jenjangPendidikanAktif;
+// return (
+//
+// );
+// })}
+//
+//
+//
+
+// {/* Slot konten */}
+// {children}
+//
+//
+//
+// );
+// }
'use client'
import colors from '@/con/colors';
+ // pastikan path benar
import {
ActionIcon,
Box,
@@ -9,44 +105,48 @@ import {
Paper,
Stack,
Text,
- VisuallyHidden
+ VisuallyHidden,
+ Loader,
} from '@mantine/core';
import { IconArrowLeft } from '@tabler/icons-react';
import { useRouter, useSearchParams } from 'next/navigation';
-import React, { useState } from 'react';
+import { useSnapshot } from 'valtio';
+import React, { useEffect, useState } from 'react';
+import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
type LayoutSekolahProps = {
title?: string;
- jenjangPendidikanList?: string[];
children: React.ReactNode;
};
export default function LayoutSekolah({
title = 'Cari Informasi Sekolah',
- jenjangPendidikanList = ['Semua', 'TK', 'SD', 'SMP', 'SMA'],
children,
}: LayoutSekolahProps) {
const router = useRouter();
const searchParams = useSearchParams();
- const initialJenjangPendidikan = searchParams.get('jenjangPendidikan') || 'Semua';
+ const snap = useSnapshot(infoSekolahPaud.jenjangPendidikan);
- const [jenjangPendidikanAktif, setJenjangPendidikanAktif] = useState(initialJenjangPendidikan);
+ const [jenjangPendidikanAktif, setJenjangPendidikanAktif] = useState(
+ searchParams.get('jenjangPendidikan') || 'Semua'
+ );
- // Cleanup timeout
+ // Load jenjang pendidikan dari backend
+ useEffect(() => {
+ if (!snap.findMany.data) infoSekolahPaud.jenjangPendidikan.findMany.load(1, 100);
+ }, []);
-
- // Handle jenjang pendidikan click
- const handleJenjangPendidikanChange = (k: string) => {
- // arahkan langsung ke route jenjang pendidikan
- if (k.toLowerCase() === 'semua') {
- setJenjangPendidikanAktif(k);
- router.push(`/darmasaba/pendidikan/info-sekolah/semua`);
- } else {
- setJenjangPendidikanAktif(k);
- router.push(`/darmasaba/pendidikan/info-sekolah/${encodeURIComponent(k.toLowerCase())}`);
- }
+ const handleJenjangPendidikanChange = (nama: string) => {
+ setJenjangPendidikanAktif(nama);
+ const path =
+ nama.toLowerCase() === 'semua'
+ ? `/darmasaba/pendidikan/info-sekolah/semua`
+ : `/darmasaba/pendidikan/info-sekolah/${encodeURIComponent(nama.toLowerCase())}`;
+ router.push(path);
};
-
+
+ // List tab dari data state
+ const jenjangList = ['Semua', ...(snap.findMany.data?.map((v) => v.nama) || [])];
return (
@@ -61,31 +161,41 @@ export default function LayoutSekolah({
{/* Search & Filter */}
- {title}
-
-
- Temukan data lengkap mengenai lembaga pendidikan, jumlah siswa terdaftar, dan tenaga pengajar berdasarkan jenjang pendidikan yang tersedia (TK, SD, SMP, SMA). Gunakan tombol di bawah untuk melihat detail sesuai kebutuhanmu.
+
+ {title}
-
- {jenjangPendidikanList.map((k) => {
- const aktif = k === jenjangPendidikanAktif;
- return (
-
- );
- })}
-
+
+
+ Temukan data lengkap mengenai lembaga pendidikan, jumlah siswa terdaftar, dan tenaga
+ pengajar berdasarkan jenjang pendidikan (TK, SD, SMP, SMA).
+
+
+ {snap.findMany.loading ? (
+
+
+
+ ) : (
+
+ {jenjangList.map((k) => {
+ const aktif = k === jenjangPendidikanAktif;
+ return (
+
+ );
+ })}
+
+ )}
- {/* Slot konten */}
+ {/* Konten anak */}
{children}
diff --git a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/[id]/page.tsx b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/[id]/page.tsx
new file mode 100644
index 00000000..319f0d64
--- /dev/null
+++ b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/[id]/page.tsx
@@ -0,0 +1,126 @@
+'use client';
+
+import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
+import colors from '@/con/colors';
+import {
+ Badge,
+ Box,
+ Button,
+ Center,
+ Image,
+ Loader,
+ Paper,
+ Stack,
+ Text,
+ Title,
+} from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconArrowLeft, IconBook2 } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import { useState } from 'react';
+import { useProxy } from 'valtio/utils';
+import ModalPeminjaman from '../../_lib/modalPeminjaman';
+
+export default function DetailBukuUser() {
+ const router = useRouter();
+ const params = useParams();
+ const stateDetail = useProxy(perpustakaanDigitalState.dataPerpustakaan);
+ const [opened, setOpened] = useState(false);
+
+ useShallowEffect(() => {
+ if (params?.id) stateDetail.findUnique.load(params.id as string);
+ }, [params?.id]);
+
+ const data = stateDetail.findUnique.data;
+
+ if (!data) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Tombol Kembali */}
+ }
+ onClick={() => router.back()}
+ mb="lg"
+ >
+ Kembali
+
+
+
+
+ {/* Cover Buku */}
+
+
+ {/* Judul & Kategori */}
+
+
+ {data.judul}
+
+ {data.kategori?.name && (
+
+ {data.kategori.name}
+
+ )}
+
+
+ {/* Deskripsi Buku */}
+
+
+ Deskripsi Buku
+
+
+
+
+ {/* Tombol Pinjam */}
+ }
+ onClick={() => setOpened(true)}
+ >
+ Pinjam Buku Ini
+
+
+
+
+ {/* Modal Peminjaman */}
+ setOpened(false)}
+ buku={data}
+ />
+
+ );
+}
diff --git a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/content.tsx b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/content.tsx
index 6b4bb90e..349af199 100644
--- a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/content.tsx
+++ b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/[kategoriBuku]/content.tsx
@@ -1,42 +1,52 @@
'use client'
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
-import { ActionIcon, Box, Center, Group, Image, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, Tooltip, Badge } from '@mantine/core';
+import { ActionIcon, Badge, Box, Button, Center, Group, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconBook2, IconRefresh } from '@tabler/icons-react';
import { motion } from 'framer-motion';
+import { useTransitionRouter } from 'next-view-transitions';
import { useSearchParams } from 'next/navigation';
import { useCallback, useState } from 'react';
import { useProxy } from 'valtio/utils';
function Content({ kategoriBuku }: { kategoriBuku: string }) {
const state = useProxy(perpustakaanDigitalState);
- const [expandedId, setExpandedId] = useState(null);
const [isLoading, setIsLoading] = useState(true);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
const searchParams = useSearchParams();
const searchQuery = searchParams.get('search') || '';
+ const router = useTransitionRouter()
const decodedKategoriBuku = decodeURIComponent(kategoriBuku);
- const kategoriFilter = decodedKategoriBuku.toLowerCase() === 'semua' ? '' : decodedKategoriBuku;
- const loadData = useCallback(async (searchQuery: string = '') => {
+ const loadData = useCallback(async (searchQuery: string = '', page: number = 1) => {
try {
setIsLoading(true);
- await state.dataPerpustakaan.findMany.load(1, 100, searchQuery, kategoriFilter);
+ const currentKategoriFilter = decodedKategoriBuku.toLowerCase() === 'semua' ? '' : decodedKategoriBuku;
+ await state.dataPerpustakaan.findMany.load(page, 3, searchQuery, currentKategoriFilter);
+ setCurrentPage(page);
+ setTotalPages(state.dataPerpustakaan.findMany.totalPages);
} finally {
setIsLoading(false);
}
- }, [kategoriFilter, state.dataPerpustakaan.findMany]);
+ }, [state.dataPerpustakaan.findMany, decodedKategoriBuku]);
useShallowEffect(() => {
loadData(searchQuery);
- }, [searchQuery, loadData]);
+ }, [searchQuery, loadData, kategoriBuku]);
const handleRefresh = () => {
loadData();
};
- if (isLoading || !state.dataPerpustakaan.findMany.load || !state.dataPerpustakaan.findMany.data) {
+ const handlePageChange = (newPage: number) => {
+ loadData(searchQuery, newPage);
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ };
+
+ if ((isLoading && !state.dataPerpustakaan.findMany.data) || !state.dataPerpustakaan.findMany.load) {
return (
@@ -57,18 +67,23 @@ function Content({ kategoriBuku }: { kategoriBuku: string }) {
Koleksi Buku
-
-
-
-
-
+
+
+ Halaman {currentPage} dari {totalPages}
+
+
+
+
+
+
+
{!state.dataPerpustakaan.findMany.data || state.dataPerpustakaan.findMany.data.length === 0 ? (
@@ -132,30 +147,23 @@ function Content({ kategoriBuku }: { kategoriBuku: string }) {
)}
-
- Lihat deskripsi
-
- }
- hideLabel={
-
- Sembunyikan deskripsi
-
- }
- expanded={expandedId === v.id}
- onExpandedChange={(isExpanded) => setExpandedId(isExpanded ? v.id : null)}
- >
-
-
+
+ {/* 📗 Tombol Detail */}
+
@@ -163,6 +171,15 @@ function Content({ kategoriBuku }: { kategoriBuku: string }) {
)}
+
+
+
);
}
diff --git a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx
index b7b3d489..e61f85ca 100644
--- a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx
+++ b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/layoutTabs.tsx
@@ -1,11 +1,24 @@
-'use client'
-import { useEffect, useState } from 'react';
-import { ActionIcon, Box, Flex, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
-import { IconSearch, IconUser } from '@tabler/icons-react';
-import Link from 'next/link';
-import { usePathname, useRouter, useSearchParams } from 'next/navigation';
-import BackButton from '../../../desa/layanan/_com/BackButto';
+'use client';
+
+import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
+import {
+ Box,
+ Grid,
+ GridCol,
+ Stack,
+ Tabs,
+ TabsList,
+ TabsTab,
+ Text,
+ TextInput
+} from '@mantine/core';
+import { IconSearch } from '@tabler/icons-react';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import { useSnapshot } from 'valtio';
+import BackButton from '../../../desa/layanan/_com/BackButto';
+
type LayoutBukuProps = {
placeholder?: string;
@@ -15,7 +28,7 @@ type LayoutBukuProps = {
children?: React.ReactNode;
};
-function LayoutTabs({
+export default function LayoutTabs({
placeholder = 'Cari buku digital...',
searchIcon = ,
children,
@@ -23,6 +36,7 @@ function LayoutTabs({
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
+ const snap = useSnapshot(perpustakaanDigitalState);
const activeTab = pathname.split('/').pop() || 'semua';
const initialSearch = searchParams.get('search') || '';
@@ -30,6 +44,11 @@ function LayoutTabs({
const [searchTimeout, setSearchTimeout] = useState(null);
const [activeTabState, setActiveTabState] = useState(activeTab);
+ // 🟦 Ambil kategori buku saat mount
+ useEffect(() => {
+ perpustakaanDigitalState.kategoriBuku.findMany.load();
+ }, []);
+
useEffect(() => {
setActiveTabState(activeTab);
}, [activeTab]);
@@ -51,7 +70,9 @@ function LayoutTabs({
if (value) params.set('search', value);
router.push(
- `/darmasaba/pendidikan/perpustakaan-digital/${activeTab}${params.toString() ? `?${params.toString()}` : ''}`
+ `/darmasaba/pendidikan/perpustakaan-digital/${activeTab}${
+ params.toString() ? `?${params.toString()}` : ''
+ }`
);
};
@@ -63,41 +84,40 @@ function LayoutTabs({
}
};
+ // 🟩 Tabs dinamis berdasarkan kategori dari state
+ const kategoriTabs =
+ snap.kategoriBuku.findMany.data?.map((item) => ({
+ label: item.name,
+ value: item.name.toLowerCase().replace(/\s+/g, '-'),
+ href: `/darmasaba/pendidikan/perpustakaan-digital/${encodeURIComponent(item.name.toLowerCase().replace(/\s+/g, '-'))}`,
+ })) ?? [];
+
const tabs = [
{ label: 'Semua', value: 'semua', href: '/darmasaba/pendidikan/perpustakaan-digital/semua' },
- { label: 'Dokumenter', value: 'dokumenter', href: '/darmasaba/pendidikan/perpustakaan-digital/dokumenter' },
- { label: 'Sayuran', value: 'sayuran', href: '/darmasaba/pendidikan/perpustakaan-digital/sayuran' },
- { label: 'Dongeng', value: 'dongeng', href: '/darmasaba/pendidikan/perpustakaan-digital/dongeng' },
+ ...kategoriTabs,
];
const handleTabChange = (value: string | null) => {
if (!value) return;
const params = new URLSearchParams(searchParams.toString());
- router.push(`/darmasaba/pendidikan/perpustakaan-digital/${value}${params.toString() ? `?${params.toString()}` : ''}`);
+ router.push(
+ `/darmasaba/pendidikan/perpustakaan-digital/${value}${
+ params.toString() ? `?${params.toString()}` : ''
+ }`
+ );
};
return (
-
-
-
-
-
Perpustakaan Digital Darmasaba
+
@@ -116,6 +136,7 @@ function LayoutTabs({
))}
+
+
{children}
);
}
-
-export default LayoutTabs;
diff --git a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/modalPeminjaman.tsx b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/modalPeminjaman.tsx
new file mode 100644
index 00000000..f509cc5e
--- /dev/null
+++ b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/_lib/modalPeminjaman.tsx
@@ -0,0 +1,220 @@
+'use client';
+
+import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
+import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
+import colors from '@/con/colors';
+import {
+ Badge,
+ Box,
+ Button,
+ Divider,
+ Group,
+ Image,
+ Modal,
+ Stack,
+ Text,
+ TextInput,
+} from '@mantine/core';
+import { DateInput } from '@mantine/dates';
+import {
+ IconArrowRight,
+ IconBook2,
+ IconUser
+} from '@tabler/icons-react';
+import { useEffect } from 'react';
+import { toast } from 'react-toastify';
+import { useSnapshot } from 'valtio';
+
+
+export interface ModalPeminjamanProps {
+ opened: boolean;
+ onClose: () => void;
+ buku: {
+ id: string;
+ judul: string;
+ deskripsi?: string;
+ image?: { link?: string };
+ kategori?: { name?: string };
+ } | null;
+}
+
+export default function ModalPeminjaman({
+ opened,
+ onClose,
+ buku,
+}: ModalPeminjamanProps) {
+ const snap = useSnapshot(perpustakaanDigitalState.peminjamanBuku);
+
+ // reset form setiap modal dibuka
+ useEffect(() => {
+ if (opened && buku) {
+ perpustakaanDigitalState.peminjamanBuku.create.form = {
+ ...perpustakaanDigitalState.peminjamanBuku.create.form,
+ bukuId: buku.id,
+ nama: '',
+ noTelp: '',
+ alamat: '',
+ tanggalPinjam: '',
+ batasKembali: '',
+ tanggalKembali: '',
+ catatan: '',
+ };
+ }
+ }, [opened, buku]);
+
+ const handleSubmit = async () => {
+ if (!buku) return toast.error('Data buku tidak ditemukan');
+ await perpustakaanDigitalState.peminjamanBuku.create.create();
+ onClose();
+ };
+
+ return (
+ Formulir Peminjaman Buku}
+ >
+ {buku ? (
+
+ {/* --- Info Buku --- */}
+
+
+
+
+
+ {buku.judul}
+
+
+ {buku.kategori?.name && (
+
+ {buku.kategori.name}
+
+ )}
+
+
+
+
+
+
+
+ {/* --- Form Input --- */}
+ }
+ value={snap.create.form.nama}
+ onChange={(e) =>
+ (perpustakaanDigitalState.peminjamanBuku.create.form.nama = e.currentTarget.value)
+ }
+ required
+ />
+
+ }
+ value={snap.create.form.noTelp}
+ onChange={(e) =>
+ (perpustakaanDigitalState.peminjamanBuku.create.form.noTelp = e.currentTarget.value)
+ }
+ required
+ />
+
+ }
+ value={snap.create.form.alamat}
+ onChange={(e) =>
+ (perpustakaanDigitalState.peminjamanBuku.create.form.alamat = e.currentTarget.value)
+ }
+ required
+ />
+
+ {
+ perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam =
+ date ? new Date(date).toISOString() : '';
+ }}
+ required
+ />
+
+
+ Catatan
+
+ (perpustakaanDigitalState.peminjamanBuku.create.form.catatan = e)
+ }
+ />
+
+
+ {
+ perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali =
+ date ? new Date(date).toISOString() : '';
+ }}
+ required
+ />
+
+ {
+ perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali =
+ date ? new Date(date).toISOString() : '';
+ }}
+ required
+ />
+
+
+ }
+ radius="xl"
+ >
+ Pinjam Buku
+
+
+ ) : (
+
+ Tidak ada data buku yang dipilih
+
+ )}
+
+ );
+}
diff --git a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/content.tsx b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/content.tsx
index e9c5294e..7b4766ab 100644
--- a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/content.tsx
+++ b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/content.tsx
@@ -1,45 +1,89 @@
-'use client'
+/* eslint-disable react-hooks/exhaustive-deps */
+'use client';
+
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
-import { Badge, Box, Center, Group, Image, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, Tooltip } from '@mantine/core';
-import { useShallowEffect } from '@mantine/hooks';
-import { motion } from 'framer-motion';
-import { useCallback, useState } from 'react';
-import { useProxy } from 'valtio/utils';
+import {
+ Badge,
+ Box,
+ Button,
+ Center,
+ Group,
+ Image,
+ Paper,
+ SimpleGrid,
+ Skeleton,
+ Stack,
+ Text,
+ Tooltip
+} from '@mantine/core';
import { IconBook2, IconInfoCircle } from '@tabler/icons-react';
+import { Pagination } from '@mantine/core';
+import { motion } from 'framer-motion';
-type ContentProps = {
- searchQuery: string;
-};
+import { useEffect, useState } from 'react';
+import { useSearchParams } from 'next/navigation';
+import { useProxy } from 'valtio/utils';
+import ModalPeminjaman from '../_lib/modalPeminjaman';
+import { useTransitionRouter } from 'next-view-transitions';
-function Content({ searchQuery }: ContentProps) {
+export default function Content() {
const state = useProxy(perpustakaanDigitalState);
- const [expandedId, setExpandedId] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
+ const router = useTransitionRouter()
+ const [opened, setOpened] = useState(false);
+ const searchParams = useSearchParams();
+ const searchQuery = searchParams.get('search') || '';
+ const [currentPage, setCurrentPage] = useState(1);
+
+ const { data: books = [], loading, totalPages } = state.dataPerpustakaan.findMany;
- const loadData = useCallback(
- async (query: string = '') => {
+ const [selectedBook, setSelectedBook] = useState<{
+ id: string;
+ judul: string;
+ deskripsi?: string;
+ image?: { link?: string };
+ kategori?: { name?: string };
+ } | null>(null);
+
+ // Handle data loading and search
+ useEffect(() => {
+ let isMounted = true;
+ const controller = new AbortController();
+
+ const loadData = async () => {
+ if (!isMounted) return;
+
try {
- setIsLoading(true);
- await state.dataPerpustakaan.findMany.load(1, 100, query, '');
+ await state.dataPerpustakaan.findMany.load(
+ currentPage,
+ 10,
+ searchQuery,
+ ''
+ );
} catch (error) {
- console.error('Gagal memuat data:', error);
- } finally {
- setIsLoading(false);
+ if (!controller.signal.aborted) {
+ console.error('Gagal memuat data:', error);
+ }
}
- },
- [state.dataPerpustakaan.findMany]
- );
+ };
- useShallowEffect(() => {
- loadData(searchQuery);
- }, [searchQuery, loadData]);
+ const timer = setTimeout(loadData, 300);
+
+ return () => {
+ isMounted = false;
+ controller.abort();
+ clearTimeout(timer);
+ };
+ }, [searchQuery, currentPage]);
- if (
- isLoading ||
- !state.dataPerpustakaan.findMany.load ||
- !state.dataPerpustakaan.findMany.data
- ) {
+ // Handle page change
+ const handlePageChange = (newPage: number) => {
+ setCurrentPage(newPage);
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ };
+
+ // Loading state
+ if (loading || !books) {
return (
@@ -53,6 +97,7 @@ function Content({ searchQuery }: ContentProps) {
return (
+ {/* 🔹 Header */}
@@ -65,101 +110,152 @@ function Content({ searchQuery }: ContentProps) {
- {!state.dataPerpustakaan.findMany.data ||
- state.dataPerpustakaan.findMany.data.length === 0 ? (
+ {/* 📚 Empty State */}
+ {books.length === 0 ? (
-
- Belum ada buku yang tersedia
+
+
+ Belum ada buku yang tersedia
+
) : (
+ // 📘 Daftar Buku
- {state.dataPerpustakaan.findMany.data?.map((v, k) => (
+ {books.map((v) => (
-
-
+
+ {/* 🖼 Gambar Buku */}
-
-
-
+
{v.judul}
{v.kategori && (
-
+
{v.kategori.name}
)}
-
- Lihat deskripsi
-
- }
- hideLabel={
-
- Sembunyikan deskripsi
-
- }
- expanded={expandedId === v.id}
- onExpandedChange={(isExpanded) => setExpandedId(isExpanded ? v.id : null)}
- >
-
-
+
+ {/* 📝 Deskripsi */}
+
+
+ {/* 📗 Tombol Detail */}
+
+
+ {/* 📗 Tombol Peminjaman */}
+ }
+ onClick={() => {
+ setSelectedBook(v);
+ setOpened(true);
+ }}
+ >
+ Peminjaman
+
))}
)}
+
+
+
+
+
+
+ {/* 🔸 Modal Peminjaman */}
+ {
+ setOpened(false);
+ setSelectedBook(null);
+ }}
+ buku={selectedBook}
+ />
);
}
-
-export default Content;
diff --git a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/page.tsx b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/page.tsx
index 0236b8cd..ab7bb140 100644
--- a/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/page.tsx
+++ b/src/app/darmasaba/(pages)/pendidikan/perpustakaan-digital/semua/page.tsx
@@ -1,13 +1,13 @@
// src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
import { Suspense } from "react";
-import Content from "./content";
+import Content from "../[kategoriBuku]/content";
export default async function Page() {
return (
Loading...}>
-
+
);
}
\ No newline at end of file