Fix UI Admin Menu Pendidikam, Add Menu User & Role

This commit is contained in:
2025-09-02 18:08:53 +08:00
parent 7ae83788b4
commit fa9601e126
86 changed files with 5349 additions and 2649 deletions

View File

@@ -4,7 +4,7 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -12,7 +12,7 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPasarDesa() {
function EditPerpustakaanDigital() {
const editState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
const router = useRouter();
const params = useParams();
@@ -41,16 +41,11 @@ function EditPasarDesa() {
imageId: data.imageId || "",
kategoriId: data.kategoriId || "",
});
// Tampilkan preview gambar
if (data.image?.link) {
setPreviewImage(data.image.link);
}
if (data.image?.link) setPreviewImage(data.image.link);
}
} catch (error) {
console.error("Error loading data perpustakaan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengambil data perpustakaan"
);
toast.error(error instanceof Error ? error.message : "Gagal mengambil data perpustakaan");
}
}
@@ -59,22 +54,12 @@ function EditPasarDesa() {
const handleSubmit = async () => {
try {
editState.update.form = {
...editState.update.form,
judul: formData.judul,
deskripsi: formData.deskripsi,
kategoriId: formData.kategoriId,
imageId: formData.imageId,
}
editState.update.form = { ...editState.update.form, ...formData }
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
// Update imageId in global state
if (!uploaded?.id) return toast.error("Gagal upload gambar");
editState.update.form.imageId = uploaded.id;
}
@@ -88,106 +73,110 @@ function EditPasarDesa() {
};
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Header */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Edit Data Perpustakaan Digital
</Title>
</Group>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Edit Data Perpustakaan</Title>
{/* Form Paper */}
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="lg" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
<Stack gap="md">
{/* Dropzone Image */}
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<Text fw="bold" fz="sm" mb={6}>Gambar</Text>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
radius="md"
p="xl"
>
<Group justify="center" gap="xl" mih={180}>
<Dropzone.Accept>
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={48} color="red" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={48} color="#868e96" stroke={1.5} />
</Dropzone.Idle>
<Stack gap="xs" align="center">
<Text size="md" fw={500}>Seret gambar atau klik untuk memilih file</Text>
<Text size="sm" c="dimmed">Maksimal 5MB, format gambar wajib</Text>
</Stack>
</Group>
</Dropzone>
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
{previewImage && (
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
<Image
src={previewImage}
alt="Preview"
radius="md"
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
/>
</Box>
)}
</Box>
{/* Judul */}
<TextInput
label="Judul"
placeholder="Masukkan judul"
value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
placeholder='Masukkan judul'
required
/>
{/* Deskripsi */}
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
setFormData({ ...formData, deskripsi: val });
}}
/>
<Text fw="bold" fz="sm" mb={6}>Deskripsi</Text>
<EditEditor value={formData.deskripsi} onChange={(val) => setFormData({ ...formData, deskripsi: val })} />
</Box>
{/* Kategori */}
<Select
value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
label="Kategori"
placeholder='Pilih kategori'
data={
perpustakaanDigitalState.kategoriBuku.findMany.data?.map((v) => ({
value: v.id,
label: v.name
})) || []
}
data={perpustakaanDigitalState.kategoriBuku.findMany.data?.map(v => ({ value: v.id, label: v.name })) || []}
clearable
searchable
required
error={!formData.kategoriId ? "Pilih kategori" : undefined}
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
{/* Submit Button */}
<Group justify="right">
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
</Button>
</Group>
</Stack>
</Paper>
@@ -195,4 +184,4 @@ function EditPasarDesa() {
);
}
export default EditPasarDesa;
export default EditPerpustakaanDigital;

View File

@@ -2,7 +2,7 @@
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -10,97 +10,130 @@ import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function DetailDataPerpustakaan() {
const stateDetail = useProxy(perpustakaanDigitalState.dataPerpustakaan)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const stateDetail = useProxy(perpustakaanDigitalState.dataPerpustakaan);
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const params = useParams();
const router = useRouter();
useShallowEffect(() => {
stateDetail.findUnique.load(params?.id as string)
}, [])
stateDetail.findUnique.load(params?.id as string);
}, []);
const handleHapus = () => {
if (selectedId) {
stateDetail.delete.delete(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan")
stateDetail.delete.delete(selectedId);
setModalHapus(false);
setSelectedId(null);
router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan");
}
}
};
if (!stateDetail.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={500} radius="md" />
</Stack>
)
);
}
const data = stateDetail.findUnique.data;
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Data Perpustakaan</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box py={10}>
<Button
variant="subtle"
onClick={() => router.back()}
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
mb={15}
>
Kembali
</Button>
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
>
<Stack gap="md">
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
Detail Data Perpustakaan
</Text>
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm">
<Box>
<Text fz={"lg"} fw={"bold"}>Judul</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.judul}</Text>
<Text fz="lg" fw="bold">Judul</Text>
<Text fz="md" c="dimmed">{data.judul || '-'}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: stateDetail.findUnique.data?.deskripsi }} />
<Text fz="lg" fw="bold">Deskripsi</Text>
<Text fz="md" dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.kategori.name}</Text>
<Text fz="lg" fw="bold">Kategori</Text>
<Text fz="md" c="dimmed">{data.kategori?.name || '-'}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
<Image src={stateDetail.findUnique.data?.image.link} alt="gambar" />
<Text fz="lg" fw="bold">Gambar</Text>
{data.image?.link ? (
<Image
src={data.image.link}
alt={data.judul || 'Gambar Perpustakaan'}
w={120}
h={120}
radius="md"
fit="cover"
/>
) : (
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
)}
</Box>
<Box>
<Flex gap={"xs"}>
<Group gap="sm">
<Tooltip label="Hapus Data Perpustakaan" withArrow position="top">
<Button
color="red"
onClick={() => {
if (stateDetail.findUnique.data) {
setSelectedId(stateDetail.findUnique.data.id);
setModalHapus(true);
}
setSelectedId(data.id);
setModalHapus(true);
}}
disabled={!stateDetail.findUnique.data}
color="red">
variant="light"
radius="md"
size="md"
>
<IconX size={20} />
</Button>
</Tooltip>
<Tooltip label="Edit Data Perpustakaan" withArrow position="top">
<Button
onClick={() => {
if (stateDetail.findUnique.data) {
router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${stateDetail.findUnique.data.id}/edit`);
}
}}
disabled={!stateDetail.findUnique.data}
color="green">
color="green"
onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${data.id}/edit`)}
variant="light"
radius="md"
size="md"
>
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Tooltip>
</Group>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus data ini?"
text="Apakah Anda yakin ingin menghapus data ini?"
/>
</Box>
);

