Fix QC Kak Inno Mobile Done

FIx QC Kak Ayu Mobile Admin Done
Fix Tampilan Admin Mobile Device All Menu Done
This commit is contained in:
2026-01-02 16:33:15 +08:00
parent 50bc54ceca
commit f436aa2ef0
117 changed files with 3812 additions and 1361 deletions

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconSchool, IconStar } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -56,35 +56,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -40,7 +40,7 @@ function DetailBeasiswaPendaftar() {
const data = state.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Button
variant="subtle"
onClick={() => router.back()}

View File

@@ -2,23 +2,24 @@
'use client'
import colors from '@/con/colors';
import {
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title
} from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -45,60 +46,81 @@ function BeasiswaPendaftar() {
function ListBeasiswaPendaftar({ search }: { search: string }) {
const state = useProxy(beasiswaDesaState.beasiswaPendaftar);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany;
useEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Beasiswa Pendaftar</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Beasiswa Pendaftar
</Title>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop: Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama Lengkap</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Tanggal Lahir</TableTh>
<TableTh>Jenis Kelamin</TableTh>
<TableTh>Aksi</TableTh>
<TableTh w="5%">No</TableTh>
<TableTh w="25%">Nama Lengkap</TableTh>
<TableTh w="30%">Alamat</TableTh>
<TableTh w="15%">Tanggal Lahir</TableTh>
<TableTh w="15%">Jenis Kelamin</TableTh>
<TableTh w="10%">Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>{index + 1}</TableTd>
<TableTd>
<Text truncate fz="sm">{item.namaLengkap}</Text>
<Text fz="md" fw={500} lh={1.45}>
{index + 1}
</Text>
</TableTd>
<TableTd>
<Text truncate fz="sm">{item.alamatKTP}</Text>
<Text truncate fz="md" fw={500} lh={1.45}>
{item.namaLengkap}
</Text>
</TableTd>
<TableTd>
<Text truncate fz="sm">
<Text truncate fz="md" fw={500} lh={1.45}>
{item.alamatKTP}
</Text>
</TableTd>
<TableTd>
<Text truncate fz="md" fw={500} lh={1.45}>
{item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '-'}
</Text>
</TableTd>
<TableTd>
<Text truncate fz="sm">{item.jenisKelamin}</Text>
<Text truncate fz="md" fw={500} lh={1.45}>
{item.jenisKelamin}
</Text>
</TableTd>
<TableTd>
<Button
@@ -107,6 +129,9 @@ function ListBeasiswaPendaftar({ search }: { search: string }) {
onClick={() =>
router.push(`/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar/${item.id}`)
}
fz="sm"
fw={500}
lh={1.4}
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
@@ -117,8 +142,10 @@ function ListBeasiswaPendaftar({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={6}>
<Center py={20}>
<Text c="dimmed">Tidak ada data pendaftar yang cocok</Text>
<Center py={{ base: 'sm', md: 'md' }}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data pendaftar yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -126,6 +153,82 @@ function ListBeasiswaPendaftar({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile: Card */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Lengkap
</Text>
<Text fz="sm" fw={500} lh={1.4} truncate>
{item.namaLengkap}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Alamat
</Text>
<Text fz="sm" fw={500} lh={1.4} truncate>
{item.alamatKTP}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Tanggal Lahir
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '-'}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Jenis Kelamin
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.jenisKelamin}
</Text>
</Box>
<Box>
<Button
variant="light"
color="blue"
onClick={() =>
router.push(`/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar/${item.id}`)
}
fullWidth
fz="sm"
fw={500}
lh={1.4}
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="sm">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data pendaftar yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
@@ -146,4 +249,4 @@ function ListBeasiswaPendaftar({ search }: { search: string }) {
);
}
export default BeasiswaPendaftar;
export default BeasiswaPendaftar;

View File

@@ -93,7 +93,7 @@ function EditProgramKreatifDesa() {
}
}
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button
variant="subtle"

View File

@@ -37,7 +37,7 @@ function CreateKeunggulanProgram() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button
variant="subtle"

View File

@@ -18,7 +18,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrashFilled } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -48,12 +48,13 @@ function ListKeunggulanProgram({ search }: { search: string }) {
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = stateList.findMany;
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const handleHapus = () => {
if (selectedId) {
@@ -67,14 +68,14 @@ function ListKeunggulanProgram({ search }: { search: string }) {
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="md">
<Skeleton height={600} radius="md" />
</Stack>
);
}
return (
<Box py={10}>
<Box py="md">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Keunggulan Program</Title>
@@ -92,34 +93,44 @@ function ListKeunggulanProgram({ search }: { search: string }) {
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ minWidth: 200 }}>Nama Keunggulan Program</TableTh>
<TableTh style={{ minWidth: 200 }}>Deskripsi</TableTh>
<TableTh style={{ minWidth: 200 }}>Edit</TableTh>
<TableTh style={{ minWidth: 200 }}>Delete</TableTh>
<TableTh style={{ width: '30%' }}>Nama Keunggulan Program</TableTh>
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
<TableTh style={{ width: '15%' }}>Edit</TableTh>
<TableTh style={{ width: '15%' }}>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd style={{ minWidth: 200 }}>
<Text fw={500} truncate="end" lineClamp={1}>
<TableTd>
<Text fz="md" fw={500} lh={1.45} truncate="end" lineClamp={1}>
{item.judul}
</Text>
</TableTd>
<TableTd style={{ minWidth: 200 }}>
<TableTd>
<Text
fz="md"
fw={500}
lh={1.45}
truncate="end"
lineClamp={1}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</TableTd>
<TableTd style={{ minWidth: 200 }}>
<TableTd>
<Button
variant="light"
color="green"
@@ -133,7 +144,7 @@ function ListKeunggulanProgram({ search }: { search: string }) {
<IconEdit size={18} />
</Button>
</TableTd>
<TableTd style={{ minWidth: 200 }}>
<TableTd>
<Button
variant="light"
color="red"
@@ -151,8 +162,8 @@ function ListKeunggulanProgram({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text c="dimmed">
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data keunggulan program yang cocok
</Text>
</Center>
@@ -162,9 +173,70 @@ function ListKeunggulanProgram({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="sm">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama Keunggulan Program</Text>
<Text fz="sm" fw={500} lh={1.4} truncate="end">
{item.judul}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
<Text
fz="sm"
fw={500}
lh={1.4}
truncate="end"
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</Box>
<Group justify="flex-end" mt="xs">
<Button
variant="light"
color="green"
size="xs"
onClick={() =>
router.push(
`/admin/pendidikan/beasiswa-desa/keunggulan-program/${item.id}`
)
}
>
<IconEdit size={16} />
</Button>
<Button
variant="light"
color="red"
size="xs"
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
<IconTrashFilled size={16} />
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data keunggulan program yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
@@ -179,9 +251,7 @@ function ListKeunggulanProgram({ search }: { search: string }) {
radius="md"
/>
</Center>
)}
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
@@ -192,4 +262,4 @@ function ListKeunggulanProgram({ search }: { search: string }) {
);
}
export default KeunggulanProgram;
export default KeunggulanProgram;

View File

@@ -1,8 +1,29 @@
'use client'
import React from 'react';
import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconBuildingCommunity, IconCalendar, IconSchool } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -62,35 +62,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -116,7 +116,7 @@ function EditFasilitasYangDisediakan() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
<Group mb="md">
<Button

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -29,28 +29,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Pratinjau Fasilitas Yang Disediakan
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Pratinjau Fasilitas Yang Disediakan
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -59,6 +55,7 @@ function Page() {
withBorder
radius="md"
shadow="xs"
visibleFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
@@ -84,6 +81,47 @@ function Page() {
</Stack>
</Box>
</Paper>
<Paper
p="xl"
bg={colors['white-1']}
withBorder
radius="md"
shadow="xs"
hiddenFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
{/* Judul */}
<Text
ta="center"
fz={{ base: '1.3rem', md: '1.8rem' }}
fw="bold"
c={colors['blue-button']}
dangerouslySetInnerHTML={{ __html: data.judul }}
/>
<Divider my="md" color={colors['blue-button']} />
{/* Deskripsi */}
<Text
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>
</Paper>
</Stack>
</Paper>
);

View File

@@ -1,7 +1,29 @@
'use client'
import React from 'react';
import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -113,7 +113,7 @@ function EditLokasiDanJadwal() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
<Group mb="md">
<Button

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -29,28 +29,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Pratinjau Lokasi & Jadwal
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Pratinjau Lokasi & Jadwal
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -59,6 +55,7 @@ function Page() {
withBorder
radius="md"
shadow="xs"
visibleFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
@@ -85,6 +82,48 @@ function Page() {
</Stack>
</Box>
</Paper>
<Paper
p="xl"
bg={colors['white-1']}
withBorder
radius="md"
shadow="xs"
hiddenFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
{/* Judul */}
<Text
ta="center"
fz={{ base: '1.3rem', md: '1.8rem' }}
fw="bold"
c={colors['blue-button']}
dangerouslySetInnerHTML={{ __html: data.judul }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
<Divider my="md" color={colors['blue-button']} />
{/* Deskripsi */}
<Text
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>
</Paper>
</Stack>
</Paper>
);

View File

@@ -109,7 +109,7 @@ function EditTujuanProgram() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
<Group mb="md">
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">

View File

@@ -4,8 +4,7 @@ import {
Box,
Button,
Divider,
Grid,
GridCol,
Group,
Paper,
Skeleton,
Stack,
@@ -40,28 +39,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Pratinjau Tujuan Program
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/bimbingan-belajar-desa/tujuan-program/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Pratinjau Tujuan Program
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/bimbingan-belajar-desa/tujuan-program/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -70,6 +65,7 @@ function Page() {
withBorder
radius="md"
shadow="xs"
visibleFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
@@ -95,6 +91,47 @@ function Page() {
</Stack>
</Box>
</Paper>
<Paper
p="xl"
bg={colors['white-1']}
withBorder
radius="md"
shadow="xs"
hiddenFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
{/* Judul */}
<Text
ta="center"
fz={{ base: '1.3rem', md: '1.8rem' }}
fw="bold"
c={colors['blue-button']}
dangerouslySetInnerHTML={{ __html: data.judul }}
/>
<Divider my="md" color={colors['blue-button']} />
{/* Deskripsi */}
<Text
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>
</Paper>
</Stack>
</Paper>
);

View File

@@ -76,7 +76,7 @@ export default function EditDataPendidikan() {
};
return (
<Box px={{ base: 'sm', md: 'xl' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md" gap="sm">
<Button variant="subtle" onClick={() => router.back()} radius="md">
<IconArrowBack size={20} stroke={2} />

View File

@@ -39,7 +39,7 @@ export default function CreateDataPendidikan() {
};
return (
<Box px={{ base: 'sm', md: 'xl' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md" gap="sm">
<Button variant="subtle" onClick={() => router.back()} radius="md">
<IconArrowBack size={20} stroke={2} />

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconBuilding, IconChalkboard, IconMicroscope, IconSchool } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -68,35 +68,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -90,7 +90,7 @@ function EditJenjangPendidikan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -51,7 +51,7 @@ function CreateJenjangPendidikan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Back button + Title */}
<Group mb="md">
<Button

View File

@@ -1,6 +1,23 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import {
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -9,29 +26,25 @@ import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud';
function JenjangPendidikan() {
// const [search, setSearch] = useState("")
return (
<Box>
<Title order={4}>Jenjang Pendidikan</Title>
{/* <HeaderSearch
title='Jenjang Pendidikan'
placeholder='Cari jenjang pendidikan...'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/> */}
<Title order={2} visibleFrom="md">
Jenjang Pendidikan
</Title>
<Title order={2} hiddenFrom="md">
Jenjang Pendidikan
</Title>
<ListJenjangPendidikan />
</Box>
);
}
function ListJenjangPendidikan() {
const stateList = useProxy(infoSekolahPaud.jenjangPendidikan)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter()
const stateList = useProxy(infoSekolahPaud.jenjangPendidikan);
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter();
const {
data,
@@ -39,50 +52,65 @@ function ListJenjangPendidikan() {
totalPages,
loading,
load,
} = stateList.findMany
} = stateList.findMany;
const handleHapus = () => {
if (selectedId) {
stateList.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
stateList.delete.byId(selectedId);
setModalHapus(false);
setSelectedId(null);
}
}
};
useShallowEffect(() => {
load(page, 10)
}, [page])
load(page, 10);
}, [page]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="md">
<Skeleton h={500} radius="md" />
</Stack>
)
);
}
return (
<Box py={10}>
<Box py="md">
<Paper bg={colors['white-1']} p="lg" withBorder shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Jenjang Pendidikan</Title>
<Title order={3} visibleFrom="md">
Daftar Jenjang Pendidikan
</Title>
<Title order={3} hiddenFrom="md">
Daftar Jenjang Pendidikan
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/pendidikan/info-sekolah/jenjang-pendidikan/create')}
onClick={() =>
router.push('/admin/pendidikan/info-sekolah/jenjang-pendidikan/create')
}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: "auto" }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh>Nama Jenjang Pendidikan</TableTh>
<TableTh style={{ width: '70%' }}>Nama Jenjang Pendidikan</TableTh>
<TableTh style={{ width: '15%' }}>Edit</TableTh>
<TableTh style={{ width: '15%' }}>Delete</TableTh>
</TableTr>
@@ -92,13 +120,19 @@ function ListJenjangPendidikan() {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end">{item.nama}</Text>
<Text fz="md" fw={500} lh={1.45} truncate="end">
{item.nama}
</Text>
</TableTd>
<TableTd>
<Button
color="green"
variant="light"
onClick={() => router.push(`/admin/pendidikan/info-sekolah/jenjang-pendidikan/${item.id}`)}
onClick={() =>
router.push(
`/admin/pendidikan/info-sekolah/jenjang-pendidikan/${item.id}`
)
}
>
<IconEdit size={20} />
</Button>
@@ -108,8 +142,8 @@ function ListJenjangPendidikan() {
color="red"
variant="light"
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
setSelectedId(item.id);
setModalHapus(true);
}}
>
<IconX size={20} />
@@ -120,8 +154,10 @@ function ListJenjangPendidikan() {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">Tidak ada data jenjang pendidikan</Text>
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data jenjang pendidikan
</Text>
</Center>
</TableTd>
</TableTr>
@@ -129,6 +165,55 @@ function ListJenjangPendidikan() {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Stack hiddenFrom="md" gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Jenjang Pendidikan
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.nama}
</Text>
</Box>
<Group mt="sm" justify="flex-end" gap="xs">
<Button
color="green"
variant="light"
size="compact-sm"
onClick={() =>
router.push(
`/admin/pendidikan/info-sekolah/jenjang-pendidikan/${item.id}`
)
}
>
<IconEdit size={16} />
</Button>
<Button
color="red"
variant="light"
size="compact-sm"
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
<IconX size={16} />
</Button>
</Group>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data jenjang pendidikan
</Text>
</Center>
)}
</Stack>
</Paper>
<Center>
@@ -151,10 +236,10 @@ function ListJenjangPendidikan() {
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus jenjang pendidikan ini?'
text="Apakah anda yakin ingin menghapus jenjang pendidikan ini?"
/>
</Box>
);
}
export default JenjangPendidikan;
export default JenjangPendidikan;

View File

@@ -1,7 +1,29 @@
'use client'
import React from 'react';
import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -96,7 +96,7 @@ export default function EditLembaga() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button

View File

@@ -41,7 +41,7 @@ function DetailLembaga() {
const data = detailState.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Button
variant="subtle"
onClick={() => router.back()}
@@ -53,7 +53,7 @@ function DetailLembaga() {
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -52,7 +52,7 @@ function CreateLembaga() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -18,7 +18,7 @@ import {
Text,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -27,7 +27,7 @@ import HeaderSearch from '../../../_com/header';
import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud';
function Lembaga() {
const [search, setSearch] = useState("")
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
@@ -43,8 +43,9 @@ function Lembaga() {
}
function ListLembaga({ search }: { search: string }) {
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
const router = useRouter()
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -55,40 +56,65 @@ function ListLembaga({ search }: { search: string }) {
} = stateList.findMany;
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" />
</Stack>
)
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Lembaga</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Lembaga
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/pendidikan/info-sekolah/lembaga/create')}
onClick={() =>
router.push('/admin/pendidikan/info-sekolah/lembaga/create')
}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: "auto" }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '40%' }}>Nama Lembaga</TableTh>
<TableTh style={{ width: '30%' }}>Jenjang Pendidikan</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
<TableTh style={{ width: '40%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Nama Lembaga
</Text>
</TableTh>
<TableTh style={{ width: '30%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Jenjang Pendidikan
</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Aksi
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -96,19 +122,29 @@ function ListLembaga({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.nama}
</Text>
</TableTd>
<TableTd>
<Text fz="sm" c="dimmed">{item.jenjangPendidikan?.nama || '-'}</Text>
<Text fz="sm" fw={500} lh={1.5} c="gray.7">
{item.jenjangPendidikan?.nama || '-'}
</Text>
</TableTd>
<TableTd>
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/pendidikan/info-sekolah/lembaga/${item.id}`)}
onClick={() =>
router.push(
`/admin/pendidikan/info-sekolah/lembaga/${item.id}`
)
}
>
<IconDeviceImac size={20} />
<Text ml={5}>Detail</Text>
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</TableTd>
</TableTr>
@@ -116,8 +152,10 @@ function ListLembaga({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">Tidak ada data lembaga yang cocok</Text>
<Center py={{ base: 20, md: 24 }}>
<Text c="gray.6" fz="sm" lh={1.5}>
Tidak ada data lembaga yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -125,6 +163,59 @@ function ListLembaga({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} p="sm" withBorder bg="white">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="gray.7">
Nama Lembaga
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.nama}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="gray.7">
Jenjang Pendidikan
</Text>
<Text fz="sm" fw={500} lh={1.4} c="gray.7">
{item.jenjangPendidikan?.nama || '-'}
</Text>
</Box>
<Box>
<Button
fullWidth
variant="light"
color="blue"
onClick={() =>
router.push(
`/admin/pendidikan/info-sekolah/lembaga/${item.id}`
)
}
>
<IconDeviceImac size={18} />
<Text ml={6} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="gray.6" fz="sm" lh={1.5}>
Tidak ada data lembaga yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
@@ -145,4 +236,4 @@ function ListLembaga({ search }: { search: string }) {
);
}
export default Lembaga;
export default Lembaga;

View File

@@ -100,7 +100,7 @@ function EditPengajar() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header Back + Title */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -41,7 +41,7 @@ function DetailPengajar() {
const data = detailState.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Button
variant="subtle"
onClick={() => router.back()}
@@ -53,7 +53,7 @@ function DetailPengajar() {
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -57,7 +57,7 @@ function CreatePengajar() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header Back + Title */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -11,6 +11,7 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud';
import { useDebouncedValue } from '@mantine/hooks';
function Pengajar() {
const [search, setSearch] = useState('');
@@ -29,8 +30,9 @@ function Pengajar() {
}
function ListPengajar({ search }: { search: string }) {
const listState = useProxy(infoSekolahPaud.pengajar)
const listState = useProxy(infoSekolahPaud.pengajar);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -41,24 +43,26 @@ function ListPengajar({ search }: { search: string }) {
} = listState.findMany;
useEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton h={500} radius="md" />
</Stack>
)
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Pengajar</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Pengajar
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -69,13 +73,33 @@ function ListPengajar({ search }: { search: string }) {
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '35%' }}>Nama Pengajar</TableTh>
<TableTh style={{ width: '35%' }}>Lembaga</TableTh>
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Nama Pengajar
</Text>
</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Lembaga
</Text>
</TableTh>
<TableTh style={{ width: '30%' }}>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Aksi
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -83,10 +107,14 @@ function ListPengajar({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
<Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.nama}
</Text>
</TableTd>
<TableTd>
<Text truncate="end" fz="sm" c="dimmed">{item.lembaga.nama}</Text>
<Text fz="md" fw={500} lh={1.5} c="gray.7" truncate="end">
{item.lembaga.nama}
</Text>
</TableTd>
<TableTd>
<Button
@@ -95,7 +123,9 @@ function ListPengajar({ search }: { search: string }) {
onClick={() => router.push(`/admin/pendidikan/info-sekolah/pengajar/${item.id}`)}
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</TableTd>
</TableTr>
@@ -103,8 +133,10 @@ function ListPengajar({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text c="dimmed">Tidak ada data pengajar yang cocok</Text>
<Center py={{ base: 'md', md: 'lg' }}>
<Text c="gray.6" fz="sm" lh={1.4}>
Tidak ada data pengajar yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -112,14 +144,63 @@ function ListPengajar({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder radius="md" p="sm">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Pengajar
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.nama}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Lembaga
</Text>
<Text fz="sm" fw={500} lh={1.4} c="gray.7">
{item.lembaga.nama}
</Text>
</Box>
<Box>
<Button
fullWidth
variant="light"
color="blue"
onClick={() => router.push(`/admin/pendidikan/info-sekolah/pengajar/${item.id}`)}
>
<IconDeviceImacCog size={18} />
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="md">
<Text c="gray.6" fz="sm" lh={1.4}>
Tidak ada data pengajar yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10, search)
window.scrollTo({ top: 0, behavior: 'smooth' })
load(newPage, 10, search);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="md"
@@ -129,7 +210,7 @@ function ListPengajar({ search }: { search: string }) {
/>
</Center>
</Box>
)
);
}
export default Pengajar;
export default Pengajar;

View File

@@ -107,7 +107,7 @@ function EditSiswa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -41,7 +41,7 @@ function DetailSiswa() {
const data = detailState.findUnique.data
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Button
variant="subtle"
@@ -55,7 +55,7 @@ function DetailSiswa() {
{/* Card Utama */}
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -52,7 +52,7 @@ function CreateSiswa() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header + Back Button */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
'use client';
import colors from '@/con/colors';
import {
Box,
@@ -17,7 +17,7 @@ import {
TableThead,
TableTr,
Text,
Title
Title,
} from '@mantine/core';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -25,6 +25,7 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud';
import { useDebouncedValue } from '@mantine/hooks';
function Siswa() {
const [search, setSearch] = useState('');
@@ -43,30 +44,33 @@ function Siswa() {
}
function ListSiswa({ search }: { search: string }) {
const stateSiswa = useProxy(infoSekolahPaud.siswa)
const stateSiswa = useProxy(infoSekolahPaud.siswa);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = stateSiswa.findMany;
useEffect(() => {
load(page, 10, search)
}, [page, search])
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || []
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={500} radius="md" />
</Stack>
)
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Siswa</Title>
<Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Siswa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -77,13 +81,35 @@ function ListSiswa({ search }: { search: string }) {
</Button>
</Group>
<Box style={{ overflowX: "auto" }}>
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
striped
withRowBorders
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '35%' }}>Nama Siswa</TableTh>
<TableTh style={{ width: '35%' }}>Lembaga</TableTh>
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Nama Siswa
</Text>
</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Lembaga
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4}>
Aksi
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -91,19 +117,27 @@ function ListSiswa({ search }: { search: string }) {
filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Text fw={500} truncate="end">{item.nama}</Text>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.nama}
</Text>
</TableTd>
<TableTd>
<Text truncate="end" c="dimmed">{item.lembaga.nama}</Text>
<Text fz="md" fw={500} lh={1.5} truncate="end">
{item.lembaga.nama}
</Text>
</TableTd>
<TableTd>
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/pendidikan/info-sekolah/siswa/${item.id}`)}
onClick={() =>
router.push(`/admin/pendidikan/info-sekolah/siswa/${item.id}`)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</TableTd>
</TableTr>
@@ -111,8 +145,10 @@ function ListSiswa({ search }: { search: string }) {
) : (
<TableTr>
<TableTd colSpan={3}>
<Center py={20}>
<Text color="dimmed">Tidak ada data siswa yang cocok</Text>
<Center py={{ base: 'sm', md: 'md' }}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data siswa yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -120,24 +156,76 @@ function ListSiswa({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Siswa
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.nama}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Lembaga
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.lembaga.nama}
</Text>
</Box>
<Box>
<Button
fullWidth
variant="light"
color="blue"
onClick={() =>
router.push(`/admin/pendidikan/info-sekolah/siswa/${item.id}`)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5} fz="sm" fw={500} lh={1.4}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="sm">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data siswa yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<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>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt={{ base: 'sm', md: 'md' }}
mb={{ base: 'sm', md: 'md' }}
color="blue"
radius="md"
/>
</Center>
</Box>
)
);
}
export default Siswa;
export default Siswa;

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconBook2, IconMapPin, IconSchool } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -63,35 +63,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -113,7 +113,7 @@ function EditJenisProgramYangDiselenggarakan() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
{/* Back Button + Title */}
<Group mb="md">

View File

@@ -4,8 +4,7 @@ import {
Box,
Button,
Divider,
Grid,
GridCol,
Group,
Paper,
Skeleton,
Stack,
@@ -40,28 +39,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Preview Jenis Program Yang Diselenggarakan
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Preview Jenis Program Yang Diselenggarakan
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -86,12 +81,20 @@ function Page() {
{/* Deskripsi */}
<Text
fz={{ base: '1rem', md: '1.2rem' }}
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta="justify"
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>

View File

@@ -1,8 +1,30 @@
'use client'
import React from 'react';
import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -120,7 +120,7 @@ function EditTempatKegiatan() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
{/* Back Button + Title */}
<Group mb="md">

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -29,28 +29,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Preview Tempat Kegiatan
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/pendidikan-non-formal/tempat-kegiatan/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Preview Tempat Kegiatan
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/pendidikan-non-formal/tempat-kegiatan/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -75,12 +71,20 @@ function Page() {
{/* Deskripsi */}
<Text
fz={{ base: '1rem', md: '1.2rem' }}
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta="justify"
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>

View File

@@ -111,7 +111,7 @@ function EditTujuanProgram() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
<Group mb="md">
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">

View File

@@ -4,8 +4,7 @@ import {
Box,
Button,
Divider,
Grid,
GridCol,
Group,
Paper,
Skeleton,
Stack,
@@ -42,28 +41,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Pratinjau Pendidikan Non Formal
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/pendidikan-non-formal/tujuan-program/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Pratinjau Pendidikan Non Formal
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/pendidikan-non-formal/tujuan-program/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -88,12 +83,20 @@ function Page() {
{/* Deskripsi */}
<Text
fz={{ base: '1rem', md: '1.2rem' }}
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta="justify"
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconBook2, IconCategory, IconUser } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -62,35 +62,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -116,7 +116,7 @@ function EditPerpustakaanDigital() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -40,7 +40,7 @@ function DetailDataPerpustakaan() {
const data = stateDetail.findUnique.data;
return (
<Box py={10}>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Button
variant="subtle"
onClick={() => router.back()}
@@ -52,7 +52,7 @@ function DetailDataPerpustakaan() {
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
w={{ base: "100%", md: "70%" }}
bg={colors['white-1']}
p="lg"
radius="md"

View File

@@ -63,7 +63,7 @@ function CreateDataPerpustakaan() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,13 +1,31 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import {
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import perpustakaanDigitalState from '../../../_state/pendidikan/perpustakaan-digital';
import { useDebouncedValue } from '@mantine/hooks';
function DataPerpustakaan() {
const [search, setSearch] = useState('');
@@ -26,85 +44,138 @@ function DataPerpustakaan() {
}
function ListDataPerpustakaan({ search }: { search: string }) {
const listDataState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
const listDataState = useProxy(perpustakaanDigitalState.dataPerpustakaan);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = listDataState.findMany;
useEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack py={10}>
<Stack py="md">
<Skeleton height={500} radius="md" />
</Stack>
)
);
}
return (
<Box py={10}>
<Box py="md">
<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>
<Title order={4} size="md" lh={1.2}>
List Data Perpustakaan
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/pendidikan/perpustakaan-digital/data-perpustakaan/create')}
onClick={() =>
router.push(
'/admin/pendidikan/perpustakaan-digital/data-perpustakaan/create'
)
}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table striped highlightOnHover withRowBorders style={{ minWidth: '700px' }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
striped
highlightOnHover
withRowBorders
miw={0}
style={{ tableLayout: 'fixed', width: '100%' }}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%' }}>No</TableTh>
<TableTh style={{ width: '25%' }}>Judul</TableTh>
<TableTh style={{ width: '25%' }}>Kategori</TableTh>
<TableTh style={{ width: '23%' }}>Deskripsi</TableTh>
<TableTh style={{ width: '22%' }}>Detail</TableTh>
<TableTh style={{ width: '5%' }}>
<Text fz="xs" fw={600} lh={1.4} ta="center">
No
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Judul
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Kategori
</Text>
</TableTh>
<TableTh style={{ width: '23%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Deskripsi
</Text>
</TableTh>
<TableTh style={{ width: '22%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Detail
</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ width: '5%' }}>
<Text truncate fz="sm">{index + 1}</Text>
<TableTd ta="center">
<Text fz="sm" lh={1.5} truncate>
{index + 1}
</Text>
</TableTd>
<TableTd style={{ width: '20%' }}>
<Text truncate fz="sm">{item.judul}</Text>
<TableTd>
<Text fz="sm" lh={1.5} truncate>
{item.judul}
</Text>
</TableTd>
<TableTd style={{ width: '20%' }}>
<Text truncate fz="sm">{item.kategori.name}</Text>
<TableTd>
<Text fz="sm" lh={1.5} truncate>
{item.kategori.name}
</Text>
</TableTd>
<TableTd style={{ width: '20%' }}>
<Box w={150}>
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} lineClamp={1} truncate fz="sm" />
</Box>
<TableTd>
<Text
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
lineClamp={1}
fz="sm"
lh={1.5}
truncate
/>
</TableTd>
<TableTd style={{ width: '20%' }}>
<TableTd>
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${item.id}`)}
onClick={() =>
router.push(
`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${item.id}`
)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
<Text ml={5} fz="sm" fw={500}>
Detail
</Text>
</Button>
</TableTd>
</TableTr>
))
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text color="dimmed">Tidak ada data perpustakaan yang cocok</Text>
<TableTd colSpan={5}>
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.5}>
Tidak ada data perpustakaan yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -112,6 +183,80 @@ function ListDataPerpustakaan({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Judul
</Text>
<Text fz="sm" fw={500} lh={1.4} truncate>
{item.judul}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Kategori
</Text>
<Text fz="sm" fw={500} lh={1.4} truncate>
{item.kategori.name}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Deskripsi
</Text>
<Text
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
fz="sm"
fw={500}
lh={1.4}
lineClamp={2}
/>
</Box>
<Box>
<Button
variant="light"
color="blue"
fullWidth
onClick={() =>
router.push(
`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${item.id}`
)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5} fz="sm" fw={500}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.5}>
Tidak ada data perpustakaan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
<Center>
<Pagination
value={page}
@@ -128,7 +273,7 @@ function ListDataPerpustakaan({ search }: { search: string }) {
</Center>
</Paper>
</Box>
)
);
}
export default DataPerpustakaan;
export default DataPerpustakaan;

View File

@@ -75,7 +75,7 @@ function EditKategoriBuku() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -34,7 +34,7 @@ function CreateKategoriBuku() {
};
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -1,7 +1,7 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -31,6 +31,7 @@ function ListKategoriBuku({ search }: { search: string }) {
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
@@ -41,8 +42,8 @@ function ListKategoriBuku({ search }: { search: string }) {
} = stateKategori.findMany;
useShallowEffect(() => {
load(page, 10, search)
}, [page, search]);
load(page, 10, debouncedSearch)
}, [page, debouncedSearch]);
const handleDelete = () => {
if (selectedId) {
@@ -64,10 +65,12 @@ function ListKategoriBuku({ search }: { search: string }) {
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Kategori Buku</Title>
<Box py={{ base: 'sm', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4} lh={1.2}>
Daftar Kategori Buku
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -77,40 +80,62 @@ function ListKategoriBuku({ search }: { search: string }) {
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '10%' }}>No</TableTh>
<TableTh style={{ width: '60%' }}>Nama Kategori</TableTh>
<TableTh style={{ width: '15%' }}>Edit</TableTh>
<TableTh style={{ width: '15%' }}>Hapus</TableTh>
<TableTh style={{ width: '10%' }}>
<Text fz="xs" fw={600} lh={1.4} c="dimmed" ta="center">No</Text>
</TableTh>
<TableTh style={{ width: '60%' }}>
<Text fz="xs" fw={600} lh={1.4} c="dimmed">Nama Kategori</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="xs" fw={600} lh={1.4} c="dimmed" ta="center">Edit</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fz="xs" fw={600} lh={1.4} c="dimmed" ta="center">Hapus</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>{index + 1}</TableTd>
<TableTd>
<Text truncate fz="sm">{item.name}</Text>
<TableTd ta="center">
<Text fz="sm" fw={500} lh={1.5}>{index + 1}</Text>
</TableTd>
<TableTd>
<Button variant="light" color="green" onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/kategori-buku/${item.id}`)}>
<IconEdit size={20} />
<Text truncate fz="sm" fw={500} lh={1.5}>
{item.name}
</Text>
</TableTd>
<TableTd ta="center">
<Button variant="light" color="green" size="xs" onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/kategori-buku/${item.id}`)}>
<IconEdit size={16} />
</Button>
</TableTd>
<TableTd>
<TableTd ta="center">
<Button
variant="light"
color="red"
size="xs"
disabled={perpustakaanDigitalState.kategoriBuku.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}
>
<IconTrash size={20} />
<IconTrash size={16} />
</Button>
</TableTd>
</TableTr>
@@ -119,7 +144,9 @@ function ListKategoriBuku({ search }: { search: string }) {
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text c="dimmed">Tidak ada data kategori buku yang cocok</Text>
<Text c="dimmed" fz="sm" fw={500} lh={1.5}>
Tidak ada data kategori buku yang cocok
</Text>
</Center>
</TableTd>
</TableTr>
@@ -127,21 +154,74 @@ function ListKategoriBuku({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="md">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper key={item.id} withBorder radius="md" p="md" bg={colors['white-1']}>
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>No</Text>
<Text fz="sm" fw={500} lh={1.5}>{index + 1}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama Kategori</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.name}</Text>
</Box>
<Group justify="flex-end" mt="xs">
<Button
variant="light"
color="green"
size="xs"
onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/kategori-buku/${item.id}`)}
>
<IconEdit size={16} />
</Button>
<Button
variant="light"
color="red"
size="xs"
disabled={perpustakaanDigitalState.kategoriBuku.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}
>
<IconTrash size={16} />
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" fw={500} lh={1.5}>
Tidak ada data kategori buku yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<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>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt={{ base: 'sm', md: 'md' }}
mb={{ base: 'sm', md: 'md' }}
color="blue"
radius="md"
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
@@ -154,4 +234,4 @@ function ListKategoriBuku({ search }: { search: string }) {
)
}
export default KategoriBuku;
export default KategoriBuku;

View File

@@ -1,8 +1,29 @@
'use client'
import React from 'react';
import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -18,7 +18,7 @@ import {
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -47,10 +47,11 @@ function ListPeminjamBuku({ search }: { search: string }) {
const router = useRouter();
const { data, page, totalPages, loading, load } = statePeminjam.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000);
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const filteredData = data || [];
@@ -81,41 +82,61 @@ function ListPeminjamBuku({ search }: { search: string }) {
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Title order={4} mb="md">Daftar Peminjam Buku</Title>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
<Box py={{ base: 'sm', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Title order={4} lh={1.2} mb={{ base: 'md', md: 'lg' }}>
Daftar Peminjam Buku
</Title>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead>
<TableTr>
<TableTh style={{ width: '10%' }}>No</TableTh>
<TableTh style={{ width: '25%' }}>Nama Peminjam</TableTh>
<TableTh style={{ width: '25%' }}>Status</TableTh>
<TableTh style={{ width: '25%' }}>Alamat</TableTh>
<TableTh style={{ width: '25%' }}>Detail</TableTh>
<TableTh style={{ width: '8%' }}>No</TableTh>
<TableTh style={{ width: '28%' }}>Nama Peminjam</TableTh>
<TableTh style={{ width: '20%' }}>Status</TableTh>
<TableTh style={{ width: '28%' }}>Alamat</TableTh>
<TableTh style={{ width: '16%' }}>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>{index + 1}</TableTd>
<TableTd>
<Text truncate fz="sm">{item.nama}</Text>
<Text fz="sm" lh={1.5}>
{index + 1}
</Text>
</TableTd>
<TableTd>
{renderStatusBadge(item.status)}
<Text fz="sm" lh={1.5} truncate>
{item.nama}
</Text>
</TableTd>
<TableTd>{renderStatusBadge(item.status)}</TableTd>
<TableTd>
{item.alamat}
<Text fz="sm" lh={1.5} truncate>
{item.alamat}
</Text>
</TableTd>
<TableTd>
<Button
variant="light"
color="green"
onClick={() =>
router.push(`/admin/pendidikan/perpustakaan-digital/peminjam/${item.id}`)
router.push(
`/admin/pendidikan/perpustakaan-digital/peminjam/${item.id}`
)
}
radius="md"
>
<IconDeviceImacCog size={20} />
</Button>
@@ -124,9 +145,9 @@ function ListPeminjamBuku({ search }: { search: string }) {
))
) : (
<TableTr>
<TableTd colSpan={4}>
<Center py={20}>
<Text c="dimmed">
<TableTd colSpan={5}>
<Center py={32}>
<Text c="dimmed" fz="sm" lh={1.5}>
Tidak ada data Peminjam buku yang cocok
</Text>
</Center>
@@ -136,10 +157,84 @@ function ListPeminjamBuku({ search }: { search: string }) {
</TableTbody>
</Table>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="md">
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper
key={item.id}
withBorder
p="md"
radius="md"
bg={colors['white-1']}
>
<Stack gap="xs">
<Box>
<Text fz="xs" fw={600} lh={1.4}>
No
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Nama Peminjam
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.nama}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Status
</Text>
{renderStatusBadge(item.status)}
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Alamat
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.alamat}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>
Detail
</Text>
<Button
variant="light"
color="green"
onClick={() =>
router.push(
`/admin/pendidikan/perpustakaan-digital/peminjam/${item.id}`
)
}
mt="xs"
radius="md"
fullWidth
>
<IconDeviceImacCog size={20} />
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={32}>
<Text c="dimmed" fz="sm" lh={1.5}>
Tidak ada data Peminjam buku yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
{totalPages > 1 && (
<Center>
<Center mt={{ base: 'sm', md: 'lg' }}>
<Pagination
value={page}
onChange={(newPage) => {
@@ -153,9 +248,8 @@ function ListPeminjamBuku({ search }: { search: string }) {
radius="md"
/>
</Center>
)}
</Box>
);
}
export default PeminjamBuku;
export default PeminjamBuku;

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconSchool, IconTarget } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -57,35 +57,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg"
keepMounted={false}
>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel

View File

@@ -1,8 +1,29 @@
'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layoutTabs"
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Contoh path:
// - /darmasaba/desa/berita/semua → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan → panjang 5 → list
// - /darmasaba/desa/berita/Pemerintahan/123 → panjang 6 → detail
const segments = pathname.split('/').filter(Boolean);
const isDetailPage = segments.length >= 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box>
{children}
</Box>
);
}
return (
<LayoutTabs>
{children}

View File

@@ -125,7 +125,7 @@ function EditTujuanProgram() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
<Group mb="md">
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -29,28 +29,24 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Pratinjau Program Unggulan
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/program-pendidikan-anak/program-unggulan/edit'
)
}
>
Edit
</Button>
</GridCol>
</Grid>
<Group align="center">
<Title order={3} c={colors['blue-button']}>
Pratinjau Program Unggulan
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() =>
router.push(
'/admin/pendidikan/program-pendidikan-anak/program-unggulan/edit'
)
}
>
Edit
</Button>
</Group>
{/* Konten Preview */}
<Paper
@@ -75,13 +71,22 @@ function Page() {
{/* Deskripsi */}
<Text
fz={{ base: '1rem', md: '1.2rem' }}
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta="justify"
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>
</Paper>

View File

@@ -111,7 +111,7 @@ function EditTujuanProgram() {
}
return (
<Box>
<Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs">
{/* Back Button + Title */}
<Group mb="md">

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { Box, Button, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -29,14 +29,11 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md">
{/* Header */}
<Grid align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>
Pratinjau Tujuan Program
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button
<Group justify='space-between' align="center">
<Title order={3} c={colors['blue-button']}>
Pratinjau Tujuan Program
</Title>
<Button
c="green"
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
@@ -49,8 +46,7 @@ function Page() {
>
Edit
</Button>
</GridCol>
</Grid>
</Group>
{/* Konten Preview */}
<Paper
@@ -59,6 +55,7 @@ function Page() {
withBorder
radius="md"
shadow="xs"
visibleFrom='md'
>
<Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg">
@@ -85,6 +82,48 @@ function Page() {
</Stack>
</Box>
</Paper>
<Paper
p="xl"
bg={colors['white-1']}
withBorder
radius="md"
shadow="xs"
hiddenFrom='md'
>
<Box px={{ base: 'sm', md: 50 }} maw={900} mx="auto">
<Stack gap="lg">
{/* Judul */}
<Text
ta="center"
fz={{ base: '1.3rem', md: '1.8rem' }}
fw="bold"
c={colors['blue-button']}
dangerouslySetInnerHTML={{ __html: data.judul }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
<Divider my="md" color={colors['blue-button']} />
{/* Deskripsi */}
<Text
fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7}
ta={{ base: 'left', md: 'justify' }}
c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textWrap: 'pretty',
hyphens: 'auto',
'& p': {
marginBottom: '0.8em',
},
}}
/>
</Stack>
</Box>
</Paper>
</Stack>
</Paper>
);