nico/12-des-25 #41

Merged
nicoarya20 merged 2 commits from nico/12-des-25 into staggingweb 2025-12-12 17:07:32 +08:00
63 changed files with 2506 additions and 1568 deletions

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
'use client';
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import {
Badge,
@@ -51,10 +51,14 @@ export default function Content({ kategori }: { kategori: string }) {
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
{/* === Berita Utama === */}
{featuredState.loading ? (
<Center><Skeleton h={400} /></Center>
<Center>
<Skeleton h={400} />
</Center>
) : featured ? (
<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>
<Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}>
@@ -74,13 +78,29 @@ export default function Content({ kategori }: { kategori: string }) {
<Badge color="blue" variant="light" mb="md">
{featured.kategoriBerita?.name || kategori}
</Badge>
<Title order={2} mb="md">{featured.judul}</Title>
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featured.deskripsi }} />
<Title order={3} mb="md">
{featured.judul}
</Title>
<Text
c="dimmed"
lineClamp={3}
mb="md"
style={{ lineHeight: 1.6 }}
dangerouslySetInnerHTML={{ __html: featured.deskripsi }}
/>
</div>
<Group justify="apart" mt="auto">
<Group gap="xs">
<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', {
day: 'numeric',
month: 'long',
@@ -91,7 +111,9 @@ export default function Content({ kategori }: { kategori: string }) {
<Button
variant="light"
rightSection={<IconArrowRight size={16} />}
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)}
onClick={() =>
router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)
}
>
Baca Selengkapnya
</Button>
@@ -105,19 +127,29 @@ export default function Content({ kategori }: { kategori: string }) {
{/* === Daftar Berita === */}
<Box mt={50}>
<Title order={2} mb="md">Daftar Berita</Title>
<Title order={2} mb="md">
Daftar Berita
</Title>
<Divider mb="xl" />
{state.findMany.loading ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
{Array(3).fill(0).map((_, i) => (
<Skeleton key={i} h={300} radius="md" />
))}
{Array(3)
.fill(0)
.map((_, i) => (
<Skeleton key={i} h={300} radius="md" />
))}
</SimpleGrid>
) : 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) => (
<Card
key={item.id}
@@ -125,19 +157,51 @@ export default function Content({ kategori }: { kategori: string }) {
p="lg"
radius="md"
withBorder
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)}
onClick={() =>
router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)
}
style={{ cursor: 'pointer' }}
>
<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>
<Badge color="blue" variant="light" mt="md">
{item.kategoriBerita?.name || kategori}
</Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Title
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">
<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', {
day: 'numeric',
month: 'short',

View File

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

View File

