- 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:
2025-10-13 11:20:38 +08:00
parent 80c5dc6361
commit a158241c0b
21 changed files with 2040 additions and 202 deletions

View File

@@ -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;

View File

@@ -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>
);
}