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

@@ -68,7 +68,7 @@ const category = proxy({
const res = await ApiFetch.api.desa.kategoripengumuman[ const res = await ApiFetch.api.desa.kategoripengumuman[
"findMany" "findMany"
].get({ ].get({
query: { page, limit }, query: { page, limit, search },
}); });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {

View File

@@ -65,7 +65,7 @@ const potensiDesa = proxy({
const res = await ApiFetch.api.desa.potensi[ const res = await ApiFetch.api.desa.potensi[
"find-many" "find-many"
].get({ ].get({
query: { page, limit }, query: { page, limit, search },
}); });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Center, Divider, Grid, GridCol, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect } from 'react'; import { useEffect } from 'react';
@@ -31,11 +31,8 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header + tombol edit */} {/* Header + tombol edit */}
<Grid align="center"> <Group justify="space-between">
<GridCol span={{ base: 12, md: 11 }}> <Title order={2} c={colors['blue-button']} lh={1.2}>Profil Perbekel</Title>
<Title order={2} c={colors['blue-button']} lh={1.2} />
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -45,8 +42,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Card Profil */} {/* Card Profil */}
<Paper p="xl" bg={colors['white-1']} withBorder radius="md" shadow="xs"> <Paper p="xl" bg={colors['white-1']} withBorder radius="md" shadow="xs">
@@ -60,7 +56,7 @@ function Page() {
<GridCol span={12}> <GridCol span={12}>
<Text <Text
ta="center" ta="center"
fz={{ base: 'sm', md: 'md' }} fz={{ base: 'sm', md: 'xl' }}
fw="bold" fw="bold"
c={colors['blue-button']} c={colors['blue-button']}
lh={{ base: 1.45, md: 1.45 }} lh={{ base: 1.45, md: 1.45 }}

View File

@@ -183,6 +183,7 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
<Text fz={"sm"} fw={600} lh={1.4}> <Text fz={"sm"} fw={600} lh={1.4}>
Deskripsi Singkat Deskripsi Singkat
</Text> </Text>
<Box pl={5}>
<Text <Text
fz="sm" fz="sm"
fw={500} fw={500}
@@ -192,6 +193,7 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
}} }}
/> />
</Box> </Box>
</Box>
<Group justify="flex-end"> <Group justify="flex-end">
<Button <Button
size="xs" size="xs"

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title, Title,
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
@@ -50,6 +50,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
const router = useRouter(); const router = useRouter();
const { data, page, totalPages, loading, load } = stateKategori.findMany; const { data, page, totalPages, loading, load } = stateKategori.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const handleHapus = () => { const handleHapus = () => {
if (selectedId) { if (selectedId) {
@@ -60,8 +61,8 @@ function ListKategoriKegiatan({ search }: { search: string }) {
}; };
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
@@ -211,7 +212,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
<Box hiddenFrom="md">{renderMobileCards()}</Box> <Box hiddenFrom="md">{renderMobileCards()}</Box>
</Paper> </Paper>
{totalPages > 1 && (
<Center mt="xl"> <Center mt="xl">
<Pagination <Pagination
value={page} value={page}
@@ -224,7 +225,6 @@ function ListKategoriKegiatan({ search }: { search: string }) {
radius="md" radius="md"
/> />
</Center> </Center>
)}
<ModalKonfirmasiHapus <ModalKonfirmasiHapus
opened={modalHapus} opened={modalHapus}

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
@@ -29,10 +29,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
const router = useRouter(); const router = useRouter();
const listState = useProxy(korupsiState.desaAntikorupsi); const listState = useProxy(korupsiState.desaAntikorupsi);
const { data, page, totalPages, loading, load } = listState.findMany; const { data, page, totalPages, loading, load } = listState.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];

View File

@@ -50,7 +50,7 @@ function ListResponden({ search }: ListRespondenProps) {
const { data, page, totalPages, loading, load } = state.findMany; const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10); load(page, 10,);
}, [page]); }, [page]);
const filteredData = (data || []).filter((item) => { const filteredData = (data || []).filter((item) => {

View File

@@ -196,6 +196,7 @@ function ListMediaSosial({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md"> <Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={"xs"}>
<Box> <Box>
<Text fz="sm" fw={600} lh={1.4}>Nama Media Sosial / Kontak</Text> <Text fz="sm" fw={600} lh={1.4}>Nama Media Sosial / Kontak</Text>
<Text fw={500} fz="sm" lh={1.45}> <Text fw={500} fz="sm" lh={1.45}>
@@ -252,6 +253,7 @@ function ListMediaSosial({ search }: { search: string }) {
Detail Detail
</Button> </Button>
</Group> </Group>
</Stack>
</Paper> </Paper>
)) ))
) : ( ) : (

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile'; import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Center, Divider, Grid, GridCol, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -29,13 +29,10 @@ function Page() {
return ( return (
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
<Grid align="center"> <Group justify="space-between">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title> <Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
style={{fontSize: 15, fontWeight: "bold"}} style={{ fontSize: 15, fontWeight: "bold" }}
c="green" c="green"
variant="light" variant="light"
radius="md" radius="md"
@@ -43,8 +40,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{dataArray.map((item) => ( {dataArray.map((item) => (
<Paper key={item.id} p="xl" bg={'white'} withBorder radius="md" shadow="xs"> <Paper key={item.id} p="xl" bg={'white'} withBorder radius="md" shadow="xs">
<Box px={{ base: "sm", md: 100 }}> <Box px={{ base: "sm", md: 100 }}>

View File

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

View File

@@ -79,7 +79,7 @@ function DetailDataLingkunganDesa() {
const data = stateDataLingkungan.findUnique.data; const data = stateDataLingkungan.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Back Button */} {/* Back Button */}
<Button <Button
variant="subtle" variant="subtle"
@@ -93,7 +93,7 @@ function DetailDataLingkunganDesa() {
{/* Main Card */} {/* Main Card */}
<Paper <Paper
withBorder withBorder
w={{ base: '100%', md: '60%' }} w={{ base: '100%', md: '70%' }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -131,8 +131,10 @@ function DetailDataLingkunganDesa() {
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Text fz="lg" fw="bold">Deskripsi</Text>
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
</Box> </Box>
</Box>
{/* Action Buttons */} {/* Action Buttons */}
<Group gap="sm" mt="sm"> <Group gap="sm" mt="sm">

View File

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

View File

@@ -1,30 +1,65 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Box,
Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Button,
Title Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconAlertTriangle,
IconDeviceImacCog, IconDroplet, IconHome, IconHomeEco, IconLeaf, IconAmbulance,
IconBuilding,
IconCash,
IconChartLine,
IconChristmasTreeFilled,
IconClipboardTextFilled,
IconDeviceImacCog,
IconDroplet,
IconFiretruck,
IconHome,
IconHomeEco,
IconHospital,
IconLeaf,
IconPlus, IconPlus,
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent, IconRecycle,
IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled IconScale,
IconSchool,
IconSearch,
IconShieldFilled,
IconShoppingCart,
IconTent,
IconTrashFilled,
IconTree,
IconTrendingUp,
IconTrophy,
IconTruckFilled,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
import dataLingkunganDesaState from '../../_state/lingkungan/data-lingkungan-desa'; import dataLingkunganDesaState from '../../_state/lingkungan/data-lingkungan-desa';
import { useDebouncedValue } from '@mantine/hooks';
function DataLingkunganDesa() { function DataLingkunganDesa() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState('');
return ( return (
<Box> <Box py={{ base: 'md', md: 'lg' }} px={{ base: 'sm', md: 0 }}>
<HeaderSearch <HeaderSearch
title='Data Lingkungan Desa' title='Data Lingkungan Desa'
placeholder='Cari data lingkungan...' placeholder='Cari data lingkungan...'
@@ -38,15 +73,16 @@ function DataLingkunganDesa() {
} }
function ListDataLingkunganDesa({ search }: { search: string }) { function ListDataLingkunganDesa({ search }: { search: string }) {
const listState = useProxy(dataLingkunganDesaState) const listState = useProxy(dataLingkunganDesaState);
const { data, loading, page, totalPages, load } = listState.findMany const { data, loading, page, totalPages, load } = listState.findMany;
const router = useRouter(); const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
useEffect(() => { useEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || [] const filteredData = data || [];
const iconMap: Record<string, React.FC<any>> = { const iconMap: Record<string, React.FC<any>> = {
ekowisata: IconLeaf, ekowisata: IconLeaf,
@@ -64,12 +100,22 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
mencegahBencana: IconShieldFilled, mencegahBencana: IconShieldFilled,
rumah: IconHome, rumah: IconHome,
pohon: IconTree, pohon: IconTree,
air: IconDroplet air: IconDroplet,
bantuan: IconCash,
pelatihan: IconSchool,
subsidi: IconShoppingCart,
layananKesehatan: IconHospital,
polisi: IconShieldFilled,
ambulans: IconAmbulance,
pemadam: IconFiretruck,
rumahSakit: IconHospital,
bangunan: IconBuilding,
darurat: IconAlertTriangle
}; };
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="md">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
@@ -77,46 +123,66 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
if (data.length === 0) { if (data.length === 0) {
return ( return (
<Box py={10}> <Box py="md">
<Paper withBorder shadow="md" radius="md" p="lg" bg={colors['white-1']}> <Paper withBorder shadow="md" radius="md" p={{ base: 'md', md: 'lg' }} bg={colors['white-1']}>
<Stack> <Stack>
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Data Lingkungan Desa</Title> <Title order={4} lh={1.2}>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}> Daftar Data Lingkungan Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}
>
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Table highlightOnHover> <Box visibleFrom="md">
<Table highlightOnHover miw={0} style={{ tableLayout: 'fixed', width: '100%' }}>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>No</TableTh> <TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh>Nama Data Lingkungan Desa</TableTh> <TableTh style={{ width: '25%' }}>Nama Data Lingkungan Desa</TableTh>
<TableTh>Jumlah</TableTh> <TableTh style={{ width: '35%' }}>Jumlah</TableTh>
<TableTh>Ikon</TableTh> <TableTh style={{ width: '15%' }}>Ikon</TableTh>
<TableTh>Detail</TableTh> <TableTh style={{ width: '20%', textAlign: 'center' }}>Detail</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
</Table> </Table>
<Center py={20}> </Box>
<Text c="dimmed">Tidak ada data lingkungan desa yang tersedia</Text> <Center py="xl">
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.4}>
Tidak ada data lingkungan desa yang tersedia
</Text>
</Center> </Center>
</Stack> </Stack>
</Paper> </Paper>
</Box > </Box>
); );
} }
return ( return (
<Box py={10}> <Box py="md">
<Paper withBorder shadow="md" radius="md" bg={colors['white-1']} p="lg"> <Paper withBorder shadow="md" radius="md" p={{ base: 'md', md: 'lg' }} bg={colors['white-1']}>
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Data Lingkungan Desa</Title> <Title order={4} lh={1.2}>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}> Daftar Data Lingkungan Desa
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}
>
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover> {/* Desktop Table */}
<Box visibleFrom="md">
<Table highlightOnHover miw={0} style={{ tableLayout: 'fixed', width: '100%' }}>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh> <TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
@@ -129,21 +195,21 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
<TableTbody> <TableTbody>
{filteredData.map((item, index) => ( {filteredData.map((item, index) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd> <TableTd ta="center">{index + 1}</TableTd>
<TableTd> <TableTd>
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Text fz="sm" c="dimmed">± {item.jumlah}</Text> <Text fz="sm" c="dimmed" lh={1.5}>
± {item.jumlah}
</Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
{iconMap[item.icon] && ( {iconMap[item.icon] && <Box title={item.icon}>{React.createElement(iconMap[item.icon], { size: 22 })}</Box>}
<Box title={item.icon}>
{React.createElement(iconMap[item.icon], { size: 22 })}
</Box>
)}
</TableTd> </TableTd>
<TableTd style={{ textAlign: 'center' }}> <TableTd ta="center">
<Button <Button
size="xs" size="xs"
radius="md" radius="md"
@@ -160,7 +226,62 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.map((item, index) => (
<Paper key={item.id} withBorder radius="md" p="sm" 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.4}>
{index + 1}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Data
</Text>
<Text fz="sm" fw={500} lh={1.4} truncate="end">
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Jumlah
</Text>
<Text fz="sm" fw={500} lh={1.4}>
± {item.jumlah}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Ikon
</Text>
{iconMap[item.icon] && <Box title={item.icon}>{React.createElement(iconMap[item.icon], { size: 22 })}</Box>}
</Box>
<Center>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${item.id}`)}
>
Detail
</Button>
</Center>
</Stack>
</Paper> </Paper>
))}
</Stack>
</Box>
</Paper>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
@@ -178,4 +299,5 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
</Box> </Box>
); );
} }
export default DataLingkunganDesa; export default DataLingkunganDesa;

View File

@@ -59,6 +59,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -69,7 +70,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
display: "flex", display: "flex",
flexWrap: "nowrap", flexWrap: "nowrap",
gap: "0.5rem", gap: "0.5rem",
paddingInline: "0.5rem", paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}} }}
> >
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (
@@ -80,22 +81,54 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
style={{ style={{
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
whiteSpace: "nowrap",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
<span style={{
display: "inline-block",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
}}>
{tab.label} {tab.label}
</span>
</TabsTab> </TabsTab>
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel key={i} value={tab.value}> <TabsPanel key={i} value={tab.value}>

View File

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

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -25,13 +25,10 @@ function Page() {
return ( return (
<Box p="md"> <Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md"> <Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}> <Group justify="space-between" align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}> <Title order={3} fw={600}>
Preview Contoh Kegiatan Di Desa Darmasaba Preview Contoh Kegiatan Di Desa Darmasaba
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@@ -46,8 +43,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
<Stack gap="md"> <Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md"> <Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,7 +1,28 @@
'use client' 'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layouTabs"; import LayoutTabs from "./_lib/layouTabs";
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

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

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -25,11 +25,8 @@ function Page() {
return ( return (
<Box p="md"> <Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md"> <Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}> <Group align="center" justify='space-between' mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}>Preview Materi Edukasi Yang Diberikan</Title> <Title order={3} fw={600}>Preview Materi Edukasi Yang Diberikan</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@@ -42,8 +39,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
<Stack gap="md"> <Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md"> <Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

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

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -25,11 +25,8 @@ function Page() {
return ( return (
<Box p="md"> <Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md"> <Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}> <Group align="center" justify='space-between' mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}>Preview Tujuan Edukasi Lingkungan</Title> <Title order={3} fw={600}>Preview Tujuan Edukasi Lingkungan</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@@ -42,8 +39,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
<Stack gap="md"> <Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md"> <Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; 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 { IconClipboardList, IconTags } from '@tabler/icons-react'; import { IconClipboardList, IconTags } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -59,6 +59,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false} keepMounted={false}
> >
{/* ✅ Scroll horizontal */} {/* ✅ Scroll horizontal */}
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -69,7 +70,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
display: "flex", display: "flex",
flexWrap: "nowrap", flexWrap: "nowrap",
gap: "0.5rem", gap: "0.5rem",
paddingInline: "0.5rem", paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}} }}
> >
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (
@@ -81,6 +82,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -88,6 +90,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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>
{/* ✅ Panel dengan gaya kartu */} {/* ✅ Panel dengan gaya kartu */}
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
@@ -28,7 +28,7 @@ import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong'; import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
function KategoriKegiatan() { function KategoriKegiatan() {
const [search, setSearch] = useState("") const [search, setSearch] = useState("");
return ( return (
<Box> <Box>
<HeaderSearch <HeaderSearch
@@ -44,10 +44,11 @@ function KategoriKegiatan() {
} }
function ListKategoriKegiatan({ search }: { search: string }) { function ListKategoriKegiatan({ search }: { search: string }) {
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan) const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan);
const [modalHapus, setModalHapus] = useState(false) const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null) const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter() const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { const {
data, data,
@@ -55,52 +56,76 @@ function ListKategoriKegiatan({ search }: { search: string }) {
totalPages, totalPages,
loading, loading,
load, load,
} = stateKategori.findMany } = stateKategori.findMany;
const handleHapus = () => { const handleHapus = () => {
if (selectedId) { if (selectedId) {
stateKategori.delete.byId(selectedId) stateKategori.delete.byId(selectedId);
setModalHapus(false) setModalHapus(false);
setSelectedId(null) setSelectedId(null);
}
} }
};
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'md', md: 'lg' }}>
<Skeleton height={500} radius="md" /> <Skeleton height={500} radius="md" />
</Stack> </Stack>
) );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Kategori Kegiatan</Title> <Title order={4} lh={1.2}>
Daftar Kategori Kegiatan
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
variant="light" variant="light"
onClick={() => router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan/create')} onClick={() =>
router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan/create')
}
> >
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}> {/* Desktop Table */}
<Table highlightOnHover> <Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '60%' }}>Nama Kategori</TableTh> <TableTh style={{ width: '60%' }}>
<TableTh style={{ width: '20%' }}>Edit</TableTh> <Text fz="sm" fw={600} lh={1.2} ta="left">
<TableTh style={{ width: '20%' }}>Delete</TableTh> Nama Kategori
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.2} ta="center">
Edit
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.2} ta="center">
Delete
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -108,21 +133,27 @@ function ListKategoriKegiatan({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <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>
<TableTd> <TableTd ta="center">
<Button <Button
size="xs" size="xs"
radius="md" radius="md"
variant="light" variant="light"
color="green" color="green"
leftSection={<IconEdit size={16} />} leftSection={<IconEdit size={16} />}
onClick={() => router.push(`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`)} onClick={() =>
router.push(
`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`
)
}
> >
Edit Edit
</Button> </Button>
</TableTd> </TableTd>
<TableTd> <TableTd ta="center">
<Button <Button
size="xs" size="xs"
radius="md" radius="md"
@@ -130,8 +161,8 @@ function ListKategoriKegiatan({ search }: { search: string }) {
color="red" color="red"
leftSection={<IconTrash size={16} />} leftSection={<IconTrash size={16} />}
onClick={() => { onClick={() => {
setSelectedId(item.id) setSelectedId(item.id);
setModalHapus(true) setModalHapus(true);
}} }}
> >
Hapus Hapus
@@ -142,8 +173,10 @@ function ListKategoriKegiatan({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={3}> <TableTd colSpan={3}>
<Center py={20}> <Center py="xl">
<Text c="dimmed">Tidak ada kategori kegiatan yang cocok</Text> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada kategori kegiatan yang cocok
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -151,6 +184,63 @@ function ListKategoriKegiatan({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Kategori
</Text>
<Text fz="sm" fw={500} lh={1.45}>
{item.nama}
</Text>
</Box>
<Group justify="flex-end" mt="xs">
<Button
size="xs"
radius="md"
variant="light"
color="green"
leftSection={<IconEdit size={16} />}
onClick={() =>
router.push(
`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`
)
}
>
Edit
</Button>
<Button
size="xs"
radius="md"
variant="light"
color="red"
leftSection={<IconTrash size={16} />}
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
Hapus
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada kategori kegiatan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper> </Paper>
<Center> <Center>

View File

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

View File

@@ -41,7 +41,7 @@ function DetailKegiatanDesa() {
const data = kegiatanDesaState.findUnique.data; const data = kegiatanDesaState.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Kembali */} {/* Tombol Kembali */}
<Button <Button
variant="subtle" variant="subtle"
@@ -55,7 +55,7 @@ function DetailKegiatanDesa() {
{/* Container Detail */} {/* Container Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "60%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -85,14 +85,18 @@ function DetailKegiatanDesa() {
{/* Deskripsi Singkat */} {/* Deskripsi Singkat */}
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi Singkat</Text> <Text fz="lg" fw="bold">Deskripsi Singkat</Text>
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat || '-' }} />
</Box> </Box>
</Box>
{/* Deskripsi Lengkap */} {/* Deskripsi Lengkap */}
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Text fz="lg" fw="bold">Deskripsi</Text>
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} />
</Box> </Box>
</Box>
{/* Kategori */} {/* Kategori */}
<Box> <Box>

View File

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

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title, Title,
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
@@ -45,6 +45,7 @@ function KegiatanDesa() {
function ListKegiatanDesa({ search }: { search: string }) { function ListKegiatanDesa({ search }: { search: string }) {
const listState = useProxy(gotongRoyongState.kegiatanDesa); const listState = useProxy(gotongRoyongState.kegiatanDesa);
const router = useRouter(); const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { const {
data, data,
@@ -55,24 +56,26 @@ function ListKegiatanDesa({ search }: { search: string }) {
} = listState.findMany; } = listState.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Kegiatan Desa</Title> <Title order={4} lh={1.2}>
Daftar Kegiatan Desa
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -84,14 +87,39 @@ function ListKegiatanDesa({ search }: { search: string }) {
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover> {/* Desktop: Table */}
<Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '30%' }}>Judul</TableTh> <TableTh style={{ width: '30%' }}>
<TableTh style={{ width: '25%' }}>Kategori</TableTh> <Text fz="sm" fw={600} lh={1.4} c="dark">
<TableTh style={{ width: '25%' }}>Lokasi</TableTh> Judul
<TableTh style={{ width: '15%' }}>Aksi</TableTh> </Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Kategori
</Text>
</TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Lokasi
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Aksi
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -99,17 +127,36 @@ function ListKegiatanDesa({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Text fw={500} truncate="end" lineClamp={1}> <Text
fz="md"
fw={500}
lh={1.5}
truncate="end"
lineClamp={1}
c="dark"
>
{item.judul} {item.judul}
</Text> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Text fz="sm" c="dimmed"> <Text
fz="sm"
fw={500}
lh={1.5}
c="dark"
>
{item.kategoriKegiatan?.nama || '-'} {item.kategoriKegiatan?.nama || '-'}
</Text> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Text fz="sm">{item.lokasi}</Text> <Text
fz="sm"
fw={500}
lh={1.5}
c="dark"
>
{item.lokasi}
</Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -133,7 +180,7 @@ function ListKegiatanDesa({ search }: { search: string }) {
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py={20}>
<Text c="dimmed"> <Text fz="sm" c="dimmed" lh={1.4}>
Tidak ada kegiatan desa yang cocok dengan pencarian Tidak ada kegiatan desa yang cocok dengan pencarian
</Text> </Text>
</Center> </Center>
@@ -143,6 +190,64 @@ function ListKegiatanDesa({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile: Card List */}
<Stack hiddenFrom="md" 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} c="dark">
Judul
</Text>
<Text fz="sm" fw={500} lh={1.4} c="dark" truncate="end">
{item.judul}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Kategori
</Text>
<Text fz="sm" fw={500} lh={1.4} c="dark">
{item.kategoriKegiatan?.nama || '-'}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4} c="dark">
Lokasi
</Text>
<Text fz="sm" fw={500} lh={1.4} c="dark">
{item.lokasi}
</Text>
</Box>
<Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(
`/admin/lingkungan/gotong-royong/kegiatan-desa/${item.id}`
)
}
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text fz="sm" c="dimmed" lh={1.4}>
Tidak ada kegiatan desa yang cocok dengan pencarian
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
<Center> <Center>
<Pagination <Pagination

View File

@@ -1,6 +1,28 @@
'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layoutTabs"; import LayoutTabs from "./_lib/layoutTabs";
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

@@ -59,6 +59,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -69,7 +70,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
display: "flex", display: "flex",
flexWrap: "nowrap", flexWrap: "nowrap",
gap: "0.5rem", gap: "0.5rem",
paddingInline: "0.5rem", paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}} }}
> >
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (
@@ -80,22 +81,54 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
style={{ style={{
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
whiteSpace: "nowrap",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
<span style={{
display: "inline-block",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
}}>
{tab.label} {tab.label}
</span>
</TabsTab> </TabsTab>
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel key={i} value={tab.value}> <TabsPanel key={i} value={tab.value}>

View File

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

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -28,13 +28,10 @@ function Page() {
<Box p="md"> <Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md"> <Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
{/* Header */} {/* Header */}
<Grid align="center" mb={{ base: 'md', md: 'lg' }}> <Group justify='space-between' align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600} c="dark"> <Title order={3} fw={600} c="dark">
Preview Bentuk Konservasi Berdasarkan Adat Preview Bentuk Konservasi Berdasarkan Adat
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@@ -49,8 +46,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten */} {/* Konten */}
<Stack gap="md"> <Stack gap="md">

View File

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

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -25,13 +25,10 @@ function Page() {
return ( return (
<Box p="md"> <Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md"> <Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}> <Group justify="space-between" align="center" mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}> <Title order={3} fw={600}>
Preview Filosofi Tri Hita Karana Preview Filosofi Tri Hita Karana
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@@ -46,8 +43,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
<Stack gap="md"> <Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md"> <Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

@@ -1,8 +1,29 @@
'use client' 'use client'
import { usePathname } from "next/navigation";
import LayoutTabs from "./_lib/layoutTabs" import LayoutTabs from "./_lib/layoutTabs"
import { Box } from "@mantine/core";
export default function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

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

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -25,13 +25,10 @@ function Page() {
return ( return (
<Box p="md"> <Box p="md">
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md"> <Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
<Grid align="center" mb={{ base: 'md', md: 'lg' }}> <Group align="center" justify='space-between' mb={{ base: 'md', md: 'lg' }}>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} fw={600}> <Title order={3} fw={600}>
Preview Nilai Konservasi Adat Preview Nilai Konservasi Adat
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@@ -44,8 +41,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
<Stack gap="md"> <Stack gap="md">
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md"> <Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">

View File

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

View File

@@ -43,7 +43,7 @@ function DetailKeteranganBankSampahTerdekat() {
} }
return ( return (
<Box> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Box mb="md"> <Box mb="md">
<Button <Button
variant="light" variant="light"
@@ -57,7 +57,7 @@ function DetailKeteranganBankSampahTerdekat() {
</Box> </Box>
<Paper <Paper
w={{ base: "100%", md: "60%" }} w={{ base: "100%", md: "70%" }}
p="xl" p="xl"
radius="lg" radius="lg"
withBorder withBorder

View File

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

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import colors from '@/con/colors'; 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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react'; import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
@@ -28,6 +28,7 @@ function KeteranganBankSampahTerdekat() {
function ListKeteranganBankSampahTerdekat({ search }: { search: string }) { function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah); const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah);
const router = useRouter(); const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { const {
data, data,
@@ -38,24 +39,26 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
} = keteranganState.findMany; } = keteranganState.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Bank Sampah Terdekat</Title> <Title order={4} lh={1.2}>
Daftar Bank Sampah Terdekat
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -66,14 +69,22 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}> {/* Desktop Table */}
<Table highlightOnHover> <Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama Bank Sampah</TableTh> <TableTh style={{ width: '30%' }}>Nama Bank Sampah</TableTh>
<TableTh>Alamat</TableTh> <TableTh style={{ width: '35%' }}>Alamat</TableTh>
<TableTh>Nama Tempat di Maps</TableTh> <TableTh style={{ width: '25%' }}>Nama Tempat di Maps</TableTh>
<TableTh>Aksi</TableTh> <TableTh style={{ width: '10%' }}>Aksi</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -81,17 +92,17 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Text fw={500}>{item.name}</Text> <Text fz="md" fw={500} lh={1.5}>
{item.name}
</Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={200}> <Text fz="sm" lh={1.5} lineClamp={2}>
<Text lineClamp={2} truncate="end" fz="sm">
{item.alamat || '-'} {item.alamat || '-'}
</Text> </Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Text fz="sm"> <Text fz="sm" lh={1.5}>
{item.namaTempatMaps || '-'} {item.namaTempatMaps || '-'}
</Text> </Text>
</TableTd> </TableTd>
@@ -100,6 +111,7 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
variant="light" variant="light"
color="blue" color="blue"
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)} onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)}
p="xs"
> >
<IconDeviceImac size={20} /> <IconDeviceImac size={20} />
</Button> </Button>
@@ -108,8 +120,10 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4} align="center" py="xl"> <TableTd colSpan={4} ta="center" py="xl">
<Text c="dimmed">Tidak ada data bank sampah terdekat</Text> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data bank sampah terdekat
</Text>
</TableTd> </TableTd>
</TableTr> </TableTr>
)} )}
@@ -117,8 +131,60 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
</Table> </Table>
</Box> </Box>
{totalPages > 1 && ( {/* Mobile Card */}
<Center mt="xl"> <Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} p="sm" radius="sm" withBorder>
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Bank Sampah
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Alamat
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.alamat || '-'}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Tempat di Maps
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.namaTempatMaps || '-'}
</Text>
</Box>
<Group justify="flex-end" mt="xs">
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)}
p="xs"
>
<IconDeviceImac size={20} />
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data bank sampah terdekat
</Text>
</Center>
)}
</Stack>
</Box>
<Center mt={{ base: 'lg', md: 'xl' }}>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => load(newPage)} onChange={(newPage) => load(newPage)}
@@ -128,7 +194,6 @@ function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
withEdges withEdges
/> />
</Center> </Center>
)}
</Paper> </Paper>
</Box> </Box>
); );

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
'use client' 'use client'
import colors from '@/con/colors'; 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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconChartLine, IconClipboardTextFilled, IconEdit, IconLeaf, IconPlus, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react'; import { IconAlertTriangle, IconAmbulance, IconBuilding, IconCash, IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDroplet, IconEdit, IconFiretruck, IconHome, IconHomeEco, IconHospital, IconLeaf, IconPlus, IconRecycle, IconScale, IconSchool, IconSearch, IconShieldFilled, IconShoppingCart, IconTent, IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -32,6 +32,7 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
const [modalHapus, setModalHapus] = useState(false) const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null) const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter() const router = useRouter()
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const { const {
data, data,
@@ -42,8 +43,8 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
} = stateList.findMany } = stateList.findMany
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch)
}, [page, search]) }, [page, debouncedSearch])
const handleHapus = () => { const handleHapus = () => {
@@ -66,6 +67,23 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
scale: IconScale, scale: IconScale,
clipboard: IconClipboardTextFilled, clipboard: IconClipboardTextFilled,
trash: IconTrashFilled, trash: IconTrashFilled,
lingkunganSehat: IconHomeEco,
sumberOksigen: IconChristmasTreeFilled,
ekonomiBerkelanjutan: IconTrendingUp,
mencegahBencana: IconShieldFilled,
rumah: IconHome,
pohon: IconTree,
air: IconDroplet,
bantuan: IconCash,
pelatihan: IconSchool,
subsidi: IconShoppingCart,
layananKesehatan: IconHospital,
polisi: IconShieldFilled,
ambulans: IconAmbulance,
pemadam: IconFiretruck,
rumahSakit: IconHospital,
bangunan: IconBuilding,
darurat: IconAlertTriangle,
}; };
if (loading || !data) { if (loading || !data) {
@@ -77,28 +95,45 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'lg' }}>
<Paper bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
<Title order={4}>Daftar Pengelolaan Sampah Bank Sampah</Title> <Title
order={4}
lh={{ base: 1.2, md: 1.15 }}
>
Daftar Pengelolaan Sampah Bank Sampah
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
variant="light" variant="light"
onClick={() => router.push('/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create')} onClick={() =>
router.push(
'/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create'
)
}
> >
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}> {/* Desktop Table */}
<Table highlightOnHover> <Box visibleFrom="md">
<Table
highlightOnHover
miw={0}
style={{
tableLayout: 'fixed',
width: '100%',
}}
>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama Pengelolaan Sampah</TableTh> <TableTh style={{ width: '50%' }}>Nama Pengelolaan Sampah</TableTh>
<TableTh>Icon</TableTh> <TableTh style={{ width: '20%' }}>Icon</TableTh>
<TableTh>Edit</TableTh> <TableTh style={{ width: '15%' }}>Edit</TableTh>
<TableTh>Delete</TableTh> <TableTh style={{ width: '15%' }}>Delete</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -106,18 +141,22 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Text lineClamp={1} truncate="end" fw={500}>{item.name}</Text> <Text fz="md" fw={500} lh={1.5} lineClamp={1} truncate="end">
{item.name}
</Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
{iconMap[item.icon] ? ( {iconMap[item.icon] ? (
<Box> <Box>
{React.createElement(iconMap[item.icon], { {React.createElement(iconMap[item.icon], {
size: 24, size: 24,
style: { color: colors['blue-button'] } style: { color: colors['blue-button'] },
})} })}
</Box> </Box>
) : ( ) : (
<Text c="dimmed" fz="sm">-</Text> <Text c="dimmed" fz="sm">
-
</Text>
)} )}
</TableTd> </TableTd>
<TableTd> <TableTd>
@@ -125,7 +164,11 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
variant="light" variant="light"
color="green" color="green"
size="sm" size="sm"
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/${item.id}`)} onClick={() =>
router.push(
`/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/${item.id}`
)
}
> >
<IconEdit size={18} /> <IconEdit size={18} />
</Button> </Button>
@@ -147,8 +190,10 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={3} align="center" py="xl"> <TableTd colSpan={4} ta="center" py="xl">
<Text c="dimmed">Tidak ada data pengelolaan sampah</Text> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data pengelolaan sampah
</Text>
</TableTd> </TableTd>
</TableTr> </TableTr>
)} )}
@@ -156,8 +201,73 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
</Table> </Table>
</Box> </Box>
{totalPages > 1 && ( {/* Mobile Cards */}
<Center mt="xl"> <Box hiddenFrom="md">
{filteredData.length > 0 ? (
<Stack gap="md">
{filteredData.map((item) => (
<Paper key={item.id} p="sm" withBorder>
<Stack gap={"xs"}>
<Text fz="sm" fw={600} lh={1.4}>
Nama Pengelolaan Sampah
</Text>
<Text fz="sm" fw={500} lh={1.45} lineClamp={1}>
{item.name}
</Text>
{iconMap[item.icon] ? (
React.createElement(iconMap[item.icon], {
size: 20,
style: { color: colors['blue-button'] },
})
) : (
<Text c="dimmed" fz="sm">
-
</Text>
)}
<Group justify='right'>
<Button
variant="light"
color="green"
size="xs"
onClick={() =>
router.push(
`/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/${item.id}`
)
}
radius="sm"
p={4}
>
<IconEdit size={18} />
</Button>
<Button
variant="light"
color="red"
size="xs"
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
radius="sm"
p={4}
>
<IconTrashFilled size={18} />
</Button>
</Group>
</Stack>
</Paper>
))}
</Stack>
) : (
<Center py="xl">
<Text c="dimmed" fz="xs" lh={1.4}>
Tidak ada data pengelolaan sampah
</Text>
</Center>
)}
</Box>
<Center mt={{ base: 'lg', md: 'xl' }}>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => load(newPage)} onChange={(newPage) => load(newPage)}
@@ -167,8 +277,6 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
withEdges withEdges
/> />
</Center> </Center>
)}
</Paper> </Paper>
{/* Modal Konfirmasi Hapus */} {/* Modal Konfirmasi Hapus */}

View File

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

View File

@@ -79,7 +79,7 @@ function DetailProgramPenghijauan() {
const data = stateProgramPenghijauan.findUnique.data const data = stateProgramPenghijauan.findUnique.data
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */} {/* Tombol kembali */}
<Button <Button
variant="subtle" variant="subtle"
@@ -93,7 +93,7 @@ function DetailProgramPenghijauan() {
{/* Konten detail */} {/* Konten detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "60%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -129,8 +129,10 @@ function DetailProgramPenghijauan() {
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Text fz="lg" fw="bold">Deskripsi</Text>
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
</Box> </Box>
</Box>
{/* Tombol aksi */} {/* Tombol aksi */}
<Group gap="sm" mt="md"> <Group gap="sm" mt="md">

View File

@@ -49,7 +49,7 @@ function CreateProgramPenghijauan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol back + title */} {/* Tombol back + title */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -6,6 +6,7 @@ import {
Box, Box,
Button, Button,
Center, Center,
Group,
Pagination, Pagination,
Paper, Paper,
Skeleton, Skeleton,
@@ -17,22 +18,33 @@ import {
TableThead, TableThead,
TableTr, TableTr,
Text, Text,
Title Title,
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconAlertTriangle,
IconAmbulance,
IconBuilding,
IconCash,
IconChartLine, IconChartLine,
IconChristmasTreeFilled, IconChristmasTreeFilled,
IconClipboardTextFilled, IconClipboardTextFilled,
IconDeviceImac, IconDeviceImac,
IconDroplet,
IconFiretruck,
IconHome,
IconHomeEco, IconHomeEco,
IconHospital,
IconLeaf, IconLeaf,
IconPlus, IconPlus,
IconRecycle, IconRecycle,
IconScale, IconScale,
IconSchool,
IconSearch, IconSearch,
IconShieldFilled, IconShieldFilled,
IconShoppingCart,
IconTent, IconTent,
IconTrashFilled, IconTrashFilled,
IconTree,
IconTrendingUp, IconTrendingUp,
IconTrophy, IconTrophy,
IconTruckFilled, IconTruckFilled,
@@ -42,9 +54,10 @@ import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
import programPenghijauanState from '../../_state/lingkungan/program-penghijauan'; import programPenghijauanState from '../../_state/lingkungan/program-penghijauan';
import { useDebouncedValue } from '@mantine/hooks';
function ProgramPenghijauan() { function ProgramPenghijauan() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState('');
return ( return (
<Box> <Box>
<HeaderSearch <HeaderSearch
@@ -60,15 +73,16 @@ function ProgramPenghijauan() {
} }
function ListProgramPenghijauan({ search }: { search: string }) { function ListProgramPenghijauan({ search }: { search: string }) {
const listState = useProxy(programPenghijauanState) const listState = useProxy(programPenghijauanState);
const { data, loading, page, totalPages, load } = listState.findMany const { data, loading, page, totalPages, load } = listState.findMany;
const router = useRouter(); const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
useEffect(() => { useEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || [] const filteredData = data || [];
const iconMap: Record<string, React.FC<any>> = { const iconMap: Record<string, React.FC<any>> = {
ekowisata: IconLeaf, ekowisata: IconLeaf,
@@ -84,70 +98,134 @@ function ListProgramPenghijauan({ search }: { search: string }) {
sumberOksigen: IconChristmasTreeFilled, sumberOksigen: IconChristmasTreeFilled,
ekonomiBerkelanjutan: IconTrendingUp, ekonomiBerkelanjutan: IconTrendingUp,
mencegahBencana: IconShieldFilled, mencegahBencana: IconShieldFilled,
rumah: IconHome,
pohon: IconTree,
air: IconDroplet,
bantuan: IconCash,
pelatihan: IconSchool,
subsidi: IconShoppingCart,
layananKesehatan: IconHospital,
polisi: IconShieldFilled,
ambulans: IconAmbulance,
pemadam: IconFiretruck,
rumahSakit: IconHospital,
bangunan: IconBuilding,
darurat: IconAlertTriangle,
}; };
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="md">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py="md">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
{/* Header Section */} {/* Header Section */}
<Box mb="md" display="flex" style={{ justifyContent: 'space-between', alignItems: 'center' }}> <Box mb="lg" visibleFrom="md">
<Title order={4}>Daftar Program Penghijauan</Title> <Group
justify="space-between"
align="center"
mb="md"
>
<Title order={4} size="md" lh={1.2}>
Daftar Program Penghijauan
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
variant="light" variant="light"
onClick={() => router.push('/admin/lingkungan/program-penghijauan/create')} onClick={() =>
router.push('/admin/lingkungan/program-penghijauan/create')
}
> >
Tambah Baru Tambah Baru
</Button> </Button>
</Box> </Group>
{/* Table Section */} {/* Desktop Table */}
<Box style={{ overflowX: 'auto' }}> <Box miw={0}>
<Table highlightOnHover> <Table
highlightOnHover
miw={0}
style={{ tableLayout: 'fixed', width: '100%' }}
>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh> <TableTh style={{ width: '5%', textAlign: 'center' }}>
<TableTh style={{ width: '25%' }}>Nama Program</TableTh> <Text fz="xs" fw={600} lh={1.4} ta="center">
<TableTh style={{ width: '35%' }}>Judul / Deskripsi</TableTh> No
<TableTh style={{ width: '15%', textAlign: 'center' }}>Ikon</TableTh> </Text>
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh> </TableTh>
<TableTh style={{ width: '25%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Nama Program
</Text>
</TableTh>
<TableTh style={{ width: '35%' }}>
<Text fz="xs" fw={600} lh={1.4}>
Judul / Deskripsi
</Text>
</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>
<Text fz="xs" fw={600} lh={1.4} ta="center">
Ikon
</Text>
</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>
<Text fz="xs" fw={600} lh={1.4} ta="center">
Aksi
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item, index) => ( filteredData.map((item, index) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd> <TableTd ta="center">
<TableTd> <Text fz="sm" fw={500} lh={1.5}>
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text> {index + 1}
</Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.judul }} /> <Text fz="sm" fw={500} lh={1.5} truncate="end">
{item.name}
</Text>
</TableTd> </TableTd>
<TableTd style={{ textAlign: 'center' }}> <TableTd>
<Text
fz="sm"
fw={500}
lh={1.5}
lineClamp={1}
dangerouslySetInnerHTML={{ __html: item.judul }}
/>
</TableTd>
<TableTd ta="center">
{iconMap[item.icon] && ( {iconMap[item.icon] && (
<Box title={item.icon} mx="auto"> <Box mx="auto" title={item.icon}>
{React.createElement(iconMap[item.icon], { size: 22 })} {React.createElement(iconMap[item.icon], {
size: 22,
})}
</Box> </Box>
)} )}
</TableTd> </TableTd>
<TableTd style={{ textAlign: 'center' }}> <TableTd ta="center">
<Button <Button
size="xs" size="xs"
radius="md" radius="md"
variant="light" variant="light"
color="blue" color="blue"
leftSection={<IconDeviceImac size={16} />} leftSection={<IconDeviceImac size={16} />}
onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${item.id}`)} onClick={() =>
router.push(
`/admin/lingkungan/program-penghijauan/${item.id}`
)
}
> >
Detail Detail
</Button> </Button>
@@ -157,8 +235,10 @@ function ListProgramPenghijauan({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={5}> <TableTd colSpan={5}>
<Center py={20}> <Center py="xl">
<Text c="dimmed">Tidak ada data program penghijauan yang cocok</Text> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data program penghijauan yang cocok
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -166,6 +246,97 @@ function ListProgramPenghijauan({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
</Box>
{/* Mobile View Card Layout */}
<Box hiddenFrom="md">
<Box mb="md">
<Title order={4} size="sm" lh={1.2}>
Daftar Program Penghijauan
</Title>
</Box>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
mb="lg"
fullWidth
onClick={() =>
router.push('/admin/lingkungan/program-penghijauan/create')
}
>
Tambah Baru
</Button>
{filteredData.length > 0 ? (
filteredData.map((item, index) => (
<Paper key={item.id} withBorder p="md" mb="sm" radius="md">
<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 Program
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Judul / Deskripsi
</Text>
<Text
fz="sm"
fw={500}
lh={1.5}
dangerouslySetInnerHTML={{ __html: item.judul }}
/>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Ikon
</Text>
<Box mt="xs" ta="center">
{iconMap[item.icon] && (
React.createElement(iconMap[item.icon], { size: 24 })
)}
</Box>
</Box>
<Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/lingkungan/program-penghijauan/${item.id}`
)
}
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data program penghijauan yang cocok
</Text>
</Center>
)}
</Box>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; 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 { IconSchool, IconStar } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -56,6 +56,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -78,6 +79,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -85,6 +87,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,29 @@
'use client' 'use client'
import React from 'react'; import React from 'react';
import LayoutTabs from './_lib/layoutTabs'; import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; 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 { IconBuildingCommunity, IconCalendar, IconSchool } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -62,6 +62,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -84,6 +85,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -91,6 +93,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel

View File

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

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import colors from '@/con/colors'; 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 { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -29,13 +29,10 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header */}
<Grid align="center"> <Group justify='space-between' align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Pratinjau Fasilitas Yang Disediakan Pratinjau Fasilitas Yang Disediakan
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -49,8 +46,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten Preview */} {/* Konten Preview */}
<Paper <Paper
@@ -59,6 +55,7 @@ function Page() {
withBorder withBorder
radius="md" radius="md"
shadow="xs" shadow="xs"
visibleFrom='md'
> >
<Box px={{ base: 'sm', md: 50 }}> <Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg"> <Stack gap="lg">
@@ -84,6 +81,47 @@ function Page() {
</Stack> </Stack>
</Box> </Box>
</Paper> </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> </Stack>
</Paper> </Paper>
); );

View File

@@ -1,7 +1,29 @@
'use client'
import React from 'react'; import React from 'react';
import LayoutTabs from './_lib/layoutTabs'; import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

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

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import colors from '@/con/colors'; 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 { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -29,13 +29,10 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header */}
<Grid align="center"> <Group justify='space-between' align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Pratinjau Lokasi & Jadwal Pratinjau Lokasi & Jadwal
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -49,8 +46,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten Preview */} {/* Konten Preview */}
<Paper <Paper
@@ -59,6 +55,7 @@ function Page() {
withBorder withBorder
radius="md" radius="md"
shadow="xs" shadow="xs"
visibleFrom='md'
> >
<Box px={{ base: 'sm', md: 50 }}> <Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg"> <Stack gap="lg">
@@ -85,6 +82,48 @@ function Page() {
</Stack> </Stack>
</Box> </Box>
</Paper> </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> </Stack>
</Paper> </Paper>
); );

View File

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

View File

@@ -4,8 +4,7 @@ import {
Box, Box,
Button, Button,
Divider, Divider,
Grid, Group,
GridCol,
Paper, Paper,
Skeleton, Skeleton,
Stack, Stack,
@@ -40,13 +39,10 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header */}
<Grid align="center"> <Group justify='space-between' align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Pratinjau Tujuan Program Pratinjau Tujuan Program
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -60,8 +56,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten Preview */} {/* Konten Preview */}
<Paper <Paper
@@ -70,6 +65,7 @@ function Page() {
withBorder withBorder
radius="md" radius="md"
shadow="xs" shadow="xs"
visibleFrom='md'
> >
<Box px={{ base: 'sm', md: 50 }}> <Box px={{ base: 'sm', md: 50 }}>
<Stack gap="lg"> <Stack gap="lg">
@@ -95,6 +91,47 @@ function Page() {
</Stack> </Stack>
</Box> </Box>
</Paper> </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> </Stack>
</Paper> </Paper>
); );

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; 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 { IconBuilding, IconChalkboard, IconMicroscope, IconSchool } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -68,6 +68,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -90,6 +91,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -97,6 +99,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,29 @@
'use client'
import React from 'react'; import React from 'react';
import LayoutTabs from './_lib/layoutTabs'; import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; 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 { IconBook2, IconMapPin, IconSchool } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -63,6 +63,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -85,6 +86,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -92,6 +94,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel

View File

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

View File

@@ -4,8 +4,7 @@ import {
Box, Box,
Button, Button,
Divider, Divider,
Grid, Group,
GridCol,
Paper, Paper,
Skeleton, Skeleton,
Stack, Stack,
@@ -40,13 +39,10 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header */}
<Grid align="center"> <Group justify='space-between' align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Preview Jenis Program Yang Diselenggarakan Preview Jenis Program Yang Diselenggarakan
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -60,8 +56,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten Preview */} {/* Konten Preview */}
<Paper <Paper
@@ -86,12 +81,20 @@ function Page() {
{/* Deskripsi */} {/* Deskripsi */}
<Text <Text
fz={{ base: '1rem', md: '1.2rem' }} fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7} lh={1.7}
ta="justify" ta={{ base: 'left', md: 'justify' }}
c="dimmed" c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }} 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> </Stack>
</Box> </Box>

View File

@@ -1,8 +1,30 @@
'use client' 'use client'
import React from 'react'; import React from 'react';
import LayoutTabs from './_lib/layoutTabs'; import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

View File

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

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import colors from '@/con/colors'; 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 { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react'; import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -29,13 +29,10 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header */}
<Grid align="center"> <Group justify='space-between' align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Preview Tempat Kegiatan Preview Tempat Kegiatan
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -49,8 +46,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten Preview */} {/* Konten Preview */}
<Paper <Paper
@@ -75,12 +71,20 @@ function Page() {
{/* Deskripsi */} {/* Deskripsi */}
<Text <Text
fz={{ base: '1rem', md: '1.2rem' }} fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7} lh={1.7}
ta="justify" ta={{ base: 'left', md: 'justify' }}
c="dimmed" c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }} 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> </Stack>
</Box> </Box>

View File

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

View File

@@ -4,8 +4,7 @@ import {
Box, Box,
Button, Button,
Divider, Divider,
Grid, Group,
GridCol,
Paper, Paper,
Skeleton, Skeleton,
Stack, Stack,
@@ -42,13 +41,10 @@ function Page() {
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm"> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header */}
<Grid align="center"> <Group justify='space-between' align="center">
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3} c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Pratinjau Pendidikan Non Formal Pratinjau Pendidikan Non Formal
</Title> </Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button <Button
c="green" c="green"
variant="light" variant="light"
@@ -62,8 +58,7 @@ function Page() {
> >
Edit Edit
</Button> </Button>
</GridCol> </Group>
</Grid>
{/* Konten Preview */} {/* Konten Preview */}
<Paper <Paper
@@ -88,12 +83,20 @@ function Page() {
{/* Deskripsi */} {/* Deskripsi */}
<Text <Text
fz={{ base: '1rem', md: '1.2rem' }} fz={{ base: '0.95rem', md: '1.2rem' }}
lh={1.7} lh={1.7}
ta="justify" ta={{ base: 'left', md: 'justify' }}
c="dimmed" c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }} 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> </Stack>
</Box> </Box>

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; 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 { IconBook2, IconCategory, IconUser } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -62,6 +62,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -84,6 +85,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -91,6 +93,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </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) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,29 @@
'use client' 'use client'
import React from 'react'; import React from 'react';
import LayoutTabs from './_lib/layoutTabs'; import LayoutTabs from './_lib/layoutTabs';
import { usePathname } from 'next/navigation';
import { Box } from '@mantine/core';
function Layout({ children }: { children: React.ReactNode }) { 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 ( return (
<LayoutTabs> <LayoutTabs>
{children} {children}

Some files were not shown because too many files have changed in this diff Show More