View File

@@ -3,7 +3,7 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -11,7 +11,6 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateDataPerpustakaan() {
const createState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
const router = useRouter();
@@ -29,7 +28,6 @@ function CreateDataPerpustakaan() {
imageId: "",
kategoriId: "",
};
setPreviewImage(null)
setFile(null)
};
@@ -56,103 +54,119 @@ function CreateDataPerpustakaan() {
};
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Box px={{ base: 'sm', md: 'lg' }} py="md">
{/* Tombol Kembali */}
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Tambah Data Perpustakaan
</Title>
</Group>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Data Perpustakaan</Title>
<Paper
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
style={{ border: '1px solid #e0e0e0' }}
>
<Stack gap="md">
{/* Judul */}
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
label={<Text fw="bold" fz="sm">Judul</Text>}
placeholder='Masukkan judul'
value={createState.create.form.judul}
onChange={(val) => {
createState.create.form.judul = val.target.value;
}}
required
/>
{/* Deskripsi */}
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
<Text fw="bold" fz="sm" mb={6}>Deskripsi</Text>
<CreateEditor
value={createState.create.form.deskripsi}
onChange={(val) => {
createState.create.form.deskripsi = val;
}}
onChange={(val) => { createState.create.form.deskripsi = val; }}
/>
</Box>
{/* Kategori */}
<Select
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
label={<Text fw="bold" fz="sm">Kategori</Text>}
placeholder='Pilih kategori'
value={createState.create.form.kategoriId || ""}
onChange={(val) => {
createState.create.form.kategoriId = val ?? "";
}}
onChange={(val) => { createState.create.form.kategoriId = val ?? ""; }}
data={perpustakaanDigitalState.kategoriBuku.findMany.data?.map((item) => ({
value: item.id,
label: item.name,
}))}
/>
{/* Gambar */}
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<Text fw="bold" fz="sm" mb={6}>Gambar</Text>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0];
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile));
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2}
accept={{ 'image/*': [] }}
radius="md"
p="xl"
>
<Group justify="center" gap="xl" mih={180}>
<Dropzone.Accept>
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
</Group>
<Text ta="center" mt="sm" size="sm" color="dimmed">
Drag gambar ke sini atau klik untuk pilih file (maks 5MB)
</Text>
</Dropzone>
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
{previewImage && (
<Box mt="sm" style={{ textAlign: 'center' }}>
<Image
src={previewImage}
alt="Preview"
radius="md"
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
/>
</Box>
)}
</Box>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
{/* Submit Button */}
<Group justify="right">
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
</Button>
</Group>
</Stack>
</Paper>

View File

@@ -1,24 +1,21 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { Box, Button, Center, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip, Pagination } from '@mantine/core';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import perpustakaanDigitalState from '../../../_state/pendidikan/perpustakaan-digital';
function DataPerpustakaan() {
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Data Perpustakaan'
placeholder='pencarian'
placeholder='Cari judul, deskripsi, atau kategori...'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
@@ -32,70 +29,99 @@ function ListDataPerpustakaan({ search }: { search: string }) {
const listDataState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
const router = useRouter();
const { data, page, totalPages, loading, load } = listDataState.findMany;
useEffect(() => {
perpustakaanDigitalState.dataPerpustakaan.findMany.load()
listDataState.findMany.load()
}, [])
load(page, 10, search);
}, [page, search]);
const filteredData = (listDataState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.judul.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.kategori.name.toLowerCase().includes(keyword)
);
});
const filteredData = data || [];
if (!listDataState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={500} radius="md" />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<JudulList
title='List Data Perpustakaan'
href='/admin/pendidikan/perpustakaan-digital/data-perpustakaan/create'
/>
<Box style={{ overflowX: 'auto' }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item, index) => (
<Paper withBorder bg={colors['white-1']} p="md" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>List Data Perpustakaan</Title>
<Tooltip label="Tambah Data Perpustakaan" withArrow>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/pendidikan/perpustakaan-digital/data-perpustakaan/create')}
>
Tambah Baru
</Button>
</Tooltip>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table striped highlightOnHover withRowBorders style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
</Box>
<Text truncate fz="sm">{index + 1}</Text>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
<Text truncate fz="sm">{item.judul}</Text>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"}>{item.kategori.name}</Text>
<Text truncate fz="sm">{item.kategori.name}</Text>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${item.id}`)}>
<IconDeviceImacCog size={25} />
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${item.id}`)}
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
))
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text color="dimmed">Tidak ada data perpustakaan yang cocok</Text>
</Center>
</TableTd>
</TableTr>
)}
</TableTbody>
</Table>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
mb="md"
color="blue"
radius="md"
/>
</Center>
</Paper>
</Box>
)