- QC User & Admin Menu Lingkungan
- Fix SubMenu : Edukasi Lingkungan & Konservasi Adat Bali dibagian User - Fix SUbMenu : Gotong Royong User ( Tabs kategori menyesuaikan dengan data kategori kegiatan )
This commit is contained in:
@@ -135,7 +135,7 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
{item.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Group justify="apart" mt="md" gap="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
|
||||
import { Suspense } from "react";
|
||||
import Content from "./Content";
|
||||
|
||||
|
||||
@@ -1,24 +1,300 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, Image } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi'
|
||||
import colors from '@/con/colors'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
Container,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
Transition,
|
||||
} from '@mantine/core'
|
||||
import { IconRefresh, IconSearch, IconUsers } from '@tabler/icons-react'
|
||||
import { OrganizationChart } from 'primereact/organizationchart'
|
||||
import { useEffect } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import BackButton from '../../desa/layanan/_com/BackButto'
|
||||
|
||||
function Page() {
|
||||
export default function Page() {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Text px={{ base: 'md', md: 100 }} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Struktur Organisasi dan SK Pengurus BUMDesa
|
||||
</Text>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Image src={'/api/img/bpddarmasaba.png'} alt='' loading="lazy"/>
|
||||
<Box
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
background: colors['Bg'],
|
||||
color: '#E6F0FF',
|
||||
paddingBottom: 48,
|
||||
}}
|
||||
>
|
||||
<Container size="xl" py="xl">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Stack align="center" gap="xl" mt="xl">
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 28, md: 36, lg: 44 }}
|
||||
|
||||
>
|
||||
Struktur Organisasi Dan SK Pengurus BumDes
|
||||
</Title>
|
||||
<Text ta="center" c="black" maw={800}>
|
||||
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
|
||||
untuk melihat detail atau klik node untuk fokus tampilan.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
<Box mt="lg">
|
||||
<StrukturOrganisasiBumDes />
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page;
|
||||
function StrukturOrganisasiBumDes() {
|
||||
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
|
||||
|
||||
useEffect(() => {
|
||||
void stateOrganisasi.findMany.load()
|
||||
}, [])
|
||||
|
||||
const isLoading =
|
||||
!stateOrganisasi.findMany.data &&
|
||||
stateOrganisasi.findMany.loading !== false
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Center py={48}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader size="lg" />
|
||||
<Text fw={600}>Memuat struktur organisasi…</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
!stateOrganisasi.findMany.data ||
|
||||
stateOrganisasi.findMany.data.length === 0
|
||||
) {
|
||||
return (
|
||||
<Center py={40}>
|
||||
<Stack align="center" gap="md">
|
||||
<Paper
|
||||
radius="md"
|
||||
p="xl"
|
||||
style={{
|
||||
width: 560,
|
||||
background: 'rgba(28,110,164,0.2)',
|
||||
border: `1px solid rgba(255,255,255,0.1)`,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Center>
|
||||
<IconUsers size={56} />
|
||||
</Center>
|
||||
<Title order={3} mt="md">
|
||||
Data pegawai belum tersedia
|
||||
</Title>
|
||||
<Text c="dimmed" mt="xs">
|
||||
Belum ada data pegawai yang tercatat untuk BumDes. Silakan coba
|
||||
muat ulang atau periksa sumber data.
|
||||
</Text>
|
||||
<Group justify="center" mt="lg">
|
||||
<Button
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
variant="gradient"
|
||||
gradient={{ from: 'indigo', to: 'cyan' }}
|
||||
onClick={() => stateOrganisasi.findMany.load()}
|
||||
>
|
||||
Muat Ulang
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<IconSearch size={16} />}
|
||||
variant="subtle"
|
||||
onClick={() =>
|
||||
stateOrganisasi.findMany.load({ query: { q: '' } })
|
||||
}
|
||||
>
|
||||
Cari Pegawai
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
const posisiMap = new Map<string, any>()
|
||||
|
||||
const aktifPegawai = stateOrganisasi.findMany.data.filter((p: any) => p.isActive);
|
||||
|
||||
for (const pegawai of aktifPegawai) {
|
||||
const posisiId = pegawai.posisi.id;
|
||||
if (!posisiMap.has(posisiId)) {
|
||||
posisiMap.set(posisiId, {
|
||||
...pegawai.posisi,
|
||||
pegawaiList: [],
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
|
||||
}
|
||||
|
||||
// First, create a map of all unique positions
|
||||
const allPositions = new Map();
|
||||
aktifPegawai.forEach((pegawai: any) => {
|
||||
if (!allPositions.has(pegawai.posisi.id)) {
|
||||
allPositions.set(pegawai.posisi.id, {
|
||||
...pegawai.posisi,
|
||||
pegawaiList: [],
|
||||
children: []
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Then assign employees to their positions
|
||||
aktifPegawai.forEach((pegawai: any) => {
|
||||
const posisi = allPositions.get(pegawai.posisi.id);
|
||||
if (posisi) {
|
||||
posisi.pegawaiList.push(pegawai);
|
||||
}
|
||||
});
|
||||
|
||||
// Now build the hierarchy
|
||||
const root = [];
|
||||
for (const [_, posisi] of allPositions) {
|
||||
if (posisi.parentId) {
|
||||
const parent = allPositions.get(posisi.parentId);
|
||||
if (parent) {
|
||||
parent.children.push(posisi);
|
||||
} else {
|
||||
// Only add to root if it's a top-level position
|
||||
if (!posisi.parentId) {
|
||||
root.push(posisi);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root.push(posisi);
|
||||
}
|
||||
}
|
||||
function toOrgChartFormat(node: any): any {
|
||||
return {
|
||||
expanded: true,
|
||||
type: 'person',
|
||||
styleClass: 'p-person',
|
||||
data: {
|
||||
name: node.pegawaiList?.[0]?.namaLengkap || 'Belum ditugaskan',
|
||||
title: node.nama || 'Tanpa jabatan',
|
||||
image: node.pegawaiList?.[0]?.image?.link || '/img/default.png',
|
||||
description: node.deskripsi || '',
|
||||
positionId: node.id || null,
|
||||
},
|
||||
children: node.children?.map(toOrgChartFormat) || [],
|
||||
}
|
||||
}
|
||||
|
||||
const chartData = root.map(toOrgChartFormat)
|
||||
|
||||
return (
|
||||
<Box py={16} >
|
||||
<Paper
|
||||
radius="md"
|
||||
p="md"
|
||||
style={{
|
||||
background: 'rgba(28,110,164,0.2)',
|
||||
border: `1px solid rgba(255,255,255,0.1)`,
|
||||
overflowX: 'auto',
|
||||
}}
|
||||
>
|
||||
<OrganizationChart
|
||||
value={chartData}
|
||||
nodeTemplate={nodeTemplate}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function nodeTemplate(node: any) {
|
||||
const imageSrc = node?.data?.image || '/img/default.png'
|
||||
const name = node?.data?.name || 'Tanpa Nama'
|
||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||
const description = node?.data?.description || ''
|
||||
|
||||
return (
|
||||
<Transition mounted transition="pop" duration={240}>
|
||||
{(styles) => (
|
||||
<Card
|
||||
radius="lg"
|
||||
withBorder
|
||||
style={{
|
||||
...styles,
|
||||
width: 260,
|
||||
padding: 16,
|
||||
background: 'rgba(28,110,164,0.3)',
|
||||
borderColor: 'rgba(255,255,255,0.15)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
radius="md"
|
||||
width={120}
|
||||
height={120}
|
||||
fit="cover"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
border: '2px solid rgba(255,255,255,0.2)',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
loading='lazy'
|
||||
/>
|
||||
<Text fw={700}>{name}</Text>
|
||||
<Text size="sm" c="dimmed" mt={4}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mt={8} lineClamp={3}>
|
||||
{description || 'Belum ada deskripsi.'}
|
||||
</Text>
|
||||
<Tooltip label="Kembali ke struktur organisasi" withArrow position="bottom">
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
mt="md"
|
||||
onClick={() => {
|
||||
const id = node?.data?.positionId
|
||||
if (id && (window as any).scrollTo) {
|
||||
;(window as any).scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
}}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
)}
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ function Page() {
|
||||
|
||||
useShallowEffect(() => {
|
||||
mitraState.findMany.load(page, 10);
|
||||
load(page, 10, search, selectedYear || '');
|
||||
load(page, 10, search, selectedYear ? `year:${selectedYear}` : '');
|
||||
}, [page, search, selectedYear]);
|
||||
|
||||
const mitraData = mitraState.findMany.data || [];
|
||||
|
||||
@@ -1,48 +1,31 @@
|
||||
'use client'
|
||||
import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, List, ListItem, Paper, SimpleGrid, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconLeaf, IconPlant2, IconRecycle } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Tujuan Edukasi Lingkungan',
|
||||
icon: <IconLeaf size={28} color={colors['blue-button']} />,
|
||||
listDeskripsi: [
|
||||
'Meningkatkan kesadaran masyarakat akan pentingnya lingkungan bersih dan sehat',
|
||||
'Mendorong partisipasi warga dalam pengelolaan sampah, penghijauan, dan konservasi',
|
||||
'Mengurangi dampak negatif kegiatan manusia terhadap lingkungan',
|
||||
'Membentuk generasi muda peduli isu-isu lingkungan',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Materi Edukasi yang Diberikan',
|
||||
icon: <IconRecycle size={28} color={colors['blue-button']} />,
|
||||
listDeskripsi: [
|
||||
'Pengelolaan sampah: pilah organik & anorganik',
|
||||
'Pencegahan pencemaran lingkungan (air, udara, tanah)',
|
||||
'Pemanfaatan lahan hijau dan penghijauan desa',
|
||||
'Daur ulang dan kreativitas dari sampah',
|
||||
'Bahaya pembakaran sampah sembarangan',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Contoh Kegiatan di Desa Darmasaba',
|
||||
icon: <IconPlant2 size={28} color={colors['blue-button']} />,
|
||||
listDeskripsi: [
|
||||
'Pelatihan membuat kompos dari sampah rumah tangga',
|
||||
'Gerakan "Jumat Bersih" rutin',
|
||||
'Workshop pembuatan ecobrick',
|
||||
'Lomba kebersihan antar banjar',
|
||||
'Sosialisasi lingkungan di sekolah dan posyandu',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function Page() {
|
||||
const tujuan = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi.findById)
|
||||
const materi = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan.findById)
|
||||
const contoh = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan.findById)
|
||||
|
||||
useShallowEffect(() => {
|
||||
tujuan.load('edit')
|
||||
materi.load('edit')
|
||||
contoh.load('edit')
|
||||
}, [])
|
||||
|
||||
if (tujuan.loading || !tujuan.data || materi.loading || !materi.data || contoh.loading || !contoh.data) {
|
||||
return (
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
@@ -60,28 +43,84 @@ function Page() {
|
||||
</Box>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||
{data.map((item) => (
|
||||
<Paper key={item.id} p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" style={{ alignItems: 'stretch' }}>
|
||||
{/* Tujuan Edukasi Lingkungan */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md" style={{ width: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Tooltip label={item.title} position="top" withArrow>
|
||||
<Tooltip label={tujuan.data?.judul} position="top" withArrow>
|
||||
<Stack gap={4} align="center">
|
||||
{item.icon}
|
||||
<IconLeaf size={28} color={colors['blue-button']} />
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{item.title}
|
||||
{tujuan.data?.judul}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<List fz="h4" spacing="sm" withPadding>
|
||||
{item.listDeskripsi.map((desc, idx) => (
|
||||
<ListItem key={idx}>{desc}</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Text
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: tujuan.data?.deskripsi || '' }}
|
||||
/>
|
||||
<Box style={{ flexGrow: 1 }} />
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
{/* Materi Edukasi Lingkungan */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Tooltip label={materi.data?.judul} position="top" withArrow>
|
||||
<Stack gap={4} align="center">
|
||||
<IconRecycle size={28} color={colors['blue-button']} />
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{materi.data?.judul}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Text
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: materi.data?.deskripsi || '' }}
|
||||
/>
|
||||
<Box style={{ flexGrow: 1 }} />
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Contoh Edukasi Lingkungan */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Tooltip label={contoh.data?.judul} position="top" withArrow>
|
||||
<Stack gap={4} align="center">
|
||||
<IconPlant2 size={28} color={colors['blue-button']} />
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{contoh.data?.judul}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Text
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: contoh.data?.deskripsi || '' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
{featured.kategoriKegiatan?.nama || kategori}
|
||||
</Badge>
|
||||
<Title order={2} mb="md">{featured.judul}</Title>
|
||||
<Text color="dimmed" lineClamp={3} mb="md">{featured.deskripsiLengkap}</Text>
|
||||
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featured.deskripsiLengkap }} />
|
||||
</div>
|
||||
<Group justify="apart" mt="auto">
|
||||
<Group gap="xs">
|
||||
@@ -135,9 +135,9 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
{item.kategoriKegiatan?.nama || kategori}
|
||||
</Badge>
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
<Text size="sm" color="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsiLengkap }} />
|
||||
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsiLengkap }} />
|
||||
<Group justify="apart" mt="md" gap="xs">
|
||||
<Text size="xs" color="dimmed">
|
||||
<Text size="xs" c="dimmed">
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
|
||||
import { Suspense } from "react";
|
||||
import Content from "./content";
|
||||
|
||||
|
||||
@@ -1,113 +1,391 @@
|
||||
// 'use client'
|
||||
// import colors from '@/con/colors';
|
||||
// import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
// import { IconSearch } from '@tabler/icons-react';
|
||||
// import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
// import React, { useEffect, useState } from 'react';
|
||||
// import BackButton from '../../../desa/layanan/_com/BackButto';
|
||||
|
||||
|
||||
// type HeaderSearchProps = {
|
||||
// placeholder?: string;
|
||||
// searchIcon?: React.ReactNode;
|
||||
// value?: string;
|
||||
// onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
// children?: React.ReactNode;
|
||||
// };
|
||||
|
||||
// function LayoutTabsGotongRoyong({
|
||||
// children,
|
||||
// placeholder = "pencarian",
|
||||
// searchIcon = <IconSearch size={20} />
|
||||
// }: HeaderSearchProps) {
|
||||
// const router = useRouter();
|
||||
// const pathname = usePathname();
|
||||
// const searchParams = useSearchParams();
|
||||
|
||||
// // Get active tab from URL path
|
||||
// const activeTab = pathname.split('/').pop() || 'semua';
|
||||
|
||||
// // Get initial search value from URL
|
||||
// const initialSearch = searchParams.get('search') || '';
|
||||
// const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
// const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// // Update active tab state when pathname changes
|
||||
// const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
// useEffect(() => {
|
||||
// setActiveTabState(activeTab);
|
||||
// }, [activeTab]);
|
||||
|
||||
// // Clean up timeouts on unmount
|
||||
// useEffect(() => {
|
||||
// return () => {
|
||||
// if (searchTimeout !== null) {
|
||||
// clearTimeout(searchTimeout);
|
||||
// }
|
||||
// };
|
||||
// }, [searchTimeout]);
|
||||
|
||||
// // Handle search input change with debounce
|
||||
// const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = event.target.value;
|
||||
// setSearchValue(value);
|
||||
|
||||
// // Clear previous timeout
|
||||
// if (searchTimeout !== null) {
|
||||
// clearTimeout(searchTimeout);
|
||||
// }
|
||||
|
||||
// // Set new timeout
|
||||
// const newTimeout = window.setTimeout(() => {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
// if (value) {
|
||||
// params.set('search', value);
|
||||
// } else {
|
||||
// params.delete('search');
|
||||
// }
|
||||
|
||||
// // Only update URL if the search value has actually changed
|
||||
// if (params.toString() !== searchParams.toString()) {
|
||||
// router.push(`/darmasaba/lingkungan/gotong-royong/${activeTab}?${params.toString()}`);
|
||||
// }
|
||||
// }, 500); // 500ms debounce delay
|
||||
|
||||
// setSearchTimeout(newTimeout);
|
||||
// };
|
||||
// const tabs = [
|
||||
// {
|
||||
// label: "Semua",
|
||||
// value: "semua",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/semua"
|
||||
// },
|
||||
// {
|
||||
// label: "Kebersihan",
|
||||
// value: "kebersihan",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/kebersihan"
|
||||
// },
|
||||
// {
|
||||
// label: "Infrastruktur",
|
||||
// value: "infrastruktur",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/infrastruktur"
|
||||
// },
|
||||
// {
|
||||
// label: "Sosial",
|
||||
// value: "sosial",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/sosial"
|
||||
// },
|
||||
// {
|
||||
// label: "Lingkungan",
|
||||
// value: "lingkungan",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/lingkungan"
|
||||
// }
|
||||
// ];
|
||||
// const handleTabChange = (value: string | null) => {
|
||||
// if (!value) return;
|
||||
// const tab = tabs.find(t => t.value === value);
|
||||
// if (tab) {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
// router.push(`/darmasaba/lingkungan/gotong-royong/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
// {/* Header */}
|
||||
// <Box px={{ base: "md", md: 100 }}>
|
||||
// <BackButton />
|
||||
// </Box>
|
||||
// <Container size="lg" px="md">
|
||||
// <Stack align="center" gap="0" >
|
||||
// <Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
// Gotong Royong Desa Darmasaba
|
||||
// </Text>
|
||||
// <Text ta="center" px="md">
|
||||
// Gotong royong rutin dilakukan oleh warga desa untuk meningkatkan kualitas hidup dan kesejahteraan masyarakat Desa Darmasaba
|
||||
// </Text>
|
||||
// </Stack>
|
||||
// </Container>
|
||||
|
||||
// <Tabs
|
||||
// color={colors['blue-button']}
|
||||
// variant="pills"
|
||||
// value={activeTabState}
|
||||
// onChange={handleTabChange}
|
||||
// >
|
||||
// <Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
// <TabsList>
|
||||
// {tabs.map((tab, index) => (
|
||||
// <TabsTab
|
||||
// key={index}
|
||||
// value={tab.value}
|
||||
// onClick={() => router.push(tab.href)}
|
||||
// >
|
||||
// {tab.label}
|
||||
// </TabsTab>
|
||||
// ))}
|
||||
// </TabsList>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
// <TextInput
|
||||
// radius="lg"
|
||||
// placeholder={placeholder}
|
||||
// leftSection={searchIcon}
|
||||
// w="100%"
|
||||
// value={searchValue}
|
||||
// onChange={handleSearchChange}
|
||||
// />
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Box>
|
||||
|
||||
// {children}
|
||||
// </Tabs>
|
||||
// </Stack>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default LayoutTabsGotongRoyong;
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// 'use client'
|
||||
// import colors from '@/con/colors';
|
||||
// import { Box, Group, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
// import { IconSearch } from '@tabler/icons-react';
|
||||
// import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
// import React, { useEffect, useState } from 'react';
|
||||
// import BackButton from '../../layanan/_com/BackButto';
|
||||
|
||||
// type HeaderSearchProps = {
|
||||
// placeholder?: string;
|
||||
// searchIcon?: React.ReactNode;
|
||||
// value?: string;
|
||||
// onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
// children?: React.ReactNode;
|
||||
// };
|
||||
|
||||
// function LayoutTabsBerita({
|
||||
// children,
|
||||
// placeholder = "pencarian",
|
||||
// searchIcon = <IconSearch size={20} />
|
||||
// }: HeaderSearchProps) {
|
||||
// const router = useRouter();
|
||||
// const pathname = usePathname();
|
||||
// const searchParams = useSearchParams();
|
||||
|
||||
// const activeTab = pathname.split('/').pop() || 'semua';
|
||||
// const initialSearch = searchParams.get('search') || '';
|
||||
// const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
// const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
// useEffect(() => {
|
||||
// setActiveTabState(activeTab);
|
||||
// }, [activeTab]);
|
||||
|
||||
// useEffect(() => {
|
||||
// return () => {
|
||||
// if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
// };
|
||||
// }, [searchTimeout]);
|
||||
|
||||
// const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = event.target.value;
|
||||
// setSearchValue(value);
|
||||
|
||||
// if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
|
||||
// const newTimeout = window.setTimeout(() => {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
// if (value) params.set('search', value);
|
||||
// else params.delete('search');
|
||||
|
||||
// if (params.toString() !== searchParams.toString()) {
|
||||
// router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||
// }
|
||||
// }, 500);
|
||||
|
||||
// setSearchTimeout(newTimeout);
|
||||
// };
|
||||
|
||||
// const tabs = [
|
||||
// { label: "Semua", value: "semua", href: "/darmasaba/desa/berita/semua" },
|
||||
// { label: "Budaya", value: "budaya", href: "/darmasaba/desa/berita/budaya" },
|
||||
// { label: "Pemerintahan", value: "pemerintahan", href: "/darmasaba/desa/berita/pemerintahan" },
|
||||
// { label: "Ekonomi", value: "ekonomi", href: "/darmasaba/desa/berita/ekonomi" },
|
||||
// { label: "Pembangunan", value: "pembangunan", href: "/darmasaba/desa/berita/pembangunan" },
|
||||
// { label: "Sosial", value: "sosial", href: "/darmasaba/desa/berita/sosial" },
|
||||
// { label: "Teknologi", value: "teknologi", href: "/darmasaba/desa/berita/teknologi" },
|
||||
// ];
|
||||
|
||||
// const handleTabChange = (value: string | null) => {
|
||||
// if (!value) return;
|
||||
// const tab = tabs.find(t => t.value === value);
|
||||
// if (tab) {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
// router.push(`/darmasaba/desa/berita/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
// {/* Header */}
|
||||
// <Box px={{ base: "md", md: 100 }}>
|
||||
// <BackButton />
|
||||
// </Box>
|
||||
|
||||
// <Box px={{ base: 'md', md: 100 }}>
|
||||
// <Group justify='space-between' align="center">
|
||||
// <Stack gap="0">
|
||||
// <Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" >
|
||||
// Portal Berita Darmasaba
|
||||
// </Text>
|
||||
// <Text>
|
||||
// Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
// </Text>
|
||||
// </Stack>
|
||||
// <Box>
|
||||
// <TextInput
|
||||
// radius="lg"
|
||||
// placeholder={placeholder}
|
||||
// leftSection={searchIcon}
|
||||
// w="100%"
|
||||
// value={searchValue}
|
||||
// onChange={handleSearchChange}
|
||||
// />
|
||||
// </Box>
|
||||
// </Group>
|
||||
// </Box>
|
||||
|
||||
// <Tabs
|
||||
// color={colors['blue-button']}
|
||||
// variant="pills"
|
||||
// value={activeTabState}
|
||||
// onChange={handleTabChange}
|
||||
// >
|
||||
// <Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
// {/* SCROLLABLE TABS */}
|
||||
// <Box style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}>
|
||||
// <TabsList style={{ display: 'flex', flexWrap: 'nowrap', gap: '0.5rem' }}>
|
||||
// {tabs.map((tab, index) => (
|
||||
// <TabsTab
|
||||
// key={index}
|
||||
// value={tab.value}
|
||||
// onClick={() => router.push(tab.href)}
|
||||
// style={{
|
||||
// flex: '0 0 auto', // Prevent shrinking
|
||||
// minWidth: 100, // optional: makes them touch-friendly
|
||||
// textAlign: 'center'
|
||||
// }}
|
||||
// >
|
||||
// {tab.label}
|
||||
// </TabsTab>
|
||||
// ))}
|
||||
// </TabsList>
|
||||
// </Box>
|
||||
// </Box>
|
||||
|
||||
// {children}
|
||||
// </Tabs>
|
||||
// </Stack>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default LayoutTabsBerita;
|
||||
|
||||
|
||||
'use client'
|
||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Group, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../../desa/layanan/_com/BackButto';
|
||||
|
||||
|
||||
type HeaderSearchProps = {
|
||||
placeholder?: string;
|
||||
searchIcon?: React.ReactNode;
|
||||
value?: string;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
function LayoutTabsGotongRoyong({
|
||||
children,
|
||||
placeholder = "pencarian",
|
||||
searchIcon = <IconSearch size={20} />
|
||||
}: HeaderSearchProps) {
|
||||
function LayoutTabsGotongRoyong({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
// Get active tab from URL path
|
||||
|
||||
const kategoriState = useProxy(gotongRoyongState.kategoriKegiatan);
|
||||
|
||||
// tab aktif dari url
|
||||
const activeTab = pathname.split('/').pop() || 'semua';
|
||||
|
||||
// Get initial search value from URL
|
||||
const initialSearch = searchParams.get('search') || '';
|
||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// Update active tab state when pathname changes
|
||||
const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
|
||||
useEffect(() => {
|
||||
kategoriState.findMany.load(); // ambil kategori dari DB
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTabState(activeTab);
|
||||
}, [activeTab]);
|
||||
|
||||
// Clean up timeouts on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (searchTimeout !== null) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
};
|
||||
}, [searchTimeout]);
|
||||
// search
|
||||
const initialSearch = searchParams.get('search') || '';
|
||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// Handle search input change with debounce
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setSearchValue(value);
|
||||
|
||||
// Clear previous timeout
|
||||
if (searchTimeout !== null) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
// Set new timeout
|
||||
|
||||
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
|
||||
const newTimeout = window.setTimeout(() => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (value) {
|
||||
params.set('search', value);
|
||||
} else {
|
||||
params.delete('search');
|
||||
}
|
||||
|
||||
// Only update URL if the search value has actually changed
|
||||
if (params.toString() !== searchParams.toString()) {
|
||||
router.push(`/darmasaba/lingkungan/gotong-royong/${activeTab}?${params.toString()}`);
|
||||
}
|
||||
}, 500); // 500ms debounce delay
|
||||
|
||||
if (value) params.set('search', value);
|
||||
else params.delete('search');
|
||||
|
||||
router.push(`/darmasaba/lingkungan/gotong-royong/${activeTab}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
}, 500);
|
||||
|
||||
setSearchTimeout(newTimeout);
|
||||
};
|
||||
|
||||
// --- tabs dinamis ---
|
||||
const tabs = [
|
||||
{
|
||||
label: "Semua",
|
||||
value: "semua",
|
||||
href: "/darmasaba/lingkungan/gotong-royong/semua"
|
||||
},
|
||||
{
|
||||
label: "Kebersihan",
|
||||
value: "kebersihan",
|
||||
href: "/darmasaba/lingkungan/gotong-royong/kebersihan"
|
||||
},
|
||||
{
|
||||
label: "Infrastruktur",
|
||||
value: "infrastruktur",
|
||||
href: "/darmasaba/lingkungan/gotong-royong/infrastruktur"
|
||||
},
|
||||
{
|
||||
label: "Sosial",
|
||||
value: "sosial",
|
||||
href: "/darmasaba/lingkungan/gotong-royong/sosial"
|
||||
},
|
||||
{
|
||||
label: "Lingkungan",
|
||||
value: "lingkungan",
|
||||
href: "/darmasaba/lingkungan/gotong-royong/lingkungan"
|
||||
}
|
||||
{ label: "Semua", value: "semua", href: "/darmasaba/lingkungan/gotong-royong/semua" },
|
||||
...(kategoriState.findMany.data || []).map((kat: any) => ({
|
||||
label: kat.nama,
|
||||
value: kat.nama.toLowerCase(),
|
||||
href: `/darmasaba/lingkungan/gotong-royong/${kat.nama.toLowerCase()}`
|
||||
}))
|
||||
];
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
if (!value) return;
|
||||
const tab = tabs.find(t => t.value === value);
|
||||
if (tab) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
router.push(`/darmasaba/lingkungan/gotong-royong/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
router.push(`${tab.href}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,17 +395,29 @@ function LayoutTabsGotongRoyong({
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="0" >
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Gotong Royong Desa Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Gotong royong rutin dilakukan oleh warga desa untuk meningkatkan kualitas hidup dan kesejahteraan masyarakat Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Group justify='space-between' align="center">
|
||||
<Stack gap="0">
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold">
|
||||
Portal Gotong royong Darmasaba
|
||||
</Text>
|
||||
<Text>Temukan berbagai kegiatan lingkungan yang dimiliki Desa Darmasaba</Text>
|
||||
</Stack>
|
||||
<Box>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder="pencarian"
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w="100%"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{/* TABS */}
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
@@ -135,31 +425,24 @@ function LayoutTabsGotongRoyong({
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
<TabsList>
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTab
|
||||
key={index}
|
||||
value={tab.value}
|
||||
onClick={() => router.push(tab.href)}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={placeholder}
|
||||
leftSection={searchIcon}
|
||||
w="100%"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}>
|
||||
<TabsList style={{ display: 'flex', flexWrap: 'nowrap', gap: '0.5rem' }}>
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTab
|
||||
key={index}
|
||||
value={tab.value}
|
||||
onClick={() => router.push(tab.href)}
|
||||
style={{
|
||||
flex: '0 0 auto',
|
||||
minWidth: 100,
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{children}
|
||||
@@ -168,4 +451,4 @@ function LayoutTabsGotongRoyong({
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsGotongRoyong;
|
||||
export default LayoutTabsGotongRoyong;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Badge, Box, Button, Card, Center, Container, Divider, Flex, Grid, GridC
|
||||
import { IconArrowRight, IconCalendar } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function Page() {
|
||||
@@ -14,8 +14,7 @@ function Page() {
|
||||
|
||||
// Parameter URL
|
||||
const search = searchParams.get('search') || '';
|
||||
const currentPage = parseInt(searchParams.get('page') || '1');
|
||||
const [page, setPage] = useState(currentPage);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
|
||||
// Gunakan proxy untuk state
|
||||
const state = useProxy(gotongRoyongState.kegiatanDesa);
|
||||
@@ -37,12 +36,14 @@ function Page() {
|
||||
}, [page, search]);
|
||||
|
||||
// Update URL saat page berubah
|
||||
useEffect(() => {
|
||||
const url = new URLSearchParams();
|
||||
const handlePageChange = (newPage: number) => {
|
||||
const url = new URLSearchParams(searchParams.toString());
|
||||
if (search) url.set('search', search);
|
||||
if (page > 1) url.set('page', page.toString());
|
||||
if (newPage > 1) url.set('page', newPage.toString());
|
||||
else url.delete('page'); // biar page=1 ga muncul di URL
|
||||
|
||||
router.replace(`?${url.toString()}`);
|
||||
}, [page, search]);
|
||||
};
|
||||
|
||||
const featuredData = featured.data;
|
||||
const paginatedNews = state.findMany.data || [];
|
||||
@@ -77,9 +78,7 @@ function Page() {
|
||||
{featuredData.kategoriKegiatan?.nama || 'Gotong royong'}
|
||||
</Badge>
|
||||
<Title order={2} mb="md">{featuredData.judul}</Title>
|
||||
<Text c="dimmed" lineClamp={3} mb="md">
|
||||
{featuredData.deskripsiSingkat}
|
||||
</Text>
|
||||
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featuredData.deskripsiSingkat }} />
|
||||
</div>
|
||||
<Group justify="apart" mt="auto">
|
||||
<Group gap="xs">
|
||||
@@ -146,7 +145,7 @@ function Page() {
|
||||
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
|
||||
<Text size="sm" c="dimmed" lineClamp={3} mt="xs">{item.deskripsiSingkat}</Text>
|
||||
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
|
||||
|
||||
<Flex align="center" justify="apart" mt="md" gap="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
@@ -169,7 +168,7 @@ function Page() {
|
||||
<Pagination
|
||||
total={totalPages}
|
||||
value={page}
|
||||
onChange={setPage}
|
||||
onChange={handlePageChange}
|
||||
siblings={1}
|
||||
boundaries={1}
|
||||
withEdges
|
||||
|
||||
@@ -1,47 +1,30 @@
|
||||
'use client'
|
||||
import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { Box, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Filosofi Tri Hita Karana',
|
||||
listDeskripsi: (
|
||||
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||
<ListItem>Parahyangan: Hubungan manusia dengan Tuhan yang dijaga penuh kesadaran spiritual</ListItem>
|
||||
<ListItem>Pawongan: Harmoni dan kerja sama antar manusia dalam masyarakat</ListItem>
|
||||
<ListItem>Palemahan: Pelestarian lingkungan dan hubungan manusia dengan alam</ListItem>
|
||||
</List>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Bentuk Konservasi Berdasarkan Adat',
|
||||
listDeskripsi: (
|
||||
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||
<ListItem>Pelestarian Hutan Adat seperti Alas Pala Sangeh dan Wana Kerthi</ListItem>
|
||||
<ListItem>Subak: Sistem irigasi tradisional yang menekankan kebersamaan dan keberlanjutan</ListItem>
|
||||
<ListItem>Hari Raya Tumpek Uduh: Perayaan untuk menghormati pohon dan tumbuhan</ListItem>
|
||||
<ListItem>Perarem & Awig-Awig: Aturan adat untuk menjaga lingkungan dari kerusakan</ListItem>
|
||||
<ListItem>Ritual penyucian alam seperti Melasti dan Piodalan Segara</ListItem>
|
||||
</List>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Nilai Konservasi Adat',
|
||||
listDeskripsi: (
|
||||
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||
<ListItem>Menjaga keseimbangan ekosistem dan lingkungan hidup</ListItem>
|
||||
<ListItem>Melestarikan spiritualitas lokal dan kesucian alam</ListItem>
|
||||
<ListItem>Meningkatkan kesadaran kolektif untuk hidup selaras dengan alam</ListItem>
|
||||
<ListItem>Menjamin keberlanjutan sumber daya alam untuk generasi mendatang</ListItem>
|
||||
</List>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Page() {
|
||||
const filosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita.findById)
|
||||
const nilai = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat.findById)
|
||||
const bentuk = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat.findById)
|
||||
|
||||
useShallowEffect(() => {
|
||||
filosofi.load('edit')
|
||||
nilai.load('edit')
|
||||
bentuk.load('edit')
|
||||
}, [])
|
||||
|
||||
if (filosofi.loading || !filosofi.data || nilai.loading || !nilai.data || bentuk.loading || !bentuk.data) {
|
||||
return (
|
||||
<Stack py={20} align="center">
|
||||
<Skeleton radius="md" height={600} width="100%" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="24">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
@@ -56,24 +39,99 @@ function Page() {
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||
{data.map((item) => (
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" style={{ alignItems: 'stretch' }}>
|
||||
{/* Filsosofi */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper
|
||||
key={item.id}
|
||||
p="lg"
|
||||
bg="linear-gradient(145deg, #DFE3E8FF 0%, #EFF1F4FF 100%)"
|
||||
style={{ borderRadius: 16, boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)' }}
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Stack gap="md" px={20}>
|
||||
<Stack gap="md" px={20} style={{ height: '100%' }}>
|
||||
<Center>
|
||||
<Text fz="xl" fw="bold" c="black">
|
||||
{item.title}
|
||||
</Text>
|
||||
{filosofi.data?.judul}
|
||||
</Text>
|
||||
</Center>
|
||||
{item.listDeskripsi}
|
||||
<div
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: filosofi.data?.deskripsi || '' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
{/* Nilai */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper
|
||||
p="lg"
|
||||
bg="linear-gradient(145deg, #DFE3E8FF 0%, #EFF1F4FF 100%)"
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Stack gap="md" px={20} style={{ height: '100%' }}>
|
||||
<Center>
|
||||
<Text fz="xl" fw="bold" c="black">
|
||||
{nilai.data?.judul}
|
||||
</Text>
|
||||
</Center>
|
||||
<div
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1,
|
||||
minHeight: 0
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: nilai.data?.deskripsi || '' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Bentuk */}
|
||||
<Box>
|
||||
<Paper
|
||||
p="lg"
|
||||
bg="linear-gradient(145deg, #DFE3E8FF 0%, #EFF1F4FF 100%)"
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Stack gap="md" px={20} style={{ height: '100%' }}>
|
||||
<Center>
|
||||
<Text fz="xl" fw="bold" c="black">
|
||||
{bentuk.data?.judul}
|
||||
</Text>
|
||||
</Center>
|
||||
<div
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1,
|
||||
minHeight: 0
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: bentuk.data?.deskripsi || '' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Flex, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Box, Center, Flex, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { Icon, IconChartLine, IconClipboardTextFilled, IconLeaf, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import dynamic from 'next/dynamic';
|
||||
@@ -20,20 +20,26 @@ function Page() {
|
||||
const state = useProxy(pengelolaanSampahState.pengelolaanSampah)
|
||||
const state2 = useProxy(pengelolaanSampahState.keteranganSampah)
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
||||
|
||||
const {
|
||||
data,
|
||||
load
|
||||
load,
|
||||
|
||||
} = state.findMany
|
||||
|
||||
const {
|
||||
data: data2,
|
||||
load: load2
|
||||
load: load2,
|
||||
page,
|
||||
totalPages,
|
||||
} = state2.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
load()
|
||||
load2()
|
||||
}, [])
|
||||
load2(page, 3, debouncedSearch)
|
||||
}, [page, debouncedSearch])
|
||||
|
||||
const iconMap: Record<string, Icon> = {
|
||||
ekowisata: IconLeaf,
|
||||
@@ -104,8 +110,10 @@ function Page() {
|
||||
px={{ base: 70, md: 150 }}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
placeholder='Cari Bank Sampah Terdekat'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||
{/* Left side - List of bank locations */}
|
||||
<Box>
|
||||
@@ -131,9 +139,17 @@ function Page() {
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
total={totalPages}
|
||||
my="md"
|
||||
/>
|
||||
</Center>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Right side - Single map showing all locations */}
|
||||
<Box style={{ position: 'sticky', top: '20px' }}>
|
||||
<Paper p="md" bg={colors['white-trans-1']} radius="lg" h="100%">
|
||||
|
||||
Reference in New Issue
Block a user