- QC User & Admin Menu Pendidikan V
- Fix SubMenu : - Beasiswa Desa ( Baca Selengkapnya terdapatkan konten ) V - Info Sekolah ( Kategori Menyesuaikan Dengan Datanya ) V - Perpustakaan Digital ( V - Kategori Menyesuaikan Dengan Datanya V - Saat Mau minjam muncul modal data diri peminjam buku V - Ada Status Peminjamannya V )
This commit is contained in:
@@ -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 (
|
||||
// <Box style={{ minHeight: '100vh', background: colors.Bg, paddingBottom: 48 }}>
|
||||
// <Container size="xl" py={{ base: 'md', md: 'xl' }}>
|
||||
// <Stack gap="lg">
|
||||
// {/* Back Button */}
|
||||
// <ActionIcon onClick={() => window.history.back()} variant="light" radius="md" size="lg">
|
||||
// <IconArrowLeft size={20} />
|
||||
// <VisuallyHidden>Kembali</VisuallyHidden>
|
||||
// </ActionIcon>
|
||||
|
||||
// {/* Search & Filter */}
|
||||
// <Paper radius="lg" p="xl" withBorder>
|
||||
// <Stack gap="md">
|
||||
// <Text ta="center" fw={800} fz={28}>{title}</Text>
|
||||
|
||||
// <Text ta="center" fz={"md"} c="black">
|
||||
// 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.
|
||||
// </Text>
|
||||
// <Group justify="center" gap="xs" wrap="wrap">
|
||||
// {jenjangPendidikanList.map((k) => {
|
||||
// const aktif = k === jenjangPendidikanAktif;
|
||||
// return (
|
||||
// <Button
|
||||
// key={k}
|
||||
// onClick={() => handleJenjangPendidikanChange(k)}
|
||||
// radius="xl"
|
||||
// size="sm"
|
||||
// variant={aktif ? 'filled' : 'light'}
|
||||
// >
|
||||
// {k}
|
||||
// </Button>
|
||||
// );
|
||||
// })}
|
||||
// </Group>
|
||||
// </Stack>
|
||||
// </Paper>
|
||||
|
||||
// {/* Slot konten */}
|
||||
// {children}
|
||||
// </Stack>
|
||||
// </Container>
|
||||
// </Box>
|
||||
// );
|
||||
// }
|
||||
'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<string>(
|
||||
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 (
|
||||
<Box style={{ minHeight: '100vh', background: colors.Bg, paddingBottom: 48 }}>
|
||||
@@ -61,31 +161,41 @@ export default function LayoutSekolah({
|
||||
{/* Search & Filter */}
|
||||
<Paper radius="lg" p="xl" withBorder>
|
||||
<Stack gap="md">
|
||||
<Text ta="center" fw={800} fz={28}>{title}</Text>
|
||||
|
||||
<Text ta="center" fz={"md"} c="black">
|
||||
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.
|
||||
<Text ta="center" fw={800} fz={28}>
|
||||
{title}
|
||||
</Text>
|
||||
<Group justify="center" gap="xs" wrap="wrap">
|
||||
{jenjangPendidikanList.map((k) => {
|
||||
const aktif = k === jenjangPendidikanAktif;
|
||||
return (
|
||||
<Button
|
||||
key={k}
|
||||
onClick={() => handleJenjangPendidikanChange(k)}
|
||||
radius="xl"
|
||||
size="sm"
|
||||
variant={aktif ? 'filled' : 'light'}
|
||||
>
|
||||
{k}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
|
||||
<Text ta="center" fz="md" c="black">
|
||||
Temukan data lengkap mengenai lembaga pendidikan, jumlah siswa terdaftar, dan tenaga
|
||||
pengajar berdasarkan jenjang pendidikan (TK, SD, SMP, SMA).
|
||||
</Text>
|
||||
|
||||
{snap.findMany.loading ? (
|
||||
<Group justify="center" py="sm">
|
||||
<Loader size="sm" />
|
||||
</Group>
|
||||
) : (
|
||||
<Group justify="center" gap="xs" wrap="wrap">
|
||||
{jenjangList.map((k) => {
|
||||
const aktif = k === jenjangPendidikanAktif;
|
||||
return (
|
||||
<Button
|
||||
key={k}
|
||||
onClick={() => handleJenjangPendidikanChange(k)}
|
||||
radius="xl"
|
||||
size="sm"
|
||||
variant={aktif ? 'filled' : 'light'}
|
||||
>
|
||||
{k}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Slot konten */}
|
||||
{/* Konten anak */}
|
||||
{children}
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
@@ -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 (
|
||||
<Center h="70vh">
|
||||
<Loader color={colors['blue-button']} size="lg" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box bg={colors.Bg} py="xl" px={{ base: 'md', md: 100 }}>
|
||||
{/* Tombol Kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
leftSection={<IconArrowLeft size={20} color={colors['blue-button']} />}
|
||||
onClick={() => router.back()}
|
||||
mb="lg"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
shadow="md"
|
||||
radius="xl"
|
||||
bg="white"
|
||||
p={{ base: 'md', md: 'xl' }}
|
||||
maw={800}
|
||||
mx="auto"
|
||||
>
|
||||
<Stack gap="lg" align="center">
|
||||
{/* Cover Buku */}
|
||||
<Image
|
||||
src={data.image?.link || '/placeholder-book.jpg'}
|
||||
alt={data.judul}
|
||||
w={220}
|
||||
h={300}
|
||||
fit="contain"
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* Judul & Kategori */}
|
||||
<Stack gap={4} align="center">
|
||||
<Title order={2} ta="center" c={colors['blue-button']}>
|
||||
{data.judul}
|
||||
</Title>
|
||||
{data.kategori?.name && (
|
||||
<Badge color="cyan" variant="light" size="md">
|
||||
{data.kategori.name}
|
||||
</Badge>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Deskripsi Buku */}
|
||||
<Paper bg="#F9FAFF" p="md" radius="md" shadow="xs" w="100%">
|
||||
<Text fw={600} mb={6} c={colors['blue-button']}>
|
||||
Deskripsi Buku
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
lh={1.6}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
{/* Tombol Pinjam */}
|
||||
<Button
|
||||
size="md"
|
||||
radius="xl"
|
||||
color="blue"
|
||||
mt="sm"
|
||||
leftSection={<IconBook2 size={20} />}
|
||||
onClick={() => setOpened(true)}
|
||||
>
|
||||
Pinjam Buku Ini
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Peminjaman */}
|
||||
<ModalPeminjaman
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
buku={data}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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<string | null>(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 (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||
@@ -57,18 +67,23 @@ function Content({ kategoriBuku }: { kategoriBuku: string }) {
|
||||
Koleksi Buku
|
||||
</Text>
|
||||
</Group>
|
||||
<Tooltip label="Muat ulang koleksi" withArrow>
|
||||
<ActionIcon
|
||||
variant="gradient"
|
||||
gradient={{ from: 'blue', to: 'cyan' }}
|
||||
onClick={handleRefresh}
|
||||
loading={isLoading}
|
||||
radius="xl"
|
||||
size="lg"
|
||||
>
|
||||
<IconRefresh size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
Halaman {currentPage} dari {totalPages}
|
||||
</Text>
|
||||
<Tooltip label="Muat ulang koleksi" withArrow>
|
||||
<ActionIcon
|
||||
variant="gradient"
|
||||
gradient={{ from: 'blue', to: 'cyan' }}
|
||||
onClick={handleRefresh}
|
||||
loading={isLoading}
|
||||
radius="xl"
|
||||
size="lg"
|
||||
>
|
||||
<IconRefresh size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{!state.dataPerpustakaan.findMany.data || state.dataPerpustakaan.findMany.data.length === 0 ? (
|
||||
@@ -132,30 +147,23 @@ function Content({ kategoriBuku }: { kategoriBuku: string }) {
|
||||
</Badge>
|
||||
)}
|
||||
</Stack>
|
||||
<Spoiler
|
||||
maxHeight={80}
|
||||
showLabel={
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']} ta="center" mt="xs">
|
||||
Lihat deskripsi
|
||||
</Text>
|
||||
}
|
||||
hideLabel={
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']} ta="center" mt="xs">
|
||||
Sembunyikan deskripsi
|
||||
</Text>
|
||||
}
|
||||
expanded={expandedId === v.id}
|
||||
onExpandedChange={(isExpanded) => setExpandedId(isExpanded ? v.id : null)}
|
||||
>
|
||||
<Text
|
||||
ta="justify"
|
||||
fz="sm"
|
||||
lh={1.5}
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
</Spoiler>
|
||||
<Text
|
||||
ta="justify"
|
||||
fz="sm"
|
||||
lh={1.5}
|
||||
lineClamp={5}
|
||||
truncate="end"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
/>
|
||||
{/* 📗 Tombol Detail */}
|
||||
<Button variant='light' color='blue' onClick={() => router.push(`/darmasaba/pendidikan/perpustakaan-digital/${v.kategori?.name}/${v.id}`)}>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
@@ -163,6 +171,15 @@ function Content({ kategoriBuku }: { kategoriBuku: string }) {
|
||||
</SimpleGrid>
|
||||
)}
|
||||
</Box>
|
||||
<Center mt="lg">
|
||||
<Pagination
|
||||
value={currentPage}
|
||||
onChange={handlePageChange}
|
||||
total={totalPages}
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 = <IconSearch size={20} />,
|
||||
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<number | null>(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 (
|
||||
<Stack pos="relative" bg="var(--mantine-color-gray-0)" py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Flex justify="space-between" align="center">
|
||||
<BackButton />
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
component={Link}
|
||||
href="/login"
|
||||
radius="xl"
|
||||
size="lg"
|
||||
aria-label="Masuk ke akun"
|
||||
>
|
||||
<IconUser size={26} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<Box pb={20}>
|
||||
<Text ta="center" fz={{ base: '1.6rem', md: '2.4rem' }} fw={700} c={colors['blue-button']}>
|
||||
Perpustakaan Digital Darmasaba
|
||||
</Text>
|
||||
|
||||
<Tabs color="blue" variant="pills" value={activeTabState} onChange={handleTabChange}>
|
||||
<Box px={{ base: 'md', md: 100 }} py="md" bg="var(--mantine-color-gray-1)" style={{ borderRadius: 16 }}>
|
||||
<Grid align="center" gutter="md">
|
||||
@@ -116,6 +136,7 @@ function LayoutTabs({
|
||||
))}
|
||||
</TabsList>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius="xl"
|
||||
@@ -130,11 +151,10 @@ function LayoutTabs({
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{children}
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
|
||||
@@ -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 (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
centered
|
||||
size="lg"
|
||||
radius="xl"
|
||||
title={<Text fw={700}>Formulir Peminjaman Buku</Text>}
|
||||
>
|
||||
{buku ? (
|
||||
<Stack>
|
||||
{/* --- Info Buku --- */}
|
||||
<Group align="flex-start">
|
||||
<Image
|
||||
src={buku.image?.link || '/placeholder-book.jpg'}
|
||||
alt={buku.judul}
|
||||
w={100}
|
||||
radius="md"
|
||||
/>
|
||||
<Stack gap={4}>
|
||||
<Group>
|
||||
<IconBook2 size={18} color={colors['blue-button']} />
|
||||
<Text fw={700}>{buku.judul}</Text>
|
||||
</Group>
|
||||
|
||||
{buku.kategori?.name && (
|
||||
<Badge color="cyan" variant="light">
|
||||
{buku.kategori.name}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<Text fz="sm" c="dimmed" lineClamp={3} dangerouslySetInnerHTML={{ __html: buku.deskripsi || 'Tidak ada deskripsi' }} />
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
<Divider my="sm" />
|
||||
|
||||
{/* --- Form Input --- */}
|
||||
<TextInput
|
||||
label="Nama Peminjam"
|
||||
placeholder="Masukkan nama lengkap"
|
||||
leftSection={<IconUser size={16} />}
|
||||
value={snap.create.form.nama}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.nama = e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="No Telp"
|
||||
placeholder="Masukkan no telp"
|
||||
leftSection={<IconUser size={16} />}
|
||||
value={snap.create.form.noTelp}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.noTelp = e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat"
|
||||
leftSection={<IconUser size={16} />}
|
||||
value={snap.create.form.alamat}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.alamat = e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
label="Tanggal Pinjam"
|
||||
placeholder="Pilih tanggal pinjam"
|
||||
value={
|
||||
snap.create.form.tanggalPinjam
|
||||
? new Date(snap.create.form.tanggalPinjam)
|
||||
: null
|
||||
}
|
||||
onChange={(date) => {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam =
|
||||
date ? new Date(date).toISOString() : '';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text>Catatan</Text>
|
||||
<CreateEditor
|
||||
value={snap.create.form.catatan}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.catatan = e)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<DateInput
|
||||
label="Tanggal Kembali"
|
||||
placeholder="Pilih tanggal kembali"
|
||||
value={
|
||||
snap.create.form.tanggalKembali
|
||||
? new Date(snap.create.form.tanggalKembali)
|
||||
: null
|
||||
}
|
||||
onChange={(date) => {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali =
|
||||
date ? new Date(date).toISOString() : '';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
label="Batas Pengembalian"
|
||||
placeholder="Pilih tanggal kembali"
|
||||
value={
|
||||
snap.create.form.batasKembali
|
||||
? new Date(snap.create.form.batasKembali)
|
||||
: null
|
||||
}
|
||||
onChange={(date) => {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali =
|
||||
date ? new Date(date).toISOString() : '';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
loading={snap.create.loading}
|
||||
disabled={
|
||||
!snap.create.form.nama ||
|
||||
!snap.create.form.tanggalPinjam ||
|
||||
!snap.create.form.batasKembali ||
|
||||
!snap.create.form.tanggalKembali
|
||||
}
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
radius="xl"
|
||||
>
|
||||
Pinjam Buku
|
||||
</Button>
|
||||
</Stack>
|
||||
) : (
|
||||
<Text c="dimmed" ta="center">
|
||||
Tidak ada data buku yang dipilih
|
||||
</Text>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -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<string | null>(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 (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||
@@ -53,6 +97,7 @@ function Content({ searchQuery }: ContentProps) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
||||
{/* 🔹 Header */}
|
||||
<Group justify="space-between" mb="lg">
|
||||
<Group gap="xs">
|
||||
<IconBook2 size={28} color={colors['blue-button']} />
|
||||
@@ -65,101 +110,152 @@ function Content({ searchQuery }: ContentProps) {
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
{!state.dataPerpustakaan.findMany.data ||
|
||||
state.dataPerpustakaan.findMany.data.length === 0 ? (
|
||||
{/* 📚 Empty State */}
|
||||
{books.length === 0 ? (
|
||||
<Center py="xl">
|
||||
<Stack gap="xs" align="center">
|
||||
<Image loading="lazy" src="/empty-books.svg" alt="Kosong" w={140} h="auto" />
|
||||
<Text c="dimmed" fz="sm">Belum ada buku yang tersedia</Text>
|
||||
<Image
|
||||
loading="lazy"
|
||||
src="/empty-books.svg"
|
||||
alt="Kosong"
|
||||
w={140}
|
||||
h="auto"
|
||||
/>
|
||||
<Text c="dimmed" fz="sm">
|
||||
Belum ada buku yang tersedia
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
// 📘 Daftar Buku
|
||||
<SimpleGrid
|
||||
cols={{ base: 1, sm: 2, md: 3 }}
|
||||
spacing="lg"
|
||||
verticalSpacing="lg"
|
||||
style={{ alignItems: 'stretch' }}
|
||||
>
|
||||
{state.dataPerpustakaan.findMany.data?.map((v, k) => (
|
||||
{books.map((v) => (
|
||||
<motion.div
|
||||
key={k}
|
||||
key={v.id}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<Paper
|
||||
p="lg"
|
||||
<Paper
|
||||
p="lg"
|
||||
radius="2xl"
|
||||
shadow="md"
|
||||
bg="white"
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between'
|
||||
justifyContent: 'space-between',
|
||||
height: '100%',
|
||||
minHeight: 440,
|
||||
}}
|
||||
>
|
||||
<Stack gap="md" style={{ flex: 1 }}>
|
||||
<Stack gap="md" style={{ flexGrow: 1 }}>
|
||||
{/* 🖼 Gambar Buku */}
|
||||
<Center>
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.judul}
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.judul}
|
||||
h={180}
|
||||
w="auto"
|
||||
fit="contain"
|
||||
fallbackSrc="/placeholder-book.jpg"
|
||||
radius="md"
|
||||
fallbackSrc="/placeholder-book.jpg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Center>
|
||||
<Stack gap={4} align="center">
|
||||
<Text
|
||||
c={colors["blue-button"]}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: "md", md: "lg" }}
|
||||
|
||||
{/* 📖 Judul & Kategori */}
|
||||
<Stack gap={4} align="center" px="sm">
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lineClamp={2}
|
||||
>
|
||||
{v.judul}
|
||||
</Text>
|
||||
{v.kategori && (
|
||||
<Badge color="cyan" radius="sm" variant="light" size="sm">
|
||||
<Badge
|
||||
color="cyan"
|
||||
radius="sm"
|
||||
variant="light"
|
||||
size="sm"
|
||||
>
|
||||
{v.kategori.name}
|
||||
</Badge>
|
||||
)}
|
||||
</Stack>
|
||||
<Spoiler
|
||||
maxHeight={80}
|
||||
showLabel={
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']} ta="center" mt="xs">
|
||||
Lihat deskripsi
|
||||
</Text>
|
||||
}
|
||||
hideLabel={
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']} ta="center" mt="xs">
|
||||
Sembunyikan deskripsi
|
||||
</Text>
|
||||
}
|
||||
expanded={expandedId === v.id}
|
||||
onExpandedChange={(isExpanded) => setExpandedId(isExpanded ? v.id : null)}
|
||||
>
|
||||
<Text
|
||||
ta="justify"
|
||||
fz="sm"
|
||||
lh={1.5}
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
</Spoiler>
|
||||
|
||||
{/* 📝 Deskripsi */}
|
||||
<Text
|
||||
ta="justify"
|
||||
fz="sm"
|
||||
lh={1.5}
|
||||
lineClamp={5}
|
||||
truncate="end"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{/* 📗 Tombol Detail */}
|
||||
<Button variant='light' color='blue' onClick={() => router.push(`/darmasaba/pendidikan/perpustakaan-digital/${v.kategori?.name}/${v.id}`)}>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
|
||||
{/* 📗 Tombol Peminjaman */}
|
||||
<Button
|
||||
mt="md"
|
||||
variant="outline"
|
||||
color="blue"
|
||||
radius="xl"
|
||||
size="md"
|
||||
fullWidth
|
||||
leftSection={<IconBook2 size={20} />}
|
||||
onClick={() => {
|
||||
setSelectedBook(v);
|
||||
setOpened(true);
|
||||
}}
|
||||
>
|
||||
Peminjaman
|
||||
</Button>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
|
||||
<Center mt="lg">
|
||||
<Pagination
|
||||
value={currentPage}
|
||||
onChange={handlePageChange}
|
||||
total={totalPages}
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
|
||||
{/* 🔸 Modal Peminjaman */}
|
||||
<ModalPeminjaman
|
||||
opened={opened}
|
||||
onClose={() => {
|
||||
setOpened(false);
|
||||
setSelectedBook(null);
|
||||
}}
|
||||
buku={selectedBook}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Content;
|
||||
|
||||
@@ -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 (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Content searchQuery="" />
|
||||
<Content kategoriBuku="semua" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user