Merge pull request 'nico/12-des-25' (#41) from nico/12-des-25 into staggingweb

Reviewed-on: http://wibugit.wibudev.com/wibu/desa-darmasaba/pulls/41
This commit is contained in:
2025-12-12 17:07:31 +08:00
63 changed files with 2506 additions and 1568 deletions

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client';
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita'; import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import { import {
Badge, Badge,
@@ -51,10 +51,14 @@ export default function Content({ kategori }: { kategori: string }) {
<Container size="xl" px={{ base: 'md', md: 'xl' }}> <Container size="xl" px={{ base: 'md', md: 'xl' }}>
{/* === Berita Utama === */} {/* === Berita Utama === */}
{featuredState.loading ? ( {featuredState.loading ? (
<Center><Skeleton h={400} /></Center> <Center>
<Skeleton h={400} />
</Center>
) : featured ? ( ) : featured ? (
<Box mb={50}> <Box mb={50}>
<Text fz="h2" fw={700} mb="md">Berita Utama</Text> <Title order={2} mb="md">
Berita Utama
</Title>
<Paper shadow="md" radius="md" withBorder> <Paper shadow="md" radius="md" withBorder>
<Grid gutter={0}> <Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}> <GridCol span={{ base: 12, md: 6 }}>
@@ -74,13 +78,29 @@ export default function Content({ kategori }: { kategori: string }) {
<Badge color="blue" variant="light" mb="md"> <Badge color="blue" variant="light" mb="md">
{featured.kategoriBerita?.name || kategori} {featured.kategoriBerita?.name || kategori}
</Badge> </Badge>
<Title order={2} mb="md">{featured.judul}</Title> <Title order={3} mb="md">
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featured.deskripsi }} /> {featured.judul}
</Title>
<Text
c="dimmed"
lineClamp={3}
mb="md"
style={{ lineHeight: 1.6 }}
dangerouslySetInnerHTML={{ __html: featured.deskripsi }}
/>
</div> </div>
<Group justify="apart" mt="auto"> <Group justify="apart" mt="auto">
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={18} /> <IconCalendar size={18} />
<Text size="sm"> <Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lh={1.5}
style={{
fontSize: '0.875rem',
lineHeight: '1.5rem',
}}
>
{new Date(featured.createdAt).toLocaleDateString('id-ID', { {new Date(featured.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
@@ -91,7 +111,9 @@ export default function Content({ kategori }: { kategori: string }) {
<Button <Button
variant="light" variant="light"
rightSection={<IconArrowRight size={16} />} rightSection={<IconArrowRight size={16} />}
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)} onClick={() =>
router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)
}
> >
Baca Selengkapnya Baca Selengkapnya
</Button> </Button>
@@ -105,19 +127,29 @@ export default function Content({ kategori }: { kategori: string }) {
{/* === Daftar Berita === */} {/* === Daftar Berita === */}
<Box mt={50}> <Box mt={50}>
<Title order={2} mb="md">Daftar Berita</Title> <Title order={2} mb="md">
Daftar Berita
</Title>
<Divider mb="xl" /> <Divider mb="xl" />
{state.findMany.loading ? ( {state.findMany.loading ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl"> <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
{Array(3).fill(0).map((_, i) => ( {Array(3)
.fill(0)
.map((_, i) => (
<Skeleton key={i} h={300} radius="md" /> <Skeleton key={i} h={300} radius="md" />
))} ))}
</SimpleGrid> </SimpleGrid>
) : paginatedNews.length === 0 ? ( ) : paginatedNews.length === 0 ? (
<Text c="dimmed" ta="center">Belum ada berita di kategori &quot;{kategori}&quot;.</Text> <Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Belum ada berita di kategori &quot;{kategori}&quot;.
</Text>
) : ( ) : (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl"> <SimpleGrid
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="xl"
verticalSpacing="xl"
>
{paginatedNews.map((item) => ( {paginatedNews.map((item) => (
<Card <Card
key={item.id} key={item.id}
@@ -125,19 +157,51 @@ export default function Content({ kategori }: { kategori: string }) {
p="lg" p="lg"
radius="md" radius="md"
withBorder withBorder
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)} onClick={() =>
router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)
}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
> >
<Card.Section> <Card.Section>
<Image src={item.image?.link} height={200} alt={item.judul} fit="cover" loading="lazy"/> <Image
src={item.image?.link}
height={200}
alt={item.judul}
fit="cover"
loading="lazy"
/>
</Card.Section> </Card.Section>
<Badge color="blue" variant="light" mt="md"> <Badge color="blue" variant="light" mt="md">
{item.kategoriBerita?.name || kategori} {item.kategoriBerita?.name || kategori}
</Badge> </Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text> <Title
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} /> order={4}
mt="sm"
fz={{ base: 'sm', md: 'md' }}
style={{ lineHeight: 1.4 }}
lineClamp={2}
>
{item.judul}
</Title>
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lineClamp={3}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
lineHeight: 1.5,
}}
mt="xs"
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
<Group justify="apart" mt="md" gap="xs"> <Group justify="apart" mt="md" gap="xs">
<Text size="xs" c="dimmed"> <Text
fz={{ base: 'xs', md: 'xs' }}
c="dimmed"
lh={1.4}
style={{ fontSize: '0.75rem', lineHeight: '1.125rem' }}
>
{new Date(item.createdAt).toLocaleDateString('id-ID', { {new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',

View File

@@ -3,18 +3,16 @@
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita'; import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import NewsReader from '@/app/darmasaba/_com/NewsReader'; import NewsReader from '@/app/darmasaba/_com/NewsReader';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Center, Container, Group, Image, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function Page() { function Page() {
const params = useParams<{ id: string }>(); const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id; const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(stateDashboardBerita.berita) const state = useProxy(stateDashboardBerita.berita);
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
@@ -27,9 +25,9 @@ function Page() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
} };
loadData() loadData();
}, [id]) }, [id]);
if (loading) { if (loading) {
return ( return (
@@ -47,41 +45,49 @@ function Page() {
); );
} }
return ( return (
<Stack pos={"relative"} bg={colors.Bg} pb={"xl"} gap={"xs"} px={{ base: "md", md: 0 }}> <Stack pos="relative" bg={colors.Bg} pb="xl" gap="xs" px={{ base: 'md', md: 0 }}>
<Group px={{ base: "md", md: 100 }}> <Group px={{ base: 'md', md: 100 }}>
<NewsReader /> <NewsReader />
</Group> </Group>
<Container w={{ base: "100%", md: "50%" }} > <Container w={{ base: '100%', md: '50%' }}>
<Box pb={20}> <Box pb={20}>
<Text id='news-title' ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}> <Title
{state.findUnique.data?.judul} id="news-title"
</Text> order={1}
<Text ta="center"
ta={"center"} c={colors['blue-button']}
fw={"bold"} fw="bold"
fz={"1.5rem"} lh={{ base: 1.2, md: 1.25 }}
>
{state.findUnique.data.judul}
</Title>
<Title
order={2}
ta="center"
fw="bold"
fz={{ base: 'md', md: 'lg' }}
lh={{ base: 1.3, md: 1.35 }}
> >
Informasi dan Pelayanan Administrasi Digital Informasi dan Pelayanan Administrasi Digital
</Text> </Title>
</Box> </Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy" /> <Image src={state.findUnique.data.image?.link || ''} alt="" w="100%" loading="lazy" />
</Container> </Container>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<Stack gap={"xs"}> <Stack gap="xs">
<Text <Text
id='news-content' id="news-content"
py={20} py={20}
fz={{ base: "sm", md: "lg" }} fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif lh={{ base: 1.6, md: 1.8 }}
ta="justify" ta="justify"
style={{ style={{
wordBreak: "break-word", wordBreak: 'break-word',
whiteSpace: "normal", whiteSpace: 'normal',
}} }}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: state.findUnique.data?.content || "", __html: state.findUnique.data.content || '',
}} }}
/> />
</Stack> </Stack>

View File

@@ -16,35 +16,30 @@ function Semua() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useTransitionRouter(); const router = useTransitionRouter();
// Ambil parameter langsung dari URL
const search = searchParams.get('search') || ''; const search = searchParams.get('search') || '';
const page = parseInt(searchParams.get('page') || '1'); const page = parseInt(searchParams.get('page') || '1');
// Gunakan proxy untuk state global
const state = useProxy(stateDashboardBerita.berita); const state = useProxy(stateDashboardBerita.berita);
const featured = useProxy(stateDashboardBerita.berita.findFirst); const featured = useProxy(stateDashboardBerita.berita.findFirst);
const loadingGrid = state.findMany.loading; const loadingGrid = state.findMany.loading;
const loadingFeatured = featured.loading; const loadingFeatured = featured.loading;
// Load berita utama sekali saja
useEffect(() => { useEffect(() => {
if (!featured.data && !loadingFeatured) { if (!featured.data && !loadingFeatured) {
stateDashboardBerita.berita.findFirst.load(); stateDashboardBerita.berita.findFirst.load();
} }
}, [featured.data, loadingFeatured]); }, [featured.data, loadingFeatured]);
// Load berita terbaru tiap page / search berubah
useEffect(() => { useEffect(() => {
const limit = 3; const limit = 3;
state.findMany.load(page, limit, search); state.findMany.load(page, limit, search);
}, [page, search]); }, [page, search]);
// Handler pagination → langsung update URL
const handlePageChange = (newPage: number) => { const handlePageChange = (newPage: number) => {
const url = new URLSearchParams(searchParams.toString()); const url = new URLSearchParams(searchParams.toString());
if (search) url.set('search', search); if (search) url.set('search', search);
if (newPage > 1) url.set('page', newPage.toString()); if (newPage > 1) url.set('page', newPage.toString());
else url.delete('page'); // biar page=1 ga muncul di URL else url.delete('page');
router.replace(`?${url.toString()}`); router.replace(`?${url.toString()}`);
}; };
@@ -61,7 +56,7 @@ function Semua() {
<Center><Skeleton h={400} /></Center> <Center><Skeleton h={400} /></Center>
) : featuredData ? ( ) : featuredData ? (
<Box mb={50}> <Box mb={50}>
<Text fz="h2" fw={700} mb="md">Berita Utama</Text> <Title order={2} mb="md">Berita Utama</Title>
<Paper shadow="md" radius="md" withBorder> <Paper shadow="md" radius="md" withBorder>
<Grid gutter={0}> <Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}> <GridCol span={{ base: 12, md: 6 }}>
@@ -81,13 +76,24 @@ function Semua() {
<Badge color="blue" variant="light" mb="md"> <Badge color="blue" variant="light" mb="md">
{featuredData.kategoriBerita?.name || 'Berita'} {featuredData.kategoriBerita?.name || 'Berita'}
</Badge> </Badge>
<Title order={2} mb="md">{featuredData.judul}</Title> <Title order={3} mb="md">{featuredData.judul}</Title>
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }} /> <Text
c="dimmed"
lineClamp={3}
mb="md"
dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }}
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
/>
</div> </div>
<Group justify="apart" mt="auto"> <Group justify="apart" mt="auto">
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={18} /> <IconCalendar size={18} />
<Text size="sm"> <Text
c="dimmed"
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
>
{new Date(featuredData.createdAt).toLocaleDateString('id-ID', { {new Date(featuredData.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
@@ -124,7 +130,9 @@ function Semua() {
))} ))}
</SimpleGrid> </SimpleGrid>
) : paginatedNews.length === 0 ? ( ) : paginatedNews.length === 0 ? (
<Text c="dimmed" ta="center">Tidak ada berita ditemukan.</Text> <Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={{ base: 1.5, md: 1.6 }}>
Tidak ada berita ditemukan.
</Text>
) : ( ) : (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl"> <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
{paginatedNews.map((item) => ( {paginatedNews.map((item) => (
@@ -143,11 +151,24 @@ function Semua() {
{item.kategoriBerita?.name || 'Berita'} {item.kategoriBerita?.name || 'Berita'}
</Badge> </Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text> <Title order={4} mt="sm" lineClamp={2}>
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} /> {item.judul}
</Title>
<Text
c="dimmed"
lineClamp={3}
mt="xs"
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.5, md: 1.6 }}
/>
<Flex align="center" justify="apart" mt="md" gap="xs"> <Flex align="center" justify="apart" mt="md" gap="xs">
<Text size="xs" c="dimmed"> <Text
c="dimmed"
fz={{ base: 'xs', md: 'xs' }}
lh={{ base: 1.4, md: 1.4 }}
>
{new Date(item.createdAt).toLocaleDateString('id-ID', { {new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',

View File

@@ -17,17 +17,11 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconPhoto } from '@tabler/icons-react'; import { IconPhoto } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
// Komponen kartu foto // Komponen kartu foto
function FotoCard({ item }: { item: any }) { function FotoCard({ item }: { item: any }) {
const router = useRouter();
const handleClick = () => {
router.push(`/darmasaba/galeri/foto/${item.id}`);
};
return ( return (
<Grid.Col span={{ base: 12, xs: 6, md: 4 }}> <Grid.Col span={{ base: 12, xs: 6, md: 4 }}>
@@ -35,19 +29,19 @@ function FotoCard({ item }: { item: any }) {
shadow="sm" shadow="sm"
radius="md" radius="md"
p={0} p={0}
onClick={handleClick} style={{ transition: 'transform 0.2s' }}
style={{ cursor: 'pointer', transition: 'transform 0.2s' }}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')} onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')} onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
> >
{item.imageGalleryFoto?.link ? ( {item.imageGalleryFoto?.link ? (
<Box <Box
pos="relative" pos="relative"
style={{ style={{
paddingBottom: '100%', // ✅ Ubah ke 1:1 (square) — atau sesuaikan paddingBottom: '100%',
overflow: 'hidden', overflow: 'hidden',
borderRadius: '4px 4px 0 0', borderRadius: '4px 4px 0 0',
backgroundColor: '#f9f9f9', // ✅ background netral backgroundColor: '#f9f9f9',
}} }}
> >
<Image <Image
@@ -61,8 +55,8 @@ function FotoCard({ item }: { item: any }) {
left: 0, left: 0,
width: '100%', width: '100%',
height: '100%', height: '100%',
objectFit: 'contain', // ✅ Tampilkan utuh, jangan crop objectFit: 'contain',
objectPosition: 'center', // rata tengah objectPosition: 'center',
}} }}
loading="lazy" loading="lazy"
/> />
@@ -74,13 +68,23 @@ function FotoCard({ item }: { item: any }) {
)} )}
<Stack p="md" gap={4}> <Stack p="md" gap={4}>
<Text fw={600} lineClamp={1}> <Text fw={600} lineClamp={1} fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
{item.name || 'Tanpa Judul'} {item.name || 'Tanpa Judul'}
</Text> </Text>
{item.deskripsi && ( {item.deskripsi && (
<Text fz="sm" c="dimmed" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /> <Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
lh={{ base: '1.4', md: '1.5' }}
/>
)} )}
<Text fz="xs" c="dimmed"> <Text
fz={{ base: 11, md: 'xs' }}
c="dimmed"
lh={{ base: '1.3', md: '1.4' }}
>
{new Date(item.createdAt).toLocaleDateString('id-ID', { {new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',
@@ -99,7 +103,7 @@ export default function GaleriFotoUser() {
return ( return (
<Box py="xl" px={{ base: 'md', md: 'lg' }}> <Box py="xl" px={{ base: 'md', md: 'lg' }}>
{/* Header */} {/* Header */}
<Title order={2} c={colors['blue-button']} mb="lg"> <Title order={2} c={colors['blue-button']} mb="lg" ta="center">
Galeri Foto Desa Darmasaba Galeri Foto Desa Darmasaba
</Title> </Title>
@@ -115,7 +119,7 @@ function FotoList({ search }: { search: string }) {
const { data, page, totalPages, loading, load } = FotoState.findMany; const { data, page, totalPages, loading, load } = FotoState.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 3, search); // ✅ 9 item per halaman load(page, 3, search);
}, [page, search]); }, [page, search]);
if (loading) { if (loading) {
@@ -135,7 +139,9 @@ function FotoList({ search }: { search: string }) {
<Center py="xl"> <Center py="xl">
<Stack align="center" c="dimmed"> <Stack align="center" c="dimmed">
<IconPhoto size={48} /> <IconPhoto size={48} />
<Text>Tidak ada foto ditemukan</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
Tidak ada foto ditemukan
</Text>
</Stack> </Stack>
</Center> </Center>
); );
@@ -150,7 +156,6 @@ function FotoList({ search }: { search: string }) {
</Grid> </Grid>
{/* Pagination */} {/* Pagination */}
<Center> <Center>
<Pagination <Pagination
value={page} value={page}

View File

@@ -11,7 +11,8 @@ import {
Paper, Paper,
SimpleGrid, SimpleGrid,
Stack, Stack,
Text Text,
Title
} from '@mantine/core'; } from '@mantine/core';
import { useTransitionRouter } from 'next-view-transitions'; import { useTransitionRouter } from 'next-view-transitions';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
@@ -19,15 +20,13 @@ import { useSnapshot } from 'valtio';
export default function VideoContent() { export default function VideoContent() {
const videoState = useSnapshot(stateGallery.video); const videoState = useSnapshot(stateGallery.video);
const router = useTransitionRouter() const router = useTransitionRouter();
const { data, page, totalPages, loading } = videoState.findMany; const { data, page, totalPages, loading } = videoState.findMany;
// Handle search and pagination changes
const loadData = useCallback((pageNum: number, searchTerm: string) => { const loadData = useCallback((pageNum: number, searchTerm: string) => {
stateGallery.video.findMany.load(pageNum, 3, searchTerm.trim()); stateGallery.video.findMany.load(pageNum, 3, searchTerm.trim());
}, []); }, []);
// Initial load and URL change handler
useEffect(() => { useEffect(() => {
const handleRouteChange = () => { const handleRouteChange = () => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
@@ -57,13 +56,14 @@ export default function VideoContent() {
loadData(newPage, search); loadData(newPage, search);
}; };
const dataVideo = data || []; const dataVideo = data || [];
if (loading && !data) { if (loading && !data) {
return ( return (
<Box py={10}> <Box py={10}>
<Text>Memuat Video...</Text> <Text fz={{ base: 'sm', md: 'md' }} c="dimmed" ta="center">
Memuat Video...
</Text>
</Box> </Box>
); );
} }
@@ -78,9 +78,8 @@ export default function VideoContent() {
p="md" p="md"
radius={26} radius={26}
bg={colors['white-trans-1']} bg={colors['white-trans-1']}
w={{ base: '100%', md: '100%' }} w="100%"
> >
<Box>
<Center> <Center>
<Box <Box
component="iframe" component="iframe"
@@ -92,41 +91,58 @@ export default function VideoContent() {
style={{ borderRadius: 8 }} style={{ borderRadius: 8 }}
/> />
</Center> </Center>
</Box>
<Box>
<Stack gap="sm" py={10}> <Stack gap="sm" py={10}>
<Text fz="sm" c="dimmed"> {/* Tanggal: Caption */}
<Text
fz={{ base: 12, md: 14 }}
c="dimmed"
ta="left"
>
{new Date(v.createdAt).toLocaleDateString('id-ID', { {new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
year: 'numeric', year: 'numeric',
})} })}
</Text> </Text>
<Text fw="bold" fz="sm" lineClamp={1}>
{/* Judul Video: Subsection (H3) */}
<Title
order={3}
c="dark"
ta="left"
lh={1.3}
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
>
{v.name} {v.name}
</Text> </Title>
{/* Deskripsi: Body kecil */}
<Text <Text
ta="justify" ta="justify"
fz="sm" fz={{ base: 13, md: 14 }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }} c="dimmed"
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: 'break-word' }}
lineClamp={3} lineClamp={3}
truncate="end" >
/> <span dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
<Group justify={"right"}> </Text>
<Group justify="right">
<Button <Button
onClick={() => router.push(`/darmasaba/desa/galery/video/${v.id}`)} onClick={() => router.push(`/darmasaba/desa/galery/video/${v.id}`)}
bg={colors['blue-button']} bg={colors['blue-button']}
fz={{ base: 'sm', md: 'md' }}
> >
Detail Detail
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</Box>
</Paper> </Paper>
</Box> </Box>
))} ))}
</SimpleGrid> </SimpleGrid>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
@@ -140,7 +156,6 @@ export default function VideoContent() {
); );
} }
// ✅ Fix: convert YouTube URL ke embed
function convertToEmbedUrl(youtubeUrl: string): string { function convertToEmbedUrl(youtubeUrl: string): string {
try { try {
const url = new URL(youtubeUrl); const url = new URL(youtubeUrl);

View File

@@ -12,16 +12,17 @@ import {
Stack, Stack,
Text, Text,
ThemeIcon, ThemeIcon,
Title,
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconInfoCircle, IconVideo } from '@tabler/icons-react'; import { IconArrowBack, IconInfoCircle, IconVideo } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; // pastikan state bisa dipakai di publik import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import BackButton from '../../../layanan/_com/BackButto'; import BackButton from '../../../layanan/_com/BackButto';
// Fungsi helper: aman dan tanpa spasi
function convertToEmbedUrl(youtubeUrl: string): string { function convertToEmbedUrl(youtubeUrl: string): string {
try { try {
const url = new URL(youtubeUrl); const url = new URL(youtubeUrl);
@@ -72,7 +73,9 @@ export default function DetailVideoUser() {
color="red" color="red"
radius="md" radius="md"
> >
<Text fz={{ base: 'sm', md: 'md' }} c="red.9">
Video yang Anda cari tidak tersedia. Video yang Anda cari tidak tersedia.
</Text>
</Alert> </Alert>
<Button <Button
leftSection={<IconArrowBack size={16} />} leftSection={<IconArrowBack size={16} />}
@@ -91,20 +94,20 @@ export default function DetailVideoUser() {
return ( return (
<Box py="xl" px={{ base: 'md', md: 100 }}> <Box py="xl" px={{ base: 'md', md: 100 }}>
{/* Tombol Kembali */} {/* Tombol Kembali */}
<Box > <Box>
<BackButton /> <BackButton />
</Box> </Box>
{/* Header */} {/* Header - Dijadikan Title */}
<Text <Title
order={1}
ta="center" ta="center"
fz={{ base: 'xl', md: '2xl' }}
fw={700}
c={colors['blue-button']} c={colors['blue-button']}
mb="lg" mb="lg"
lh={{ base: 1.2, md: 1.25 }}
> >
{data.name || 'Video Galeri Desa'} {data.name || 'Video Galeri Desa'}
</Text> </Title>
{/* Konten Utama */} {/* Konten Utama */}
<Card <Card
@@ -118,7 +121,7 @@ export default function DetailVideoUser() {
{embedUrl ? ( {embedUrl ? (
<Box <Box
pos="relative" pos="relative"
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }} // 16:9 aspect ratio style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }}
> >
<iframe <iframe
src={embedUrl} src={embedUrl}
@@ -144,7 +147,9 @@ export default function DetailVideoUser() {
title="Gagal memuat video" title="Gagal memuat video"
radius="md" radius="md"
> >
<Text fz={{ base: 'xs', md: 'sm' }} c="orange.9">
Mohon maaf, video tidak dapat diputar. Mohon maaf, video tidak dapat diputar.
</Text>
</Alert> </Alert>
) : ( ) : (
<Alert <Alert
@@ -153,7 +158,9 @@ export default function DetailVideoUser() {
title="Tidak ada video" title="Tidak ada video"
radius="md" radius="md"
> >
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
Konten video belum tersedia. Konten video belum tersedia.
</Text>
</Alert> </Alert>
)} )}
@@ -163,7 +170,11 @@ export default function DetailVideoUser() {
<ThemeIcon variant="light" size="sm" radius="xl"> <ThemeIcon variant="light" size="sm" radius="xl">
<IconInfoCircle size={14} /> <IconInfoCircle size={14} />
</ThemeIcon> </ThemeIcon>
<Text fz="sm" c="dimmed"> <Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lh={{ base: 1.4, md: 1.5 }}
>
Diunggah pada{' '} Diunggah pada{' '}
{new Date(data.createdAt).toLocaleDateString('id-ID', { {new Date(data.createdAt).toLocaleDateString('id-ID', {
weekday: 'long', weekday: 'long',
@@ -179,8 +190,9 @@ export default function DetailVideoUser() {
{data.deskripsi && ( {data.deskripsi && (
<Paper p="md" bg="gray.0" radius="md"> <Paper p="md" bg="gray.0" radius="md">
<Text <Text
fz="md" fz={{ base: 'sm', md: 'md' }}
c="dark" c="dark"
ta={"justify"}
dangerouslySetInnerHTML={{ __html: data.deskripsi }} dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ style={{
wordBreak: 'break-word', wordBreak: 'break-word',

View File

@@ -3,7 +3,7 @@
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react'; import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -39,30 +39,38 @@ function PelayananPendudukNonPermanent() {
) : ( ) : (
<Stack gap="xl"> <Stack gap="xl">
<Box> <Box>
<Text fz={{ base: "xl", md: "2xl" }} fw={700} lh={1.3} c="dark"> <Title
order={1}
fz={{ base: 'lg', md: 'xl' }}
fw={700}
lh={{ base: 1.3, md: 1.3 }}
c="dark"
>
{data?.name || "Judul belum tersedia"} {data?.name || "Judul belum tersedia"}
</Text> </Title>
</Box> </Box>
<Box> <Box>
{data?.deskripsi ? ( {data?.deskripsi ? (
<Text <Text
fz={{ base: "sm", md: "md" }} fz={{ base: 'sm', md: 'md' }}
lh={1.7} lh={{ base: 1.6, md: 1.7 }}
ta="justify" ta="justify"
c="dimmed" c="black"
dangerouslySetInnerHTML={{ __html: data?.deskripsi }} dangerouslySetInnerHTML={{ __html: data?.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/> />
) : ( ) : (
<Text fz="sm" c="gray">Deskripsi belum tersedia.</Text> <Text fz="xs" c="gray">
Deskripsi belum tersedia.
</Text>
)} )}
</Box> </Box>
<Divider color={colors["blue-button"]} size="sm" /> <Divider color={colors["blue-button"]} size="sm" />
<Flex justify="space-between" align="center" wrap="wrap" gap="md"> <Flex justify="space-between" align="center" wrap="wrap" gap="md">
<Text fz={{ base: "xs", md: "sm" }} c="dimmed"> <Text fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.5 }} c="black">
25 Mei 2021 Darmasaba 25 Mei 2021 Darmasaba
</Text> </Text>
<Group gap="md"> <Group gap="md">

View File

@@ -47,7 +47,7 @@ function PelayananPerizinanBerusaha() {
return ( return (
<Center mih={300}> <Center mih={300}>
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Text fz="lg" fw={500} c="dimmed"> <Text fz={{ base: 'md', md: 'lg' }} fw={500} c="dimmed" lh="sm">
Belum ada informasi layanan yang tersedia Belum ada informasi layanan yang tersedia
</Text> </Text>
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl"> <Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
@@ -67,10 +67,10 @@ function PelayananPerizinanBerusaha() {
) : ( ) : (
<Stack gap="lg"> <Stack gap="lg">
<Box> <Box>
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm"> <Title order={2} fw={700} mb="sm">
Perizinan Berusaha Berbasis Risiko melalui OSS Perizinan Berusaha Berbasis Risiko melalui OSS
</Title> </Title>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed"> <Text fz={{ base: 'sm', md: 'md' }} c="black" lh="sm">
Sistem Online Single Submission (OSS) untuk pendaftaran NIB Sistem Online Single Submission (OSS) untuk pendaftaran NIB
</Text> </Text>
</Box> </Box>
@@ -83,13 +83,13 @@ function PelayananPerizinanBerusaha() {
/> />
<Box> <Box>
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}> <Title order={3} fw={600} mb="sm">
Alur pendaftaran NIB: Alur pendaftaran NIB:
</Text> </Title>
<Stepper <Stepper
active={active} active={active}
onStepClick={(step) => { onStepClick={(step) => {
if (step <= active) { // Only allow clicking on previous or current steps if (step <= active) {
setActive(step); setActive(step);
} }
}} }}
@@ -102,28 +102,42 @@ function PelayananPerizinanBerusaha() {
}} }}
> >
<StepperStep label="Langkah 1" description="Daftar Akun"> <StepperStep label="Langkah 1" description="Daftar Akun">
<Text fz="sm">Membuat akun di portal OSS</Text> <Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Membuat akun di portal OSS
</Text>
</StepperStep> </StepperStep>
<StepperStep label="Langkah 2" description="Isi Data Perusahaan"> <StepperStep label="Langkah 2" description="Isi Data Perusahaan">
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text> <Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Lengkapi informasi perusahaan, data pemegang saham, dan alamat
</Text>
</StepperStep> </StepperStep>
<StepperStep label="Langkah 3" description="Pilih KBLI"> <StepperStep label="Langkah 3" description="Pilih KBLI">
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text> <Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Menentukan kode KBLI sesuai jenis usaha
</Text>
</StepperStep> </StepperStep>
<StepperStep label="Langkah 4" description="Unggah Dokumen"> <StepperStep label="Langkah 4" description="Unggah Dokumen">
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text> <Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Unggah akta pendirian, surat izin, dan dokumen wajib lainnya
</Text>
</StepperStep> </StepperStep>
<StepperStep label="Langkah 5" description="Verifikasi Instansi"> <StepperStep label="Langkah 5" description="Verifikasi Instansi">
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text> <Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Menunggu verifikasi dan persetujuan dari pihak berwenang
</Text>
</StepperStep> </StepperStep>
<StepperStep label="Langkah 6" description="Terbit NIB"> <StepperStep label="Langkah 6" description="Terbit NIB">
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text> <Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Menerima NIB sebagai identitas resmi usaha
</Text>
</StepperStep> </StepperStep>
<StepperCompleted> <StepperCompleted>
<Center> <Center>
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<IconCheck size={40} color="green" /> <IconCheck size={40} color="green" />
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text> <Text fz={{ base: 'xs', md: 'sm' }} fw={500} lh="sm">
Proses pendaftaran selesai
</Text>
</Stack> </Stack>
</Center> </Center>
</StepperCompleted> </StepperCompleted>
@@ -159,7 +173,7 @@ function PelayananPerizinanBerusaha() {
)} )}
</Box> </Box>
<Text fz="sm" ta="justify" c="dimmed" mt="md"> <Text fz={{ base: 'xs', md: 'sm' }} ta="justify" c="black" lh="sm" mt="md">
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '} Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer"> <a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
oss.go.id oss.go.id

View File

@@ -2,7 +2,7 @@
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react'; import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -35,7 +35,7 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
<Center py="xl"> <Center py="xl">
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} /> <IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} />
<Text c="dimmed" ta="center"> <Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh="sm">
Tidak ada layanan surat keterangan yang ditemukan Tidak ada layanan surat keterangan yang ditemukan
</Text> </Text>
</Stack> </Stack>
@@ -48,9 +48,9 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
<Group justify="space-between" align="center" mb="md"> <Group justify="space-between" align="center" mb="md">
<Group gap="xs"> <Group gap="xs">
<IconFileDescription size={28} stroke={1.8} /> <IconFileDescription size={28} stroke={1.8} />
<Text fz={{ base: "h4", md: "h2" }} fw={700}> <Title order={2} c="black">
Layanan Surat Keterangan Layanan Surat Keterangan
</Text> </Title>
</Group> </Group>
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow> <Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
<IconInfoCircle size={22} stroke={1.8} /> <IconInfoCircle size={22} stroke={1.8} />
@@ -82,15 +82,15 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
style={{ borderRadius: 16 }} style={{ borderRadius: 16 }}
/> />
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative"> <Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
<Text <Title
order={3}
c="white" c="white"
fw={600}
fz="lg"
ta="center" ta="center"
lineClamp={2} lineClamp={2}
lh="sm"
> >
{v.name} {v.name}
</Text> </Title>
<Group justify="center"> <Group justify="center">
<Button <Button
size="md" size="md"

View File

@@ -42,9 +42,10 @@ function PelayananTelunjukSaktiDesa() {
return ( return (
<Box> <Box>
<Title order={2} mb="lg" fz={{ base: 22, md: 28 }} fw={700} style={{ lineHeight: 1.4 }}> <Title order={2} mb="lg" fw={700} style={{ lineHeight: 1.3 }} ta="left">
Layanan Telunjuk Sakti Desa <br /> Layanan Telunjuk Sakti Desa
<Text span c="dimmed" fz="lg" fw={400}> <Text span c="black" fz={{ base: 'sm', md: 'md' }} fw={400} style={{ lineHeight: 1.5 }}>
{' '}
Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman
</Text> </Text>
</Title> </Title>
@@ -53,7 +54,7 @@ function PelayananTelunjukSaktiDesa() {
<Skeleton h={400} radius="lg" /> <Skeleton h={400} radius="lg" />
) : data.length === 0 ? ( ) : data.length === 0 ? (
<Card shadow="sm" radius="lg" withBorder> <Card shadow="sm" radius="lg" withBorder>
<Text c="dimmed" ta="center" py="xl"> <Text c="black" ta="center" py="xl" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Belum ada layanan tersedia untuk saat ini Belum ada layanan tersedia untuk saat ini
</Text> </Text>
</Card> </Card>
@@ -72,9 +73,9 @@ function PelayananTelunjukSaktiDesa() {
}} }}
> >
<Stack gap="sm"> <Stack gap="sm">
<Text fw={700} fz="lg" lh={1.4}> <Title order={3} fw={700} lh={1.3}>
{v.name} {v.name}
</Text> </Title>
<Flex gap="xs" align="center"> <Flex gap="xs" align="center">
<IconExternalLink size={18} stroke={1.5} /> <IconExternalLink size={18} stroke={1.5} />
<Text <Text
@@ -82,7 +83,7 @@ function PelayananTelunjukSaktiDesa() {
href={v.link} href={v.link}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
fz="sm" fz={{ base: 'xs', md: 'sm' }}
c="blue" c="blue"
td="underline" td="underline"
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}

View File

@@ -1,58 +1,94 @@
'use client' 'use client'
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman'; import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Container, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import BackButton from '../../../layanan/_com/BackButto';
import NewsReader from '@/app/darmasaba/_com/NewsReader'; import NewsReader from '@/app/darmasaba/_com/NewsReader';
import BackButton from '../../../layanan/_com/BackButto';
function Page() { function Page() {
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique) const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique);
const params = useParams();
const params = useParams()
useShallowEffect(() => { useShallowEffect(() => {
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string) stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string);
}, []) }, []);
if (!detail.data) { if (!detail.data) {
return ( return (
<Box> <Box>
<Skeleton h={400} /> <Skeleton h={400} />
</Box> </Box>
) );
} }
return ( return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md"> <Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */} {/* Header */}
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Container size="lg" px="md"> <Container size="lg" px="md">
<Group> <Group>
<NewsReader /> <NewsReader />
</Group> </Group>
<Stack gap="xs" >
<Group justify={"space-between"} align={"center"}> <Stack gap="xs">
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" > <Group justify="space-between" align="flex-start" wrap="wrap">
<Title
order={1}
c={colors['blue-button']}
fz={{ base: 28, md: 36 }}
style={{
wordBreak: 'break-word',
flex: '1 1 auto',
minWidth: 0
}}
>
{detail.data?.judul} {detail.data?.judul}
</Title>
<Paper bg={colors['blue-button']} p={8} style={{ flexShrink: 0 }}>
<Text c={colors['white-1']} fz={{ base: 'xs', md: 'sm' }} lh={1.2}>
{detail.data?.CategoryPengumuman?.name}
</Text> </Text>
<Group justify='end'>
<Paper bg={colors['blue-button']} p={5}>
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
</Paper> </Paper>
</Group> </Group>
</Group>
<Paper bg={colors["white-1"]} p="md"> <Paper
<Text px="lg" id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} /> bg={colors['white-1']}
<Text px="lg" fz={"md"} c={colors["blue-button"]} fw="bold" > p="md"
w="100%"
mih={{ base: 200, md: 300 }}
>
<Text
px="lg"
id="news-content"
fz={{ base: 14, md: 16 }}
lh={{ base: 1.6, md: 1.6 }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
width: '100%'
}}
dangerouslySetInnerHTML={{ __html: detail.data?.content }}
/>
<Text
px="lg"
fz={{ base: 12, md: 14 }}
c={colors['blue-button']}
fw="bold"
lh={{ base: 1.4, md: 1.4 }}
mt="md"
>
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', { {new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
weekday: 'long', weekday: 'long',
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
year: 'numeric' year: 'numeric',
})} })}
</Text> </Text>
</Paper> </Paper>

View File

@@ -2,14 +2,13 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman'; import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Container, Group, Paper, Stack, Text } from '@mantine/core'; import { Box, Container, Group, Paper, Stack, Text, Title } from '@mantine/core';
import { IconCalendar } from '@tabler/icons-react'; import { IconCalendar } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto'; import BackButton from '../../layanan/_com/BackButto';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
function Page() { function Page() {
const unwrappedParams = useParams(); const unwrappedParams = useParams();
const kategoriState = useProxy(stateDesaPengumuman); const kategoriState = useProxy(stateDesaPengumuman);
@@ -26,45 +25,82 @@ function Page() {
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" > <Container size="lg" px="md">
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center"> <Stack align="center" gap="xs">
<Title
order={1}
c={colors["blue-button"]}
ta="center"
style={{ fontWeight: 'bold' }}
>
{categoryName.split('-').map(word => {categoryName.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1) word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')} ).join(' ')}
</Text> </Title>
<Text ta="center" px="md" pb={10}> <Text
ta="center"
px="md"
pb="sm"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
c="dimmed"
>
Informasi dan pengumuman resmi terkait {categoryName.split('-').join(' ')} Informasi dan pengumuman resmi terkait {categoryName.split('-').join(' ')}
</Text> </Text>
</Stack> </Stack>
</Container> </Container>
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
{!kategoriState.pengumuman.findMany.data?.length ? ( {!kategoriState.pengumuman.findMany.data?.length ? (
<Paper p="lg" radius="md" shadow="md" bg={colors["white-1"]}> <Paper p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text
fz={{ base: 'sm', md: 'md' }}
ta="center"
c="dimmed"
>
Tidak ada pengumuman yang ditemukan Tidak ada pengumuman yang ditemukan
</Text>
</Paper> </Paper>
) : kategoriState.pengumuman.findMany.data?.map((v, k) => { ) : (
return ( kategoriState.pengumuman.findMany.data?.map((v, k) => (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}> <Paper
<Text fz={'h3'}>{v.judul}</Text> mb="md"
<Group style={{ color: 'black' }} pb={20}> key={k}
withBorder
p="lg"
radius="md"
shadow="md"
bg={colors["white-1"]}
>
<Title order={3}>{v.judul}</Title>
<Group style={{ color: 'black' }} pb="sm">
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={18} /> <IconCalendar size={18} />
<Text size="sm"> <Text
{v.createdAt ? new Date(v.createdAt).toLocaleDateString('id-ID', { fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
>
{v.createdAt
? new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
year: 'numeric', year: 'numeric',
}) : 'No date available'} })
: 'No date available'}
</Text> </Text>
</Group> </Group>
</Group> </Group>
<Text ta={'justify'}> <Text
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
>
{v.deskripsi} {v.deskripsi}
</Text> </Text>
</Paper> </Paper>
) ))
})} )}
</Box> </Box>
</Stack> </Stack>
); );

View File

@@ -9,7 +9,6 @@ import {
Center, Center,
Container, Container,
Divider, Divider,
Flex,
Grid, Grid,
GridCol, GridCol,
Group, Group,
@@ -22,7 +21,7 @@ import {
Text, Text,
TextInput, TextInput,
Title, Title,
UnstyledButton, UnstyledButton
} from '@mantine/core'; } from '@mantine/core';
import { IconCalendar, IconClock, IconSearch } from '@tabler/icons-react'; import { IconCalendar, IconClock, IconSearch } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions'; import { useTransitionRouter } from 'next-view-transitions';
@@ -98,10 +97,14 @@ function Page() {
<Container size="lg" px="md"> <Container size="lg" px="md">
<Stack align="center" gap="0"> <Stack align="center" gap="0">
<Text fz={{ base: '2rem', md: '3.4rem' }} c={colors['blue-button']} fw="bold" ta="center"> <Title
order={1}
c={colors['blue-button']}
ta="center"
>
Pengumuman Desa Darmasaba Pengumuman Desa Darmasaba
</Text> </Title>
<Text ta="center" px="md" pb={10}> <Text ta="center" px="md" pb={10} fz={{ base: 'sm', md: 'md' }} lh="sm">
Informasi dan pengumuman resmi terkait kegiatan dan kebijakan Desa Darmasaba Informasi dan pengumuman resmi terkait kegiatan dan kebijakan Desa Darmasaba
</Text> </Text>
</Stack> </Stack>
@@ -126,17 +129,17 @@ function Page() {
withCloseButton={false} withCloseButton={false}
title={item.CategoryPengumuman?.name || 'Pengumuman'} title={item.CategoryPengumuman?.name || 'Pengumuman'}
> >
<Stack gap={"xs"}> <Stack gap="xs">
<Text fz="sm" fw="bold" c="black" style={{ textTransform: 'uppercase' }}> <Text fz={{ base: 'sm', md: 'sm' }} fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
{item.judul} {item.judul}
</Text> </Text>
<Text ta="justify" fz="sm" c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /> <Text ta="justify" fz={{ base: 'xs', md: 'sm' }} c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Stack> </Stack>
<Flex pt={20} gap="md" justify="space-between"> <Group pt={20} gap="md" justify="space-between">
<Group style={{ color: 'black' }}> <Group style={{ color: 'black' }}>
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={18} /> <IconCalendar size={18} />
<Text size="sm"> <Text fz={{ base: 'xs', md: 'sm' }}>
{new Date(item.createdAt).toLocaleDateString('id-ID', { {new Date(item.createdAt).toLocaleDateString('id-ID', {
weekday: 'long', weekday: 'long',
day: 'numeric', day: 'numeric',
@@ -147,7 +150,7 @@ function Page() {
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconClock size={18} /> <IconClock size={18} />
<Text size="sm"> <Text fz={{ base: 'xs', md: 'sm' }}>
{new Date(item.createdAt).toLocaleTimeString('id-ID', { {new Date(item.createdAt).toLocaleTimeString('id-ID', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
@@ -157,11 +160,11 @@ function Page() {
</Group> </Group>
</Group> </Group>
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}> <Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
<Text fs="unset" c={colors['blue-button']} fz="sm"> <Text fs="unset" c={colors['blue-button']} fz={{ base: 'xs', md: 'sm' }}>
Baca Selengkapnya Baca Selengkapnya
</Text> </Text>
</Anchor> </Anchor>
</Flex> </Group>
</Notification> </Notification>
)) ))
)} )}
@@ -169,19 +172,19 @@ function Page() {
<Paper p="md"> <Paper p="md">
<Stack gap="xs"> <Stack gap="xs">
<Text fw="bold" fz="lg" c={colors['blue-button']}> <Title order={3} c={colors['blue-button']}>
Kategori Kategori
</Text> </Title>
{stateDesaPengumuman.category.findMany.data?.map((v: any, k) => { {stateDesaPengumuman.category.findMany.data?.map((v: any, k) => {
const count = v._count?.pengumumans || 0; const count = v._count?.pengumumans || 0;
return ( return (
<UnstyledButton component={Link} href={`/darmasaba/desa/pengumuman/${v.name}`} key={k}> <UnstyledButton component={Link} href={`/darmasaba/desa/pengumuman/${v.name}`} key={k}>
<Paper bg={colors['BG-trans']} p={5}> <Paper bg={colors['BG-trans']} p={5}>
<Group px={3} justify="space-between"> <Group px={3} justify="space-between">
<Text fz="md" c="black"> <Text fz={{ base: 'sm', md: 'md' }} c="black">
{v.name} {v.name}
</Text> </Text>
<Text fz="md" c="black"> <Text fz={{ base: 'sm', md: 'md' }} c="black">
{count} {count}
</Text> </Text>
</Group> </Group>
@@ -200,7 +203,7 @@ function Page() {
<Divider mb={10} color={colors['blue-button']} /> <Divider mb={10} color={colors['blue-button']} />
<Grid> <Grid>
<GridCol span={{ base: 12, md: 8 }}> <GridCol span={{ base: 12, md: 8 }}>
<Title order={3}>Daftar Pengumuman</Title> <Title order={2}>Daftar Pengumuman</Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 4 }}> <GridCol span={{ base: 12, md: 4 }}>
<TextInput <TextInput
@@ -210,6 +213,7 @@ function Page() {
w="100%" w="100%"
value={searchInput} value={searchInput}
onChange={(e) => setSearchInput(e.target.value)} onChange={(e) => setSearchInput(e.target.value)}
fz={{ base: 'sm', md: 'md' }}
/> />
</GridCol> </GridCol>
</Grid> </Grid>
@@ -223,7 +227,9 @@ function Page() {
</SimpleGrid> </SimpleGrid>
) : !state.findMany.data?.length ? ( ) : !state.findMany.data?.length ? (
<Notification withCloseButton={false} h={100}> <Notification withCloseButton={false} h={100}>
<Text fz={{ base: 'sm', md: 'md' }} ta="center">
Tidak ada pengumuman yang ditemukan Tidak ada pengumuman yang ditemukan
</Text>
</Notification> </Notification>
) : ( ) : (
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" verticalSpacing="lg"> <SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" verticalSpacing="lg">
@@ -231,26 +237,26 @@ function Page() {
<Paper key={item.id} p="md" withBorder radius="md" h="100%"> <Paper key={item.id} p="md" withBorder radius="md" h="100%">
<Stack h="100%" justify="space-between"> <Stack h="100%" justify="space-between">
<div> <div>
<Text fw={600} c={colors['blue-button']} mb={5}> <Text fw={600} c={colors['blue-button']} mb={5} fz={{ base: 'sm', md: 'md' }}>
{item.CategoryPengumuman?.name || 'Pengumuman'} {item.CategoryPengumuman?.name || 'Pengumuman'}
</Text> </Text>
<Text fz="lg" fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }}> <Text fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }} fz={{ base: 'sm', md: 'lg' }}>
{item.judul} {item.judul}
</Text> </Text>
<Text <Text
fz="sm"
c="dimmed" c="dimmed"
lineClamp={4} lineClamp={4}
dangerouslySetInnerHTML={{ __html: item.deskripsi }} dangerouslySetInnerHTML={{ __html: item.deskripsi }}
mb="md" mb="md"
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
fz={{ base: 'xs', md: 'sm' }}
/> />
</div> </div>
<div> <div>
<Group mb="sm" c="dimmed"> <Group mb="sm" c="dimmed">
<Group gap={5}> <Group gap={5}>
<IconCalendar size={16} /> <IconCalendar size={16} />
<Text size="xs"> <Text fz={{ base: 'xs', md: 'xs' }}>
{new Date(item.createdAt).toLocaleDateString('id-ID', { {new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',
@@ -260,19 +266,19 @@ function Page() {
</Group> </Group>
<Group gap={5}> <Group gap={5}>
<IconClock size={16} /> <IconClock size={16} />
<Text size="xs"> <Text fz={{ base: 'xs', md: 'xs' }}>
{new Date(item.createdAt).toLocaleTimeString('id-ID', { {new Date(item.createdAt).toLocaleTimeString('id-ID', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
})} })}
</Text> </Text>
</Group> </Group>
</Group>
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}> <Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
<Text fw={600} c={colors['blue-button']} size="sm"> <Text fw={600} c={colors['blue-button']} fz={{ base: 'sm', md: 'sm' }}>
Baca Selengkapnya Baca Selengkapnya
</Text> </Text>
</Anchor> </Anchor>
</Group>
</div> </div>
</Stack> </Stack>
</Paper> </Paper>
@@ -289,6 +295,7 @@ function Page() {
siblings={1} siblings={1}
boundaries={1} boundaries={1}
withEdges withEdges
fz={{ base: 'xs', md: 'sm' }}
/> />
</Center> </Center>
</Stack> </Stack>

View File

@@ -9,6 +9,7 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto'; import BackButton from '../../layanan/_com/BackButto';
function Page() { function Page() {
const params = useParams<{ id: string }>(); const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id; const id = Array.isArray(params.id) ? params.id[0] : params.id;
@@ -35,7 +36,9 @@ function Page() {
<Center h="80vh"> <Center h="80vh">
<Stack align="center" gap="md"> <Stack align="center" gap="md">
<Loader size="lg" color="blue" /> <Loader size="lg" color="blue" />
<Text c="dimmed" fz="sm">Sedang memuat informasi...</Text> <Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
Sedang memuat informasi...
</Text>
</Stack> </Stack>
</Center> </Center>
); );
@@ -46,28 +49,31 @@ function Page() {
<Center h="80vh"> <Center h="80vh">
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" /> <IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
<Title order={3}>Data Tidak Ditemukan</Title> <Title order={3} ta="center">
<Text c="dimmed" fz="sm">Mohon periksa kembali atau coba beberapa saat lagi</Text> Data Tidak Ditemukan
</Title>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
Mohon periksa kembali atau coba beberapa saat lagi
</Text>
</Stack> </Stack>
</Center> </Center>
); );
} }
return ( return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: "md", md: 0 }}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: 'md', md: 0 }}>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Container w={{ base: "100%", md: "60%" }}> <Container w={{ base: '100%', md: '60%' }}>
<Paper radius="2xl" shadow="lg" p="xl" withBorder> <Paper radius="2xl" shadow="lg" p="xl" withBorder>
<Stack gap="lg" align="center"> <Stack gap="lg" align="center">
<Title ta="center" fz={{ base: "2rem", md: "3rem" }} c={colors["blue-button"]} fw={800}> <Title order={1} ta="center" c={colors['blue-button']} fw={800}>
{state.findUnique.data?.name} {state.findUnique.data?.name}
</Title> </Title>
<Text ta="center" fw={600} fz={{ base: "md", md: "lg" }} c="dimmed"> <Text ta="center" fw={600} fz={{ base: 'md', md: 'lg' }} c="dimmed">
Informasi & Pelayanan Potensi Desa Digital Informasi & Pelayanan Potensi Desa Digital
</Text> </Text>
{/* ✅ Bagian gambar dibuat konsisten tanpa CSS manual */}
<Box <Box
w="100%" w="100%"
h={{ base: 220, md: 400 }} h={{ base: 220, md: 400 }}
@@ -87,7 +93,15 @@ function Page() {
radius="lg" radius="lg"
/> />
</Box> </Box>
<Text py="md" fz={{ base: "sm", md: "md" }} ta="justify" lh={1.8} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || 'Belum ada deskripsi untuk potensi desa ini.' }} /> <Text
py="md"
fz={{ base: 'sm', md: 'md' }}
ta="justify"
lh={{ base: 1.6, md: 1.8 }}
dangerouslySetInnerHTML={{
__html: state.findUnique.data?.content || 'Belum ada deskripsi untuk potensi desa ini.',
}}
/>
</Stack> </Stack>
</Paper> </Paper>
</Container> </Container>

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi'; import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { BackgroundImage, Box, Button, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconEye } from '@tabler/icons-react'; import { IconEye } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions'; import { useTransitionRouter } from 'next-view-transitions';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -41,10 +41,10 @@ function Page() {
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg"> <Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
<Stack gap="sm" maw={600}> <Stack gap="sm" maw={600}>
<Text fz={{ base: "2rem", md: "3rem" }} fw={900} c={colors["blue-button"]} lh={1.2}> <Title order={1} fz={{ base: 28, md: 36 }} lh={1.2} c={colors["blue-button"]}>
Potensi Desa Darmasaba Potensi Desa Darmasaba
</Text> </Title>
<Text fz="lg" ta="justify"> <Text fz={{ base: 14, md: 16 }} lh={1.6} ta="justify">
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa. Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
</Text> </Text>
</Stack> </Stack>
@@ -58,18 +58,18 @@ function Page() {
> >
<Flex justify="center" align="center" gap="xl"> <Flex justify="center" align="center" gap="xl">
<Box> <Box>
<Text ta="center" fz="2rem" fw={800} c="white"> <Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0} {data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
</Text> </Text>
<Text ta="center" fz="sm" c="white" fw={500}> <Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
Potensi Potensi
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text ta="center" fz="2rem" fw={800} c="white"> <Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0} {data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0}
</Text> </Text>
<Text ta="center" fz="sm" c="white" fw={500}> <Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
Wisata Wisata
</Text> </Text>
</Box> </Box>
@@ -98,7 +98,6 @@ function Page() {
transition: 'transform 0.3s ease' transition: 'transform 0.3s ease'
}} }}
> >
{/* Overlay with smooth transition */}
<Box <Box
pos="absolute" pos="absolute"
inset={0} inset={0}
@@ -112,7 +111,6 @@ function Page() {
/> />
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative"> <Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
{/* Kategori badge - always visible */}
<Group> <Group>
<Paper <Paper
radius="lg" radius="lg"
@@ -121,15 +119,12 @@ function Page() {
shadow="md" shadow="md"
withBorder withBorder
bg="rgba(255,255,255,0.9)" bg="rgba(255,255,255,0.9)"
style={{ style={{ transition: 'all 0.3s ease' }}
transition: 'all 0.3s ease'
}}
> >
<Text fz="sm" fw={600}>{v.kategori?.nama}</Text> <Text fz={{ base: 11, md: 14 }} fw={600}>{v.kategori?.nama}</Text>
</Paper> </Paper>
</Group> </Group>
{/* Nama potensi - visible on hover */}
<Box <Box
style={{ style={{
opacity: hoveredId === v.id ? 1 : 0, opacity: hoveredId === v.id ? 1 : 0,
@@ -138,19 +133,19 @@ function Page() {
pointerEvents: hoveredId === v.id ? 'auto' : 'none' pointerEvents: hoveredId === v.id ? 'auto' : 'none'
}} }}
> >
<Text <Title
order={3}
fw={800} fw={800}
c="white" c="white"
fz="xl" fz={{ base: 18, md: 20 }}
ta="center" ta="center"
lineClamp={2} lineClamp={2}
lh={1.3} lh={1.3}
> >
{v.name} {v.name}
</Text> </Title>
</Box> </Box>
{/* Button - visible on hover */}
<Group <Group
justify="center" justify="center"
style={{ style={{
@@ -169,23 +164,21 @@ function Page() {
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }} gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)} onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
> >
Lihat Detail <Text c={'white'} fz={{ base: 12, md: 14 }} fw={500}>Lihat Detail</Text>
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</BackgroundImage> </BackgroundImage>
)) ))
) : ( ) : (
<Center h={240}>
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<Text fz="lg" fw={600} c="dimmed"> <Text fz={{ base: 14, md: 16 }} fw={600} c="dimmed">
Belum ada potensi desa Belum ada potensi desa
</Text> </Text>
<Text fz="sm" c="dimmed"> <Text fz={{ base: 12, md: 14 }} c="dimmed">
Data potensi akan tampil di sini setelah tersedia. Data potensi akan tampil di sini setelah tersedia.
</Text> </Text>
</Stack> </Stack>
</Center>
)} )}
</SimpleGrid> </SimpleGrid>
</Box> </Box>

View File

@@ -26,7 +26,6 @@ function DetailPegawaiUser() {
statePegawai.findUnique.load(params?.id as string); statePegawai.findUnique.load(params?.id as string);
}, []); }, []);
if (!statePegawai.findUnique.data) { if (!statePegawai.findUnique.data) {
return ( return (
<Stack py="lg"> <Stack py="lg">
@@ -41,7 +40,7 @@ function DetailPegawaiUser() {
<Box px={{ base: 'md', md: 100 }} py="xl"> <Box px={{ base: 'md', md: 100 }} py="xl">
{/* Back button */} {/* Back button */}
<Group mb="lg" px={{ base: 'md', md: 100 }}> <Group mb="lg" px={{ base: 'md', md: 100 }}>
<BackButton/> <BackButton />
</Group> </Group>
<Paper <Paper
@@ -69,11 +68,17 @@ function DetailPegawaiUser() {
/> />
{/* Nama & Jabatan */} {/* Nama & Jabatan */}
<Stack align="center" gap={2}> <Stack align="center" gap={4}>
<Title order={3} fw={700} c={colors['blue-button']}> {/* Title utama → H2 karena ini judul profil */}
<Title order={2} c={colors['blue-button']} lh={1.2}>
{data.namaLengkap || '-'} {data.gelarAkademik || ''} {data.namaLengkap || '-'} {data.gelarAkademik || ''}
</Title> </Title>
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'sm', md: 'md' }}
lh={1.4}
c="dimmed"
>
{data.posisi?.nama || 'Posisi tidak tersedia'} {data.posisi?.nama || 'Posisi tidak tersedia'}
</Text> </Text>
</Stack> </Stack>
@@ -82,7 +87,11 @@ function DetailPegawaiUser() {
<Divider my="lg" /> <Divider my="lg" />
{/* Informasi Detail */} {/* Informasi Detail */}
<Stack gap="md"> <Stack gap="lg">
<Title order={3} lh={1.3}>
Informasi Pegawai
</Title>
<InfoRow label="Email" value={data.email} /> <InfoRow label="Email" value={data.email} />
<InfoRow label="Telepon" value={data.telepon} /> <InfoRow label="Telepon" value={data.telepon} />
<InfoRow label="Alamat" value={data.alamat} multiline /> <InfoRow label="Alamat" value={data.alamat} multiline />
@@ -123,11 +132,18 @@ function InfoRow({
}) { }) {
return ( return (
<Box> <Box>
<Text fz="sm" fw={600} c="dark"> <Text
fz={{ base: 'sm', md: 'md' }}
fw={600}
lh={1.3}
c="dark"
>
{label} {label}
</Text> </Text>
<Text <Text
fz="sm" fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c={valueColor || 'dimmed'} c={valueColor || 'dimmed'}
style={{ style={{
whiteSpace: multiline ? 'normal' : 'nowrap', whiteSpace: multiline ? 'normal' : 'nowrap',

View File

@@ -36,11 +36,12 @@ import { useTransitionRouter } from 'next-view-transitions'
import { OrganizationChart } from 'primereact/organizationchart' import { OrganizationChart } from 'primereact/organizationchart'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils' import { useProxy } from 'valtio/utils'
import './struktur.css'
import BackButton from '../_com/BackButto'
import { useMediaQuery } from '@mantine/hooks'
export default function StrukturPerangkatDesa() { import './struktur.css'
import { useMediaQuery } from '@mantine/hooks'
import BackButton from '../_com/BackButto'
export default function Page() {
return ( return (
<Box <Box
style={{ style={{
@@ -59,10 +60,11 @@ export default function StrukturPerangkatDesa() {
ta="center" ta="center"
c={colors['blue-button']} c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }} fz={{ base: 28, md: 36, lg: 44 }}
lh={{ base: 1.05, md: 1.03 }}
> >
Struktur Perangkat Desa Struktur Perangkat Desa
</Title> </Title>
<Text ta="center" c="black" maw={800}> <Text ta="center" c="black" maw={800} fz={{ base: 13, md: 15 }} lh={1.45}>
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
untuk melihat detail atau klik node untuk fokus tampilan. untuk melihat detail atau klik node untuk fokus tampilan.
</Text> </Text>
@@ -105,8 +107,8 @@ function StrukturPerangkatDesaNode() {
<Center py={48}> <Center py={48}>
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Loader size="lg" /> <Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text> <Text fw={600} fz={{ base: 15, md: 16 }} lh={1.2}>Memuat struktur organisasi</Text>
<Text c="dimmed" size="sm"> <Text c="dimmed" fz={{ base: 12, md: 13 }} lh={1.4}>
Mengambil data pegawai dan posisi. Mohon tunggu sebentar. Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
</Text> </Text>
</Stack> </Stack>
@@ -132,10 +134,10 @@ function StrukturPerangkatDesaNode() {
<Center> <Center>
<IconUsers size={56} /> <IconUsers size={56} />
</Center> </Center>
<Title order={3} mt="md"> <Title order={3} mt="md" fz={{ base: 16, md: 18 }} lh={1.15}>
Data pegawai belum tersedia Data pegawai belum tersedia
</Title> </Title>
<Text c="dimmed" mt="xs"> <Text c="dimmed" mt="xs" fz={{ base: 13, md: 14 }} lh={1.4}>
Belum ada data pegawai yang tercatat untuk PPID. Belum ada data pegawai yang tercatat untuk PPID.
</Text> </Text>
<Group justify="center" mt="lg"> <Group justify="center" mt="lg">
@@ -232,11 +234,18 @@ function StrukturPerangkatDesaNode() {
{/* 🔍 Controls */} {/* 🔍 Controls */}
<Paper <Paper
shadow="xs" shadow="xs"
w={{
base: '100%', // Mobile: 100%
sm: '40%', // Tablet: 95%
md: '39%', // Desktop: 70%
lg: '38%', // Desktop L: 60%
xl: '37%', // 4K: 50%
'2xl': '36%', // Ultra-wide: 45%
}}
p="md" p="md"
radius="md" radius="md"
style={{ style={{
background: colors['blue-button'], background: colors['blue-button'], // ⬅️ penting
width: '100%', // ⬅️ penting
maxWidth: '100%', // ⬅️ penting maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow overflowX: 'auto' // ⬅️ untuk mencegah overflow
}} }}
@@ -269,30 +278,33 @@ function StrukturPerangkatDesaNode() {
fontSize: '0.875rem', fontSize: '0.875rem',
padding: '6px 12px', padding: '6px 12px',
minHeight: 'auto', minHeight: 'auto',
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil flexShrink: 0,
}, },
}} }}
style={{ width: '100%' }} // 👈 penting
> >
<TabsList <TabsList
style={{ style={{
display: 'flex', display: 'flex',
overflowX: 'auto', overflowX: 'auto',
overflowY: 'hidden', // 👈 tambahkan ini overflowY: 'hidden',
gap: '4px', gap: '4px',
paddingBottom: '4px', paddingBottom: '4px',
flexWrap: 'nowrap', flexWrap: 'nowrap',
WebkitOverflowScrolling: 'touch', // 👈 smooth scroll di iOS WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'thin', // 👈 scrollbar tipis di Firefox scrollbarWidth: 'thin',
msOverflowStyle: '-ms-autohiding-scrollbar', // 👈 untuk IE/Edge msOverflowStyle: '-ms-autohiding-scrollbar',
maxWidth: '100%',
scrollBehavior: 'smooth', // 👈 smooth scroll
}} }}
> >
<TabsTab <TabsTab
value="zoom-out" value="zoom-out"
onClick={handleZoomOut} onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />} leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil style={{ flexShrink: 0 }}
> >
Zoom Out <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
</TabsTab> </TabsTab>
<Box <Box
@@ -301,7 +313,6 @@ function StrukturPerangkatDesaNode() {
px={12} px={12}
py={6} py={6}
style={{ style={{
fontSize: 14,
fontWeight: 700, fontWeight: 700,
borderRadius: '6px', borderRadius: '6px',
minWidth: 60, minWidth: 60,
@@ -310,10 +321,12 @@ function StrukturPerangkatDesaNode() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
flexShrink: 0, flexShrink: 0,
whiteSpace: 'nowrap', // 👈 mencegah text wrap whiteSpace: 'nowrap',
}} }}
> >
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
{Math.round(scale * 100)}% {Math.round(scale * 100)}%
</Text>
</Box> </Box>
<TabsTab <TabsTab
@@ -322,7 +335,7 @@ function StrukturPerangkatDesaNode() {
leftSection={<IconZoomIn size={16} />} leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
> >
Zoom In <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
</TabsTab> </TabsTab>
<TabsTab <TabsTab
@@ -330,7 +343,7 @@ function StrukturPerangkatDesaNode() {
onClick={resetZoom} onClick={resetZoom}
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
> >
Reset <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
</TabsTab> </TabsTab>
<TabsTab <TabsTab
@@ -345,7 +358,9 @@ function StrukturPerangkatDesaNode() {
} }
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
> >
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
{isFullscreen ? 'Exit' : 'Fullscreen'} {isFullscreen ? 'Exit' : 'Fullscreen'}
</Text>
</TabsTab> </TabsTab>
</TabsList> </TabsList>
</Tabs> </Tabs>
@@ -451,17 +466,17 @@ function NodeCard({ node, router }: any) {
{/* Name */} {/* Name */}
<Text <Text
fw={700} fw={700}
size="sm"
ta="center" ta="center"
c={colors['blue-button']} c={colors['blue-button']}
lineClamp={2} lineClamp={2}
fz={{ base: 13, md: 15 }}
lh={1.2}
style={{ style={{
minHeight: 40, minHeight: 40,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
wordBreak: 'break-word', wordBreak: 'break-word',
lineHeight: 1.3,
}} }}
> >
{name} {name}
@@ -469,18 +484,18 @@ function NodeCard({ node, router }: any) {
{/* Title/Position */} {/* Title/Position */}
<Text <Text
size="xs"
c="dimmed" c="dimmed"
ta="center" ta="center"
fw={500} fw={500}
lineClamp={2} lineClamp={2}
fz={{ base: 12, md: 13 }}
lh={1.3}
style={{ style={{
minHeight: 32, minHeight: 32,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
wordBreak: 'break-word', wordBreak: 'break-word',
lineHeight: 1.2,
}} }}
> >
{title} {title}
@@ -496,14 +511,14 @@ function NodeCard({ node, router }: any) {
mt={8} mt={8}
radius="md" radius="md"
onClick={() => onClick={() =>
router.push(`/darmasaba/desa/profile/struktur-perangkat-desa/${node.data.id}`) router.push(`/darmasaba/desa/profil/struktur-perangkat-desa/${node.data.id}`)
} }
style={{ style={{
height: 32, height: 32,
fontWeight: 600, fontWeight: 600,
}} }}
> >
Lihat Detail <Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
</Button> </Button>
)} )}
</Stack> </Stack>

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile' import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
import colors from '@/con/colors' import colors from '@/con/colors'
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core' import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useProxy } from 'valtio/utils' import { useProxy } from 'valtio/utils'
@@ -26,6 +26,8 @@ function LambangDesa() {
return ( return (
<Box> <Box>
<Stack align="center" gap="lg"> <Stack align="center" gap="lg">
{/* HEADER */}
<Box pb="lg"> <Box pb="lg">
<Center> <Center>
<Image <Image
@@ -36,17 +38,20 @@ function LambangDesa() {
loading="lazy" loading="lazy"
/> />
</Center> </Center>
<Text
{/* TITLE - H1 */}
<Title
order={1}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
fw={800}
fz={{ base: 28, md: 40 }}
mt="sm" mt="sm"
style={{ letterSpacing: '-0.5px' }} style={{ letterSpacing: '-0.5px' }}
> >
Lambang Desa Lambang Desa
</Text> </Title>
</Box> </Box>
{/* DESKRIPSI */}
<Paper <Paper
p="xl" p="xl"
radius="xl" radius="xl"
@@ -59,14 +64,19 @@ function LambangDesa() {
}} }}
> >
<Text <Text
fz={{ base: '1.125rem', md: '1.375rem' }} fz={{ base: 'sm', md: 'md' }} // Body text mobile & desktop
lh={1.8} lh={1.7}
c="dark" c="dark"
ta="justify" ta="justify"
style={{ fontWeight: 400, wordBreak: "break-word", whiteSpace: "normal", }} style={{
fontWeight: 400,
wordBreak: "break-word",
whiteSpace: "normal",
}}
dangerouslySetInnerHTML={{ __html: data.deskripsi }} dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/> />
</Paper> </Paper>
</Stack> </Stack>
</Box> </Box>
) )

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core'; import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text, Title } from '@mantine/core';
import { IconPhoto } from '@tabler/icons-react'; import { IconPhoto } from '@tabler/icons-react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -21,7 +21,9 @@ function MaskotDesa() {
<Center mih={500}> <Center mih={500}>
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Loader size="lg" color="blue" /> <Loader size="lg" color="blue" />
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text> <Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
Sedang memuat data maskot desa...
</Text>
</Stack> </Stack>
</Center> </Center>
); );
@@ -31,8 +33,21 @@ function MaskotDesa() {
<Box> <Box>
<Stack align="center" gap="xl"> <Stack align="center" gap="xl">
<Stack align="center" gap={10}> <Stack align="center" gap={10}>
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} loading="lazy"/> <Image
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text> src="/pudak-icon.png"
alt="Ikon Desa"
w={{ base: 160, md: 240 }}
loading="lazy"
/>
{/* Page Title */}
<Title
order={1}
ta="center"
c={colors['blue-button']}
>
Maskot Desa
</Title>
</Stack> </Stack>
<Paper <Paper
@@ -42,13 +57,14 @@ function MaskotDesa() {
withBorder withBorder
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }} style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
> >
{/* Body Description */}
<Text <Text
fz={{ base: 'sm', md: 'lg' }} fz={{ base: 'sm', md: 'lg' }}
lh={1.7} lh={1.7}
ta="justify" ta="justify"
c="dark" c="dark"
dangerouslySetInnerHTML={{ __html: data.deskripsi }} dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />
<Group justify="center" gap="lg" mt="lg"> <Group justify="center" gap="lg" mt="lg">
@@ -75,7 +91,15 @@ function MaskotDesa() {
radius="md" radius="md"
loading="lazy" loading="lazy"
/> />
<Text ta="center" mt="sm" fw={600} fz="sm" c="dark">
{/* Image Label */}
<Text
ta="center"
mt="sm"
fw={600}
fz={{ base: 'xs', md: 'sm' }}
c="dark"
>
{img.label} {img.label}
</Text> </Text>
</Card> </Card>
@@ -83,7 +107,10 @@ function MaskotDesa() {
) : ( ) : (
<Stack align="center" gap="xs" mt="lg"> <Stack align="center" gap="xs" mt="lg">
<IconPhoto size={48} stroke={1.5} color="gray" /> <IconPhoto size={48} stroke={1.5} color="gray" />
<Text c="dimmed" fz="sm">Belum ada gambar maskot yang ditambahkan</Text>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
Belum ada gambar maskot yang ditambahkan
</Text>
</Stack> </Stack>
)} )}
</Group> </Group>

View File

@@ -1,35 +1,15 @@
'use client' 'use client'
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { IconSparkles } from '@tabler/icons-react'; import { IconSparkles } from '@tabler/icons-react';
import colors from '@/con/colors'; import colors from '@/con/colors';
const dataText = [ const dataText = [
{ { id: 1, title: "Santun", description: "Pelayanan ramah, penuh empati, sopan, dan beretika." },
id: 1, { id: 2, title: "Adaptif", description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif." },
title: "Santun", { id: 3, title: "Inovatif", description: "Berani menciptakan pembaruan dan ide-ide kreatif." },
description: "Pelayanan ramah, penuh empati, sopan, dan beretika." { id: 4, title: "Profesional", description: "Berpengetahuan luas, terampil, dan bertanggung jawab." },
}, { id: 5, title: "Gesit", description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja." },
{
id: 2,
title: "Adaptif",
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
},
{
id: 3,
title: "Inovatif",
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
},
{
id: 4,
title: "Profesional",
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
},
{
id: 5,
title: "Gesit",
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
},
]; ];
const letters = ["S", "I", "G", "A", "P"]; const letters = ["S", "I", "G", "A", "P"];
@@ -38,11 +18,14 @@ function MotoDesa() {
return ( return (
<Box px={{ base: "md", md: "xl" }}> <Box px={{ base: "md", md: "xl" }}>
<Stack align="center" gap="lg"> <Stack align="center" gap="lg">
{/* Page Title */}
<Box> <Box>
<Text <Title
order={1}
ta="center" ta="center"
fw={800} fw={800}
fz={{ base: "2rem", md: "2.8rem" }} fz={{ base: 28, md: 36 }}
lh={{ base: 1.2, md: 1.3 }}
style={{ style={{
background: "linear-gradient(90deg, #0D5594FF, #094678FF)", background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
WebkitBackgroundClip: "text", WebkitBackgroundClip: "text",
@@ -50,9 +33,10 @@ function MotoDesa() {
}} }}
> >
Moto Desa Darmasaba Moto Desa Darmasaba
</Text> </Title>
</Box> </Box>
{/* Letter Icons */}
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center"> <Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
{letters.map((letter, i) => ( {letters.map((letter, i) => (
<motion.div <motion.div
@@ -71,7 +55,7 @@ function MotoDesa() {
backdropFilter: "blur(6px)", backdropFilter: "blur(6px)",
}} }}
> >
<Text c="white" fw={800} fz="xl"> <Text c="white" fw={800} fz={{ base: 20, md: 24 }}>
{letter} {letter}
</Text> </Text>
</ActionIcon> </ActionIcon>
@@ -79,6 +63,7 @@ function MotoDesa() {
))} ))}
</Flex> </Flex>
{/* Values Card */}
<Paper <Paper
radius="lg" radius="lg"
p="xl" p="xl"
@@ -90,19 +75,22 @@ function MotoDesa() {
> >
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl"> <SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
{dataText.map((v) => ( {dataText.map((v) => (
<motion.div <motion.div key={v.id} whileHover={{ scale: 1.02 }} transition={{ duration: 0.2 }}>
key={v.id}
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<Stack gap={4}> <Stack gap={4}>
{/* Section Title */}
<Flex align="center" gap="sm"> <Flex align="center" gap="sm">
<IconSparkles size={20} color={colors['blue-button']} /> <IconSparkles size={20} color={colors['blue-button']} />
<Text fw={700} fz={{ base: "lg", md: "xl" }} c={colors['blue-button']}> <Title
order={3}
fw={700}
fz={{ base: 20, md: 24 }}
c={colors['blue-button']}
>
{v.title} {v.title}
</Text> </Title>
</Flex> </Flex>
<Text fz={{ base: "sm", md: "md" }} c="gray.7"> {/* Body Text */}
<Text fz={{ base: 14, md: 16 }} lh={{ base: 1.5, md: 1.6 }} c="gray.7">
{v.description} {v.description}
</Text> </Text>
</Stack> </Stack>
@@ -111,16 +99,15 @@ function MotoDesa() {
</SimpleGrid> </SimpleGrid>
</Paper> </Paper>
{/* Motto Description */}
<Text <Text
ta="center" ta="center"
fw={700} fw={700}
fz={{ base: "md", md: "xl" }} fz={{ base: 15, md: 20 }}
lh={{ base: 1.6, md: 1.8 }}
c="blue.8" c="blue.8"
mt="md" mt="md"
style={{ style={{ maxWidth: 720 }}
maxWidth: 720,
lineHeight: 1.6,
}}
> >
&quot;Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "} &quot;Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
<Text span fw={800} c="cyan.6"> <Text span fw={800} c="cyan.6">

View File

@@ -2,44 +2,45 @@
'use client' 'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react'; import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function ProfilPerbekel() { function ProfilPerbekel() {
const state = useProxy(stateProfileDesa.profilPerbekel) const state = useProxy(stateProfileDesa.profilPerbekel);
useEffect(() => { useEffect(() => {
state.findUnique.load("edit") state.findUnique.load("edit");
}, []) }, []);
const { data, loading } = state.findUnique const { data, loading } = state.findUnique;
if (loading || !data) { if (loading || !data) {
return ( return (
<Box py={20} px="md"> <Box py={20} px="md">
<Skeleton h={500} radius="lg" /> <Skeleton h={500} radius="lg" />
</Box> </Box>
) );
} }
return ( return (
<Box px="md"> <Box px="md">
{/* ===== PAGE TITLE ===== */}
<Stack align="center" gap={0} mb={40}> <Stack align="center" gap={0} mb={40}>
<Text <Title
order={1}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
fw="bold"
fz={{ base: "2rem", md: "2.8rem" }}
style={{ letterSpacing: "0.5px" }} style={{ letterSpacing: "0.5px" }}
> >
Profil Perbekel Profil Perbekel
</Text> </Title>
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} /> <Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
</Stack> </Stack>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}> <SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
{/* ========== FOTO PERBEKEL ========== */}
<Box> <Box>
<Paper <Paper
bg={colors['white-trans-1']} bg={colors['white-trans-1']}
@@ -60,6 +61,8 @@ function ProfilPerbekel() {
}} }}
loading="lazy" loading="lazy"
/> />
{/* ===== NAMA DAN JABATAN ===== */}
<Paper <Paper
bg={colors['blue-button']} bg={colors['blue-button']}
px="lg" px="lg"
@@ -67,22 +70,23 @@ function ProfilPerbekel() {
className="glass3" className="glass3"
py={{ base: 20, md: 50 }} py={{ base: 20, md: 50 }}
> >
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}> <Title order={3} c={colors['white-1']}>
Perbekel Desa Darmasaba Perbekel Desa Darmasaba
</Text> </Title>
<Text
<Title
order={2}
c={colors['white-1']} c={colors['white-1']}
fw="bolder"
fz={{ base: "xl", md: "h2" }}
mt={8} mt={8}
> >
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."} {"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
</Text> </Title>
</Paper> </Paper>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>
{/* ========== BIODATA & PENGALAMAN ========== */}
<Paper <Paper
p="xl" p="xl"
bg={colors['white-trans-1']} bg={colors['white-trans-1']}
@@ -92,34 +96,39 @@ function ProfilPerbekel() {
withBorder withBorder
> >
<Stack gap="xl"> <Stack gap="xl">
{/* ===== BIODATA ===== */}
<Box> <Box>
<Stack gap={6}> <Stack gap={6}>
<Stack align="center" gap={6}> <Stack align="center" gap={6}>
<IconUser size={22} /> <IconUser size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text> <Title order={3}>Biodata</Title>
</Stack> </Stack>
<Text <Text
fz={{ base: "1rem", md: "1.2rem" }} fz={{ base: "sm", md: "md" }}
ta="justify" ta="justify"
lh={1.6} lh={1.7}
dangerouslySetInnerHTML={{ __html: data.biodata }} dangerouslySetInnerHTML={{ __html: data.biodata }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word" }}
/> />
</Stack> </Stack>
</Box> </Box>
{/* ===== PENGALAMAN ===== */}
<Box> <Box>
<Stack gap={6}> <Stack gap={6}>
<Stack align="center" gap={6}> <Stack align="center" gap={6}>
<IconBriefcase size={22} /> <IconBriefcase size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text> <Title order={3}>Pengalaman</Title>
</Stack> </Stack>
<Text <Text
fz={{ base: "1rem", md: "1.2rem" }} fz={{ base: "sm", md: "md" }}
ta="left" ta="left"
lh={1.6} lh={1.7}
dangerouslySetInnerHTML={{ __html: data.pengalaman }} dangerouslySetInnerHTML={{ __html: data.pengalaman }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word" }}
/> />
</Stack> </Stack>
</Box> </Box>
@@ -127,6 +136,7 @@ function ProfilPerbekel() {
</Paper> </Paper>
</SimpleGrid> </SimpleGrid>
{/* ========== ORGANISASI & PROGRAM UNGGULAN ========== */}
<Paper <Paper
p="xl" p="xl"
bg={colors['white-trans-1']} bg={colors['white-trans-1']}
@@ -136,35 +146,41 @@ function ProfilPerbekel() {
withBorder withBorder
> >
<Stack gap="xl"> <Stack gap="xl">
{/* ===== PENGALAMAN ORGANISASI ===== */}
<Box> <Box>
<Stack align="center" gap={6} > <Stack align="center" gap={6}>
<IconUsers size={22} /> <IconUsers size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text> <Title order={3}>Pengalaman Organisasi</Title>
</Stack> </Stack>
<Text <Text
fz={{ base: "1rem", md: "1.2rem" }} fz={{ base: "sm", md: "md" }}
ta="justify" ta="justify"
lh={1.6} lh={1.7}
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }} dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word" }}
/> />
</Box> </Box>
{/* ===== PROGRAM UNGGULAN ===== */}
<Box> <Box>
<Stack align="center" gap={6} mb={6}> <Stack align="center" gap={6} mb={6}>
<IconTargetArrow size={22} /> <IconTargetArrow size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Program Kerja Unggulan</Text> <Title order={3}>Program Kerja Unggulan</Title>
</Stack> </Stack>
<Box px={10}> <Box px={10}>
<Text <Text
fz={{ base: "1rem", md: "1.2rem" }} fz={{ base: "sm", md: "md" }}
ta="justify" ta="justify"
lh={1.6} lh={1.7}
dangerouslySetInnerHTML={{ __html: data.programUnggulan }} dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word" }}
/> />
</Box> </Box>
</Box> </Box>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -26,29 +26,32 @@ function SejarahDesa() {
return ( return (
<Box> <Box>
<Stack align="center" gap="xl"> <Stack align="center" gap="xl">
{/* HEADER ICON + TITLE */}
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Center> <Center>
<Image <Image
src="/darmasaba-icon.png" src="/darmasaba-icon.png"
alt="Ikon Desa Darmasaba" alt="Ikon Desa Darmasaba"
w={{ base: 180, md: 260 }} w={{ base: 160, md: 240 }}
radius="md" radius="md"
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }} style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
loading="lazy" loading="lazy"
/> />
</Center> </Center>
<Center> <Center>
<Text <Title
order={1}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
fw={700}
fz={{ base: '2rem', md: '2.8rem' }}
style={{ letterSpacing: '-0.5px' }} style={{ letterSpacing: '-0.5px' }}
> >
Sejarah Desa Sejarah Desa
</Text> </Title>
</Center> </Center>
</Stack> </Stack>
{/* CONTENT */}
<Paper <Paper
p="xl" p="xl"
radius="lg" radius="lg"
@@ -61,10 +64,14 @@ function SejarahDesa() {
> >
<Stack gap="md"> <Stack gap="md">
<Text <Text
fz={{ base: 'md', md: 'lg' }} fz={{ base: 'sm', md: 'md' }}
lh={1.8} lh={1.75}
ta="justify" ta="justify"
style={{ color: '#2a2a2a', wordBreak: "break-word", whiteSpace: "normal" }} c="dark.7"
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
}}
dangerouslySetInnerHTML={{ __html: data.deskripsi }} dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/> />
</Stack> </Stack>

View File

@@ -28,8 +28,10 @@ function SemuaPerbekel() {
<Center py="xl"> <Center py="xl">
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<IconUser size={48} stroke={1.5} /> <IconUser size={48} stroke={1.5} />
<Title fw="bold" order={2}>Belum ada data Perbekel</Title> <Title order={2} ta="center">Belum ada data Perbekel</Title>
<Text c="dimmed" fz="sm" ta="center">Data mantan Perbekel akan muncul di sini ketika sudah tersedia</Text> <Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.6 }} ta="center">
Data mantan Perbekel akan muncul di sini ketika sudah tersedia
</Text>
</Stack> </Stack>
</Center> </Center>
); );
@@ -38,17 +40,20 @@ function SemuaPerbekel() {
return ( return (
<Box> <Box>
<Stack align="center" gap="lg"> <Stack align="center" gap="lg">
<Box> <Title
<Text order={1}
ta="center" ta="center"
style={{
background: 'linear-gradient(45deg, blue, cyan)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
fz={{ base: 28, md: 36 }}
lh={{ base: 1.2, md: 1.3 }}
fw={900} fw={900}
fz={{ base: "2rem", md: "2.5rem" }}
variant="gradient"
gradient={{ from: "blue", to: "cyan", deg: 45 }}
> >
Perbekel Dari Masa ke Masa Perbekel Dari Masa ke Masa
</Text> </Title>
</Box>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%"> <SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
{data.map((v: any, k: number) => ( {data.map((v: any, k: number) => (
@@ -59,9 +64,7 @@ function SemuaPerbekel() {
withBorder withBorder
p="lg" p="lg"
bg="white" bg="white"
style={{ style={{ transition: "all 250ms ease" }}
transition: "all 250ms ease",
}}
className="hover:shadow-xl hover:scale-[1.02]" className="hover:shadow-xl hover:scale-[1.02]"
> >
<Stack gap="md" align="center"> <Stack gap="md" align="center">
@@ -77,15 +80,15 @@ function SemuaPerbekel() {
</Box> </Box>
<Stack gap={4} align="center"> <Stack gap={4} align="center">
<Text fw={700} fz="lg" ta="center"> <Title order={3} fz={{ base: 18, md: 20 }} ta="center" fw={700}>
{v.nama} {v.nama}
</Text> </Title>
<Text c="dimmed" fz="sm" ta="center"> <Text c="dimmed" fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
{v.daerah} {v.daerah}
</Text> </Text>
<Text c="blue" fw={600} fz="sm" ta="center"> <Text c="blue" fw={600} fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
{v.periode} {v.periode}
</Text> </Text>
</Stack> </Stack>

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -34,60 +34,57 @@ function VisiMisiDesa() {
loading="lazy" loading="lazy"
/> />
{/* VISI */}
<Paper <Paper
p="xl" p="xl"
radius="lg" radius="lg"
shadow="md" shadow="md"
withBorder withBorder
w="100%" w="100%"
style={{ style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
}}
> >
<Text <Title
order={1}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
fw={700}
fz={{ base: '2rem', md: '2.5rem' }}
mb="md" mb="md"
> >
Visi Desa Visi Desa
</Text> </Title>
<Text <Text
fz={{ base: '1.125rem', md: '1.375rem' }} fz={{ base: 'sm', md: 'md' }} // body text responsive
lh={1.7}
ta="center" ta="center"
fw={500}
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.visi }} dangerouslySetInnerHTML={{ __html: data.visi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />
</Paper> </Paper>
{/* MISI */}
<Paper <Paper
p="xl" p="xl"
radius="lg" radius="lg"
shadow="md" shadow="md"
withBorder withBorder
w="100%" w="100%"
style={{ style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
}}
> >
<Text <Title
order={1}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
fw={700}
fz={{ base: '2rem', md: '2.5rem' }}
mb="md" mb="md"
> >
Misi Desa Misi Desa
</Text> </Title>
<Text <Text
fz={{ base: '1.125rem', md: '1.375rem' }} fz={{ base: 'sm', md: 'md' }} // body text responsive
fw={500} lh={1.7}
lh={1.6} ta="left"
dangerouslySetInnerHTML={{ __html: data.misi }} dangerouslySetInnerHTML={{ __html: data.misi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />
</Paper> </Paper>
</Stack> </Stack>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core'; import { Box, Flex, Group, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
@@ -30,196 +30,265 @@ function Page() {
// Hasil akhir // Hasil akhir
const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan; const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan;
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(value);
};
return ( return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg"> <Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
{/* Page Title */}
<Title
ta="center"
c={colors["blue-button"]}
fw="bold"
order={1}
fz={{ base: 28, md: 36 }}
>
Pendapatan Asli Desa Pendapatan Asli Desa
</Text> </Title>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<Stack gap="lg" justify="center"> <Stack gap="lg" justify="center">
<Paper bg={colors['white-1']} p="xl"> <Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }}>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md"> <SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{/* Pendapatan Card */} {/* Pendapatan Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}> <Box
<Stack gap={"xs"}> p="md"
<Title order={3}>Pendapatan</Title>
{latestApb?.pendapatan?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text
fz="md"
fw={500}
style={{ style={{
wordBreak: 'break-word', border: '1px solid #e9ecef',
whiteSpace: 'normal', borderRadius: '8px',
textAlign: 'right', height: '100%'
}} }}
> >
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(item.value)} <Stack gap="md">
<Title order={3} fz={{ base: 18, md: 20 }} c={colors['blue-button']}>
Pendapatan
</Title>
<Stack gap="sm">
{latestApb?.pendapatan?.map((item) => (
<Box key={item.id}>
<Flex gap={1}>
<Text
fz={{ base: 13, md: 14 }}
fw={500}
lh={1.4}
c="black"
style={{ wordBreak: 'break-word' }}
>
{item.name} {formatCurrency(item.value)}
</Text> </Text>
</GridCol> </Flex>
</Grid>
</Box> </Box>
))} ))}
<Grid> </Stack>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text> <Box
</GridCol> pt="sm"
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}> mt="auto"
<Text style={{ style={{
wordBreak: 'break-word', borderTop: `2px solid ${colors['blue-button']}`
whiteSpace: 'normal' }}
}} fz="xl" fw={700} c={colors['blue-button']}> >
{new Intl.NumberFormat('id-ID', { <Flex direction="column" gap={4}>
style: 'currency', <Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
currency: 'IDR', Total Pendapatan
minimumFractionDigits: 0
}).format(totalPendapatan)}
</Text> </Text>
</GridCol> <Text
</Grid> fz={{ base: 18, md: 22 }}
fw={700}
c={colors['blue-button']}
lh={1.4}
>
{formatCurrency(totalPendapatan)}
</Text>
</Flex>
</Box>
</Stack> </Stack>
</Box> </Box>
{/* Belanja Card */} {/* Belanja Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}> <Box
<Stack gap={"xs"}> p="md"
<Title order={3}>Belanja</Title>
{latestApb?.belanja?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text
fz="md"
fw={500}
style={{ style={{
wordBreak: 'break-word', border: '1px solid #e9ecef',
whiteSpace: 'normal', borderRadius: '8px',
textAlign: 'right', height: '100%'
}} }}
> >
{new Intl.NumberFormat('id-ID', { <Stack gap="md">
style: 'currency', <Title order={3} fz={{ base: 18, md: 20 }} c="orange">
currency: 'IDR', Belanja
minimumFractionDigits: 0 </Title>
}).format(item.value)}
<Stack gap="sm">
{latestApb?.belanja?.map((item) => (
<Box key={item.id}>
<Group gap={1}>
<Text
fz={{ base: 13, md: 14 }}
fw={500}
lh={1.4}
c="black"
style={{ wordBreak: 'break-word' }}
>
{item.name} {formatCurrency(item.value)}
</Text> </Text>
</GridCol> </Group>
</Grid>
</Box> </Box>
))} ))}
<Grid> </Stack>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text> <Box
</GridCol> pt="sm"
<GridCol span={{ base: 12, md: 6 }}> mt="auto"
<Text fz="xl" fw={700} c="orange"> style={{
{new Intl.NumberFormat('id-ID', { borderTop: '2px solid orange'
style: 'currency', }}
currency: 'IDR', >
minimumFractionDigits: 0 <Flex direction="column" gap={4}>
}).format(totalBelanja)} <Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
Total Belanja
</Text> </Text>
</GridCol> <Text
</Grid> fz={{ base: 18, md: 22 }}
fw={700}
c="orange"
lh={1.4}
>
{formatCurrency(totalBelanja)}
</Text>
</Flex>
</Box>
</Stack> </Stack>
</Box> </Box>
{/* Pembiayaan Card */} {/* Pembiayaan Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}> <Box
<Stack gap={"xs"}> p="md"
<Title order={3}>Pembiayaan</Title>
{latestApb?.pembiayaan?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text
fz="md"
fw={500}
style={{ style={{
wordBreak: 'break-word', border: '1px solid #e9ecef',
whiteSpace: 'normal', borderRadius: '8px',
textAlign: 'right', height: '100%'
}} }}
> >
{new Intl.NumberFormat('id-ID', { <Stack gap="md">
style: 'currency', <Title order={3} fz={{ base: 18, md: 20 }} c="green">
currency: 'IDR', Pembiayaan
minimumFractionDigits: 0 </Title>
}).format(item.value)}
<Stack gap="sm">
{latestApb?.pembiayaan?.map((item) => (
<Box key={item.id}>
<Group gap={1}>
<Text
fz={{ base: 13, md: 14 }}
fw={500}
lh={1.4}
c="black"
style={{ wordBreak: 'break-word' }}
>
{item.name} {formatCurrency(item.value)}
</Text> </Text>
</GridCol> </Group>
</Grid>
</Box> </Box>
))} ))}
<Grid> </Stack>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text> <Box
</GridCol> pt="sm"
<GridCol span={{ base: 12, md: 6 }}> mt="auto"
<Text fz="xl" fw={700} c="green"> style={{
{new Intl.NumberFormat('id-ID', { borderTop: '2px solid green'
style: 'currency', }}
currency: 'IDR', >
minimumFractionDigits: 0 <Flex direction="column" gap={4}>
}).format(totalPembiayaan)} <Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
Total Pembiayaan
</Text> </Text>
</GridCol> <Text
</Grid> fz={{ base: 18, md: 22 }}
fw={700}
c="green"
lh={1.4}
>
{formatCurrency(totalPembiayaan)}
</Text>
</Flex>
</Box>
</Stack> </Stack>
</Box> </Box>
</SimpleGrid> </SimpleGrid>
</Paper> </Paper>
{/* 🔽 Tambahan Ringkasan Anggaran */} {/* Ringkasan Anggaran */}
<Paper bg={colors['white-1']} p="xl" shadow="sm" withBorder> <Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="sm" withBorder>
<Title order={3} mb="md">Ringkasan Anggaran</Title> <Title order={3} mb="md" fz={{ base: 18, md: 20 }}>
Ringkasan Anggaran
</Title>
<Table striped highlightOnHover withTableBorder> <Table striped highlightOnHover withTableBorder>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
<Table.Th>Keterangan</Table.Th> <Table.Th>
<Table.Th ta={"right"}>Jumlah</Table.Th> <Text fz={{ base: 13, md: 14 }} fw={600}>Keterangan</Text>
</Table.Th>
<Table.Th ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600}>Jumlah</Text>
</Table.Th>
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
<Table.Tr> <Table.Tr>
<Table.Td>Total Pendapatan</Table.Td> <Table.Td>
<Table.Td align="right"> <Text fz={{ base: 13, md: 14 }} lh={1.4}>Total Pendapatan</Text>
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPendapatan)} </Table.Td>
<Table.Td ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4}>
{formatCurrency(totalPendapatan)}
</Text>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
<Table.Tr> <Table.Tr>
<Table.Td>Total Belanja</Table.Td> <Table.Td>
<Table.Td align="right" c="orange"> <Text fz={{ base: 13, md: 14 }} lh={1.4} c="orange">Total Belanja</Text>
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalBelanja)} </Table.Td>
<Table.Td ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="orange">
{formatCurrency(totalBelanja)}
</Text>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
<Table.Tr> <Table.Tr>
<Table.Td>Total Pembiayaan</Table.Td> <Table.Td>
<Table.Td align="right" c="green"> <Text fz={{ base: 13, md: 14 }} lh={1.4} c="green">Total Pembiayaan</Text>
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPembiayaan)} </Table.Td>
<Table.Td ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="green">
{formatCurrency(totalPembiayaan)}
</Text>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
<Table.Tr> <Table.Tr style={{ backgroundColor: '#f8f9fa' }}>
<Table.Td><b>Sisa Anggaran</b></Table.Td> <Table.Td>
<Table.Td align="right" c={sisaAnggaran >= 0 ? "blue" : "red"}> <Text fz={{ base: 14, md: 15 }} fw={700} lh={1.4}>Sisa Anggaran</Text>
<b> </Table.Td>
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(sisaAnggaran)} <Table.Td ta="right">
</b> <Text
fz={{ base: 14, md: 15 }}
fw={700}
c={sisaAnggaran >= 0 ? colors['blue-button'] : "red"}
lh={1.4}
>
{formatCurrency(sisaAnggaran)}
</Text>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
</Table.Tbody> </Table.Tbody>

View File

@@ -76,13 +76,13 @@ function Page() {
</Box> </Box>
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}> <Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<Box> <Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}> <Flex gap={{base: 7, md: 5}} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Laki-Laki</Text> <Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Laki-Laki</Text>
<ColorSwatch color="#5082EE" size={30} /> <ColorSwatch color="#5082EE" size={30} />
</Flex> </Flex>
</Box> </Box>
<Box> <Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}> <Flex gap={{base: 7, md: 5}} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Perempuan</Text> <Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Perempuan</Text>
<ColorSwatch color="#6EDF9C" size={30} /> <ColorSwatch color="#6EDF9C" size={30} />
</Flex> </Flex>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa'; import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react'; import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react';
import { motion } from 'motion/react'; import { motion } from 'motion/react';
@@ -14,7 +14,7 @@ function Page() {
const router = useRouter() const router = useRouter()
const state = useProxy(pasarDesaState.pasarDesa) const state = useProxy(pasarDesaState.pasarDesa)
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null); const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const { const {
data, data,
@@ -28,7 +28,6 @@ function Page() {
pasarDesaState.kategoriProduk.findManyAll.load() pasarDesaState.kategoriProduk.findManyAll.load()
}, []) }, [])
// Filter data based on selected category
const filteredData = selectedCategory const filteredData = selectedCategory
? data?.filter(item => ? data?.filter(item =>
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory) item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
@@ -39,7 +38,6 @@ function Page() {
load(page, 4, debouncedSearch, selectedCategory || undefined) load(page, 4, debouncedSearch, selectedCategory || undefined)
}, [page, debouncedSearch, selectedCategory]) }, [page, debouncedSearch, selectedCategory])
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
@@ -49,44 +47,38 @@ function Page() {
} }
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Box> <Box>
<Grid align='center' px={{ base: 'md', md: 100 }}> <Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title order={1} c={colors["blue-button"]} fw="bold">
Pasar Desa Pasar Desa
</Text> </Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 3 }}> <GridCol span={{ base: 12, md: 3 }}>
<TextInput <TextInput
radius={"lg"} radius="lg"
placeholder='Cari Produk' placeholder="Cari Produk"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }} w={"100%"}
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan <Text px={{ base: 'md', md: 100 }} pt={20} ta="justify" fz={{ base: 'sm', md: 'md' }}>
</Text> Pasar Desa Online adalah media promosi untuk membantu warga memasarkan dan memperkenalkan produk mereka.
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
dan memperkenalkan produk mereka.
</Text> </Text>
</Box> </Box>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}> <Stack gap="lg">
<SimpleGrid <SimpleGrid pb={30} cols={{ base: 1, md: 2 }}>
pb={30}
cols={{
base: 1,
md: 2
}}
>
<Box> <Box>
<Select <Select
placeholder="Pilih Kategori" placeholder="Pilih Kategori"
@@ -103,36 +95,44 @@ function Page() {
/> />
</Box> </Box>
</SimpleGrid> </SimpleGrid>
<SimpleGrid cols={{ base: 1, md: 4 }}> <SimpleGrid cols={{ base: 1, md: 4 }}>
{filteredData?.map((v, k) => { {filteredData?.map((v, k) => (
return (
<Stack key={k}> <Stack key={k}>
<motion.div <motion.div
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)} onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.8 }} whileTap={{ scale: 0.8 }}
> >
<Paper p={'lg'}> <Paper p="lg">
<Image <Image
radius={'lg'} radius="lg"
src={v.image?.link || '/placeholder-product.jpg'} src={v.image?.link || '/placeholder-product.jpg'}
alt={v.nama} alt={v.nama}
h={200} h={200}
w='100%' w="100%"
style={{ objectFit: 'cover' }} style={{ objectFit: 'cover' }}
loading="lazy" loading="lazy"
/> />
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text> <Text py="sm" fw="bold" fz={{ base: 'md', md: 'lg' }}>
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text> {v.nama}
<Flex py={10} gap={'md'}> </Text>
<IconStarFilled size={20} color='#EBCB09' /> <Text fz={{ base: 'sm', md: 'md' }}>
<Text fz={'sm'} ml={2}>{v.rating}</Text> Rp {v.harga.toLocaleString('id-ID')}
</Text>
<Flex py="sm" gap="md">
<IconStarFilled size={20} color="#EBCB09" />
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
{v.rating}
</Text>
</Flex> </Flex>
<Flex justify={'space-between'} align={'center'}> <Flex justify="space-between" align="center">
<Box> <Box>
<Flex gap={'md'} align={'center'}> <Flex gap="md" align="center">
<IconMapPinFilled size={20} color='red' /> <IconMapPinFilled size={20} color="red" />
<Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text> <Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
{v.alamatUsaha}
</Text>
</Flex> </Flex>
</Box> </Box>
<IconBrandWhatsapp size={20} color={colors['blue-button']} /> <IconBrandWhatsapp size={20} color={colors['blue-button']} />
@@ -140,13 +140,13 @@ function Page() {
</Paper> </Paper>
</motion.div> </motion.div>
</Stack> </Stack>
) ))}
})}
</SimpleGrid> </SimpleGrid>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => load(newPage)} // ini penting! onChange={(newPage) => load(newPage)}
total={totalPages} total={totalPages}
my="md" my="md"
/> />

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan'; import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react'; import { IconSearch } from '@tabler/icons-react';
import { useState } from 'react'; import { useState } from 'react';
@@ -32,10 +32,9 @@ interface ProgramKemiskinanData {
function Page() { function Page() {
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const state = useProxy(programKemiskinanState) const state = useProxy(programKemiskinanState)
// 🔧 Get valid statistics data with proper type checking
const statistikData = state.findMany.data const statistikData = state.findMany.data
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => { .filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
return !!item?.statistik && return !!item?.statistik &&
@@ -43,11 +42,11 @@ function Page() {
item.statistik.jumlah !== undefined; item.statistik.jumlah !== undefined;
}) })
.map(item => ({ .map(item => ({
tahun: Number(item.statistik.tahun) || 0, // Ensure tahun is a number tahun: Number(item.statistik.tahun) || 0,
jumlah: Number(item.statistik.jumlah) || 0, // Ensure jumlah is a number jumlah: Number(item.statistik.jumlah) || 0,
})) }))
.sort((a, b) => a.tahun - b.tahun) .sort((a, b) => a.tahun - b.tahun)
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah)); // Remove any invalid entries .filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah));
const { const {
data, data,
@@ -74,12 +73,18 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Box px={{ base: 'md', md: 100 }} > <Box px={{ base: 'md', md: 100 }}>
<Grid align='center'> <Grid align='center'>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title
order={1}
c={colors["blue-button"]}
fw={"bold"}
fz={{ base: '28px', md: '32px' }}
lh={{ base: '1.2', md: '1.25' }}
>
Program Kemiskinan Program Kemiskinan
</Text> </Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 3 }}> <GridCol span={{ base: 12, md: 3 }}>
<TextInput <TextInput
@@ -92,7 +97,15 @@ function Page() {
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text fz={'h4'}>Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat</Text> <Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
c="black"
ta={{ base: 'left', md: 'left' }}
pt={20}
>
Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat
</Text>
</Box> </Box>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'> <Stack gap={'lg'} justify='center'>
@@ -106,8 +119,22 @@ function Page() {
{state.findMany.data.map(v => { {state.findMany.data.map(v => {
return ( return (
<Paper p={'xl'} key={v.id}> <Paper p={'xl'} key={v.id}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text> <Title
<Text fz={'lg'} c={'black'} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text> order={3}
fw={'bold'}
c={colors['blue-button']}
fz={{ base: '18px', md: '20px' }}
lh={{ base: '1.3', md: '1.35' }}
>
{v.nama}
</Title>
<Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
c={'black'}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Paper> </Paper>
) )
})} })}
@@ -124,7 +151,16 @@ function Page() {
/> />
</Center> </Center>
<Paper p={'xl'}> <Paper p={'xl'}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text> <Title
order={3}
fw={'bold'}
c={colors['blue-button']}
fz={{ base: '18px', md: '20px' }}
lh={{ base: '1.3', md: '1.35' }}
mb="md"
>
Statistik Kemiskinan Masyarakat
</Title>
<Box style={{ width: '100%', height: 'auto' }}> <Box style={{ width: '100%', height: 'auto' }}>
{statistikData.length > 0 ? ( {statistikData.length > 0 ? (
<Box w="100%" style={{ overflowX: 'auto' }}> <Box w="100%" style={{ overflowX: 'auto' }}>
@@ -162,7 +198,11 @@ function Page() {
</Box> </Box>
) : ( ) : (
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}> <Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
<Text c="dimmed"> <Text
fz={{ base: '12px', md: '14px' }}
c="dimmed"
lh={{ base: '1.4', md: '1.5' }}
>
{state.findMany.loading {state.findMany.loading
? 'Memuat data statistik...' ? 'Memuat data statistik...'
: 'Belum ada data statistik yang tersedia atau data tidak valid'} : 'Belum ada data statistik yang tersedia atau data tidak valid'}

View File

@@ -53,6 +53,7 @@ function Page() {
Ton: item.value, Ton: item.value,
})); }));
const chartWidth = Math.max(600, chartData.length * 150); // contoh: 150px per bar
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
@@ -78,7 +79,7 @@ function Page() {
<Box style={{ width: '100%', overflowX: 'auto' }}> <Box style={{ width: '100%', overflowX: 'auto' }}>
<Paper p="xl"> <Paper p="xl">
<Text pb={10} fw="bold" fz="h4">Statistik Sektor Unggulan Darmasaba</Text> <Text pb={10} fw="bold" fz="h4">Statistik Sektor Unggulan Darmasaba</Text>
<Box style={{ width: '100%', minWidth: '600px' }}> <Box style={{ width: '100%', overflowX: 'auto', maxWidth: `${chartWidth}px` }}>
<BarChart <BarChart
p={10} p={10}
h={300} h={300}
@@ -90,11 +91,14 @@ function Page() {
tickLine="y" tickLine="y"
tooltipAnimationDuration={200} tooltipAnimationDuration={200}
withTooltip withTooltip
style={{ withXAxis
fontFamily: 'inherit', withYAxis
}}
xAxisLabel="Sektor" xAxisLabel="Sektor"
yAxisLabel="Ton" yAxisLabel="Ton"
style={{
fontFamily: 'inherit',
fontSize: '12px', // ukuran font lebih kecil di mobile
}}
/> />
</Box> </Box>
</Paper> </Paper>

View File

@@ -14,6 +14,9 @@ import {
Loader, Loader,
Paper, Paper,
Stack, Stack,
Tabs,
TabsList,
TabsTab,
Text, Text,
TextInput, TextInput,
Title, Title,
@@ -33,6 +36,8 @@ import { OrganizationChart } from 'primereact/organizationchart'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils' import { useProxy } from 'valtio/utils'
import BackButton from '../../desa/layanan/_com/BackButto' import BackButton from '../../desa/layanan/_com/BackButto'
import { useMediaQuery } from '@mantine/hooks'
import { useTransitionRouter } from 'next-view-transitions'
export default function Page() { export default function Page() {
return ( return (
@@ -51,11 +56,11 @@ export default function Page() {
order={1} order={1}
ta="center" ta="center"
c={colors['blue-button']} c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }} fz={{ base: 28, md: 36 }}
> >
Struktur Organisasi & SK Pengurus BumDes Struktur Organisasi & SK Pengurus BumDes
</Title> </Title>
<Text ta="center" c="black" maw={800}> <Text ta="center" c="black" maw={800} fz={{ base: 14, md: 16 }} lh={1.6}>
Gambaran visual peran dan pengurus yang ditugaskan. Gunakan kontrol Gambaran visual peran dan pengurus yang ditugaskan. Gunakan kontrol
di bawah untuk mencari, memperbesar, atau melihat lebih jelas. di bawah untuk mencari, memperbesar, atau melihat lebih jelas.
</Text> </Text>
@@ -70,13 +75,14 @@ export default function Page() {
} }
function StrukturOrganisasiBumDes() { function StrukturOrganisasiBumDes() {
const router = useTransitionRouter()
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai) const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
const chartContainerRef = useRef<HTMLDivElement>(null) const chartContainerRef = useRef<HTMLDivElement>(null)
const [scale, setScale] = useState(1) const [scale, setScale] = useState(1)
const [isFullscreen, setFullscreen] = useState(false) const [isFullscreen, setFullscreen] = useState(false)
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const debouncedSearch = useRef( const debouncedSearch = useRef(
debounce((value: string) => setSearchQuery(value), 400) debounce((value: string) => setSearchQuery(value), 1000)
).current ).current
useEffect(() => { useEffect(() => {
@@ -92,8 +98,10 @@ function StrukturOrganisasiBumDes() {
<Center py={48}> <Center py={48}>
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Loader size="lg" /> <Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text> <Text fw={600} fz={{ base: 15, md: 16 }} lh={1.4}>
<Text c="dimmed" size="sm"> Memuat struktur organisasi
</Text>
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={1.4}>
Mengambil data pengurus dan posisi. Mohon tunggu sebentar. Mengambil data pengurus dan posisi. Mohon tunggu sebentar.
</Text> </Text>
</Stack> </Stack>
@@ -119,10 +127,10 @@ function StrukturOrganisasiBumDes() {
<Center> <Center>
<IconUsers size={56} /> <IconUsers size={56} />
</Center> </Center>
<Title order={3} mt="md"> <Title order={3} mt="md" ta="center">
Data pengurus belum tersedia Data pengurus belum tersedia
</Title> </Title>
<Text c="dimmed" mt="xs"> <Text c="dimmed" mt="xs" fz={{ base: 12, md: 14 }} lh={1.4}>
Belum ada data pengurus yang tercatat untuk BumDes. Belum ada data pengurus yang tercatat untuk BumDes.
</Text> </Text>
<Group justify="center" mt="lg"> <Group justify="center" mt="lg">
@@ -218,8 +226,26 @@ function StrukturOrganisasiBumDes() {
return ( return (
<Stack align="center" mt="xl"> <Stack align="center" mt="xl">
{/* 🧭 Kontrol atas */} {/* 🧭 Kontrol atas */}
<Paper shadow="xs" p="md" radius="md" bg={colors['blue-button']}> <Paper
<Group gap="sm" wrap="wrap" justify="center"> shadow="xs"
w={{
base: '100%', // Mobile: 100%
sm: '40%', // Tablet: 95%
md: '39%', // Desktop: 70%
lg: '38%', // Desktop L: 60%
xl: '37%', // 4K: 50%
'2xl': '36%', // Ultra-wide: 45%
}}
p="md"
radius="md"
style={{
background: colors['blue-button'], // ⬅️ penting
maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow
}}
>
<Stack gap="sm">
<Group justify='center'>
<TextInput <TextInput
placeholder="Cari nama atau jabatan..." placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />} leftSection={<IconSearch size={16} />}
@@ -230,56 +256,91 @@ function StrukturOrganisasiBumDes() {
}, },
}} }}
/> />
<Group gap="xs"> </Group>
<Button <Tabs
variant="light" defaultValue="zoom-out"
bg={colors['blue-button-2']} variant="outline"
c={colors['blue-button']} radius="md"
size="sm" styles={{
panel: { display: 'none' },
tab: {
color: colors['blue-button'],
backgroundColor: colors['blue-button-2'],
border: 'none',
fontWeight: 600,
fontSize: '0.875rem',
padding: '6px 12px',
minHeight: 'auto',
flexShrink: 0,
},
}}
style={{ width: '100%' }} // 👈 penting
>
<TabsList
style={{
display: 'flex',
overflowX: 'auto',
overflowY: 'hidden',
gap: '4px',
paddingBottom: '4px',
flexWrap: 'nowrap',
WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'thin',
msOverflowStyle: '-ms-autohiding-scrollbar',
maxWidth: '100%',
scrollBehavior: 'smooth', // 👈 smooth scroll
}}
>
<TabsTab
value="zoom-out"
onClick={handleZoomOut} onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />} leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }}
> >
Zoom Out <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
</Button> </TabsTab>
<Box <Box
bg={colors['blue-button-2']} bg={colors['blue-button-2']}
c={colors['blue-button']} c={colors['blue-button']}
px={16} px={12}
py={8} py={6}
style={{ style={{
fontSize: 14,
fontWeight: 700, fontWeight: 700,
borderRadius: '8px', borderRadius: '6px',
minWidth: 70, minWidth: 60,
textAlign: 'center', textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
whiteSpace: 'nowrap',
}} }}
> >
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
{Math.round(scale * 100)}% {Math.round(scale * 100)}%
</Text>
</Box> </Box>
<Button
variant="light" <TabsTab
bg={colors['blue-button-2']} value="zoom-in"
c={colors['blue-button']}
size="sm"
onClick={handleZoomIn} onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />} leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
> >
Zoom In <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
</Button> </TabsTab>
<Button
variant="light" <TabsTab
bg={colors['blue-button-2']} value="reset"
c={colors['blue-button']}
size="sm"
onClick={resetZoom} onClick={resetZoom}
style={{ flexShrink: 0 }}
> >
Reset <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
</Button> </TabsTab>
<Button
variant="light" <TabsTab
bg={colors['blue-button-2']} value="fullscreen"
c={colors['blue-button']}
size="sm"
onClick={toggleFullscreen} onClick={toggleFullscreen}
leftSection={ leftSection={
isFullscreen ? ( isFullscreen ? (
@@ -288,14 +349,18 @@ function StrukturOrganisasiBumDes() {
<IconArrowsMaximize size={16} /> <IconArrowsMaximize size={16} />
) )
} }
style={{ flexShrink: 0 }}
> >
Fullscreen <Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
</Button> {isFullscreen ? 'Exit' : 'Fullscreen'}
</Group> </Text>
</Group> </TabsTab>
</TabsList>
</Tabs>
</Stack>
</Paper> </Paper>
{/* 📊 Chart Container */} {/* 🧩 Chart Container */}
<Center style={{ width: '100%' }}> <Center style={{ width: '100%' }}>
<Box <Box
ref={chartContainerRef} ref={chartContainerRef}
@@ -303,28 +368,36 @@ function StrukturOrganisasiBumDes() {
overflowX: 'auto', overflowX: 'auto',
overflowY: 'auto', overflowY: 'auto',
width: '100%', width: '100%',
maxWidth: '100%',
padding: '32px 16px', padding: '32px 16px',
transition: 'transform 0.2s ease', transition: 'transform 0.2s ease',
transform: `scale(${scale})`,
transformOrigin: 'center top',
}} }}
> >
<Box style={{
transform: `scale(${scale})`,
transformOrigin: 'center top',
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
}}>
<OrganizationChart <OrganizationChart
value={chartData} value={chartData}
nodeTemplate={(node) => <NodeCard node={node} />} nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal" className="p-organizationchart p-organizationchart-horizontal"
/> />
</Box> </Box>
</Box>
</Center> </Center>
</Stack> </Stack>
) )
} }
function NodeCard({ node }: any) { function NodeCard({ node, router }: any) {
const imageSrc = node?.data?.image || '/img/default.png' const imageSrc = node?.data?.image || '/img/default.png'
const name = node?.data?.name || 'Tanpa Nama' const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan' const title = node?.data?.title || 'Tanpa Jabatan'
const description = node?.data?.description || '' const hasId = Boolean(node?.data?.id)
const isMobile = useMediaQuery("(max-width: 768px)");
return ( return (
<Transition mounted transition="pop" duration={300}> <Transition mounted transition="pop" duration={300}>
@@ -333,40 +406,119 @@ function NodeCard({ node }: any) {
shadow="md" shadow="md"
radius="xl" radius="xl"
withBorder withBorder
style={{ style={{
...styles, ...styles,
width: 240, width: '100%',
padding: 20, maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
background: minHeight: isMobile ? 240 : 280,
'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)', padding: isMobile ? 16 : 20,
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
borderColor: 'rgba(28, 110, 164, 0.3)', borderColor: 'rgba(28, 110, 164, 0.3)',
borderWidth: 2,
transition: 'all 0.3s ease', transition: 'all 0.3s ease',
cursor: hasId ? 'pointer' : 'default',
}}
onMouseEnter={(e) => {
if (hasId) {
e.currentTarget.style.transform = 'translateY(-4px)'
e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)'
}
}}
onMouseLeave={(e) => {
if (hasId) {
e.currentTarget.style.transform = 'translateY(0)'
e.currentTarget.style.boxShadow = ''
}
}} }}
> >
<Stack align="center" gap={10}> <Stack align="center" gap={12}>
{/* Photo */}
<Box <Box
style={{ style={{
width: 90, width: 96,
height: 90, height: 96,
borderRadius: '50%', borderRadius: '50%',
overflow: 'hidden', overflow: 'hidden',
border: '3px solid rgba(28, 110, 164, 0.4)', border: '3px solid rgba(28, 110, 164, 0.4)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
background: 'white',
}} }}
> >
<Image src={imageSrc} alt={name} fit="cover" loading="lazy" /> <Image
src={imageSrc}
alt={name}
width={96}
height={96}
fit="cover"
loading="lazy"
style={{
objectFit: 'cover',
}}
/>
</Box> </Box>
<Text fw={700} size="sm" ta="center" c={colors['blue-button']}>
{/* Name */}
<Text
fw={700}
ta="center"
c={colors['blue-button']}
lineClamp={2}
fz={{ base: 13, md: 15 }}
lh={1.2}
style={{
minHeight: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
}}
>
{name} {name}
</Text> </Text>
<Text size="xs" c="dimmed" ta="center">
{/* Title/Position */}
<Text
c="dimmed"
ta="center"
fw={500}
lineClamp={2}
fz={{ base: 12, md: 13 }}
lh={1.3}
style={{
minHeight: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
}}
>
{title} {title}
</Text> </Text>
<Text size="xs" c="dimmed" ta="center" lineClamp={3}>
{description || 'Belum ada deskripsi.'} {/* Detail Button */}
</Text> {hasId && (
<Button
variant="gradient"
gradient={{ from: 'blue', to: 'cyan' }}
size="xs"
fullWidth
mt={8}
radius="md"
onClick={() =>
router.push(`/darmasaba/ppid/struktur-ppid/${node.data.id}`)
}
style={{
height: 32,
fontWeight: 600,
}}
>
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
</Button>
)}
</Stack> </Stack>
</Card> </Card>
)} )}
</Transition> </Transition>
) )
} }

View File

@@ -1,20 +1,18 @@
'use client' 'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif'; import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core'; import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react'; import { IconBulbFilled } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
function Page() { function Page() {
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
const ideInovatif = useProxy(ajukanIdeInovatifState) const ideInovatif = useProxy(ajukanIdeInovatifState);
const resetForm = () => { const resetForm = () => {
// Reset state di valtio
ideInovatif.create.form = { ideInovatif.create.form = {
name: "", name: "",
deskripsi: "", deskripsi: "",
@@ -23,53 +21,66 @@ function Page() {
masalah: "", masalah: "",
benefit: "", benefit: "",
}; };
// Reset state lokal
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
// Submit data berita
await ideInovatif.create.create(); await ideInovatif.create.create();
// Reset form setelah submit
resetForm(); resetForm();
close(); close();
}; };
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Box px={{ base: 'md', md: 100 }}>
Ajukan Ide Inovatif <Title
</Text> order={1}
<Text ta={'center'} fz={'h4'}>Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform &quot;Ajukan Ide Inovatif&quot; hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.</Text> ta="center"
</Box> c={colors["blue-button"]}
<Box px={{ base: "md", md: 100 }}> fw="bold"
<Stack gap={'lg'} p={'lg'}> style={{ fontSize: 'clamp(1.75rem, 4vw, 2.25rem)' }}
<SimpleGrid
cols={{
base: 1,
md: 2,
}}
> >
<Paper p={'xl'} > Ajukan Ide Inovatif
<Stack gap={"xs"}> </Title>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Tujuan Ide Inovatif Ini</Text> <Text ta="center" fz={{ base: 'sm', md: 'md' }} c="black" lh="1.6">
Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform &quot;Ajukan Ide Inovatif&quot; hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.
</Text>
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg" p="lg">
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Paper p="xl">
<Stack gap="xs">
<Title order={2} c={colors['blue-button']} fw="bold">
Tujuan Ide Inovatif Ini
</Title>
<List> <List>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mendorong partisipasi aktif masyarakat</ListItem> <ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memfasilitasi inovasi berbasis lokal</ListItem> Mendorong partisipasi aktif masyarakat
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memecahkan tantangan komunal</ListItem> </ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mengembangkan potensi kreativitas warga</ListItem> <ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Memfasilitasi inovasi berbasis lokal
</ListItem>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Memecahkan tantangan komunal
</ListItem>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Mengembangkan potensi kreativitas warga
</ListItem>
</List> </List>
</Stack> </Stack>
</Paper> </Paper>
<Paper p={'xl'} >
<Flex align={'center'} justify={'space-between'}> <Paper p="xl">
<Flex align="center" justify="space-between" direction={{ base: 'column', md: 'row' }} gap="md">
<Box> <Box>
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar Di Samping</Text> <Title order={3} c={colors['blue-button']} fw="bold" ta={{ base: 'center', md: 'start' }}>
<IconArrowRight size={30} color={colors['blue-button']} /> Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar
</Title>
</Box> </Box>
<Box px={{ base: 5, md: 10 }} py={5}> <Box px={{ base: 5, md: 10 }} py={5}>
<ActionIcon variant="transparent" size={150} onClick={open}> <ActionIcon variant="transparent" size={150} onClick={open}>
@@ -88,32 +99,46 @@ function Page() {
radius={0} radius={0}
transitionProps={{ transition: 'fade', duration: 200 }} transitionProps={{ transition: 'fade', duration: 200 }}
> >
<Paper p={"md"} withBorder> <Paper p="md" withBorder>
<Stack gap={"xs"}> <Stack gap="xs">
<Title order={3}>Ajukan Ide Inovatif</Title> <Title order={3}>Ajukan Ide Inovatif</Title>
<TextInput <TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>} label={
<Text fz="sm" fw="bold">
Nama
</Text>
}
placeholder="masukkan nama" placeholder="masukkan nama"
onChange={(val) => { onChange={(val) => {
ideInovatif.create.form.name = val.target.value ideInovatif.create.form.name = val.target.value;
}} }}
/> />
<TextInput <TextInput
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>} label={
<Text fz="sm" fw="bold">
Alamat
</Text>
}
placeholder="masukkan alamat" placeholder="masukkan alamat"
onChange={(val) => { onChange={(val) => {
ideInovatif.create.form.alamat = val.target.value ideInovatif.create.form.alamat = val.target.value;
}} }}
/> />
<TextInput <TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama Ide</Text>} label={
<Text fz="sm" fw="bold">
Nama Ide
</Text>
}
placeholder="masukkan nama ide" placeholder="masukkan nama ide"
onChange={(val) => { onChange={(val) => {
ideInovatif.create.form.namaIde = val.target.value ideInovatif.create.form.namaIde = val.target.value;
}} }}
/> />
<Box> <Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text> <Text fz="sm" fw="bold">
Deskripsi
</Text>
<CreateEditor <CreateEditor
value={ideInovatif.create.form.deskripsi} value={ideInovatif.create.form.deskripsi}
onChange={(htmlContent) => { onChange={(htmlContent) => {
@@ -122,24 +147,33 @@ function Page() {
/> />
</Box> </Box>
<TextInput <TextInput
label={<Text fz={"sm"} fw={"bold"}>Masalah</Text>} label={
<Text fz="sm" fw="bold">
Masalah
</Text>
}
placeholder="masukkan masalah" placeholder="masukkan masalah"
onChange={(val) => { onChange={(val) => {
ideInovatif.create.form.masalah = val.target.value ideInovatif.create.form.masalah = val.target.value;
}} }}
/> />
<TextInput <TextInput
label={<Text fz={"sm"} fw={"bold"}>Benefit</Text>} label={
<Text fz="sm" fw="bold">
Benefit
</Text>
}
placeholder="masukkan benefit" placeholder="masukkan benefit"
onChange={(val) => { onChange={(val) => {
ideInovatif.create.form.benefit = val.target.value ideInovatif.create.form.benefit = val.target.value;
}} }}
/> />
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button> <Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan
</Button>
</Stack> </Stack>
</Paper> </Paper>
</Modal> </Modal>
</Stack> </Stack>
); );
} }

View File

@@ -58,7 +58,7 @@ function Page() {
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text> <Text pt={20} fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text>
<Text fz={'md'}>dan peningkatan ekonomi berbasis teknologi.</Text> <Text fz={'md'}>dan peningkatan ekonomi berbasis teknologi.</Text>
</Box> </Box>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>

View File

@@ -1,17 +1,17 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react'; import { useState } from 'react';
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno'; import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
import { IconSearch } from '@tabler/icons-react'; import { IconSearch } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() { function Page() {
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const state = useProxy(infoTeknoState) const state = useProxy(infoTeknoState)
const { const {
data, data,
@@ -34,17 +34,24 @@ function Page() {
</Stack> </Stack>
) )
} }
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Box px={{ base: 'md', md: 100 }} >
<Box px={{ base: 'md', md: 100 }}>
<Grid align='center'> <Grid align='center'>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title
order={1}
c={colors["blue-button"]}
fw={"bold"}
ta={{ base: 'center', md: 'left' }}
>
Info Teknologi Tepat Guna Info Teknologi Tepat Guna
</Text> </Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 3 }}> <GridCol span={{ base: 12, md: 3 }}>
<TextInput <TextInput
@@ -53,13 +60,19 @@ function Page() {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }} w={{ base: "100%", md: "100%" }}
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text fz={'md'}>Desa Darmasaba berkomitmen mengembangkan teknologi tepat guna yang sesuai dengan kebutuhan masyarakat,</Text>
<Text fz={'md'}>mendukung pembangunan berkelanjutan, dan meningkatkan kualitas hidup warga.</Text> <Text pt={20} fz={{ base: 'sm', md: 'md' }} ta={{ base: 'center', md: 'left' }} lh={1.5}>
Desa Darmasaba berkomitmen mengembangkan teknologi tepat guna yang sesuai dengan kebutuhan masyarakat,
</Text>
<Text fz={{ base: 'sm', md: 'md' }} ta={{ base: 'center', md: 'left' }} lh={1.5}>
mendukung pembangunan berkelanjutan, dan meningkatkan kualitas hidup warga.
</Text>
</Box> </Box>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} p={'lg'}> <Stack gap={'lg'} p={'lg'}>
<SimpleGrid <SimpleGrid
@@ -74,12 +87,14 @@ function Page() {
<Paper p={'xl'} key={k}> <Paper p={'xl'} key={k}>
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Image src={v.image.link || ''} pb={10} radius={10} alt='' loading="lazy" /> <Image src={v.image.link || ''} pb={10} radius={10} alt='' loading="lazy" />
<Text fz={'h3'} fw={'bold'}>{v.name}</Text> <Title order={3} fw={'bold'} ta="left">
{v.name}
</Title>
<Box pr={'lg'} pb={10}> <Box pr={'lg'} pb={10}>
<Text <Text
size="md" fz={{ base: 'xs', md: 'sm' }}
ta="justify" ta="justify"
lh={1} // line height biar enak dibaca lh={1.5}
style={{ style={{
wordBreak: "break-word", wordBreak: "break-word",
whiteSpace: "normal", whiteSpace: "normal",
@@ -94,10 +109,11 @@ function Page() {
</SimpleGrid> </SimpleGrid>
</Stack> </Stack>
</Box> </Box>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => load(newPage)} // ini penting! onChange={(newPage) => load(newPage)}
total={totalPages} total={totalPages}
my="md" my="md"
/> />

View File

@@ -58,7 +58,7 @@ function Page() {
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" mt={4} > <Text px={{ base: 'md', md: 100 }} pt={20} ta={"justify"} fz="md" mt={4} >
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga. Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
</Text> </Text>
</Box> </Box>

View File

@@ -71,14 +71,14 @@ function Page() {
<Stack gap="md"> <Stack gap="md">
<Box> <Box>
<Text fw={600} fz="lg" >Judul</Text> <Text fw={600} fz="lg" >Judul</Text>
<Text fz="sm" c="dimmed">{data.judul || '-'}</Text> <Text fz="sm" c="black">{data.judul || '-'}</Text>
</Box> </Box>
<Divider /> <Divider />
<Box> <Box>
<Text fw={600} fz="lg" >Tanggal</Text> <Text fw={600} fz="lg" >Tanggal</Text>
<Text fz="sm" c="dimmed"> <Text fz="sm" c="black">
{data.tanggalWaktu {data.tanggalWaktu
? new Date(data.tanggalWaktu).toLocaleString('id-ID', { dateStyle: 'full', timeStyle: 'short' }) ? new Date(data.tanggalWaktu).toLocaleString('id-ID', { dateStyle: 'full', timeStyle: 'short' })
: '-'} : '-'}
@@ -89,7 +89,7 @@ function Page() {
<Box> <Box>
<Text fw={600} fz="lg" >Lokasi</Text> <Text fw={600} fz="lg" >Lokasi</Text>
<Text fz="sm" c="dimmed">{data.lokasi || '-'}</Text> <Text fz="sm" c="black">{data.lokasi || '-'}</Text>
</Box> </Box>
<Divider /> <Divider />
@@ -120,7 +120,7 @@ function Page() {
<Box> <Box>
<Text fw={600} fz="lg" >Kronologi</Text> <Text fw={600} fz="lg" >Kronologi</Text>
<Text fz="sm" c="dimmed" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} /> <Text fz="sm" c="black" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
</Box> </Box>
<Divider /> <Divider />
@@ -136,11 +136,11 @@ function Page() {
radius="md" radius="md"
shadow="xs" shadow="xs"
withBorder withBorder
bg="dark.5" bg={colors['blue-button-1']}
> >
<Text <Text
fz="sm" fz="sm"
c="dimmed" c="black"
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{wordBreak: "break-word", whiteSpace: "normal"}}
/> />

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan'; import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
@@ -8,11 +8,10 @@ import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react'; import { useState } from 'react';
import { IconSearch } from '@tabler/icons-react'; import { IconSearch } from '@tabler/icons-react';
function Page() { function Page() {
const state = useProxy(tipsKeamananState) const state = useProxy(tipsKeamananState);
const [search, setSearch] = useState('') const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const { const {
data, data,
page, page,
@@ -22,84 +21,114 @@ function Page() {
} = state.findMany; } = state.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 3, debouncedSearch) load(page, 3, debouncedSearch);
}, [page, debouncedSearch]) }, [page, debouncedSearch]);
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Skeleton h={500} /> <Skeleton h={500} />
</Stack> </Stack>
) );
} }
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Box> <Box>
<Grid align='center' px={{ base: 'md', md: 100 }}> <Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title
order={1}
c={colors['blue-button']}
style={{ lineHeight: '1.2' }}
>
Tips Keamanan Tips Keamanan
</Text> </Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 3 }}> <GridCol span={{ base: 12, md: 3 }}>
<TextInput <TextInput
radius={"lg"} radius="lg"
placeholder='Cari Tips' placeholder="Cari Tips"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }} w={'100%'}
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
<Text
px={{ base: 'md', md: 100 }}
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
mt="sm"
>
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal). Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal).
</Text> </Text>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" > <Text
px={{ base: 'md', md: 100 }}
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
mt="xs"
>
Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga. Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
</Text> </Text>
</Box> </Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}> <Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
<SimpleGrid <SimpleGrid
pb={10} pb="10"
cols={{ cols={{ base: 1, md: 3 }}
base: 1, >
md: 3, {data.map((v, k) => (
}}> <Paper radius={10} key={k} bg={colors['white-trans-1']}>
{data.map((v, k) => { <Stack gap="xs">
return ( <Center p="10">
<Paper radius={10} key={k} bg={colors["white-trans-1"]}> <Image
<Stack gap={'xs'}> src={v.image?.link}
<Center p={10}> radius={10}
<Image src={v.image?.link} radius={10} loading="lazy" loading="lazy"
alt='' /> alt=""
/>
</Center> </Center>
<Box px={'xl'}> <Box px="xl">
<Box pb={20}> <Box pb="20">
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}> <Title
order={3}
c={colors['blue-button']}
style={{ lineHeight: '1.3' }}
>
{v.judul} {v.judul}
</Text> </Title>
<Box> <Box>
<Text pb={10} fz={"md"} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} /> <Text
pb="10"
fz={{ base: 'xs', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Box> </Box>
</Box> </Box>
</Box> </Box>
</Stack> </Stack>
</Paper> </Paper>
) ))}
})}
</SimpleGrid> </SimpleGrid>
</Stack> </Stack>
</Box> </Box>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => load(newPage)} // ini penting! onChange={(newPage) => load(newPage)}
total={totalPages} total={totalPages}
my="md" my="md"
/> />

View File

@@ -2,7 +2,7 @@
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto'; import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Divider, Flex, Group, Image, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; import { Box, Divider, Flex, Group, Image, List, ListItem, 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 { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react'; import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
@@ -37,9 +37,9 @@ function Page() {
<Stack gap="lg"> <Stack gap="lg">
<Paper radius="xl" shadow="md" withBorder> <Paper radius="xl" shadow="md" withBorder>
<Box style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} bg={colors['blue-button']}> <Box style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} bg={colors['blue-button']}>
<Text p="md" fz={{ base: 'h3', md: 'h2' }} c={colors['white-1']} fw="bold"> <Title order={1} p="md" c={colors['white-1']} fw="bold">
{state.findUnique.data.title || 'Detail Artikel Kesehatan'} {state.findUnique.data.title || 'Detail Artikel Kesehatan'}
</Text> </Title>
</Box> </Box>
<Box p="lg"> <Box p="lg">
@@ -64,7 +64,7 @@ function Page() {
<Stack gap="lg"> <Stack gap="lg">
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={18} color={colors['blue-button']} /> <IconCalendar size={18} color={colors['blue-button']} />
<Text c="dimmed" fz="sm"> <Text fz={{ base: 'xs', md: 'sm' }} lh={1.5}>
{new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', { {new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
@@ -74,48 +74,47 @@ function Page() {
</Group> </Group>
<Stack gap="lg"> <Stack gap="lg">
<Box> <Box>
<Text fz="h4" fw="bold">Pendahuluan</Text> <Title order={2} fw="bold">Pendahuluan</Title>
<Divider my="xs" /> <Divider my="xs" />
<Box pl={20}> <Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} /> <Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text> <Title order={2} fw="bold">{state.findUnique.data.symptom?.title}</Title>
<Divider my="xs" /> <Divider my="xs" />
<Box pl={20}> <Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} /> <Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Text fz="h4" fw="bold">{state.findUnique.data.prevention?.title}</Text> <Title order={2} fw="bold">{state.findUnique.data.prevention?.title}</Title>
<Divider my="xs" /> <Divider my="xs" />
<Box pl={20}> <Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} /> <Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Text fz="h4" fw="bold">{state.findUnique.data.firstaid?.title}</Text> <Title order={2} fw="bold">{state.findUnique.data.firstaid?.title}</Title>
<Divider my="xs" /> <Divider my="xs" />
<Box pl={20}> <Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} /> <Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Text fz="h4" fw="bold">{state.findUnique.data.mythvsfact?.title}</Text> <Title order={2} fw="bold">{state.findUnique.data.mythvsfact?.title}</Title>
<Divider my="xs" /> <Divider my="xs" />
<Box pb="md"> <Box pb="md">
<Table highlightOnHover withTableBorder withColumnBorders striped> <Table highlightOnHover withTableBorder withColumnBorders striped>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh fz="sm" fw="bold">Mitos</TableTh> <TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Mitos</TableTh>
<TableTh fz="sm" fw="bold">Fakta</TableTh> <TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Fakta</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -123,12 +122,12 @@ function Page() {
<TableTr> <TableTr>
<TableTd> <TableTd>
<Box pl={20}> <Box pl={20}>
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} /> <Text fz={{ base: 'xs', md: 'sm' }} lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
</Box> </Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box pl={20}> <Box pl={20}>
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} /> <Text fz={{ base: 'xs', md: 'sm' }} lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
</Box> </Box>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -143,34 +142,35 @@ function Page() {
</Box> </Box>
<Box> <Box>
<Text fz="h4" fw="bold">Kapan Harus ke Dokter?</Text> <Title order={2} fw="bold">Kapan Harus ke Dokter?</Title>
<Divider my="xs" /> <Divider my="xs" />
<Flex justify={'flex-start'} gap={"xs"} align={"center"} mb="xs"> <Flex justify={'flex-start'} gap={"xs"} align={"center"} mb="xs">
<IconAlertCircle size={18} color="red" /> <IconAlertCircle size={18} color="red" />
<Text fz="md">Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
</Flex> </Flex>
<Box pl={20}> <Box pl={20}>
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} /></Box> <Text fz={{ base: 'sm', md: 'md' }} lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} />
</Box>
</Box> </Box>
<Box> <Box>
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder> <Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
<Group gap="xs" mb="sm"> <Group gap="xs" mb="sm">
<IconInfoCircle size={20} color={colors['white-1']} /> <IconInfoCircle size={20} color={colors['white-1']} />
<Text fz="h4" c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Text> <Title order={3} c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Title>
</Group> </Group>
<Stack gap={4}> <Stack gap={4}>
<Text fz="sm" c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text> <Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
<Text fz="sm" c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text> <Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
<Text fz="sm" c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text> <Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>
<Box> <Box>
<Text fz="h4" fw="bold">Referensi</Text> <Title order={2} fw="bold">Referensi</Title>
<Divider my="xs" /> <Divider my="xs" />
<List spacing="xs" size="sm" type="ordered"> <List spacing="xs" fz={{ base: 'xs', md: 'sm' }} type="ordered">
<ListItem>Kementerian Kesehatan RI. (2024). Pedoman Pencegahan dan Pengendalian DBD.</ListItem> <ListItem>Kementerian Kesehatan RI. (2024). Pedoman Pencegahan dan Pengendalian DBD.</ListItem>
<ListItem>World Health Organization. (2024). Dengue Guidelines for Diagnosis, Treatment, Prevention and Control.</ListItem> <ListItem>World Health Organization. (2024). Dengue Guidelines for Diagnosis, Treatment, Prevention and Control.</ListItem>
<ListItem>Dinas Kesehatan Kabupaten Badung. (2025). Laporan Surveilans DBD Triwulan I 2025.</ListItem> <ListItem>Dinas Kesehatan Kabupaten Badung. (2025). Laporan Surveilans DBD Triwulan I 2025.</ListItem>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Card, Divider, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core'; import { Box, Button, Card, Divider, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconChevronRight } from '@tabler/icons-react'; import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -17,9 +17,8 @@ function ArtikelKesehatanPage() {
if (!state.findMany.data) { if (!state.findMany.data) {
return ( return (
<Box py="xl" ta="center"> <Box py="lg">
<Loader size="lg" color={colors['blue-button']} /> <Skeleton h={500} radius="lg" />
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
</Box> </Box>
) )
} }
@@ -28,13 +27,13 @@ function ArtikelKesehatanPage() {
<Box> <Box>
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md"> <Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
<Stack gap="lg"> <Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}> <Title ta="center" order={1} c={colors['blue-button']}>
Artikel Kesehatan Artikel Kesehatan
</Text> </Title>
<Divider size="sm" color={colors['blue-button']} /> <Divider size="sm" color={colors['blue-button']} />
{state.findMany.data.length === 0 ? ( {state.findMany.data.length === 0 ? (
<Box py="xl" ta="center"> <Box py="xl" ta="center">
<Text fz="lg" c="dimmed"> <Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
Belum ada artikel kesehatan yang tersedia Belum ada artikel kesehatan yang tersedia
</Text> </Text>
</Box> </Box>
@@ -51,17 +50,26 @@ function ArtikelKesehatanPage() {
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')} onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
> >
<Card.Section> <Card.Section>
<Image style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }} src={item.image?.link} alt={item.title} height={200} fit="cover" loading="lazy" /> <Image
style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }}
src={item.image?.link}
alt={item.title}
height={200}
fit="cover"
loading="lazy"
/>
</Card.Section> </Card.Section>
<Stack gap="xs" mt="md"> <Stack gap="xs" mt="md">
<Text fw="bold" fz="xl" c={colors['blue-button']}>{item.title}</Text> <Title order={3} c={colors['blue-button']}>
{item.title}
</Title>
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={16} color='gray' /> <IconCalendar size={16} color='gray' />
<Text fz="sm" c="dimmed"> <Text fz={{ base: 'xs', sm: 'sm' }} c="dimmed">
{new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} Dinas Kesehatan {new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} Dinas Kesehatan
</Text> </Text>
</Group> </Group>
<Text fz="md" lineClamp={3}> <Text fz={{ base: 'sm', sm: 'md' }} lh={{ base: 'sm', sm: 'md' }} lineClamp={3}>
{item.content} {item.content}
</Text> </Text>
<Group justify="flex-start"> <Group justify="flex-start">

View File

@@ -16,7 +16,6 @@ interface Kontak {
email: string; email: string;
} }
interface Lokasi { interface Lokasi {
mapsEmbed: string; mapsEmbed: string;
} }
@@ -35,7 +34,7 @@ function Page() {
state.findUnique.load(params?.id as string); state.findUnique.load(params?.id as string);
}, []); }, []);
const data = state.findUnique.data as any; // Temporary any to fix type issues const data = state.findUnique.data as any;
const nama = data?.name || 'Fasilitas Kesehatan'; const nama = data?.name || 'Fasilitas Kesehatan';
const prosedur = data?.prosedurpendaftaran.content || ''; const prosedur = data?.prosedurpendaftaran.content || '';
@@ -111,11 +110,11 @@ function Page() {
<Group gap="md" wrap="wrap"> <Group gap="md" wrap="wrap">
<Group gap="xs"> <Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconMapPin size={18} /></ThemeIcon> <ThemeIcon variant="light" radius="xl"><IconMapPin size={18} /></ThemeIcon>
<Text>{alamat}</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{alamat}</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconDeviceLandlinePhone size={18} /></ThemeIcon> <ThemeIcon variant="light" radius="xl"><IconDeviceLandlinePhone size={18} /></ThemeIcon>
<Text>{kontak.telepon}</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.telepon}</Text>
<CopyButton value={kontak.telepon}> <CopyButton value={kontak.telepon}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin nomor'}> <Tooltip label={copied ? 'Disalin' : 'Salin nomor'}>
@@ -126,7 +125,7 @@ function Page() {
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconBrandWhatsapp size={18} /></ThemeIcon> <ThemeIcon variant="light" radius="xl"><IconBrandWhatsapp size={18} /></ThemeIcon>
<Text>{kontak.whatsapp}</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.whatsapp}</Text>
<CopyButton value={kontak.whatsapp}> <CopyButton value={kontak.whatsapp}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin WhatsApp'}> <Tooltip label={copied ? 'Disalin' : 'Salin WhatsApp'}>
@@ -137,7 +136,7 @@ function Page() {
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconMail size={18} /></ThemeIcon> <ThemeIcon variant="light" radius="xl"><IconMail size={18} /></ThemeIcon>
<Text>{kontak.email}</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.email}</Text>
<CopyButton value={kontak.email}> <CopyButton value={kontak.email}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin email'}> <Tooltip label={copied ? 'Disalin' : 'Salin email'}>
@@ -163,33 +162,43 @@ function Page() {
<Divider /> <Divider />
<Group gap="xl" align="start"> <Group gap="xl" align="start">
<Stack gap={2}> <Stack gap={2}>
<Text c="dimmed" fz="sm">Nama Fasilitas</Text> <Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>Nama Fasilitas</Text>
<Text fw={600}>{nama}</Text> <Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.5}>{nama}</Text>
</Stack> </Stack>
<Stack gap={2}> <Stack gap={2}>
<Text c="dimmed" fz="sm">Jam Operasional</Text> <Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>Jam Operasional</Text>
<Text fw={600}>{jam}</Text> <Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.5}>{jam}</Text>
</Stack> </Stack>
</Group> </Group>
<Divider /> <Divider />
<Title order={4}>Layanan Unggulan</Title> <Title order={4}>Layanan Unggulan</Title>
<Divider /> <Divider />
{layananUnggulan ? ( {layananUnggulan ? (
<Box pl={"lg"}> <Box pl="lg">
<Text fz="md" style={{ lineHeight: 1.7, wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: layananUnggulan }} /> <Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: layananUnggulan }}
/>
</Box> </Box>
) : ( ) : (
<Paper withBorder radius="md" p="md"> <Paper withBorder radius="md" p="md">
<Group gap="sm"> <Group gap="sm">
<IconMoodEmpty /> <IconMoodEmpty />
<Text>Belum ada informasi fasilitas pendukung.</Text> <Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada informasi layanan unggulan.</Text>
</Group> </Group>
</Paper> </Paper>
)} )}
<Divider /> <Divider />
<Title order={4}>Peta Lokasi</Title> <Title order={4}>Peta Lokasi</Title>
<AspectRatio ratio={16 / 9}> <AspectRatio ratio={16 / 9}>
<iframe src={lokasi.mapsEmbed} style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }} loading="lazy" aria-label="Peta Lokasi" /> <iframe
src={lokasi.mapsEmbed}
style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }}
loading="lazy"
aria-label="Peta Lokasi"
/>
</AspectRatio> </AspectRatio>
</Stack> </Stack>
</Card> </Card>
@@ -201,9 +210,15 @@ function Page() {
<Stack gap="md"> <Stack gap="md">
<Title order={4}>Kontak Cepat</Title> <Title order={4}>Kontak Cepat</Title>
<Group gap="sm" wrap="wrap"> <Group gap="sm" wrap="wrap">
<Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">Telepon</Button> <Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">WhatsApp</Button> <Text fz={{ base: 'xs', md: 'sm' }}>Telepon</Text>
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">Email</Button> </Button>
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">
<Text fz={{ base: 'xs', md: 'sm' }}>WhatsApp</Text>
</Button>
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">
<Text fz={{ base: 'xs', md: 'sm' }}>Email</Text>
</Button>
</Group> </Group>
</Stack> </Stack>
</Card> </Card>
@@ -214,9 +229,15 @@ function Page() {
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Dokter"> <Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Dokter">
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama</TableTh> <TableTh>
<TableTh>Spesialisasi</TableTh> <Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Nama</Text>
<TableTh>Jadwal</TableTh> </TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Spesialisasi</Text>
</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Jadwal</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -226,11 +247,15 @@ function Page() {
<TableTd> <TableTd>
<Group gap="xs"> <Group gap="xs">
<IconUser size={16} /> <IconUser size={16} />
<Text>{dokter.name || '-'}</Text> <Text fz={{ base: 'sm', md: 'md' }}>{dokter.name || '-'}</Text>
</Group> </Group>
</TableTd> </TableTd>
<TableTd>{dokter.specialist || '-'}</TableTd> <TableTd>
<TableTd>{dokter.jadwal || '-'}</TableTd> <Text fz={{ base: 'sm', md: 'md' }}>{dokter.specialist || '-'}</Text>
</TableTd>
<TableTd>
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.jadwal || '-'}</Text>
</TableTd>
</TableTr> </TableTr>
)) ))
) : ( ) : (
@@ -238,7 +263,7 @@ function Page() {
<TableTd colSpan={3}> <TableTd colSpan={3}>
<Group justify="center" gap="xs" c="dimmed"> <Group justify="center" gap="xs" c="dimmed">
<IconSearch size={18} /> <IconSearch size={18} />
<Text>Tidak ada data tenaga medis.</Text> <Text fz={{ base: 'sm', md: 'md' }}>Tidak ada data tenaga medis.</Text>
</Group> </Group>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -254,13 +279,18 @@ function Page() {
<Divider /> <Divider />
{fasilitasPendukungHtml ? ( {fasilitasPendukungHtml ? (
<Box pl="lg"> <Box pl="lg">
<Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} /> <Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }}
/>
</Box> </Box>
) : ( ) : (
<Paper withBorder radius="md" p="md"> <Paper withBorder radius="md" p="md">
<Group gap="sm"> <Group gap="sm">
<IconMoodEmpty /> <IconMoodEmpty />
<Text>Belum ada informasi fasilitas pendukung.</Text> <Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada informasi fasilitas pendukung.</Text>
</Group> </Group>
</Paper> </Paper>
)} )}
@@ -274,16 +304,24 @@ function Page() {
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif"> <Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Layanan</TableTh> <TableTh>
<TableTh>Tarif</TableTh> <Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Layanan</Text>
</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Tarif</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{Array.isArray(data?.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? ( {Array.isArray(data?.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? (
data.tarifdanlayanan.map((item: any) => ( data.tarifdanlayanan.map((item: any) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd>{item.layanan || '-'}</TableTd> <TableTd>
<TableTd>{formatRupiah(item.tarif)}</TableTd> <Text fz={{ base: 'sm', md: 'md' }}>{item.layanan || '-'}</Text>
</TableTd>
<TableTd>
<Text fz={{ base: 'sm', md: 'md' }}>{formatRupiah(item.tarif)}</Text>
</TableTd>
</TableTr> </TableTr>
)) ))
) : ( ) : (
@@ -291,7 +329,7 @@ function Page() {
<TableTd colSpan={2}> <TableTd colSpan={2}>
<Group justify="center" gap="xs" c="dimmed"> <Group justify="center" gap="xs" c="dimmed">
<IconSearch size={18} /> <IconSearch size={18} />
<Text>Tidak ada data tarif.</Text> <Text fz={{ base: 'sm', md: 'md' }}>Tidak ada data tarif.</Text>
</Group> </Group>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -301,7 +339,7 @@ function Page() {
{gratisBpjs && ( {gratisBpjs && (
<Group gap="xs"> <Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon> <ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text> <Text fz={{ base: 'sm', md: 'md' }} fw={600}>Gratis dengan BPJS Kesehatan</Text>
</Group> </Group>
)} )}
</Stack> </Stack>
@@ -317,9 +355,16 @@ function Page() {
<Title order={3}>Prosedur Pendaftaran</Title> <Title order={3}>Prosedur Pendaftaran</Title>
<Divider /> <Divider />
{prosedur ? ( {prosedur ? (
<Box pl="lg"><Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: prosedur }} /></Box> <Box pl="lg">
<Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: prosedur }}
/>
</Box>
) : ( ) : (
<Text fz="md" c="dimmed">Belum ada prosedur pendaftaran</Text> <Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada prosedur pendaftaran</Text>
)} )}
</Stack> </Stack>
</Paper> </Paper>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react'; import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -17,12 +17,8 @@ function FasilitasKesehatanPage() {
if (!state.findMany.data) { if (!state.findMany.data) {
return ( return (
<Box py="xl" px="md"> <Box py="lg">
<Stack gap="md"> <Skeleton h={500} radius="lg" />
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
</Stack>
</Box> </Box>
); );
} }
@@ -31,14 +27,24 @@ function FasilitasKesehatanPage() {
<Box> <Box>
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%"> <Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%">
<Stack gap="lg"> <Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}> <Title
order={1}
ta="center"
fw={700}
c={colors['blue-button']}
style={{ lineHeight: '1.2' }}
>
Fasilitas Kesehatan Fasilitas Kesehatan
</Text> </Title>
<Divider size="sm" color={colors['blue-button']} /> <Divider size="sm" color={colors['blue-button']} />
<Stack gap="lg"> <Stack gap="lg">
{state.findMany.data.length === 0 ? ( {state.findMany.data.length === 0 ? (
<Box py="xl" ta="center"> <Box py="xl" ta="center">
<Text fz="lg" c="dimmed"> <Text
fz={{ base: 'sm', sm: 'md' }}
c={colors['blue-button']}
lh={{ base: '1.5', sm: '1.6' }}
>
Belum ada fasilitas kesehatan yang tersedia Belum ada fasilitas kesehatan yang tersedia
</Text> </Text>
</Box> </Box>
@@ -65,22 +71,36 @@ function FasilitasKesehatanPage() {
> >
<Stack gap="sm"> <Stack gap="sm">
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Text fw={700} fz="lg" c={colors['blue-button']}> <Title
order={3}
fw={700}
c={colors['blue-button']}
fz={{ base: 'sm', sm: 'md' }}
lh={{ base: '1.3', sm: '1.3' }}
>
{item.name} {item.name}
</Text> </Title>
<Badge color="blue" radius="sm" variant="light" fz="xs"> <Badge color="blue" radius="sm" variant="light" size="xs">
Aktif Aktif
</Badge> </Badge>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconMapPin size={18} stroke={1.5} /> <IconMapPin size={18} stroke={1.5} />
<Text fz="sm"> <Text
fz={{ base: 'xs', sm: 'sm' }}
lh={{ base: '1.5', sm: '1.5' }}
c="text"
>
{item.informasiumum.alamat} {item.informasiumum.alamat}
</Text> </Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconClock size={18} stroke={1.5} /> <IconClock size={18} stroke={1.5} />
<Text fz="sm"> <Text
fz={{ base: 'xs', sm: 'sm' }}
lh={{ base: '1.5', sm: '1.5' }}
c="text"
>
{item.informasiumum.jamOperasional} {item.informasiumum.jamOperasional}
</Text> </Text>
</Group> </Group>

View File

@@ -11,7 +11,8 @@ import {
Paper, Paper,
Skeleton, Skeleton,
Stack, Stack,
Text Text,
Title
} from '@mantine/core'; } from '@mantine/core';
import { useDisclosure, useShallowEffect } from '@mantine/hooks'; import { useDisclosure, useShallowEffect } from '@mantine/hooks';
import { IconMail, IconPhone, IconUser } from '@tabler/icons-react'; import { IconMail, IconPhone, IconUser } from '@tabler/icons-react';
@@ -51,55 +52,97 @@ function Page() {
style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
bg={colors['blue-button']} bg={colors['blue-button']}
> >
<Text p="md" fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw="bold"> <Title
p="md"
order={1}
c={colors['white-1']}
fw="bold"
ta={{ base: 'center', md: 'left' }}
>
Detail & Pendaftaran Kegiatan Detail & Pendaftaran Kegiatan
</Text> </Title>
</Box> </Box>
<Box p="lg"> <Box p="lg">
<Stack gap="xl"> <Stack gap="xl">
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Informasi Kegiatan</Text> <Title order={2} fw="bold">Informasi Kegiatan</Title>
<Divider /> <Divider />
<Text fz="md" fw="bold">Nama Kegiatan: <Text span>{state.findUnique.data.informasijadwalkegiatan.name}</Text></Text> <Text fw="bold">
<Text fz="md" fw="bold">Tanggal: <Text span>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text></Text> Nama Kegiatan:&nbsp;
<Text fz="md" fw="bold">Waktu: <Text span>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text></Text> <Text span fw="normal">
<Text fz="md" fw="bold">Lokasi: <Text span>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text></Text> {state.findUnique.data.informasijadwalkegiatan.name}
</Text>
</Text>
<Text fw="bold">
Tanggal:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.tanggal}
</Text>
</Text>
<Text fw="bold">
Waktu:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.waktu}
</Text>
</Text>
<Text fw="bold">
Lokasi:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.lokasi}
</Text>
</Text>
</Stack> </Stack>
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text> <Title order={2} fw="bold">Deskripsi Kegiatan</Title>
<Divider /> <Divider />
<Box pl={20}> <Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} /> <Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }}
/>
</Box> </Box>
</Stack> </Stack>
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text> <Title order={2} fw="bold">Layanan yang Tersedia</Title>
<Divider /> <Divider />
<Box pl={20}> <Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} /> <Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }}
/>
</Box> </Box>
</Stack> </Stack>
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text> <Title order={2} fw="bold">Syarat & Ketentuan</Title>
<Divider /> <Divider />
<Box pl={20}> <Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} /> <Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }}
/>
</Box> </Box>
</Stack> </Stack>
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text> <Title order={2} fw="bold">Dokumen yang Perlu Dibawa</Title>
<Divider /> <Divider />
<Box pl={20}> <Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} /> <Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }}
/>
</Box> </Box>
</Stack> </Stack>
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Pendaftaran Kegiatan</Text> <Title order={2} fw="bold">Pendaftaran Kegiatan</Title>
<Divider /> <Divider />
<Group> <Group>
<Button onClick={open}>Buat Pendaftaran</Button> <Button onClick={open}>Buat Pendaftaran</Button>
@@ -112,18 +155,21 @@ function Page() {
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm"> <Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
<Stack gap="xs"> <Stack gap="xs">
<Text fz="lg" c={colors['white-1']} fw="bold">Informasi Kontak</Text> <Title order={3} c={colors['white-1']} fw="bold">Informasi Kontak</Title>
<Group gap="xs"> <Group gap="xs" justify="flex-start">
<IconUser size={18} color="white" /> <IconUser size={18} color="white" />
<Text fz="md" c={colors['white-1']}>Penanggung Jawab: <Text span fw="bold">Bidan Komang Ayu</Text></Text> <Text c={colors['white-1']}>
Penanggung Jawab:&nbsp;
<Text span fw="bold">Bidan Komang Ayu</Text>
</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconPhone size={18} color="white" /> <IconPhone size={18} color="white" />
<Text fz="md" c={colors['white-1']}>081234567890</Text> <Text c={colors['white-1']}>081234567890</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconMail size={18} color="white" /> <IconMail size={18} color="white" />
<Text fz="md" c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text> <Text c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text>
</Group> </Group>
</Stack> </Stack>
</Paper> </Paper>

View File

@@ -2,15 +2,14 @@
'use client' 'use client'
import pendaftaranJadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/pendafataranJadwalKegiatan'; import pendaftaranJadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/pendafataranJadwalKegiatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Button, Divider, Stack, Text, Textarea, TextInput } from '@mantine/core'; import { Button, Divider, Stack, Text, Textarea, TextInput, Title } from '@mantine/core';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function CreatePendaftaran() { function CreatePendaftaran() {
const stateCreate = useProxy(pendaftaranJadwalKegiatanState); const stateCreate = useProxy(pendaftaranJadwalKegiatanState);
useEffect(() => {
useEffect(() => {
stateCreate.findMany.load(); stateCreate.findMany.load();
}, []); }, []);
@@ -32,7 +31,7 @@ useEffect(() => {
return ( return (
<Stack gap="sm"> <Stack gap="sm">
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text> <Title order={2} ta="left">Formulir Pendaftaran</Title>
<Divider /> <Divider />
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
@@ -41,6 +40,10 @@ useEffect(() => {
size="md" size="md"
value={stateCreate.create.form.name} value={stateCreate.create.form.name}
onChange={(e) => stateCreate.create.form.name = e.target.value} onChange={(e) => stateCreate.create.form.name = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/> />
<TextInput <TextInput
type='date' type='date'
@@ -50,6 +53,10 @@ useEffect(() => {
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }} w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
value={stateCreate.create.form.tanggal} value={stateCreate.create.form.tanggal}
onChange={(e) => stateCreate.create.form.tanggal = e.target.value} onChange={(e) => stateCreate.create.form.tanggal = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/> />
<TextInput <TextInput
label="Nama Orang Tua / Wali" label="Nama Orang Tua / Wali"
@@ -57,6 +64,10 @@ useEffect(() => {
size="md" size="md"
value={stateCreate.create.form.namaOrangtua} value={stateCreate.create.form.namaOrangtua}
onChange={(e) => stateCreate.create.form.namaOrangtua = e.target.value} onChange={(e) => stateCreate.create.form.namaOrangtua = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/> />
<TextInput <TextInput
label="Nomor Telepon" label="Nomor Telepon"
@@ -64,6 +75,10 @@ useEffect(() => {
size="md" size="md"
value={stateCreate.create.form.nomor} value={stateCreate.create.form.nomor}
onChange={(e) => stateCreate.create.form.nomor = e.target.value} onChange={(e) => stateCreate.create.form.nomor = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/> />
<TextInput <TextInput
label="Alamat" label="Alamat"
@@ -71,6 +86,10 @@ useEffect(() => {
size="md" size="md"
value={stateCreate.create.form.alamat} value={stateCreate.create.form.alamat}
onChange={(e) => stateCreate.create.form.alamat = e.target.value} onChange={(e) => stateCreate.create.form.alamat = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/> />
<Textarea <Textarea
label="Catatan Khusus (Opsional)" label="Catatan Khusus (Opsional)"
@@ -78,9 +97,15 @@ useEffect(() => {
size="md" size="md"
value={stateCreate.create.form.catatan} value={stateCreate.create.form.catatan}
onChange={(e) => stateCreate.create.form.catatan = e.target.value} onChange={(e) => stateCreate.create.form.catatan = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/> />
<Button size="md" radius="lg" bg={colors['blue-button']} onClick={handleSubmit}> <Button size="md" radius="lg" bg={colors['blue-button']} onClick={handleSubmit}>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} c="white">
Daftar Sekarang Daftar Sekarang
</Text>
</Button> </Button>
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconChevronRight, IconClockHour4, IconMapPin } from '@tabler/icons-react'; import { IconChevronRight, IconClockHour4, IconMapPin } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -27,13 +27,13 @@ function JadwalKegiatanPage() {
<Box> <Box>
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="auto" mih="100vh"> <Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="auto" mih="100vh">
<Stack gap="lg"> <Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}> <Title ta="center" order={1} c={colors['blue-button']} fw={700}>
Jadwal Kegiatan Warga Jadwal Kegiatan Warga
</Text> </Title>
<Divider size="sm" color={colors['blue-button']} /> <Divider size="sm" color={colors['blue-button']} />
{state.findMany.data.length === 0 ? ( {state.findMany.data.length === 0 ? (
<Box py="xl" ta="center"> <Box py="xl" ta="center">
<Text fz="lg" c="dimmed"> <Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
Belum ada jadwal kegiatan yang tersedia Belum ada jadwal kegiatan yang tersedia
</Text> </Text>
</Box> </Box>
@@ -48,11 +48,11 @@ function JadwalKegiatanPage() {
style={{ backdropFilter: 'blur(8px)' }} style={{ backdropFilter: 'blur(8px)' }}
> >
<Stack gap="sm"> <Stack gap="sm">
<Group justify="space-between"> <Group justify="space-between" wrap="nowrap">
<Text fw={700} fz="xl" c={colors['blue-button']}> <Title order={2} c={colors['blue-button']} fw={700} fz={{ base: 'md', sm: 'xl' }}>
{item.informasijadwalkegiatan.name} {item.informasijadwalkegiatan.name}
</Text> </Title>
<Text fw={600} fz="sm" c={colors['blue-button']}> <Text fw={600} fz={{ base: 'xs', sm: 'sm' }} c={colors['blue-button']} lh="1.4">
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString('id-ID', { {new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString('id-ID', {
day: '2-digit', day: '2-digit',
month: 'long', month: 'long',
@@ -63,12 +63,16 @@ function JadwalKegiatanPage() {
<Group gap="xs"> <Group gap="xs">
<IconClockHour4 size={18} /> <IconClockHour4 size={18} />
<Text fz="sm">{item.informasijadwalkegiatan.waktu}</Text> <Text fz={{ base: 'xs', sm: 'sm' }} lh="1.5">
{item.informasijadwalkegiatan.waktu}
</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconMapPin size={18} /> <IconMapPin size={18} />
<Text fz="sm">{item.informasijadwalkegiatan.lokasi}</Text> <Text fz={{ base: 'xs', sm: 'sm' }} lh="1.5">
{item.informasijadwalkegiatan.lokasi}
</Text>
</Group> </Group>
<Divider my="sm" /> <Divider my="sm" />

View File

@@ -6,9 +6,7 @@ import { Box, Center, ColorSwatch, Flex, Paper, SimpleGrid, Skeleton, Stack, Tex
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
// import { useRouter } from 'next/navigation';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran'; import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -17,7 +15,6 @@ import GrafikPenyakit from './grafik-penyakit/page';
import JadwalKegiatan from './jadwal-kegiatan-page/page'; import JadwalKegiatan from './jadwal-kegiatan-page/page';
import ArtikelKesehatanPage from './artikel-kesehatan-page/page'; import ArtikelKesehatanPage from './artikel-kesehatan-page/page';
function Page() { function Page() {
type DataTahunan = { type DataTahunan = {
tahun: string; tahun: string;
@@ -31,7 +28,6 @@ function Page() {
}>; }>;
}; };
// Count occurrences per year
const countByYear = (data: any[], dateField: string) => { const countByYear = (data: any[], dateField: string) => {
const counts: Record<string, number> = {}; const counts: Record<string, number> = {};
data?.forEach(item => { data?.forEach(item => {
@@ -43,28 +39,23 @@ function Page() {
const statePersentase = useProxy(persentasekelahiran); const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<DataTahunan[]>([]); const [chartData, setChartData] = useState<DataTahunan[]>([]);
const isTablet = useMediaQuery('(max-width: 1024px)');
const isMobile = useMediaQuery('(max-width: 768px)'); const isMobile = useMediaQuery('(max-width: 768px)');
useShallowEffect(() => { useShallowEffect(() => {
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data statePersentase.kelahiran.findMany.load(1, 1000);
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data statePersentase.kematian.findMany.load(1, 1000);
}, []); }, []);
useEffect(() => { useEffect(() => {
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) { if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
// Count kelahiran and kematian by year
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal'); const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal'); const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
// Get all unique years
const allYears = new Set([ const allYears = new Set([
...Object.keys(kelahiranByYear), ...Object.keys(kelahiranByYear),
...Object.keys(kematianByYear) ...Object.keys(kematianByYear)
]); ]);
// Create data structure for the chart
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => { const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
acc[year] = { acc[year] = {
tahun: year, tahun: year,
@@ -93,32 +84,44 @@ function Page() {
</Stack> </Stack>
); );
} }
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
{/* Page Title */}
<Title
order={1}
ta="center"
c={colors['blue-button']}
fw="bold"
lh={1.2}
>
Data Kesehatan Masyarakat Puskesmas Darmasaba Data Kesehatan Masyarakat Puskesmas Darmasaba
</Text> </Title>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}> <Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
{/* Bar Chart Kematian Kelahiran */} {/* Bar Chart Kematian Kelahiran */}
<Box> <Box>
<Paper p={"xl"} bg={colors['white-trans-1']}> <Paper p="xl" bg={colors['white-trans-1']}>
<Box pb={30}> <Box pb={30}>
<Title order={2} mb="md">Data Kematian dan Kelahiran</Title> <Title order={2} mb="md" ta="center">
Data Kematian dan Kelahiran
</Title>
{chartData.length === 0 ? ( {chartData.length === 0 ? (
<Text c="dimmed" ta="center" py="xl"> <Text c="dimmed" ta="center" py="xl" size="md">
Belum ada data yang tersedia untuk ditampilkan Belum ada data yang tersedia untuk ditampilkan
</Text> </Text>
) : ( ) : (
<> <>
{/* Main Chart */}
<Center> <Center>
<Box h={400}> <Box h={400}>
<Box style={{ <Box style={{
width: isMobile ? '90vw' : isTablet ? '700px' : '800px', width: isMobile ? '90vw' : '800px',
maxWidth: '100%', maxWidth: '100%',
margin: '0 auto' margin: '0 auto'
}}> }}>
@@ -137,16 +140,21 @@ function Page() {
</Center> </Center>
</> </>
)} )}
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<Flex pb={30} justify="center" gap="xl" align="center" wrap="wrap">
<Box> <Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}> <Flex gap={{ base: 'xs', md: 'sm' }} align="center">
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
Angka Kematian
</Text>
<ColorSwatch color="#EF3E3E" size={30} /> <ColorSwatch color="#EF3E3E" size={30} />
</Flex> </Flex>
</Box> </Box>
<Box> <Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}> <Flex gap={{ base: 'xs', md: 'sm' }} align="center">
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
Angka Kelahiran
</Text>
<ColorSwatch color="#3290CA" size={30} /> <ColorSwatch color="#3290CA" size={30} />
</Flex> </Flex>
</Box> </Box>
@@ -154,20 +162,13 @@ function Page() {
</Box> </Box>
</Paper> </Paper>
</Box> </Box>
<GrafikPenyakit /> <GrafikPenyakit />
{/* Artikel Kesehatan */}
<Box> <Box>
<SimpleGrid <SimpleGrid cols={{ base: 1, md: 3 }}>
cols={{
base: 1,
md: 3,
}}
>
{/* Fasilitas Kesehatan */}
<FasilitasKesehatan /> <FasilitasKesehatan />
{/* Jadwal Kegiatan */}
<JadwalKegiatan /> <JadwalKegiatan />
{/* Artikel Kesehatan */}
<ArtikelKesehatanPage /> <ArtikelKesehatanPage />
</SimpleGrid> </SimpleGrid>
</Box> </Box>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit'; import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Button, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react'; import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
@@ -51,10 +51,15 @@ function DetailInfoWabahPenyakitUser() {
shadow="sm" shadow="sm"
> >
<Stack gap="lg"> <Stack gap="lg">
{/* Judul */} {/* Judul — H1 */}
<Text fz="xl" fw="bold" c={colors['blue-button']} ta="center"> <Title
order={1}
fw="bold"
c={colors['blue-button']}
ta="center"
>
{data.name || 'Kontak Darurat'} {data.name || 'Kontak Darurat'}
</Text> </Title>
{/* Gambar */} {/* Gambar */}
{data.image?.link && ( {data.image?.link && (
@@ -69,16 +74,22 @@ function DetailInfoWabahPenyakitUser() {
)} )}
{/* Deskripsi */} {/* Deskripsi */}
<Box> <Stack gap={"xs"}>
<Text fz="lg" fw="bold">Deskripsi</Text> {/* Section Title — H2 */}
<Title order={3} fw="bold" ta="left">
Deskripsi
</Title>
<Box pl={20}> <Box pl={20}>
<Text <Text
fz="md" fz={{ base: 'sm', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
c="dark"
ta="justify"
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/> />
</Box> </Box>
</Box> </Stack>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>

View File

@@ -17,7 +17,8 @@ import {
Skeleton, Skeleton,
Stack, Stack,
Text, Text,
TextInput TextInput,
Title
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconInfoCircle, IconSearch } from '@tabler/icons-react'; import { IconInfoCircle, IconSearch } from '@tabler/icons-react';
@@ -30,7 +31,7 @@ function Page() {
const state = useProxy(infoWabahPenyakit); const state = useProxy(infoWabahPenyakit);
const router = useTransitionRouter(); const router = useTransitionRouter();
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000) const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany; const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => { useShallowEffect(() => {
@@ -53,15 +54,19 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }}> <Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 8 }}> <GridCol span={{ base: 12, md: 8 }}>
<Text <Title
fz={{ base: '1.8rem', md: '2.8rem' }} order={1}
c={colors['blue-button']} c={colors['blue-button']}
fw="bold" fw="bold"
lh={1.2} lh={{ base: 1.2, md: 1.2 }}
> >
Informasi Wabah & Penyakit Informasi Wabah & Penyakit
</Text> </Title>
<Text fz="md" mt={4}> <Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.5 }}
mt={4}
>
Dapatkan informasi terbaru mengenai wabah dan penyakit yang sedang Dapatkan informasi terbaru mengenai wabah dan penyakit yang sedang
diawasi. diawasi.
</Text> </Text>
@@ -84,9 +89,9 @@ function Page() {
<Center py="6xl"> <Center py="6xl">
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<IconInfoCircle size={50} color={colors['blue-button']} /> <IconInfoCircle size={50} color={colors['blue-button']} />
<Text fz="lg" fw={500} > <Title order={2} fz={{ base: 'md', md: 'lg' }} fw={500}>
Tidak ada data yang cocok dengan pencarian Anda. Tidak ada data yang cocok dengan pencarian Anda.
</Text> </Title>
</Stack> </Stack>
</Center> </Center>
) : ( ) : (
@@ -131,15 +136,24 @@ function Page() {
{/* Judul dan badge */} {/* Judul dan badge */}
<Group justify="space-between" mt="sm"> <Group justify="space-between" mt="sm">
<Text fw={700} fz="lg" c={colors['blue-button']}> <Title
order={3}
c={colors['blue-button']}
fw={700}
fz={{ base: 'sm', md: 'lg' }}
>
{v.name} {v.name}
</Text> </Title>
<Badge color="blue" variant="light" radius="sm"> <Badge color="blue" variant="light" radius="sm">
Wabah Wabah
</Badge> </Badge>
</Group> </Group>
<Text fz="sm" c="dimmed"> <Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lh={{ base: 1.4, md: 1.4 }}
>
Diposting:{' '} Diposting:{' '}
{new Date(v.createdAt).toLocaleDateString('id-ID', { {new Date(v.createdAt).toLocaleDateString('id-ID', {
day: '2-digit', day: '2-digit',
@@ -153,8 +167,8 @@ function Page() {
{/* Bagian deskripsi dan tombol */} {/* Bagian deskripsi dan tombol */}
<Box style={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}> <Box style={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
<Text <Text
fz="sm" fz={{ base: 'xs', md: 'sm' }}
lh={1.5} lh={{ base: 1.5, md: 1.5 }}
lineClamp={3} lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsiSingkat }} dangerouslySetInnerHTML={{ __html: v.deskripsiSingkat }}
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
@@ -174,14 +188,11 @@ function Page() {
</Box> </Box>
</Stack> </Stack>
</Paper> </Paper>
))} ))}
</SimpleGrid> </SimpleGrid>
)} )}
</Box> </Box>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
@@ -192,7 +203,6 @@ function Page() {
mt="lg" mt="lg"
/> />
</Center> </Center>
</Stack> </Stack>
); );
} }

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat'; import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconBrandWhatsapp } from '@tabler/icons-react'; import { IconArrowBack, IconBrandWhatsapp } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
@@ -27,15 +27,16 @@ function Page() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box px={{base: 'md', md: 100}} py={10}> <Box px={{ base: 'md', md: 100 }} py={10}>
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
onClick={() => router.back()} onClick={() => router.back()}
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />} leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
mb={15} mb={15}
style={{ lineHeight: 1.2 }}
> >
Kembali <Text fz={{ base: 'sm', md: 'md' }} fw={500}>Kembali</Text>
</Button> </Button>
{/* Wrapper Detail */} {/* Wrapper Detail */}
@@ -48,34 +49,38 @@ function Page() {
shadow="sm" shadow="sm"
> >
<Stack gap="md"> <Stack gap="md">
<Text fz="2xl" fw="bold" c={colors['blue-button']}> <Title order={1} c={colors['blue-button']}>
Detail Kontak Darurat Detail Kontak Darurat
</Text> </Title>
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs"> <Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm"> <Stack gap="sm">
<Box> <Box>
<Text fz="lg" fw="bold">Judul</Text> <Title order={3}>Judul</Title>
<Text fz="md" c="dimmed">{data.name || '-'}</Text> <Text fz={{ base: 'sm', md: 'md' }} c={data.name ? 'black' : 'dimmed'}>
{data.name || '-'}
</Text>
</Box> </Box>
<Box> <Box>
<Text fz="lg" fw="bold">Whatsapp</Text> <Title order={3}>Whatsapp</Title>
<Text fz="md" c="dimmed">{data.whatsapp || '-'}</Text> <Text fz={{ base: 'sm', md: 'md' }} c={data.whatsapp ? 'black' : 'dimmed'}>
{data.whatsapp || '-'}
</Text>
</Box> </Box>
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Title order={3}>Deskripsi</Title>
<Text <Text
fz="md" fz={{ base: 'sm', md: 'md' }}
c="dimmed" c={data.deskripsi ? 'black' : 'dimmed'}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: 'break-word', whiteSpace: 'normal', lineHeight: 1.5 }}
/> />
</Box> </Box>
<Box> <Box>
<Text fz="lg" fw="bold">Gambar</Text> <Title order={3}>Gambar</Title>
{data.image?.link ? ( {data.image?.link ? (
<Image <Image
src={data.image.link} src={data.image.link}
@@ -85,9 +90,10 @@ function Page() {
loading="lazy" loading="lazy"
/> />
) : ( ) : (
<Text fz="md" c="dimmed">-</Text> <Text fz={{ base: 'sm', md: 'md' }} c="dimmed">-</Text>
)} )}
</Box> </Box>
<Group> <Group>
<Button <Button
variant="light" variant="light"
@@ -96,6 +102,7 @@ function Page() {
href={`https://wa.me/${data.whatsapp.replace(/\D/g, '')}`} href={`https://wa.me/${data.whatsapp.replace(/\D/g, '')}`}
target="_blank" target="_blank"
aria-label="Hubungi WhatsApp" aria-label="Hubungi WhatsApp"
fz={{ base: 'sm', md: 'md' }}
> >
WhatsApp WhatsApp
</Button> </Button>

View File

@@ -16,6 +16,7 @@ import {
Stack, Stack,
Text, Text,
TextInput, TextInput,
Title,
Tooltip Tooltip
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
@@ -52,10 +53,21 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} gutter="lg"> <Grid align="center" px={{ base: 'md', md: 100 }} gutter="lg">
<GridCol span={{ base: 12, md: 8 }}> <GridCol span={{ base: 12, md: 8 }}>
<Text fz={{ base: '2rem', md: '2.8rem' }} c={colors['blue-button']} fw={800}> <Title
order={1}
c={colors['blue-button']}
fw={800}
fz={{ base: '28px', md: '32px' }}
lh={{ base: 1.2, md: 1.25 }}
>
Kontak Darurat Kontak Darurat
</Text> </Title>
<Text fz="md" mt={4}> <Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
mt={4}
c="dark.9"
>
Hubungi layanan penting dengan cepat dan mudah Hubungi layanan penting dengan cepat dan mudah
</Text> </Text>
</GridCol> </GridCol>
@@ -79,10 +91,20 @@ function Page() {
<Center mih={300}> <Center mih={300}>
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<IconSearch size={50} color={colors['blue-button']} /> <IconSearch size={50} color={colors['blue-button']} />
<Text fz="lg" fw={600} c={colors['blue-button']}> <Title
order={2}
fw={600}
c={colors['blue-button']}
fz={{ base: '20px', md: '24px' }}
lh={{ base: 1.3, md: 1.35 }}
>
Tidak ada kontak ditemukan Tidak ada kontak ditemukan
</Text> </Title>
<Text fz="sm" c="dimmed"> <Text
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
c="dark.7"
>
Coba kata kunci lain untuk pencarian Coba kata kunci lain untuk pencarian
</Text> </Text>
</Stack> </Stack>
@@ -102,8 +124,8 @@ function Page() {
cursor: 'pointer', cursor: 'pointer',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'space-between', // ✅ biar button selalu di bawah justifyContent: 'space-between',
height: '100%', // ✅ bikin tinggi seragam height: '100%',
}} }}
> >
<Stack align="center" gap="sm" style={{ flexGrow: 1 }}> <Stack align="center" gap="sm" style={{ flexGrow: 1 }}>
@@ -131,27 +153,24 @@ function Page() {
/> />
</Box> </Box>
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}> <Text ta="center" fw={700} fz={{ base: '18px', md: '20px' }} lh={1.3} c={colors['blue-button']}>
{v.name} {v.name}
</Text> </Text>
<Text <Text
fz="sm" fz={{ base: 'xs', md: 'sm' }}
ta="center" ta="center"
lineClamp={3} lineClamp={3}
lh={1.6} lh={{ base: 1.5, md: 1.6 }}
style={{ style={{
minHeight: '4.8em', // tinggi tetap 3 baris minHeight: '4.8em',
wordBreak: 'break-word',
whiteSpace: 'normal',
}} }}
>
<span
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }} dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/> />
</Text>
</Stack> </Stack>
{/* ✅ Tombol selalu di bagian bawah card */}
<Group mt="md" justify='center'> <Group mt="md" justify='center'>
<Button <Button
bg={colors['blue-button']} bg={colors['blue-button']}
@@ -161,8 +180,6 @@ function Page() {
</Button> </Button>
</Group> </Group>
</Paper> </Paper>
))} ))}
</SimpleGrid> </SimpleGrid>
)} )}

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat'; import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -31,24 +31,30 @@ function DetailPenangananDaruratUser() {
<Box py={20}> <Box py={20}>
{/* Tombol Back */} {/* Tombol Back */}
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton/> <BackButton />
</Box> </Box>
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Box pt={20} px={{ base: 'md', md: 100 }}>
<Paper <Paper
withBorder withBorder
w={{ base: '100%', md: '70%', lg: '60%' }} w={'100%'}
mx="auto"
bg={colors['white-1']} bg={colors['white-1']}
p="xl" p="xl"
radius="lg" radius="lg"
shadow="sm" shadow="sm"
> >
<Stack gap="md" align="center" ta="center"> <Stack gap="md">
<Text fz="xl" fw={700} c={colors['blue-button']}> <Title
ta={"center"}
order={1}
fw={700}
c={colors['blue-button']}
style={{ wordBreak: 'break-word' }}
>
{data.name || 'Penanganan Darurat'} {data.name || 'Penanganan Darurat'}
</Text> </Title>
<Center>
{data.image?.link && ( {data.image?.link && (
<Image <Image
src={data.image.link} src={data.image.link}
@@ -60,10 +66,10 @@ function DetailPenangananDaruratUser() {
mb="md" mb="md"
/> />
)} )}
</Center>
<Box> <Box >
<Text <Text
fz="md"
ta="justify" ta="justify"
dangerouslySetInnerHTML={{ __html: data.deskripsi }} dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
@@ -72,6 +78,7 @@ function DetailPenangananDaruratUser() {
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>
</Box>
); );
} }

View File

@@ -15,6 +15,7 @@ import {
Stack, Stack,
Text, Text,
TextInput, TextInput,
Title,
Tooltip Tooltip
} from '@mantine/core' } from '@mantine/core'
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks' import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'
@@ -49,10 +50,19 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} mb="lg"> <Grid align="center" px={{ base: 'md', md: 100 }} mb="lg">
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: 30, md: 40 }} c={colors['blue-button']} fw={800} lh={1.2}> <Title
order={1}
c={colors['blue-button']}
fw={800}
lh={{ base: 1.3, md: 1.2 }}
>
Penanganan Darurat Penanganan Darurat
</Text> </Title>
<Text fz="md" mt={4}> <Text
fz={{ base: 'sm', md: 'md' }}
mt={4}
lh={{ base: 1.5, md: 1.5 }}
>
Informasi cepat dan jelas untuk situasi darurat kesehatan Informasi cepat dan jelas untuk situasi darurat kesehatan
</Text> </Text>
</GridCol> </GridCol>
@@ -74,10 +84,10 @@ function Page() {
{data.length === 0 ? ( {data.length === 0 ? (
<Center py={100}> <Center py={100}>
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<Text fz="lg" fw={600} c={colors['blue-button']}> <Title order={2} fw={600} c={colors['blue-button']}>
Tidak ada data ditemukan Tidak ada data ditemukan
</Text> </Title>
<Text fz="sm" c="dimmed"> <Text fz={{ base: 'xs', md: 'sm' }}>
Coba gunakan kata kunci lain Coba gunakan kata kunci lain
</Text> </Text>
</Stack> </Stack>
@@ -128,18 +138,21 @@ function Page() {
</Box> </Box>
<Stack gap={4} w="100%"> <Stack gap={4} w="100%">
<Text <Title
fz="lg" order={3}
fw={700} fw={700}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
lineClamp={2} lineClamp={2}
fz={{ base: 'sm', md: 'lg' }}
lh={{ base: 1.4, md: 1.4 }}
> >
{v.name} {v.name}
</Text> </Title>
<Box> <Box>
<Text <Text
fz="md" fz={{ base: 'xs', md: 'md' }}
lh={{ base: 1.5, md: 1.5 }}
lineClamp={3} lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsi }} dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
@@ -177,10 +190,8 @@ function Page() {
'&:hover': { backgroundColor: colors['blue-button'], color: 'white' }, '&:hover': { backgroundColor: colors['blue-button'], color: 'white' },
}, },
}} }}
/> />
</Center> </Center>
</Stack> </Stack>
) )
} }

View File

@@ -1,7 +1,18 @@
'use client'; 'use client';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Button, Center, Flex, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; import {
Button,
Center,
Flex,
Group,
Image,
Paper,
Skeleton,
Stack,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react'; import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
@@ -53,15 +64,14 @@ export default function DetailPosyanduUser() {
mx="auto" mx="auto"
> >
<Stack gap="md"> <Stack gap="md">
{/* Header */} {/* Header — Dijadikan Title */}
<Text <Title
ta="center" ta="center"
fz={{ base: '1.8rem', md: '2.2rem' }} order={1}
fw={700}
c={colors['blue-button']} c={colors['blue-button']}
> >
{data.name || 'Posyandu Desa'} {data.name || 'Posyandu Desa'}
</Text> </Title>
{/* Gambar */} {/* Gambar */}
{data.image?.link ? ( {data.image?.link ? (
@@ -78,7 +88,11 @@ export default function DetailPosyanduUser() {
</Center> </Center>
) : ( ) : (
<Center> <Center>
<Text fz="sm" c="dimmed"> <Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
ta="center"
>
Tidak ada gambar Tidak ada gambar
</Text> </Text>
</Center> </Center>
@@ -88,7 +102,11 @@ export default function DetailPosyanduUser() {
<Stack gap="sm" mt="md"> <Stack gap="sm" mt="md">
<Flex align="center" gap="xs"> <Flex align="center" gap="xs">
<IconPhone size={18} stroke={1.5} /> <IconPhone size={18} stroke={1.5} />
<Text fz="sm" c="dimmed"> <Text
fz={{ base: 'sm', md: 'md' }}
c="black"
lh={{ base: 1.5, md: 1.6 }}
>
{data.nomor || 'Nomor tidak tersedia'} {data.nomor || 'Nomor tidak tersedia'}
</Text> </Text>
</Flex> </Flex>
@@ -96,8 +114,9 @@ export default function DetailPosyanduUser() {
<Flex align="center" gap="xs"> <Flex align="center" gap="xs">
<IconCalendar size={18} stroke={1.5} /> <IconCalendar size={18} stroke={1.5} />
<Text <Text
fz="sm" fz={{ base: 'sm', md: 'md' }}
c="dimmed" c="black"
lh={{ base: 1.5, md: 1.6 }}
dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }} dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />
@@ -106,9 +125,9 @@ export default function DetailPosyanduUser() {
<Flex align="center" gap="xs"> <Flex align="center" gap="xs">
<IconInfoCircle size={18} stroke={1.5} /> <IconInfoCircle size={18} stroke={1.5} />
<Text <Text
fz="sm" fz={{ base: 'sm', md: 'md' }}
c="dimmed" c="black"
lh={1.6} lh={{ base: 1.5, md: 1.6 }}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu"; import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
import colors from "@/con/colors"; import colors from "@/con/colors";
import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from "@mantine/core"; import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks"; import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react"; import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
import { useState } from "react"; import { useState } from "react";
@@ -12,8 +12,8 @@ import { useTransitionRouter } from "next-view-transitions";
export default function Page() { export default function Page() {
const state = useProxy(posyandustate); const state = useProxy(posyandustate);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const router = useTransitionRouter() const router = useTransitionRouter();
const { data, page, totalPages, loading, load } = state.findMany; const { data, page, totalPages, loading, load } = state.findMany;
@@ -35,14 +35,13 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<BackButton /> <BackButton />
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md"> <Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
<Text <Title
order={1}
ta="left" ta="left"
fz={{ base: "1.8rem", md: "2.5rem" }}
c={colors["blue-button"]} c={colors["blue-button"]}
fw="bold"
> >
Posyandu Desa Darmasaba Posyandu Desa Darmasaba
</Text> </Title>
<TextInput <TextInput
placeholder="Cari posyandu berdasarkan nama..." placeholder="Cari posyandu berdasarkan nama..."
aria-label="Pencarian Posyandu" aria-label="Pencarian Posyandu"
@@ -55,6 +54,7 @@ export default function Page() {
/> />
</Flex> </Flex>
</Box> </Box>
<Title c={"dimmed"} order={3} ta={"center"}>Belum ada posyandu yang terdaftar</Title>
</Stack> </Stack>
); );
} }
@@ -64,14 +64,13 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<BackButton /> <BackButton />
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md"> <Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
<Text <Title
order={1}
ta="left" ta="left"
fz={{ base: "1.8rem", md: "2.5rem" }}
c={colors["blue-button"]} c={colors["blue-button"]}
fw="bold"
> >
Posyandu Desa Darmasaba Posyandu Desa Darmasaba
</Text> </Title>
<TextInput <TextInput
placeholder="Cari posyandu berdasarkan nama..." placeholder="Cari posyandu berdasarkan nama..."
aria-label="Pencarian Posyandu" aria-label="Pencarian Posyandu"
@@ -116,9 +115,9 @@ export default function Page() {
> >
<Stack gap="sm"> <Stack gap="sm">
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Text c={colors["blue-button"]} fw="bold" fz="lg" lineClamp={1}> <Title order={3} c={colors["blue-button"]} fw="bold" lineClamp={1}>
{v.name} {v.name}
</Text> </Title>
<Badge color="blue" variant="light" size="sm" radius="sm"> <Badge color="blue" variant="light" size="sm" radius="sm">
Aktif Aktif
</Badge> </Badge>
@@ -136,39 +135,45 @@ export default function Page() {
</Center> </Center>
<Flex align="flex-start" gap="xs"> <Flex align="flex-start" gap="xs">
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} /> <IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
<Box> <Text
<Text fz="sm" c="dimmed" lh={1.4}> fz={{ base: "sm", md: "md" }}
c="black"
lh={{ base: 1.4, md: 1.5 }}
>
{v.nomor || "Tidak tersedia"} {v.nomor || "Tidak tersedia"}
</Text> </Text>
</Box>
</Flex> </Flex>
<Flex align="flex-start" gap="xs"> <Flex align="flex-start" gap="xs">
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} /> <IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
<Box> <Text
<Text fz="sm" c="dimmed" lh={1.4}> fz={{ base: "sm", md: "md" }}
c="black"
lh={{ base: 1.4, md: 1.5 }}
>
<strong>Jadwal:</strong>{" "} <strong>Jadwal:</strong>{" "}
<span <span
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }}
/> />
</Text> </Text>
</Box>
</Flex> </Flex>
<Flex align="flex-start" gap="xs"> <Flex align="flex-start" gap="xs">
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} /> <IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
<Text <Text
fz="sm" fz={{ base: "sm", md: "md" }}
lh={1.5} lh={{ base: 1.4, md: 1.5 }}
c="dimmed" c="black"
dangerouslySetInnerHTML={{ __html: v.deskripsi }} dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
lineClamp={3} lineClamp={3}
truncate="end" truncate="end"
/> />
</Flex> </Flex>
<Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>Detail</Button> <Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>
Detail
</Button>
</Stack> </Stack>
</Paper> </Paper>
))} ))}
@@ -191,11 +196,11 @@ export default function Page() {
<Stack gap="sm"> <Stack gap="sm">
<Flex align="center" gap="xs"> <Flex align="center" gap="xs">
<IconInfoCircle size={22} color={colors["blue-button"]} /> <IconInfoCircle size={22} color={colors["blue-button"]} />
<Text fz="lg" fw="bold" c={colors["blue-button"]}> <Title order={2} c={colors["blue-button"]}>
Layanan Utama Posyandu Layanan Utama Posyandu
</Text> </Title>
</Flex> </Flex>
<List spacing="xs" size="sm" center> <List spacing="xs" fz={{ base: "sm", md: "md" }} lh={{ base: 1.4, md: 1.5 }} c="black">
<ListItem>Penimbangan bayi dan balita</ListItem> <ListItem>Penimbangan bayi dan balita</ListItem>
<ListItem>Pemantauan status gizi</ListItem> <ListItem>Pemantauan status gizi</ListItem>
<ListItem>Imunisasi dasar lengkap</ListItem> <ListItem>Imunisasi dasar lengkap</ListItem>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan'; import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Group, Image, Loader, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { Box, Center, Group, Image, Loader, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconUser } from '@tabler/icons-react'; import { IconCalendar, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
@@ -9,12 +9,12 @@ import { useProxy } from 'valtio/utils';
import BackButton from '../../../desa/layanan/_com/BackButto'; import BackButton from '../../../desa/layanan/_com/BackButto';
function Page() { function Page() {
const state = useProxy(programKesehatan) const state = useProxy(programKesehatan);
const params = useParams() const params = useParams();
useShallowEffect(() => { useShallowEffect(() => {
state.findUnique.load(params.id as string) state.findUnique.load(params.id as string);
}, [params.id]) }, [params.id]);
if (!state.findUnique.data) { if (!state.findUnique.data) {
return ( return (
@@ -24,7 +24,7 @@ function Page() {
<Text c="dimmed" fz="sm">Memuat data program kesehatan...</Text> <Text c="dimmed" fz="sm">Memuat data program kesehatan...</Text>
</Stack> </Stack>
</Center> </Center>
) );
} }
return ( return (
@@ -44,36 +44,41 @@ function Page() {
<Center> <Center>
{state.findUnique.data.image?.link ? ( {state.findUnique.data.image?.link ? (
<Image <Image
radius="xl"
src={state.findUnique.data.image?.link} src={state.findUnique.data.image?.link}
alt={state.findUnique.data.name} alt={state.findUnique.data.name}
w="100%" radius="md"
maw={800} mah={300}
fit="cover" fit="contain"
loading="lazy" loading="lazy"
/> />
) : ( ) : (
<Skeleton h={300} w="100%" radius="xl" /> <Skeleton h={300} w="100%" radius="xl" />
)} )}
</Center> </Center>
<Box> <Box pl={20}>
<Text pb="sm" c={colors["blue-button"]} fw="bold" fz={{ base: 24, md: 32 }} lh={1.2}> <Title
order={1}
pb="sm"
c={colors["blue-button"]}
fw="bold"
lh={{ base: 1.2, md: 1.15 }}
>
{state.findUnique.data.name} {state.findUnique.data.name}
</Text> </Title>
<Text <Text
ta="justify" ta="justify"
fz="md" fz={{ base: 'sm', md: 'md' }}
lh={1.6} lh={{ base: 1.5, md: 1.6 }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />
</Box> </Box>
<Group gap="xl"> <Group gap="xl">
<Group gap="xs"> <Group gap="xs">
<Tooltip label="Tanggal dibuat" withArrow> <Tooltip label="Tanggal dibuat" withArrow>
<IconCalendar color='gray' size={20} stroke={1.5} /> <IconCalendar color="gray" size={20} stroke={1.5} />
</Tooltip> </Tooltip>
<Text size="sm" c="dimmed"> <Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
{state.findUnique.data.createdAt {state.findUnique.data.createdAt
? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', { ? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
@@ -85,9 +90,9 @@ function Page() {
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<Tooltip label="Dibuat oleh" withArrow> <Tooltip label="Dibuat oleh" withArrow>
<IconUser color='gray' size={20} stroke={1.5} /> <IconUser color="gray" size={20} stroke={1.5} />
</Tooltip> </Tooltip>
<Text size="sm" c="dimmed">Admin Desa</Text> <Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">Admin Desa</Text>
</Group> </Group>
</Group> </Group>
</Stack> </Stack>

View File

@@ -16,6 +16,7 @@ import {
Stack, Stack,
Text, Text,
TextInput, TextInput,
Title,
Transition Transition
} from "@mantine/core"; } from "@mantine/core";
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks"; import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
@@ -57,7 +58,7 @@ export default function Page() {
const state = useProxy(programKesehatan); const state = useProxy(programKesehatan);
const router = useRouter(); const router = useRouter();
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany; const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => { useShallowEffect(() => {
@@ -80,14 +81,19 @@ export default function Page() {
<Grid px={{ base: "md", md: 100 }} align="center" gutter="lg"> <Grid px={{ base: "md", md: 100 }} align="center" gutter="lg">
<GridCol span={{ base: 12, md: 8 }}> <GridCol span={{ base: 12, md: 8 }}>
<Text <Title
fz={{ base: "2rem", md: "2.8rem" }} order={1}
c={colors["blue-button"]} c={colors["blue-button"]}
fw="bold" fw="bold"
fz={{ base: '28px', md: '32px' }}
> >
Program Kesehatan Desa Program Kesehatan Desa
</Text> </Title>
<Text fz="lg" mt="xs"> <Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
mt="xs"
>
Temukan berbagai program kesehatan untuk mendukung kualitas hidup Temukan berbagai program kesehatan untuk mendukung kualitas hidup
masyarakat Darmasaba. masyarakat Darmasaba.
</Text> </Text>
@@ -129,11 +135,9 @@ export default function Page() {
<Box <Box
style={{ style={{
width: '100%', width: '100%',
height: 180, // 🔥 tinggi fix biar semua seragam aspectRatio: '16/9', // thumbnail landscape aman untuk portrait/landscape
borderRadius: 12, borderRadius: 12,
overflow: 'hidden', overflow: 'hidden',
position: 'relative',
backgroundColor: '#f0f2f5', // fallback kalau gambar loading
}} }}
> >
<Image <Image
@@ -142,32 +146,28 @@ export default function Page() {
fit="cover" fit="cover"
width="100%" width="100%"
height="100%" height="100%"
style={{ objectFit: 'cover', objectPosition: 'center' }}
loading="lazy" loading="lazy"
style={{
objectFit: 'cover',
objectPosition: 'center',
transition: 'transform 0.4s ease',
}}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')} onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')} onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
/> />
</Box> </Box>
</Center> </Center>
<Box px="lg" pb="lg"> <Box px="lg" pb="lg">
<Text <Title
fw="bold" order={3}
fz="xl"
c={colors["blue-button"]} c={colors["blue-button"]}
fw="bold"
fz={{ base: '18px', md: '20px' }}
mb="xs" mb="xs"
> >
{v.name} {v.name}
</Text> </Title>
<Text <Text
fz="sm" fz={{ base: '13px', md: '14px' }}
ta={"justify"} lh="1.6"
ta="justify"
lineClamp={3} lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsi }} dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
@@ -175,7 +175,10 @@ export default function Page() {
<Group justify="space-between" mt="md"> <Group justify="space-between" mt="md">
<Group gap="xs"> <Group gap="xs">
<IconCalendar size={18} /> <IconCalendar size={18} />
<Text size="sm"> <Text
fz={{ base: '12px', md: '14px' }}
lh="1.5"
>
{v.createdAt {v.createdAt
? new Date(v.createdAt).toLocaleDateString( ? new Date(v.createdAt).toLocaleDateString(
"id-ID", "id-ID",
@@ -190,7 +193,12 @@ export default function Page() {
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<IconUser size={18} /> <IconUser size={18} />
<Text size="sm">Admin Desa</Text> <Text
fz={{ base: '12px', md: '14px' }}
lh="1.5"
>
Admin Desa
</Text>
</Group> </Group>
</Group> </Group>
<Button <Button
@@ -239,14 +247,19 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }} py="xl"> <Box px={{ base: "md", md: 100 }} py="xl">
<Stack gap="sm" mb="lg"> <Stack gap="sm" mb="lg">
<Text <Title
fz={{ base: "2rem", md: "2.5rem" }} order={2}
c={colors["blue-button"]} c={colors["blue-button"]}
fw="bold" fw="bold"
fz={{ base: '24px', md: '28px' }}
> >
Manfaat Program Kesehatan Manfaat Program Kesehatan
</Text> </Title>
<Text fz="lg" maw={700}> <Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
maw={700}
>
Program kesehatan Desa Darmasaba berperan penting dalam meningkatkan Program kesehatan Desa Darmasaba berperan penting dalam meningkatkan
kesejahteraan dan kualitas hidup warganya. kesejahteraan dan kualitas hidup warganya.
</Text> </Text>
@@ -273,10 +286,20 @@ export default function Page() {
> >
<Center>{v.icon}</Center> <Center>{v.icon}</Center>
</Paper> </Paper>
<Text ta="center" fw="bold" fz="xl" c={colors["blue-button"]}> <Title
order={3}
ta="center"
fw="bold"
c={colors["blue-button"]}
fz={{ base: '18px', md: '20px' }}
>
{v.title} {v.title}
</Text> </Title>
<Text ta="center" fz="sm"> <Text
ta="center"
fz={{ base: '13px', md: '14px' }}
lh="1.5"
>
{v.desc} {v.desc}
</Text> </Text>
</Stack> </Stack>

View File

@@ -59,12 +59,16 @@ function Page() {
left={20} left={20}
gap={6} gap={6}
> >
<Text fw="bold" fz={{ base: 'lg', md: 'h2' }} c={colors['white-1']}> <Title
order={1}
c={colors['white-1']}
fz={{ base: 'lg', md: 'xl' }}
>
{data.name} {data.name}
</Text> </Title>
<Group gap={6}> <Group gap={6}>
<IconMapPin size={20} color="white" /> <IconMapPin size={20} color="white" />
<Text fz={{ base: 'sm', md: 'md' }} c={colors['white-1']}> <Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>
{data.alamat} {data.alamat}
</Text> </Text>
</Group> </Group>
@@ -75,35 +79,43 @@ function Page() {
<GridCol span={{ base: 12, md: 6 }}> <GridCol span={{ base: 12, md: 6 }}>
<Stack gap="lg"> <Stack gap="lg">
<Box> <Box>
<Title order={3} mb={10}>Informasi Kontak</Title> <Title order={2} mb="md">Informasi Kontak</Title>
<Stack gap={8}> <Stack gap={8}>
<Group gap={8}> <Group gap={8}>
<IconPhone size={18} /> <IconPhone size={18} />
<Text fz="md">{data.kontak.kontakPuskesmas || '-'}</Text> <Text fz={{ base: 'sm', md: 'md' }}>
{data.kontak.kontakPuskesmas || '-'}
</Text>
</Group> </Group>
<Group gap={8}> <Group gap={8}>
<IconMail size={18} /> <IconMail size={18} />
<Text fz="md">{data.kontak.email || '-'}</Text> <Text fz={{ base: 'sm', md: 'md' }}>
{data.kontak.email || '-'}
</Text>
</Group> </Group>
</Stack> </Stack>
</Box> </Box>
<Divider /> <Divider />
<Stack gap={"xs"}> <Stack gap="xs">
<Title order={3} mb={10}>Jam Operasional</Title> <Title order={2} mb="md">Jam Operasional</Title>
<Text fw="bold" fz="md">Senin - Jumat</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Senin - Jumat</Text>
<Group gap={8}> <Group gap={8}>
<IconClock size={18} /> <IconClock size={18} />
<Text fw="bold" fz="md">{data.jam.workDays} - {data.jam.weekDays}</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
{data.jam.workDays} {data.jam.weekDays}
</Text>
<Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow> <Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow>
<Badge size="sm" variant="light" color="blue">Aktif</Badge> <Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip> </Tooltip>
</Group> </Group>
<Text fw="bold" fz="md">Sabtu - Minggu / Hari Libur</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Sabtu Minggu / Hari Libur</Text>
<Group gap={8}> <Group gap={8}>
<IconClock size={18} /> <IconClock size={18} />
<Text fw="bold" fz="md">{data.jam.holiday}</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
{data.jam.holiday}
</Text>
<Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow> <Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow>
<Badge size="sm" variant="light" color="blue">Aktif</Badge> <Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip> </Tooltip>
@@ -114,20 +126,24 @@ function Page() {
<GridCol span={{ base: 12, md: 6 }}> <GridCol span={{ base: 12, md: 6 }}>
<Paper p="xl" radius="lg" bg="linear-gradient(135deg, #EAF0FB, #BFD4F5)" shadow="sm"> <Paper p="xl" radius="lg" bg="linear-gradient(135deg, #EAF0FB, #BFD4F5)" shadow="sm">
<Title order={3} mb="lg" ta="center" c="dark">Layanan Unggulan</Title> <Title order={2} mb="lg" ta="center" c="dark">Layanan Unggulan</Title>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg"> <SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs"> <Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}> <Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} /> <IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poliklinik Umum</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Poliklinik Umum</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text> <Text fz={{ base: 'h1', md: 'h1' }} fw="bold" c={colors['blue-button']} lh={1}>
26
</Text>
</Stack> </Stack>
</Paper> </Paper>
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs"> <Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}> <Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} /> <IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poli Gigi</Text> <Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Poli Gigi</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text> <Text fz={{ base: 'h1', md: 'h1' }} fw="bold" c={colors['blue-button']} lh={1}>
26
</Text>
</Stack> </Stack>
</Paper> </Paper>
</SimpleGrid> </SimpleGrid>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas'; import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Anchor, Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Badge, Group } from '@mantine/core'; import { Anchor, Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Badge, Group, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react'; import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react';
import { useState } from 'react'; import { useState } from 'react';
@@ -42,10 +42,10 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} mb="md"> <Grid align="center" px={{ base: 'md', md: 100 }} mb="md">
<GridCol span={{ base: 12, md: 8 }}> <GridCol span={{ base: 12, md: 8 }}>
<Text fz={{ base: "2rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold"> <Title order={1} c={colors["blue-button"]}>
Daftar Puskesmas Daftar Puskesmas
</Text> </Title>
<Text fz="md"> <Text fz={{ base: "sm", md: "md" }} ta="start">
Temukan informasi lengkap mengenai layanan, kontak, dan lokasi Puskesmas Darmasaba Temukan informasi lengkap mengenai layanan, kontak, dan lokasi Puskesmas Darmasaba
</Text> </Text>
</GridCol> </GridCol>
@@ -65,8 +65,8 @@ function Page() {
{data.length === 0 ? ( {data.length === 0 ? (
<Center py="xl"> <Center py="xl">
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<Text fz="lg" fw={500} c="dimmed">Tidak ada data ditemukan</Text> <Title order={2} fw={500} c="dimmed">Tidak ada data ditemukan</Title>
<Text fz="sm" c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text> <Text fz={{ base: "xs", md: "sm" }} c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text>
</Stack> </Stack>
</Center> </Center>
) : ( ) : (
@@ -92,29 +92,29 @@ function Page() {
loading="lazy" loading="lazy"
/> />
<Group justify="space-between"> <Group justify="space-between">
<Text fw={600} fz="lg" lineClamp={1}>{v.name}</Text> <Title order={3} fw={600} lineClamp={1}>{v.name}</Title>
<Badge color="blue" variant="light" radius="sm" fz="xs">Aktif</Badge> <Badge color="blue" variant="light" radius="sm" fz="xs">Aktif</Badge>
</Group> </Group>
<Stack gap={6}> <Stack gap={6}>
<Group gap="xs" align="flex-start" wrap="nowrap"> <Group gap="xs" align="flex-start" wrap="nowrap">
<Box pt={2}><IconMapPin size={20} /></Box> <Box pt={2}><IconMapPin size={20} /></Box>
<Text fz="sm" c="dimmed">{v.alamat}</Text> <Text fz={{ base: "sm", md: "md" }}>{v.alamat}</Text>
</Group> </Group>
<Group gap="xs" align="flex-start" wrap="nowrap"> <Group gap="xs" align="flex-start" wrap="nowrap">
<Box pt={2}><IconPhone size={20} /></Box> <Box pt={2}><IconPhone size={20} /></Box>
<Text fz="sm" c="dimmed">{v.kontak.kontakPuskesmas}</Text> <Text fz={{ base: "sm", md: "md" }}>{v.kontak.kontakPuskesmas}</Text>
</Group> </Group>
<Group gap="xs" align="flex-start" wrap="nowrap"> <Group gap="xs" align="flex-start" wrap="nowrap">
<Box pt={2}><IconMail size={20} /></Box> <Box pt={2}><IconMail size={20} /></Box>
<Text fz="sm" c="dimmed">{v.kontak.email}</Text> <Text fz={{ base: "sm", md: "md" }}>{v.kontak.email}</Text>
</Group> </Group>
</Stack> </Stack>
<Anchor <Anchor
href={`/darmasaba/kesehatan/puskesmas/${v.id}`} href={`/darmasaba/kesehatan/puskesmas/${v.id}`}
fz="sm" fz={{ base: "sm", md: "md" }}
fw={500} fw={500}
c={colors['blue-button']} c={colors['blue-button']}
> >

View File

@@ -64,14 +64,14 @@ function Page() {
</Text> </Text>
<TextInput <TextInput
radius="xl" radius="xl"
w={'30%'} w={{base: "100%", md: "30%"}}
placeholder="Cari Data Lingkungan Desa" placeholder="Cari Data Lingkungan Desa"
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
value={search} value={search}
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch(e.currentTarget.value)}
/> />
</Group> </Group>
<Text fz="md" > <Text fz="md" pt={20}>
Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya. Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya.
</Text> </Text>
<Text fz="md"> <Text fz="md">

View File

@@ -91,7 +91,7 @@ function StrukturOrganisasiPPID() {
const debouncedSearch = useRef( const debouncedSearch = useRef(
debounce((value: string) => { debounce((value: string) => {
setSearchQuery(value) setSearchQuery(value)
}, 400) }, 1000)
).current ).current
useEffect(() => { useEffect(() => {

View File

@@ -2,7 +2,7 @@
'use client'; 'use client';
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan"; import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import colors from "@/con/colors"; import colors from "@/con/colors";
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text } from "@mantine/core"; import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text, Title } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks"; import { useMediaQuery } from "@mantine/hooks";
import { IconArrowRight, IconAward } from "@tabler/icons-react"; import { IconArrowRight, IconAward } from "@tabler/icons-react";
import { useTransitionRouter } from "next-view-transitions"; import { useTransitionRouter } from "next-view-transitions";
@@ -20,11 +20,21 @@ export default function Page() {
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Group gap="xs"> <Group gap="xs">
<IconAward size={40} color={colors["blue-button"]} /> <IconAward size={40} color={colors["blue-button"]} />
<Text fz={{ base: "2rem", md: "3.2rem" }} fw={800} variant="gradient" gradient={{ from: "#1C6EA4", to: "#69BFF8" }}> <Title
order={1}
fw={800}
c={colors["blue-button"]}
ta="center"
>
Penghargaan Desa Penghargaan Desa
</Text> </Title>
</Group> </Group>
<Text fz="lg" c="dimmed" ta="center"> <Text
fz={{ base: "sm", md: "md" }}
lh={{ base: "1.5", md: "1.6" }}
c="black"
ta="center"
>
Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan. Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan.
</Text> </Text>
</Stack> </Stack>
@@ -61,10 +71,8 @@ function Slider() {
const data = state.findMany.data || []; const data = state.findMany.data || [];
const loading = state.findMany.loading; const loading = state.findMany.loading;
// Triple data untuk infinite loop (desktop only)
const slidesData = mobile ? data : [...data, ...data, ...data]; const slidesData = mobile ? data : [...data, ...data, ...data];
// Auto-scroll animation untuk desktop
useEffect(() => { useEffect(() => {
if (loading || !containerRef.current || data.length === 0 || mobile) return; if (loading || !containerRef.current || data.length === 0 || mobile) return;
@@ -72,7 +80,6 @@ function Slider() {
const slideWidth = container.scrollWidth / slidesData.length; const slideWidth = container.scrollWidth / slidesData.length;
const originalLength = data.length; const originalLength = data.length;
// Start dari middle set
scrollPosRef.current = slideWidth * originalLength; scrollPosRef.current = slideWidth * originalLength;
container.scrollLeft = scrollPosRef.current; container.scrollLeft = scrollPosRef.current;
@@ -88,7 +95,6 @@ function Slider() {
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL; const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
scrollPosRef.current += speed; scrollPosRef.current += speed;
// Reset untuk infinite loop
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) { if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
scrollPosRef.current -= slideWidth * originalLength; scrollPosRef.current -= slideWidth * originalLength;
} }
@@ -100,7 +106,6 @@ function Slider() {
} else { } else {
scrollPosRef.current = container.scrollLeft; scrollPosRef.current = container.scrollLeft;
// Momentum untuk drag release
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) { if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
scrollPosRef.current += velocityRef.current; scrollPosRef.current += velocityRef.current;
velocityRef.current *= VELOCITY_DECAY; velocityRef.current *= VELOCITY_DECAY;
@@ -185,7 +190,7 @@ function Slider() {
return ( return (
<Stack align="center" py="xl"> <Stack align="center" py="xl">
<IconAward size={56} color={colors["blue-button"]} /> <IconAward size={56} color={colors["blue-button"]} />
<Text fz="lg" fw={600} c="dimmed"> <Text fz={{ base: "sm", md: "md" }} fw={600} c="dimmed" ta="center">
Belum ada penghargaan yang ditambahkan Belum ada penghargaan yang ditambahkan
</Text> </Text>
</Stack> </Stack>
@@ -213,7 +218,6 @@ function Slider() {
msOverflowStyle: "none", msOverflowStyle: "none",
}} }}
> >
{/* Blur edges - hanya untuk desktop */}
{!mobile && ( {!mobile && (
<> <>
<Box <Box
@@ -291,8 +295,8 @@ function Slider() {
style={{ borderRadius: 16 }} style={{ borderRadius: 16 }}
/> />
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative"> <Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
<Text <Title
fz={{ base: "lg", sm: "xl", md: "1.5rem" }} order={3}
fw={700} fw={700}
ta="center" ta="center"
c="white" c="white"
@@ -300,7 +304,7 @@ function Slider() {
style={{ textShadow: "0 2px 8px rgba(0,0,0,0.8)" }} style={{ textShadow: "0 2px 8px rgba(0,0,0,0.8)" }}
> >
{item.name} {item.name}
</Text> </Title>
<Group justify="center"> <Group justify="center">
<Button <Button
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)} onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}

View File

@@ -74,7 +74,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
<Tooltip label="Profil Saya" position="bottom" withArrow> <Tooltip label="Profil Saya" position="bottom" withArrow>
<ActionIcon <ActionIcon
onClick={() => { onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi") next.push("/admin/landing-page/profil/program-inovasi")
}} }}
color={colors["blue-button"]} color={colors["blue-button"]}
radius="xl" radius="xl"

View File

@@ -96,7 +96,7 @@ function Potensi() {
</Stack> </Stack>
) : ( ) : (
/* CARD LIST */ /* CARD LIST */
<SimpleGrid cols={{ base: 1, sm: 2 }}> <SimpleGrid cols={{ base: 1, sm: 2 }} px={{base: 'md', md: 80}}>
{_.take(data, 4).map((v, k) => ( {_.take(data, 4).map((v, k) => (
<motion.div <motion.div
key={k} key={k}

View File

@@ -6,20 +6,20 @@ const getDetailUrl = (item: { type?: string; id: string | number;[key: string]:
sdgsdesa: () => '/darmasaba/sdgs-desa', sdgsdesa: () => '/darmasaba/sdgs-desa',
apbdes: () => '/darmasaba/apbdes', apbdes: () => '/darmasaba/apbdes',
prestasidesa: () => '/darmasaba/prestasi-desa', prestasidesa: () => '/darmasaba/prestasi-desa',
pejabatdesa: () => '/darmasaba/ppid/profile-ppid', pejabatdesa: () => '/darmasaba/ppid/profil-ppid',
strukturppid: () => '/darmasaba/ppid/struktur-ppid', strukturppid: () => '/darmasaba/ppid/struktur-ppid',
visimisippid: () => '/darmasaba/ppid/visi-misi', visimisippid: () => '/darmasaba/ppid/visi-misi',
dasarhukumppid: () => '/darmasaba/ppid/dasar-hukum', dasarhukumppid: () => '/darmasaba/ppid/dasar-hukum',
profileppid: () => '/darmasaba/ppid/profile', profileppid: () => '/darmasaba/ppid/profil',
daftarinformasipublik: () => '/darmasaba/ppid/daftar-informasi-publik', daftarinformasipublik: () => '/darmasaba/ppid/daftar-informasi-publik',
perbekeldarmasaba: () => '/darmasaba/desa/profile', perbekeldarmasaba: () => '/darmasaba/desa/profil',
berita: (id, kategori) => `/darmasaba/desa/berita/${kategori}/${id}`, berita: (id, kategori) => `/darmasaba/desa/berita/${kategori}/${id}`,
pengumuman: (id, kategori) => `/darmasaba/desa/pengumuman/${kategori}/${id}`, pengumuman: (id, kategori) => `/darmasaba/desa/pengumuman/${kategori}/${id}`,
sejarahdesa: () => '/darmasaba/desa/profile', sejarahdesa: () => '/darmasaba/desa/profil',
visimisidesa: () => '/darmasaba/desa/profile', visimisidesa: () => '/darmasaba/desa/profil',
lambangdesa: () => '/darmasaba/desa/profile', lambangdesa: () => '/darmasaba/desa/profil',
maskotdesa: () => '/darmasaba/desa/profile', maskotdesa: () => '/darmasaba/desa/profil',
profilperbekel: () => '/darmasaba/desa/profile', profilperbekel: () => '/darmasaba/desa/profil',
potensi: () => '/darmasaba/desa/potensi-desa', potensi: () => '/darmasaba/desa/potensi-desa',
galleryFoto: () => '/darmasaba/desa/gallery/foto', galleryFoto: () => '/darmasaba/desa/gallery/foto',
galleryVideo: () => '/darmasaba/desa/gallery/video', galleryVideo: () => '/darmasaba/desa/gallery/video',