@@ -16,35 +16,30 @@ function Semua() {
const searchParams = useSearchParams();
const router = useTransitionRouter();
// Ambil parameter langsung dari URL
const search = searchParams.get('search') || '';
const page = parseInt(searchParams.get('page') || '1');
// Gunakan proxy untuk state global
const state = useProxy(stateDashboardBerita.berita);
const featured = useProxy(stateDashboardBerita.berita.findFirst);
const loadingGrid = state.findMany.loading;
const loadingFeatured = featured.loading;
// Load berita utama sekali saja
useEffect(() => {
if (!featured.data && !loadingFeatured) {
stateDashboardBerita.berita.findFirst.load();
}
}, [featured.data, loadingFeatured]);
// Load berita terbaru tiap page / search berubah
useEffect(() => {
const limit = 3;
state.findMany.load(page, limit, search);
}, [page, search]);
// Handler pagination → langsung update URL
const handlePageChange = (newPage: number) => {
const url = new URLSearchParams(searchParams.toString());
if (search) url.set('search', search);
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()}`);
};
@@ -61,7 +56,7 @@ function Semua() {
<Center><Skeleton h={400} /></Center>
) : featuredData ? (
<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>
<Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}>
@@ -81,13 +76,24 @@ function Semua() {
<Badge color="blue" variant="light" mb="md">
{featuredData.kategoriBerita?.name || 'Berita'}
</Badge>
<Title order={2} mb="md">{featuredData.judul}</Title>
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }} />
<Title order={3} mb="md">{featuredData.judul}</Title>
<Text
c="dimmed"
lineClamp={3}
mb="md"
dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }}
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
/>
</div>
<Group justify="apart" mt="auto">
<Group gap="xs">
<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', {
day: 'numeric',
month: 'long',
@@ -124,7 +130,9 @@ function Semua() {
))}
</SimpleGrid>
) : 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">
{paginatedNews.map((item) => (
@@ -143,11 +151,24 @@ function Semua() {
{item.kategoriBerita?.name || 'Berita'}
</Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Title order={4} mt="sm" lineClamp={2}>
{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">
<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', {
day: 'numeric',
month: 'short',
@@ -187,4 +208,4 @@ function Semua() {
);
}
export default Semua;
export default Semua;

View File

@@ -17,17 +17,11 @@ import {
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconPhoto } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
// Komponen kartu foto
function FotoCard({ item }: { item: any }) {
const router = useRouter();
const handleClick = () => {
router.push(`/darmasaba/galeri/foto/${item.id}`);
};
return (
<Grid.Col span={{ base: 12, xs: 6, md: 4 }}>
@@ -35,19 +29,19 @@ function FotoCard({ item }: { item: any }) {
shadow="sm"
radius="md"
p={0}
onClick={handleClick}
style={{ cursor: 'pointer', transition: 'transform 0.2s' }}
style={{ transition: 'transform 0.2s' }}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
>
{item.imageGalleryFoto?.link ? (
<Box
pos="relative"
style={{
paddingBottom: '100%', // ✅ Ubah ke 1:1 (square) — atau sesuaikan
paddingBottom: '100%',
overflow: 'hidden',
borderRadius: '4px 4px 0 0',
backgroundColor: '#f9f9f9', // ✅ background netral
backgroundColor: '#f9f9f9',
}}
>
<Image
@@ -61,8 +55,8 @@ function FotoCard({ item }: { item: any }) {
left: 0,
width: '100%',
height: '100%',
objectFit: 'contain', // ✅ Tampilkan utuh, jangan crop
objectPosition: 'center', // rata tengah
objectFit: 'contain',
objectPosition: 'center',
}}
loading="lazy"
/>
@@ -74,13 +68,23 @@ function FotoCard({ item }: { item: any }) {
)}
<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'}
</Text>
{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', {
day: 'numeric',
month: 'short',
@@ -99,7 +103,7 @@ export default function GaleriFotoUser() {
return (
<Box py="xl" px={{ base: 'md', md: 'lg' }}>
{/* 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
</Title>
@@ -115,7 +119,7 @@ function FotoList({ search }: { search: string }) {
const { data, page, totalPages, loading, load } = FotoState.findMany;
useShallowEffect(() => {
load(page, 3, search); // ✅ 9 item per halaman
load(page, 3, search);
}, [page, search]);
if (loading) {
@@ -135,7 +139,9 @@ function FotoList({ search }: { search: string }) {
<Center py="xl">
<Stack align="center" c="dimmed">
<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>
</Center>
);
@@ -150,19 +156,18 @@ function FotoList({ search }: { search: string }) {
</Grid>
{/* Pagination */}
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 3, search);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
color="blue"
radius="md"
/>
</Center>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 3, search);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
color="blue"
radius="md"
/>
</Center>
</Stack>
);
}

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
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 { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -39,30 +39,38 @@ function PelayananPendudukNonPermanent() {
) : (
<Stack gap="xl">
<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"}
</Text>
</Title>
</Box>
<Box>
{data?.deskripsi ? (
<Text
fz={{ base: "sm", md: "md" }}
lh={1.7}
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
ta="justify"
c="dimmed"
c="black"
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>
<Divider color={colors["blue-button"]} size="sm" />
<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
</Text>
<Group gap="md">
@@ -96,4 +104,4 @@ function PelayananPendudukNonPermanent() {
);
}
export default PelayananPendudukNonPermanent;
export default PelayananPendudukNonPermanent;

View File

@@ -47,7 +47,7 @@ function PelayananPerizinanBerusaha() {
return (
<Center mih={300}>
<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
</Text>
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
@@ -67,10 +67,10 @@ function PelayananPerizinanBerusaha() {
) : (
<Stack gap="lg">
<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
</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
</Text>
</Box>
@@ -83,13 +83,13 @@ function PelayananPerizinanBerusaha() {
/>
<Box>
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>
<Title order={3} fw={600} mb="sm">
Alur pendaftaran NIB:
</Text>
</Title>
<Stepper
active={active}
onStepClick={(step) => {
if (step <= active) { // Only allow clicking on previous or current steps
if (step <= active) {
setActive(step);
}
}}
@@ -102,28 +102,42 @@ function PelayananPerizinanBerusaha() {
}}
>
<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 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 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 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 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 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>
<StepperCompleted>
<Center>
<Stack align="center" gap="xs">
<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>
</Center>
</StepperCompleted>
@@ -159,7 +173,7 @@ function PelayananPerizinanBerusaha() {
)}
</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{' '}
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
oss.go.id

View File

@@ -2,7 +2,7 @@
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
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 { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -35,7 +35,7 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
<Center py="xl">
<Stack align="center" gap="xs">
<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
</Text>
</Stack>
@@ -48,9 +48,9 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
<Group justify="space-between" align="center" mb="md">
<Group gap="xs">
<IconFileDescription size={28} stroke={1.8} />
<Text fz={{ base: "h4", md: "h2" }} fw={700}>
<Title order={2} c="black">
Layanan Surat Keterangan
</Text>
</Title>
</Group>
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
<IconInfoCircle size={22} stroke={1.8} />
@@ -82,15 +82,15 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
style={{ borderRadius: 16 }}
/>
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
<Text
<Title
order={3}
c="white"
fw={600}
fz="lg"
ta="center"
lineClamp={2}
lh="sm"
>
{v.name}
</Text>
</Title>
<Group justify="center">
<Button
size="md"
@@ -128,4 +128,4 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
);
}
export default PelayananSuratKeterangan;
export default PelayananSuratKeterangan;

View File

@@ -42,9 +42,10 @@ function PelayananTelunjukSaktiDesa() {
return (
<Box>
<Title order={2} mb="lg" fz={{ base: 22, md: 28 }} fw={700} style={{ lineHeight: 1.4 }}>
Layanan Telunjuk Sakti Desa <br />
<Text span c="dimmed" fz="lg" fw={400}>
<Title order={2} mb="lg" fw={700} style={{ lineHeight: 1.3 }} ta="left">
Layanan Telunjuk Sakti Desa
<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
</Text>
</Title>
@@ -53,7 +54,7 @@ function PelayananTelunjukSaktiDesa() {
<Skeleton h={400} radius="lg" />
) : data.length === 0 ? (
<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
</Text>
</Card>
@@ -72,9 +73,9 @@ function PelayananTelunjukSaktiDesa() {
}}
>
<Stack gap="sm">
<Text fw={700} fz="lg" lh={1.4}>
<Title order={3} fw={700} lh={1.3}>
{v.name}
</Text>
</Title>
<Flex gap="xs" align="center">
<IconExternalLink size={18} stroke={1.5} />
<Text
@@ -82,7 +83,7 @@ function PelayananTelunjukSaktiDesa() {
href={v.link}
target="_blank"
rel="noopener noreferrer"
fz="sm"
fz={{ base: 'xs', md: 'sm' }}
c="blue"
td="underline"
style={{ cursor: 'pointer' }}
@@ -100,4 +101,4 @@ function PelayananTelunjukSaktiDesa() {
);
}
export default PelayananTelunjukSaktiDesa;
export default PelayananTelunjukSaktiDesa;

View File

@@ -1,58 +1,94 @@
'use client'
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
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 { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import BackButton from '../../../layanan/_com/BackButto';
import NewsReader from '@/app/darmasaba/_com/NewsReader';
import BackButton from '../../../layanan/_com/BackButto';
function Page() {
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique)
const params = useParams()
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique);
const params = useParams();
useShallowEffect(() => {
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string)
}, [])
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string);
}, []);
if (!detail.data) {
return (
<Box>
<Skeleton h={400} />
</Box>
)
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md">
<Group>
<NewsReader />
</Group>
<Stack gap="xs" >
<Group justify={"space-between"} align={"center"}>
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
{detail.data?.judul}
<Stack gap="xs">
<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}
</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>
<Group justify='end'>
<Paper bg={colors['blue-button']} p={5}>
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
</Paper>
</Group>
</Group>
<Paper bg={colors["white-1"]} p="md">
<Text px="lg" id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
<Text px="lg" fz={"md"} c={colors["blue-button"]} fw="bold" >
</Paper>
</Group>
<Paper
bg={colors['white-1']}
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', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
year: 'numeric',
})}
</Text>
</Paper>
@@ -62,4 +98,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
@@ -35,7 +36,9 @@ function Page() {
<Center h="80vh">
<Stack align="center" gap="md">
<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>
</Center>
);
@@ -46,28 +49,31 @@ function Page() {
<Center h="80vh">
<Stack align="center" gap="sm">
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
<Title order={3}>Data Tidak Ditemukan</Title>
<Text c="dimmed" fz="sm">Mohon periksa kembali atau coba beberapa saat lagi</Text>
<Title order={3} ta="center">
Data Tidak Ditemukan
</Title>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
Mohon periksa kembali atau coba beberapa saat lagi
</Text>
</Stack>
</Center>
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: 'md', md: 0 }}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "60%" }}>
<Container w={{ base: '100%', md: '60%' }}>
<Paper radius="2xl" shadow="lg" p="xl" withBorder>
<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}
</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
</Text>
{/* ✅ Bagian gambar dibuat konsisten tanpa CSS manual */}
<Box
w="100%"
h={{ base: 220, md: 400 }}
@@ -87,7 +93,15 @@ function Page() {
radius="lg"
/>
</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>
</Paper>
</Container>
@@ -95,4 +109,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,7 +2,7 @@
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
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 { useTransitionRouter } from 'next-view-transitions';
import { useEffect, useState } from 'react';
@@ -41,10 +41,10 @@ function Page() {
<Box px={{ base: "md", md: 100 }}>
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
<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
</Text>
<Text fz="lg" ta="justify">
</Title>
<Text fz={{ base: 14, md: 16 }} lh={1.6} ta="justify">
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
</Text>
</Stack>
@@ -58,18 +58,18 @@ function Page() {
>
<Flex justify="center" align="center" gap="xl">
<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}
</Text>
<Text ta="center" fz="sm" c="white" fw={500}>
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
Potensi
</Text>
</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}
</Text>
<Text ta="center" fz="sm" c="white" fw={500}>
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
Wisata
</Text>
</Box>
@@ -91,45 +91,40 @@ function Page() {
radius="xl"
onMouseEnter={() => setHoveredId(v.id)}
onMouseLeave={() => setHoveredId(null)}
style={{
overflow: 'hidden',
style={{
overflow: 'hidden',
position: 'relative',
cursor: 'pointer',
transition: 'transform 0.3s ease'
}}
>
{/* Overlay with smooth transition */}
<Box
pos="absolute"
inset={0}
bg={hoveredId === v.id
? "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.75) 100%)"
bg={hoveredId === v.id
? "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.75) 100%)"
: "linear-gradient(180deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.15) 100%)"
}
style={{
transition: 'background 0.3s ease'
}}
/>
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
{/* Kategori badge - always visible */}
<Group>
<Paper
radius="lg"
py={6}
px={12}
shadow="md"
withBorder
<Paper
radius="lg"
py={6}
px={12}
shadow="md"
withBorder
bg="rgba(255,255,255,0.9)"
style={{
transition: 'all 0.3s ease'
}}
style={{ 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>
</Group>
{/* Nama potensi - visible on hover */}
<Box
style={{
opacity: hoveredId === v.id ? 1 : 0,
@@ -138,20 +133,20 @@ function Page() {
pointerEvents: hoveredId === v.id ? 'auto' : 'none'
}}
>
<Text
<Title
order={3}
fw={800}
c="white"
fz="xl"
fz={{ base: 18, md: 20 }}
ta="center"
lineClamp={2}
lh={1.3}
>
{v.name}
</Text>
</Title>
</Box>
{/* Button - visible on hover */}
<Group
<Group
justify="center"
style={{
opacity: hoveredId === v.id ? 1 : 0,
@@ -169,23 +164,21 @@ function Page() {
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
>
Lihat Detail
<Text c={'white'} fz={{ base: 12, md: 14 }} fw={500}>Lihat Detail</Text>
</Button>
</Group>
</Stack>
</BackgroundImage>
))
) : (
<Center h={240}>
<Stack align="center" gap="xs">
<Text fz="lg" fw={600} c="dimmed">
Belum ada potensi desa
</Text>
<Text fz="sm" c="dimmed">
Data potensi akan tampil di sini setelah tersedia.
</Text>
</Stack>
</Center>
<Stack align="center" gap="xs">
<Text fz={{ base: 14, md: 16 }} fw={600} c="dimmed">
Belum ada potensi desa
</Text>
<Text fz={{ base: 12, md: 14 }} c="dimmed">
Data potensi akan tampil di sini setelah tersedia.
</Text>
</Stack>
)}
</SimpleGrid>
</Box>
@@ -193,4 +186,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
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 { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
@@ -21,7 +21,9 @@ function MaskotDesa() {
<Center mih={500}>
<Stack align="center" gap="sm">
<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>
</Center>
);
@@ -31,8 +33,21 @@ function MaskotDesa() {
<Box>
<Stack align="center" gap="xl">
<Stack align="center" gap={10}>
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} loading="lazy"/>
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
<Image
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>
<Paper
@@ -42,48 +57,60 @@ function MaskotDesa() {
withBorder
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
>
{/* Body Description */}
<Text
fz={{ base: 'sm', md: 'lg' }}
lh={1.7}
ta="justify"
c="dark"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
<Group justify="center" gap="lg" mt="lg">
{data.images.length > 0 ? (
data.images.map((img, index) => (
<Card
<Card
key={index}
radius="lg"
shadow="md"
withBorder
w={220}
p="sm"
style={{
transition: 'transform 200ms ease, box-shadow 200ms ease',
}}
className="hover:scale-105 hover:shadow-lg"
radius="lg"
shadow="md"
withBorder
w={220}
p="sm"
style={{
transition: 'transform 200ms ease, box-shadow 200ms ease',
}}
className="hover:scale-105 hover:shadow-lg"
>
<Image
src={img.image.link}
alt={img.label}
w="100%"
h={200}
fit="cover"
radius="md"
loading="lazy"
/>
{/* Image Label */}
<Text
ta="center"
mt="sm"
fw={600}
fz={{ base: 'xs', md: 'sm' }}
c="dark"
>
<Image
src={img.image.link}
alt={img.label}
w="100%"
h={200}
fit="cover"
radius="md"
loading="lazy"
/>
<Text ta="center" mt="sm" fw={600} fz="sm" c="dark">
{img.label}
</Text>
</Card>
{img.label}
</Text>
</Card>
))
) : (
<Stack align="center" gap="xs" mt="lg">
<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>
)}
</Group>

View File

@@ -1,35 +1,15 @@
'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 { IconSparkles } from '@tabler/icons-react';
import colors from '@/con/colors';
const dataText = [
{
id: 1,
title: "Santun",
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
},
{
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."
},
{ id: 1, title: "Santun", description: "Pelayanan ramah, penuh empati, sopan, dan beretika." },
{ 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"];
@@ -38,11 +18,14 @@ function MotoDesa() {
return (
<Box px={{ base: "md", md: "xl" }}>
<Stack align="center" gap="lg">
{/* Page Title */}
<Box>
<Text
<Title
order={1}
ta="center"
fw={800}
fz={{ base: "2rem", md: "2.8rem" }}
fz={{ base: 28, md: 36 }}
lh={{ base: 1.2, md: 1.3 }}
style={{
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
WebkitBackgroundClip: "text",
@@ -50,9 +33,10 @@ function MotoDesa() {
}}
>
Moto Desa Darmasaba
</Text>
</Title>
</Box>
{/* Letter Icons */}
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
{letters.map((letter, i) => (
<motion.div
@@ -71,7 +55,7 @@ function MotoDesa() {
backdropFilter: "blur(6px)",
}}
>
<Text c="white" fw={800} fz="xl">
<Text c="white" fw={800} fz={{ base: 20, md: 24 }}>
{letter}
</Text>
</ActionIcon>
@@ -79,6 +63,7 @@ function MotoDesa() {
))}
</Flex>
{/* Values Card */}
<Paper
radius="lg"
p="xl"
@@ -90,19 +75,22 @@ function MotoDesa() {
>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
{dataText.map((v) => (
<motion.div
key={v.id}
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<motion.div key={v.id} whileHover={{ scale: 1.02 }} transition={{ duration: 0.2 }}>
<Stack gap={4}>
{/* Section Title */}
<Flex align="center" gap="sm">
<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}
</Text>
</Title>
</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}
</Text>
</Stack>
@@ -111,16 +99,15 @@ function MotoDesa() {
</SimpleGrid>
</Paper>
{/* Motto Description */}
<Text
ta="center"
fw={700}
fz={{ base: "md", md: "xl" }}
fz={{ base: 15, md: 20 }}
lh={{ base: 1.6, md: 1.8 }}
c="blue.8"
mt="md"
style={{
maxWidth: 720,
lineHeight: 1.6,
}}
style={{ maxWidth: 720 }}
>
&quot;Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
<Text span fw={800} c="cyan.6">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,13 +76,13 @@ function Page() {
</Box>
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<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>
<ColorSwatch color="#5082EE" size={30} />
</Flex>
</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>
<ColorSwatch color="#6EDF9C" size={30} />
</Flex>

View File

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

View File

@@ -1,7 +1,7 @@
'use client'
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
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 { IconSearch } from '@tabler/icons-react';
import { useState } from 'react';
@@ -32,10 +32,9 @@ interface ProgramKemiskinanData {
function Page() {
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000);
const state = useProxy(programKemiskinanState)
// 🔧 Get valid statistics data with proper type checking
const statistikData = state.findMany.data
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
return !!item?.statistik &&
@@ -43,11 +42,11 @@ function Page() {
item.statistik.jumlah !== undefined;
})
.map(item => ({
tahun: Number(item.statistik.tahun) || 0, // Ensure tahun is a number
jumlah: Number(item.statistik.jumlah) || 0, // Ensure jumlah is a number
tahun: Number(item.statistik.tahun) || 0,
jumlah: Number(item.statistik.jumlah) || 0,
}))
.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 {
data,
@@ -74,12 +73,18 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Box px={{ base: 'md', md: 100 }}>
<Grid align='center'>
<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
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
@@ -92,7 +97,15 @@ function Page() {
/>
</GridCol>
</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 px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
@@ -106,8 +119,22 @@ function Page() {
{state.findMany.data.map(v => {
return (
<Paper p={'xl'} key={v.id}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
<Text fz={'lg'} c={'black'} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
<Title
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>
)
})}
@@ -124,7 +151,16 @@ function Page() {
/>
</Center>
<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' }}>
{statistikData.length > 0 ? (
<Box w="100%" style={{ overflowX: 'auto' }}>
@@ -162,7 +198,11 @@ function Page() {
</Box>
) : (
<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
? 'Memuat data statistik...'
: 'Belum ada data statistik yang tersedia atau data tidak valid'}
@@ -177,4 +217,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

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

View File

@@ -14,6 +14,9 @@ import {
Loader,
Paper,
Stack,
Tabs,
TabsList,
TabsTab,
Text,
TextInput,
Title,
@@ -33,6 +36,8 @@ import { OrganizationChart } from 'primereact/organizationchart'
import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils'
import BackButton from '../../desa/layanan/_com/BackButto'
import { useMediaQuery } from '@mantine/hooks'
import { useTransitionRouter } from 'next-view-transitions'
export default function Page() {
return (
@@ -51,11 +56,11 @@ export default function Page() {
order={1}
ta="center"
c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }}
fz={{ base: 28, md: 36 }}
>
Struktur Organisasi & SK Pengurus BumDes
</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
di bawah untuk mencari, memperbesar, atau melihat lebih jelas.
</Text>
@@ -70,13 +75,14 @@ export default function Page() {
}
function StrukturOrganisasiBumDes() {
const router = useTransitionRouter()
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
const chartContainerRef = useRef<HTMLDivElement>(null)
const [scale, setScale] = useState(1)
const [isFullscreen, setFullscreen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const debouncedSearch = useRef(
debounce((value: string) => setSearchQuery(value), 400)
debounce((value: string) => setSearchQuery(value), 1000)
).current
useEffect(() => {
@@ -92,8 +98,10 @@ function StrukturOrganisasiBumDes() {
<Center py={48}>
<Stack align="center" gap="sm">
<Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text>
<Text c="dimmed" size="sm">
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.4}>
Memuat struktur organisasi
</Text>
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={1.4}>
Mengambil data pengurus dan posisi. Mohon tunggu sebentar.
</Text>
</Stack>
@@ -119,10 +127,10 @@ function StrukturOrganisasiBumDes() {
<Center>
<IconUsers size={56} />
</Center>
<Title order={3} mt="md">
<Title order={3} mt="md" ta="center">
Data pengurus belum tersedia
</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.
</Text>
<Group justify="center" mt="lg">
@@ -218,155 +226,299 @@ function StrukturOrganisasiBumDes() {
return (
<Stack align="center" mt="xl">
{/* 🧭 Kontrol atas */}
<Paper shadow="xs" p="md" radius="md" bg={colors['blue-button']}>
<Group gap="sm" wrap="wrap" justify="center">
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
<Paper
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
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
styles={{
input: {
minWidth: 250,
},
}}
/>
</Group>
<Tabs
defaultValue="zoom-out"
variant="outline"
radius="md"
styles={{
input: {
minWidth: 250,
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,
},
}}
/>
<Group gap="xs">
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
>
Zoom Out
</Button>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={16}
py={8}
style={{ width: '100%' }} // 👈 penting
>
<TabsList
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '8px',
minWidth: 70,
textAlign: 'center',
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
}}
>
{Math.round(scale * 100)}%
</Box>
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
>
Zoom In
</Button>
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={resetZoom}
>
Reset
</Button>
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
>
Fullscreen
</Button>
</Group>
</Group>
<TabsTab
value="zoom-out"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
</TabsTab>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={12}
py={6}
style={{
fontWeight: 700,
borderRadius: '6px',
minWidth: 60,
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)}%
</Text>
</Box>
<TabsTab
value="zoom-in"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
</TabsTab>
<TabsTab
value="reset"
onClick={resetZoom}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
</TabsTab>
<TabsTab
value="fullscreen"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
{isFullscreen ? 'Exit' : 'Fullscreen'}
</Text>
</TabsTab>
</TabsList>
</Tabs>
</Stack>
</Paper>
{/* 📊 Chart Container */}
<Center style={{ width: '100%' }}>
<Box
ref={chartContainerRef}
style={{
overflowX: 'auto',
overflowY: 'auto',
width: '100%',
padding: '32px 16px',
transition: 'transform 0.2s ease',
transform: `scale(${scale})`,
transformOrigin: 'center top',
}}
>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
</Box>
</Center>
</Stack>
)
}
function NodeCard({ node }: any) {
const imageSrc = node?.data?.image || '/img/default.png'
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const description = node?.data?.description || ''
return (
<Transition mounted transition="pop" duration={300}>
{(styles) => (
<Card
shadow="md"
radius="xl"
withBorder
style={{
...styles,
width: 240,
padding: 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)',
transition: 'all 0.3s ease',
}}
>
<Stack align="center" gap={10}>
<Box
style={{
width: 90,
height: 90,
borderRadius: '50%',
overflow: 'hidden',
border: '3px solid rgba(28, 110, 164, 0.4)',
}}
>
<Image src={imageSrc} alt={name} fit="cover" loading="lazy" />
</Box>
<Text fw={700} size="sm" ta="center" c={colors['blue-button']}>
{name}
</Text>
<Text size="xs" c="dimmed" ta="center">
{title}
</Text>
<Text size="xs" c="dimmed" ta="center" lineClamp={3}>
{description || 'Belum ada deskripsi.'}
</Text>
{/* 🧩 Chart Container */}
<Center style={{ width: '100%' }}>
<Box
ref={chartContainerRef}
style={{
overflowX: 'auto',
overflowY: 'auto',
width: '100%',
maxWidth: '100%',
padding: '32px 16px',
transition: 'transform 0.2s ease',
}}
>
<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
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
</Box>
</Box>
</Center>
</Stack>
</Card>
)}
</Transition>
)
}
)
}
function NodeCard({ node, router }: any) {
const imageSrc = node?.data?.image || '/img/default.png'
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const hasId = Boolean(node?.data?.id)
const isMobile = useMediaQuery("(max-width: 768px)");
return (
<Transition mounted transition="pop" duration={300}>
{(styles) => (
<Card
shadow="md"
radius="xl"
withBorder
style={{
...styles,
width: '100%',
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
minHeight: isMobile ? 240 : 280,
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)',
borderWidth: 2,
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={12}>
{/* Photo */}
<Box
style={{
width: 96,
height: 96,
borderRadius: '50%',
overflow: 'hidden',
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}
width={96}
height={96}
fit="cover"
loading="lazy"
style={{
objectFit: 'cover',
}}
/>
</Box>
{/* 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}
</Text>
{/* 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}
</Text>
{/* Detail Button */}
{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>
</Card>
)}
</Transition>
)
}

View File

@@ -1,20 +1,18 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react';
import { IconBulbFilled } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
function Page() {
const [opened, { open, close }] = useDisclosure(false);
const ideInovatif = useProxy(ajukanIdeInovatifState)
const ideInovatif = useProxy(ajukanIdeInovatifState);
const resetForm = () => {
// Reset state di valtio
ideInovatif.create.form = {
name: "",
deskripsi: "",
@@ -23,53 +21,66 @@ function Page() {
masalah: "",
benefit: "",
};
// Reset state lokal
};
const handleSubmit = async () => {
// Submit data berita
await ideInovatif.create.create();
// Reset form setelah submit
resetForm();
close();
};
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 }}>
<BackButton />
</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 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
style={{ fontSize: 'clamp(1.75rem, 4vw, 2.25rem)' }}
>
Ajukan Ide Inovatif
</Title>
<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>
<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>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} p={'lg'}>
<SimpleGrid
cols={{
base: 1,
md: 2,
}}
>
<Paper p={'xl'} >
<Stack gap={"xs"}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Tujuan Ide Inovatif Ini</Text>
<List>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mendorong partisipasi aktif masyarakat</ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memfasilitasi inovasi berbasis lokal</ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memecahkan tantangan komunal</ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mengembangkan potensi kreativitas warga</ListItem>
</List>
<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>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Mendorong partisipasi aktif masyarakat
</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>
</Stack>
</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>
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar Di Samping</Text>
<IconArrowRight size={30} color={colors['blue-button']} />
<Title order={3} c={colors['blue-button']} fw="bold" ta={{ base: 'center', md: 'start' }}>
Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar
</Title>
</Box>
<Box px={{ base: 5, md: 10 }} py={5}>
<ActionIcon variant="transparent" size={150} onClick={open}>
@@ -88,32 +99,46 @@ function Page() {
radius={0}
transitionProps={{ transition: 'fade', duration: 200 }}
>
<Paper p={"md"} withBorder>
<Stack gap={"xs"}>
<Paper p="md" withBorder>
<Stack gap="xs">
<Title order={3}>Ajukan Ide Inovatif</Title>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
label={
<Text fz="sm" fw="bold">
Nama
</Text>
}
placeholder="masukkan nama"
onChange={(val) => {
ideInovatif.create.form.name = val.target.value
ideInovatif.create.form.name = val.target.value;
}}
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
label={
<Text fz="sm" fw="bold">
Alamat
</Text>
}
placeholder="masukkan alamat"
onChange={(val) => {
ideInovatif.create.form.alamat = val.target.value
ideInovatif.create.form.alamat = val.target.value;
}}
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama Ide</Text>}
label={
<Text fz="sm" fw="bold">
Nama Ide
</Text>
}
placeholder="masukkan nama ide"
onChange={(val) => {
ideInovatif.create.form.namaIde = val.target.value
ideInovatif.create.form.namaIde = val.target.value;
}}
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
<Text fz="sm" fw="bold">
Deskripsi
</Text>
<CreateEditor
value={ideInovatif.create.form.deskripsi}
onChange={(htmlContent) => {
@@ -122,26 +147,35 @@ function Page() {
/>
</Box>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Masalah</Text>}
label={
<Text fz="sm" fw="bold">
Masalah
</Text>
}
placeholder="masukkan masalah"
onChange={(val) => {
ideInovatif.create.form.masalah = val.target.value
ideInovatif.create.form.masalah = val.target.value;
}}
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Benefit</Text>}
label={
<Text fz="sm" fw="bold">
Benefit
</Text>
}
placeholder="masukkan benefit"
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>
</Paper>
</Modal>
</Stack>
);
}
export default Page;
export default Page;

View File

@@ -58,7 +58,7 @@ function Page() {
/>
</GridCol>
</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>
</Box>
<Box px={{ base: "md", md: 100 }}>

View File

@@ -1,17 +1,17 @@
'use client'
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
import { IconSearch } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const [search, setSearch] = useState("")
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000);
const state = useProxy(infoTeknoState)
const {
data,
@@ -34,17 +34,24 @@ function Page() {
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Box px={{ base: 'md', md: 100 }}>
<Grid align='center'>
<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
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
@@ -53,13 +60,19 @@ function Page() {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
w={{ base: "100%", md: "100%" }}
/>
</GridCol>
</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 px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} p={'lg'}>
<SimpleGrid
@@ -74,12 +87,14 @@ function Page() {
<Paper p={'xl'} key={k}>
<Stack gap={"xs"}>
<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}>
<Text
size="md"
fz={{ base: 'xs', md: 'sm' }}
ta="justify"
lh={1} // line height biar enak dibaca
lh={1.5}
style={{
wordBreak: "break-word",
whiteSpace: "normal",
@@ -94,10 +109,11 @@ function Page() {
</SimpleGrid>
</Stack>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
@@ -106,4 +122,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -58,7 +58,7 @@ function Page() {
/>
</GridCol>
</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.
</Text>
</Box>

View File

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

View File

@@ -1,6 +1,6 @@
'use client'
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 tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
@@ -8,11 +8,10 @@ import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import { IconSearch } from '@tabler/icons-react';
function Page() {
const state = useProxy(tipsKeamananState)
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const state = useProxy(tipsKeamananState);
const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
page,
@@ -22,84 +21,114 @@ function Page() {
} = state.findMany;
useShallowEffect(() => {
load(page, 3, debouncedSearch)
}, [page, debouncedSearch])
load(page, 3, debouncedSearch);
}, [page, debouncedSearch]);
if (loading || !data) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Skeleton h={500} />
</Stack>
)
);
}
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 }}>
<BackButton />
</Box>
<Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<Grid align="center" px={{ base: 'md', md: 100 }}>
<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
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Tips'
radius="lg"
placeholder="Cari Tips"
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
w={'100%'}
/>
</GridCol>
</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).
</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.
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
<SimpleGrid
pb={10}
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center p={10}>
<Image src={v.image?.link} radius={10} loading="lazy"
alt='' />
</Center>
<Box px={'xl'}>
<Box pb={20}>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.judul}
</Text>
<Box>
<Text pb={10} fz={"md"} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Box>
pb="10"
cols={{ base: 1, md: 3 }}
>
{data.map((v, k) => (
<Paper radius={10} key={k} bg={colors['white-trans-1']}>
<Stack gap="xs">
<Center p="10">
<Image
src={v.image?.link}
radius={10}
loading="lazy"
alt=""
/>
</Center>
<Box px="xl">
<Box pb="20">
<Title
order={3}
c={colors['blue-button']}
style={{ lineHeight: '1.3' }}
>
{v.judul}
</Title>
<Box>
<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>
</Stack>
</Paper>
)
})}
</Box>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
@@ -108,4 +137,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,7 +2,7 @@
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
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 { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
@@ -37,9 +37,9 @@ function Page() {
<Stack gap="lg">
<Paper radius="xl" shadow="md" withBorder>
<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'}
</Text>
</Title>
</Box>
<Box p="lg">
@@ -64,7 +64,7 @@ function Page() {
<Stack gap="lg">
<Group gap="xs">
<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', {
year: 'numeric',
month: 'long',
@@ -74,48 +74,47 @@ function Page() {
</Group>
<Stack gap="lg">
<Box>
<Text fz="h4" fw="bold">Pendahuluan</Text>
<Title order={2} fw="bold">Pendahuluan</Title>
<Divider my="xs" />
<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>
<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" />
<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>
<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" />
<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>
<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" />
<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>
<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" />
<Box pb="md">
<Table highlightOnHover withTableBorder withColumnBorders striped>
<TableThead>
<TableTr>
<TableTh fz="sm" fw="bold">Mitos</TableTh>
<TableTh fz="sm" fw="bold">Fakta</TableTh>
<TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Mitos</TableTh>
<TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Fakta</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -123,12 +122,12 @@ function Page() {
<TableTr>
<TableTd>
<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>
</TableTd>
<TableTd>
<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>
</TableTd>
</TableTr>
@@ -143,34 +142,35 @@ function Page() {
</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" />
<Flex justify={'flex-start'} gap={"xs"} align={"center"} mb="xs">
<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>
<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>
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
<Group gap="xs" mb="sm">
<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>
<Stack gap={4}>
<Text fz="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="sm" c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
</Stack>
</Paper>
</Box>
<Box>
<Text fz="h4" fw="bold">Referensi</Text>
<Title order={2} fw="bold">Referensi</Title>
<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>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>
@@ -186,4 +186,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
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 { IconCalendar, IconChevronRight } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -17,9 +17,8 @@ function ArtikelKesehatanPage() {
if (!state.findMany.data) {
return (
<Box py="xl" ta="center">
<Loader size="lg" color={colors['blue-button']} />
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
<Box py="lg">
<Skeleton h={500} radius="lg" />
</Box>
)
}
@@ -28,13 +27,13 @@ function ArtikelKesehatanPage() {
<Box>
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
<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
</Text>
</Title>
<Divider size="sm" color={colors['blue-button']} />
{state.findMany.data.length === 0 ? (
<Box py="xl" ta="center">
<Text fz="lg" c="dimmed">
<Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
Belum ada artikel kesehatan yang tersedia
</Text>
</Box>
@@ -51,17 +50,26 @@ function ArtikelKesehatanPage() {
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
>
<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>
<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">
<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
</Text>
</Group>
<Text fz="md" lineClamp={3}>
<Text fz={{ base: 'sm', sm: 'md' }} lh={{ base: 'sm', sm: 'md' }} lineClamp={3}>
{item.content}
</Text>
<Group justify="flex-start">
@@ -85,4 +93,4 @@ function ArtikelKesehatanPage() {
);
}
export default ArtikelKesehatanPage;
export default ArtikelKesehatanPage;

View File

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

View File

@@ -1,7 +1,7 @@
'use client'
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
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 { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -17,12 +17,8 @@ function FasilitasKesehatanPage() {
if (!state.findMany.data) {
return (
<Box py="xl" px="md">
<Stack gap="md">
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
</Stack>
<Box py="lg">
<Skeleton h={500} radius="lg" />
</Box>
);
}
@@ -31,14 +27,24 @@ function FasilitasKesehatanPage() {
<Box>
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%">
<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
</Text>
</Title>
<Divider size="sm" color={colors['blue-button']} />
<Stack gap="lg">
{state.findMany.data.length === 0 ? (
<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
</Text>
</Box>
@@ -65,22 +71,36 @@ function FasilitasKesehatanPage() {
>
<Stack gap="sm">
<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}
</Text>
<Badge color="blue" radius="sm" variant="light" fz="xs">
</Title>
<Badge color="blue" radius="sm" variant="light" size="xs">
Aktif
</Badge>
</Group>
<Group gap="xs">
<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}
</Text>
</Group>
<Group gap="xs">
<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}
</Text>
</Group>
@@ -110,4 +130,4 @@ function FasilitasKesehatanPage() {
);
}
export default FasilitasKesehatanPage;
export default FasilitasKesehatanPage;

View File

@@ -11,7 +11,8 @@ import {
Paper,
Skeleton,
Stack,
Text
Text,
Title
} from '@mantine/core';
import { useDisclosure, useShallowEffect } from '@mantine/hooks';
import { IconMail, IconPhone, IconUser } from '@tabler/icons-react';
@@ -51,55 +52,97 @@ function Page() {
style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
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
</Text>
</Title>
</Box>
<Box p="lg">
<Stack gap="xl">
<Stack gap="sm">
<Text fz="lg" fw="bold">Informasi Kegiatan</Text>
<Title order={2} fw="bold">Informasi Kegiatan</Title>
<Divider />
<Text fz="md" fw="bold">Nama Kegiatan: <Text span>{state.findUnique.data.informasijadwalkegiatan.name}</Text></Text>
<Text fz="md" fw="bold">Tanggal: <Text span>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text></Text>
<Text fz="md" fw="bold">Waktu: <Text span>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text></Text>
<Text fz="md" fw="bold">Lokasi: <Text span>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text></Text>
<Text fw="bold">
Nama Kegiatan:&nbsp;
<Text span fw="normal">
{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 gap="sm">
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
<Title order={2} fw="bold">Deskripsi Kegiatan</Title>
<Divider />
<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>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
<Title order={2} fw="bold">Layanan yang Tersedia</Title>
<Divider />
<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>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
<Title order={2} fw="bold">Syarat & Ketentuan</Title>
<Divider />
<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>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
<Title order={2} fw="bold">Dokumen yang Perlu Dibawa</Title>
<Divider />
<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>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Pendaftaran Kegiatan</Text>
<Title order={2} fw="bold">Pendaftaran Kegiatan</Title>
<Divider />
<Group>
<Button onClick={open}>Buat Pendaftaran</Button>
@@ -112,18 +155,21 @@ function Page() {
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
<Stack gap="xs">
<Text fz="lg" c={colors['white-1']} fw="bold">Informasi Kontak</Text>
<Group gap="xs">
<Title order={3} c={colors['white-1']} fw="bold">Informasi Kontak</Title>
<Group gap="xs" justify="flex-start">
<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 gap="xs">
<IconPhone size={18} color="white" />
<Text fz="md" c={colors['white-1']}>081234567890</Text>
<Text c={colors['white-1']}>081234567890</Text>
</Group>
<Group gap="xs">
<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>
</Stack>
</Paper>
@@ -136,4 +182,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,15 +2,14 @@
'use client'
import pendaftaranJadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/pendafataranJadwalKegiatan';
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 { useProxy } from 'valtio/utils';
function CreatePendaftaran() {
const stateCreate = useProxy(pendaftaranJadwalKegiatanState);
useEffect(() => {
useEffect(() => {
stateCreate.findMany.load();
}, []);
@@ -32,15 +31,19 @@ useEffect(() => {
return (
<Stack gap="sm">
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text>
<Title order={2} ta="left">Formulir Pendaftaran</Title>
<Divider />
<Stack gap="md">
<TextInput
label="Nama Balita"
placeholder="Masukkan nama balita"
size="md"
value={stateCreate.create.form.name}
onChange={(e) => stateCreate.create.form.name = e.target.value}
label="Nama Balita"
placeholder="Masukkan nama balita"
size="md"
value={stateCreate.create.form.name}
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
type='date'
@@ -50,41 +53,63 @@ useEffect(() => {
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
value={stateCreate.create.form.tanggal}
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
label="Nama Orang Tua / Wali"
placeholder="Masukkan nama orang tua / wali"
size="md"
value={stateCreate.create.form.namaOrangtua}
onChange={(e) => stateCreate.create.form.namaOrangtua = e.target.value}
label="Nama Orang Tua / Wali"
placeholder="Masukkan nama orang tua / wali"
size="md"
value={stateCreate.create.form.namaOrangtua}
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
label="Nomor Telepon"
placeholder="Masukkan nomor telepon"
size="md"
value={stateCreate.create.form.nomor}
onChange={(e) => stateCreate.create.form.nomor = e.target.value}
label="Nomor Telepon"
placeholder="Masukkan nomor telepon"
size="md"
value={stateCreate.create.form.nomor}
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
label="Alamat"
placeholder="Masukkan alamat lengkap"
size="md"
value={stateCreate.create.form.alamat}
onChange={(e) => stateCreate.create.form.alamat = e.target.value}
label="Alamat"
placeholder="Masukkan alamat lengkap"
size="md"
value={stateCreate.create.form.alamat}
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
label="Catatan Khusus (Opsional)"
placeholder="Masukkan catatan jika ada"
size="md"
value={stateCreate.create.form.catatan}
onChange={(e) => stateCreate.create.form.catatan = e.target.value}
label="Catatan Khusus (Opsional)"
placeholder="Masukkan catatan jika ada"
size="md"
value={stateCreate.create.form.catatan}
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}>
Daftar Sekarang
<Text fz={{ base: 'sm', md: 'md' }} fw={600} c="white">
Daftar Sekarang
</Text>
</Button>
</Stack>
</Stack>
);
}
export default CreatePendaftaran;
export default CreatePendaftaran;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ import {
Stack,
Text,
TextInput,
Title,
Tooltip
} from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
@@ -52,10 +53,21 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} gutter="lg">
<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
</Text>
<Text fz="md" mt={4}>
</Title>
<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
</Text>
</GridCol>
@@ -79,10 +91,20 @@ function Page() {
<Center mih={300}>
<Stack align="center" gap="xs">
<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
</Text>
<Text fz="sm" c="dimmed">
</Title>
<Text
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
c="dark.7"
>
Coba kata kunci lain untuk pencarian
</Text>
</Stack>
@@ -102,8 +124,8 @@ function Page() {
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between', // ✅ biar button selalu di bawah
height: '100%', // ✅ bikin tinggi seragam
justifyContent: 'space-between',
height: '100%',
}}
>
<Stack align="center" gap="sm" style={{ flexGrow: 1 }}>
@@ -131,27 +153,24 @@ function Page() {
/>
</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}
</Text>
<Text
fz="sm"
fz={{ base: 'xs', md: 'sm' }}
ta="center"
lineClamp={3}
lh={1.6}
lh={{ base: 1.5, md: 1.6 }}
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 }}
/>
</Text>
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Stack>
{/* ✅ Tombol selalu di bagian bawah card */}
<Group mt="md" justify='center'>
<Button
bg={colors['blue-button']}
@@ -161,8 +180,6 @@ function Page() {
</Button>
</Group>
</Paper>
))}
</SimpleGrid>
)}
@@ -186,4 +203,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,12 +59,16 @@ function Page() {
left={20}
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}
</Text>
</Title>
<Group gap={6}>
<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}
</Text>
</Group>
@@ -75,37 +79,45 @@ function Page() {
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="lg">
<Box>
<Title order={3} mb={10}>Informasi Kontak</Title>
<Title order={2} mb="md">Informasi Kontak</Title>
<Stack gap={8}>
<Group gap={8}>
<IconPhone size={18} />
<Text fz="md">{data.kontak.kontakPuskesmas || '-'}</Text>
<Text fz={{ base: 'sm', md: 'md' }}>
{data.kontak.kontakPuskesmas || '-'}
</Text>
</Group>
<Group gap={8}>
<IconMail size={18} />
<Text fz="md">{data.kontak.email || '-'}</Text>
<Text fz={{ base: 'sm', md: 'md' }}>
{data.kontak.email || '-'}
</Text>
</Group>
</Stack>
</Box>
<Divider />
<Stack gap={"xs"}>
<Title order={3} mb={10}>Jam Operasional</Title>
<Text fw="bold" fz="md">Senin - Jumat</Text>
<Stack gap="xs">
<Title order={2} mb="md">Jam Operasional</Title>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Senin - Jumat</Text>
<Group gap={8}>
<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>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip>
</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}>
<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>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip>
</Group>
</Stack>
@@ -114,20 +126,24 @@ function Page() {
<GridCol span={{ base: 12, md: 6 }}>
<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">
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poliklinik Umum</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Poliklinik Umum</Text>
<Text fz={{ base: 'h1', md: 'h1' }} fw="bold" c={colors['blue-button']} lh={1}>
26
</Text>
</Stack>
</Paper>
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poli Gigi</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Poli Gigi</Text>
<Text fz={{ base: 'h1', md: 'h1' }} fw="bold" c={colors['blue-button']} lh={1}>
26
</Text>
</Stack>
</Paper>
</SimpleGrid>
@@ -141,4 +157,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
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 { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react';
import { useState } from 'react';
@@ -42,10 +42,10 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} mb="md">
<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
</Text>
<Text fz="md">
</Title>
<Text fz={{ base: "sm", md: "md" }} ta="start">
Temukan informasi lengkap mengenai layanan, kontak, dan lokasi Puskesmas Darmasaba
</Text>
</GridCol>
@@ -65,8 +65,8 @@ function Page() {
{data.length === 0 ? (
<Center py="xl">
<Stack align="center" gap="xs">
<Text fz="lg" fw={500} c="dimmed">Tidak ada data ditemukan</Text>
<Text fz="sm" c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text>
<Title order={2} fw={500} c="dimmed">Tidak ada data ditemukan</Title>
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text>
</Stack>
</Center>
) : (
@@ -92,29 +92,29 @@ function Page() {
loading="lazy"
/>
<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>
</Group>
<Stack gap={6}>
<Group gap="xs" align="flex-start" wrap="nowrap">
<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 gap="xs" align="flex-start" wrap="nowrap">
<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 gap="xs" align="flex-start" wrap="nowrap">
<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>
</Stack>
<Anchor
href={`/darmasaba/kesehatan/puskesmas/${v.id}`}
fz="sm"
fz={{ base: "sm", md: "md" }}
fw={500}
c={colors['blue-button']}
>
@@ -143,4 +143,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
'use client';
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
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 { IconArrowRight, IconAward } from "@tabler/icons-react";
import { useTransitionRouter } from "next-view-transitions";
@@ -20,11 +20,21 @@ export default function Page() {
<Stack align="center" gap="sm">
<Group gap="xs">
<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
</Text>
</Title>
</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.
</Text>
</Stack>
@@ -61,10 +71,8 @@ function Slider() {
const data = state.findMany.data || [];
const loading = state.findMany.loading;
// Triple data untuk infinite loop (desktop only)
const slidesData = mobile ? data : [...data, ...data, ...data];
// Auto-scroll animation untuk desktop
useEffect(() => {
if (loading || !containerRef.current || data.length === 0 || mobile) return;
@@ -72,7 +80,6 @@ function Slider() {
const slideWidth = container.scrollWidth / slidesData.length;
const originalLength = data.length;
// Start dari middle set
scrollPosRef.current = slideWidth * originalLength;
container.scrollLeft = scrollPosRef.current;
@@ -88,7 +95,6 @@ function Slider() {
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
scrollPosRef.current += speed;
// Reset untuk infinite loop
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
scrollPosRef.current -= slideWidth * originalLength;
}
@@ -100,7 +106,6 @@ function Slider() {
} else {
scrollPosRef.current = container.scrollLeft;
// Momentum untuk drag release
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
scrollPosRef.current += velocityRef.current;
velocityRef.current *= VELOCITY_DECAY;
@@ -185,7 +190,7 @@ function Slider() {
return (
<Stack align="center" py="xl">
<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
</Text>
</Stack>
@@ -213,7 +218,6 @@ function Slider() {
msOverflowStyle: "none",
}}
>
{/* Blur edges - hanya untuk desktop */}
{!mobile && (
<>
<Box
@@ -291,8 +295,8 @@ function Slider() {
style={{ borderRadius: 16 }}
/>
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
<Text
fz={{ base: "lg", sm: "xl", md: "1.5rem" }}
<Title
order={3}
fw={700}
ta="center"
c="white"
@@ -300,7 +304,7 @@ function Slider() {
style={{ textShadow: "0 2px 8px rgba(0,0,0,0.8)" }}
>
{item.name}
</Text>
</Title>
<Group justify="center">
<Button
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>
<ActionIcon
onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")
next.push("/admin/landing-page/profil/program-inovasi")
}}
color={colors["blue-button"]}
radius="xl"

View File

@@ -96,7 +96,7 @@ function Potensi() {
</Stack>
) : (
/* 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) => (
<motion.div
key={k}

View File

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