Fix Konsisten teks di tampilan mobile dan desktop
Fix QC Kak Inno tgl 10 Des Fix QC Kak Ayu tgl 10 Des
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
@@ -51,10 +51,14 @@ export default function Content({ kategori }: { kategori: string }) {
|
|||||||
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
|
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
|
||||||
{/* === Berita Utama === */}
|
{/* === Berita Utama === */}
|
||||||
{featuredState.loading ? (
|
{featuredState.loading ? (
|
||||||
<Center><Skeleton h={400} /></Center>
|
<Center>
|
||||||
|
<Skeleton h={400} />
|
||||||
|
</Center>
|
||||||
) : featured ? (
|
) : featured ? (
|
||||||
<Box mb={50}>
|
<Box mb={50}>
|
||||||
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
|
<Title order={2} mb="md">
|
||||||
|
Berita Utama
|
||||||
|
</Title>
|
||||||
<Paper shadow="md" radius="md" withBorder>
|
<Paper shadow="md" radius="md" withBorder>
|
||||||
<Grid gutter={0}>
|
<Grid gutter={0}>
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
@@ -74,13 +78,29 @@ export default function Content({ kategori }: { kategori: string }) {
|
|||||||
<Badge color="blue" variant="light" mb="md">
|
<Badge color="blue" variant="light" mb="md">
|
||||||
{featured.kategoriBerita?.name || kategori}
|
{featured.kategoriBerita?.name || kategori}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Title order={2} mb="md">{featured.judul}</Title>
|
<Title order={3} mb="md">
|
||||||
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featured.deskripsi }} />
|
{featured.judul}
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={3}
|
||||||
|
mb="md"
|
||||||
|
style={{ lineHeight: 1.6 }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: featured.deskripsi }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Group justify="apart" mt="auto">
|
<Group justify="apart" mt="auto">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconCalendar size={18} />
|
<IconCalendar size={18} />
|
||||||
<Text size="sm">
|
<Text
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
c="dimmed"
|
||||||
|
lh={1.5}
|
||||||
|
style={{
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
lineHeight: '1.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{new Date(featured.createdAt).toLocaleDateString('id-ID', {
|
{new Date(featured.createdAt).toLocaleDateString('id-ID', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
@@ -91,7 +111,9 @@ export default function Content({ kategori }: { kategori: string }) {
|
|||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
rightSection={<IconArrowRight size={16} />}
|
rightSection={<IconArrowRight size={16} />}
|
||||||
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)}
|
onClick={() =>
|
||||||
|
router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Baca Selengkapnya
|
Baca Selengkapnya
|
||||||
</Button>
|
</Button>
|
||||||
@@ -105,19 +127,29 @@ export default function Content({ kategori }: { kategori: string }) {
|
|||||||
|
|
||||||
{/* === Daftar Berita === */}
|
{/* === Daftar Berita === */}
|
||||||
<Box mt={50}>
|
<Box mt={50}>
|
||||||
<Title order={2} mb="md">Daftar Berita</Title>
|
<Title order={2} mb="md">
|
||||||
|
Daftar Berita
|
||||||
|
</Title>
|
||||||
<Divider mb="xl" />
|
<Divider mb="xl" />
|
||||||
|
|
||||||
{state.findMany.loading ? (
|
{state.findMany.loading ? (
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
|
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
|
||||||
{Array(3).fill(0).map((_, i) => (
|
{Array(3)
|
||||||
<Skeleton key={i} h={300} radius="md" />
|
.fill(0)
|
||||||
))}
|
.map((_, i) => (
|
||||||
|
<Skeleton key={i} h={300} radius="md" />
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
) : paginatedNews.length === 0 ? (
|
) : paginatedNews.length === 0 ? (
|
||||||
<Text c="dimmed" ta="center">Belum ada berita di kategori "{kategori}".</Text>
|
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||||
|
Belum ada berita di kategori "{kategori}".
|
||||||
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
<SimpleGrid
|
||||||
|
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||||
|
spacing="xl"
|
||||||
|
verticalSpacing="xl"
|
||||||
|
>
|
||||||
{paginatedNews.map((item) => (
|
{paginatedNews.map((item) => (
|
||||||
<Card
|
<Card
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -125,19 +157,51 @@ export default function Content({ kategori }: { kategori: string }) {
|
|||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)}
|
onClick={() =>
|
||||||
|
router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)
|
||||||
|
}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
>
|
>
|
||||||
<Card.Section>
|
<Card.Section>
|
||||||
<Image src={item.image?.link} height={200} alt={item.judul} fit="cover" loading="lazy"/>
|
<Image
|
||||||
|
src={item.image?.link}
|
||||||
|
height={200}
|
||||||
|
alt={item.judul}
|
||||||
|
fit="cover"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Badge color="blue" variant="light" mt="md">
|
<Badge color="blue" variant="light" mt="md">
|
||||||
{item.kategoriBerita?.name || kategori}
|
{item.kategoriBerita?.name || kategori}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
<Title
|
||||||
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
order={4}
|
||||||
|
mt="sm"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
style={{ lineHeight: 1.4 }}
|
||||||
|
lineClamp={2}
|
||||||
|
>
|
||||||
|
{item.judul}
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={3}
|
||||||
|
style={{
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
lineHeight: 1.5,
|
||||||
|
}}
|
||||||
|
mt="xs"
|
||||||
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
|
/>
|
||||||
<Group justify="apart" mt="md" gap="xs">
|
<Group justify="apart" mt="md" gap="xs">
|
||||||
<Text size="xs" c="dimmed">
|
<Text
|
||||||
|
fz={{ base: 'xs', md: 'xs' }}
|
||||||
|
c="dimmed"
|
||||||
|
lh={1.4}
|
||||||
|
style={{ fontSize: '0.75rem', lineHeight: '1.125rem' }}
|
||||||
|
>
|
||||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
|||||||
@@ -3,18 +3,16 @@
|
|||||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||||
import NewsReader from '@/app/darmasaba/_com/NewsReader';
|
import NewsReader from '@/app/darmasaba/_com/NewsReader';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||||
const state = useProxy(stateDashboardBerita.berita)
|
const state = useProxy(stateDashboardBerita.berita);
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -27,9 +25,9 @@ function Page() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [id])
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -47,41 +45,49 @@ function Page() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} pb={"xl"} gap={"xs"} px={{ base: "md", md: 0 }}>
|
<Stack pos="relative" bg={colors.Bg} pb="xl" gap="xs" px={{ base: 'md', md: 0 }}>
|
||||||
<Group px={{ base: "md", md: 100 }}>
|
<Group px={{ base: 'md', md: 100 }}>
|
||||||
<NewsReader />
|
<NewsReader />
|
||||||
</Group>
|
</Group>
|
||||||
<Container w={{ base: "100%", md: "50%" }} >
|
<Container w={{ base: '100%', md: '50%' }}>
|
||||||
<Box pb={20}>
|
<Box pb={20}>
|
||||||
<Text id='news-title' ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
<Title
|
||||||
{state.findUnique.data?.judul}
|
id="news-title"
|
||||||
</Text>
|
order={1}
|
||||||
<Text
|
ta="center"
|
||||||
ta={"center"}
|
c={colors['blue-button']}
|
||||||
fw={"bold"}
|
fw="bold"
|
||||||
fz={"1.5rem"}
|
lh={{ base: 1.2, md: 1.25 }}
|
||||||
|
>
|
||||||
|
{state.findUnique.data.judul}
|
||||||
|
</Title>
|
||||||
|
<Title
|
||||||
|
order={2}
|
||||||
|
ta="center"
|
||||||
|
fw="bold"
|
||||||
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
|
lh={{ base: 1.3, md: 1.35 }}
|
||||||
>
|
>
|
||||||
Informasi dan Pelayanan Administrasi Digital
|
Informasi dan Pelayanan Administrasi Digital
|
||||||
</Text>
|
</Title>
|
||||||
</Box>
|
</Box>
|
||||||
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy" />
|
<Image src={state.findUnique.data.image?.link || ''} alt="" w="100%" loading="lazy" />
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="xs">
|
||||||
<Text
|
<Text
|
||||||
id='news-content'
|
id="news-content"
|
||||||
py={20}
|
py={20}
|
||||||
fz={{ base: "sm", md: "lg" }}
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif
|
lh={{ base: 1.6, md: 1.8 }}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
style={{
|
style={{
|
||||||
wordBreak: "break-word",
|
wordBreak: 'break-word',
|
||||||
whiteSpace: "normal",
|
whiteSpace: 'normal',
|
||||||
}}
|
}}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: state.findUnique.data?.content || "",
|
__html: state.findUnique.data.content || '',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -16,35 +16,30 @@ function Semua() {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useTransitionRouter();
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
// Ambil parameter langsung dari URL
|
|
||||||
const search = searchParams.get('search') || '';
|
const search = searchParams.get('search') || '';
|
||||||
const page = parseInt(searchParams.get('page') || '1');
|
const page = parseInt(searchParams.get('page') || '1');
|
||||||
|
|
||||||
// Gunakan proxy untuk state global
|
|
||||||
const state = useProxy(stateDashboardBerita.berita);
|
const state = useProxy(stateDashboardBerita.berita);
|
||||||
const featured = useProxy(stateDashboardBerita.berita.findFirst);
|
const featured = useProxy(stateDashboardBerita.berita.findFirst);
|
||||||
const loadingGrid = state.findMany.loading;
|
const loadingGrid = state.findMany.loading;
|
||||||
const loadingFeatured = featured.loading;
|
const loadingFeatured = featured.loading;
|
||||||
|
|
||||||
// Load berita utama sekali saja
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!featured.data && !loadingFeatured) {
|
if (!featured.data && !loadingFeatured) {
|
||||||
stateDashboardBerita.berita.findFirst.load();
|
stateDashboardBerita.berita.findFirst.load();
|
||||||
}
|
}
|
||||||
}, [featured.data, loadingFeatured]);
|
}, [featured.data, loadingFeatured]);
|
||||||
|
|
||||||
// Load berita terbaru tiap page / search berubah
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const limit = 3;
|
const limit = 3;
|
||||||
state.findMany.load(page, limit, search);
|
state.findMany.load(page, limit, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
// Handler pagination → langsung update URL
|
|
||||||
const handlePageChange = (newPage: number) => {
|
const handlePageChange = (newPage: number) => {
|
||||||
const url = new URLSearchParams(searchParams.toString());
|
const url = new URLSearchParams(searchParams.toString());
|
||||||
if (search) url.set('search', search);
|
if (search) url.set('search', search);
|
||||||
if (newPage > 1) url.set('page', newPage.toString());
|
if (newPage > 1) url.set('page', newPage.toString());
|
||||||
else url.delete('page'); // biar page=1 ga muncul di URL
|
else url.delete('page');
|
||||||
|
|
||||||
router.replace(`?${url.toString()}`);
|
router.replace(`?${url.toString()}`);
|
||||||
};
|
};
|
||||||
@@ -61,7 +56,7 @@ function Semua() {
|
|||||||
<Center><Skeleton h={400} /></Center>
|
<Center><Skeleton h={400} /></Center>
|
||||||
) : featuredData ? (
|
) : featuredData ? (
|
||||||
<Box mb={50}>
|
<Box mb={50}>
|
||||||
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
|
<Title order={2} mb="md">Berita Utama</Title>
|
||||||
<Paper shadow="md" radius="md" withBorder>
|
<Paper shadow="md" radius="md" withBorder>
|
||||||
<Grid gutter={0}>
|
<Grid gutter={0}>
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
@@ -81,13 +76,24 @@ function Semua() {
|
|||||||
<Badge color="blue" variant="light" mb="md">
|
<Badge color="blue" variant="light" mb="md">
|
||||||
{featuredData.kategoriBerita?.name || 'Berita'}
|
{featuredData.kategoriBerita?.name || 'Berita'}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Title order={2} mb="md">{featuredData.judul}</Title>
|
<Title order={3} mb="md">{featuredData.judul}</Title>
|
||||||
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }} />
|
<Text
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={3}
|
||||||
|
mb="md"
|
||||||
|
dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }}
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={{ base: 1.5, md: 1.6 }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Group justify="apart" mt="auto">
|
<Group justify="apart" mt="auto">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconCalendar size={18} />
|
<IconCalendar size={18} />
|
||||||
<Text size="sm">
|
<Text
|
||||||
|
c="dimmed"
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
lh={{ base: 1.4, md: 1.5 }}
|
||||||
|
>
|
||||||
{new Date(featuredData.createdAt).toLocaleDateString('id-ID', {
|
{new Date(featuredData.createdAt).toLocaleDateString('id-ID', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
@@ -124,7 +130,9 @@ function Semua() {
|
|||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
) : paginatedNews.length === 0 ? (
|
) : paginatedNews.length === 0 ? (
|
||||||
<Text c="dimmed" ta="center">Tidak ada berita ditemukan.</Text>
|
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={{ base: 1.5, md: 1.6 }}>
|
||||||
|
Tidak ada berita ditemukan.
|
||||||
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
||||||
{paginatedNews.map((item) => (
|
{paginatedNews.map((item) => (
|
||||||
@@ -143,11 +151,24 @@ function Semua() {
|
|||||||
{item.kategoriBerita?.name || 'Berita'}
|
{item.kategoriBerita?.name || 'Berita'}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
<Title order={4} mt="sm" lineClamp={2}>
|
||||||
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
{item.judul}
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={3}
|
||||||
|
mt="xs"
|
||||||
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
lh={{ base: 1.5, md: 1.6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Flex align="center" justify="apart" mt="md" gap="xs">
|
<Flex align="center" justify="apart" mt="md" gap="xs">
|
||||||
<Text size="xs" c="dimmed">
|
<Text
|
||||||
|
c="dimmed"
|
||||||
|
fz={{ base: 'xs', md: 'xs' }}
|
||||||
|
lh={{ base: 1.4, md: 1.4 }}
|
||||||
|
>
|
||||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
|||||||
@@ -17,17 +17,11 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconPhoto } from '@tabler/icons-react';
|
import { IconPhoto } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
// Komponen kartu foto
|
// Komponen kartu foto
|
||||||
function FotoCard({ item }: { item: any }) {
|
function FotoCard({ item }: { item: any }) {
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
router.push(`/darmasaba/galeri/foto/${item.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid.Col span={{ base: 12, xs: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, xs: 6, md: 4 }}>
|
||||||
@@ -35,19 +29,19 @@ function FotoCard({ item }: { item: any }) {
|
|||||||
shadow="sm"
|
shadow="sm"
|
||||||
radius="md"
|
radius="md"
|
||||||
p={0}
|
p={0}
|
||||||
onClick={handleClick}
|
style={{ transition: 'transform 0.2s' }}
|
||||||
style={{ cursor: 'pointer', transition: 'transform 0.2s' }}
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
|
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||||
|
|
||||||
>
|
>
|
||||||
{item.imageGalleryFoto?.link ? (
|
{item.imageGalleryFoto?.link ? (
|
||||||
<Box
|
<Box
|
||||||
pos="relative"
|
pos="relative"
|
||||||
style={{
|
style={{
|
||||||
paddingBottom: '100%', // ✅ Ubah ke 1:1 (square) — atau sesuaikan
|
paddingBottom: '100%',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
borderRadius: '4px 4px 0 0',
|
borderRadius: '4px 4px 0 0',
|
||||||
backgroundColor: '#f9f9f9', // ✅ background netral
|
backgroundColor: '#f9f9f9',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@@ -61,8 +55,8 @@ function FotoCard({ item }: { item: any }) {
|
|||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
objectFit: 'contain', // ✅ Tampilkan utuh, jangan crop
|
objectFit: 'contain',
|
||||||
objectPosition: 'center', // rata tengah
|
objectPosition: 'center',
|
||||||
}}
|
}}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
@@ -74,13 +68,23 @@ function FotoCard({ item }: { item: any }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Stack p="md" gap={4}>
|
<Stack p="md" gap={4}>
|
||||||
<Text fw={600} lineClamp={1}>
|
<Text fw={600} lineClamp={1} fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
|
||||||
{item.name || 'Tanpa Judul'}
|
{item.name || 'Tanpa Judul'}
|
||||||
</Text>
|
</Text>
|
||||||
{item.deskripsi && (
|
{item.deskripsi && (
|
||||||
<Text fz="sm" c="dimmed" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={2}
|
||||||
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
|
lh={{ base: '1.4', md: '1.5' }}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text fz="xs" c="dimmed">
|
<Text
|
||||||
|
fz={{ base: 11, md: 'xs' }}
|
||||||
|
c="dimmed"
|
||||||
|
lh={{ base: '1.3', md: '1.4' }}
|
||||||
|
>
|
||||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
@@ -99,7 +103,7 @@ export default function GaleriFotoUser() {
|
|||||||
return (
|
return (
|
||||||
<Box py="xl" px={{ base: 'md', md: 'lg' }}>
|
<Box py="xl" px={{ base: 'md', md: 'lg' }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Title order={2} c={colors['blue-button']} mb="lg">
|
<Title order={2} c={colors['blue-button']} mb="lg" ta="center">
|
||||||
Galeri Foto Desa Darmasaba
|
Galeri Foto Desa Darmasaba
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
@@ -115,7 +119,7 @@ function FotoList({ search }: { search: string }) {
|
|||||||
const { data, page, totalPages, loading, load } = FotoState.findMany;
|
const { data, page, totalPages, loading, load } = FotoState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, search); // ✅ 9 item per halaman
|
load(page, 3, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -135,7 +139,9 @@ function FotoList({ search }: { search: string }) {
|
|||||||
<Center py="xl">
|
<Center py="xl">
|
||||||
<Stack align="center" c="dimmed">
|
<Stack align="center" c="dimmed">
|
||||||
<IconPhoto size={48} />
|
<IconPhoto size={48} />
|
||||||
<Text>Tidak ada foto ditemukan</Text>
|
<Text fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
|
||||||
|
Tidak ada foto ditemukan
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@@ -150,19 +156,18 @@ function FotoList({ search }: { search: string }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
<Center>
|
<Pagination
|
||||||
<Pagination
|
value={page}
|
||||||
value={page}
|
onChange={(newPage) => {
|
||||||
onChange={(newPage) => {
|
load(newPage, 3, search);
|
||||||
load(newPage, 3, search);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
}}
|
||||||
}}
|
total={totalPages}
|
||||||
total={totalPages}
|
color="blue"
|
||||||
color="blue"
|
radius="md"
|
||||||
radius="md"
|
/>
|
||||||
/>
|
</Center>
|
||||||
</Center>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text
|
Text,
|
||||||
|
Title
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
@@ -19,15 +20,13 @@ import { useSnapshot } from 'valtio';
|
|||||||
|
|
||||||
export default function VideoContent() {
|
export default function VideoContent() {
|
||||||
const videoState = useSnapshot(stateGallery.video);
|
const videoState = useSnapshot(stateGallery.video);
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter();
|
||||||
const { data, page, totalPages, loading } = videoState.findMany;
|
const { data, page, totalPages, loading } = videoState.findMany;
|
||||||
|
|
||||||
// Handle search and pagination changes
|
|
||||||
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
||||||
stateGallery.video.findMany.load(pageNum, 3, searchTerm.trim());
|
stateGallery.video.findMany.load(pageNum, 3, searchTerm.trim());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Initial load and URL change handler
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleRouteChange = () => {
|
const handleRouteChange = () => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@@ -57,13 +56,14 @@ export default function VideoContent() {
|
|||||||
loadData(newPage, search);
|
loadData(newPage, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const dataVideo = data || [];
|
const dataVideo = data || [];
|
||||||
|
|
||||||
if (loading && !data) {
|
if (loading && !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Text>Memuat Video...</Text>
|
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed" ta="center">
|
||||||
|
Memuat Video...
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -78,55 +78,71 @@ export default function VideoContent() {
|
|||||||
p="md"
|
p="md"
|
||||||
radius={26}
|
radius={26}
|
||||||
bg={colors['white-trans-1']}
|
bg={colors['white-trans-1']}
|
||||||
w={{ base: '100%', md: '100%' }}
|
w="100%"
|
||||||
>
|
>
|
||||||
<Box>
|
<Center>
|
||||||
<Center>
|
<Box
|
||||||
<Box
|
component="iframe"
|
||||||
component="iframe"
|
src={convertToEmbedUrl(v.linkVideo)}
|
||||||
src={convertToEmbedUrl(v.linkVideo)}
|
width="100%"
|
||||||
width="100%"
|
height={300}
|
||||||
height={300}
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
allowFullScreen
|
||||||
allowFullScreen
|
style={{ borderRadius: 8 }}
|
||||||
style={{ borderRadius: 8 }}
|
/>
|
||||||
/>
|
</Center>
|
||||||
</Center>
|
|
||||||
</Box>
|
<Stack gap="sm" py={10}>
|
||||||
<Box>
|
{/* Tanggal: Caption */}
|
||||||
<Stack gap="sm" py={10}>
|
<Text
|
||||||
<Text fz="sm" c="dimmed">
|
fz={{ base: 12, md: 14 }}
|
||||||
{new Date(v.createdAt).toLocaleDateString('id-ID', {
|
c="dimmed"
|
||||||
day: 'numeric',
|
ta="left"
|
||||||
month: 'long',
|
>
|
||||||
year: 'numeric',
|
{new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||||
})}
|
day: 'numeric',
|
||||||
</Text>
|
month: 'long',
|
||||||
<Text fw="bold" fz="sm" lineClamp={1}>
|
year: 'numeric',
|
||||||
{v.name}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
|
||||||
ta="justify"
|
{/* Judul Video: Subsection (H3) */}
|
||||||
fz="sm"
|
<Title
|
||||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
order={3}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
c="dark"
|
||||||
lineClamp={3}
|
ta="left"
|
||||||
truncate="end"
|
lh={1.3}
|
||||||
/>
|
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
||||||
<Group justify={"right"}>
|
>
|
||||||
<Button
|
{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}`)}
|
onClick={() => router.push(`/darmasaba/desa/galery/video/${v.id}`)}
|
||||||
bg={colors['blue-button']}
|
bg={colors['blue-button']}
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -140,7 +156,6 @@ export default function VideoContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Fix: convert YouTube URL ke embed
|
|
||||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||||
try {
|
try {
|
||||||
const url = new URL(youtubeUrl);
|
const url = new URL(youtubeUrl);
|
||||||
|
|||||||
@@ -12,16 +12,17 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconInfoCircle, IconVideo } from '@tabler/icons-react';
|
import { IconArrowBack, IconInfoCircle, IconVideo } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; // pastikan state bisa dipakai di publik
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
import BackButton from '../../../layanan/_com/BackButto';
|
import BackButton from '../../../layanan/_com/BackButto';
|
||||||
|
|
||||||
// Fungsi helper: aman dan tanpa spasi
|
|
||||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||||
try {
|
try {
|
||||||
const url = new URL(youtubeUrl);
|
const url = new URL(youtubeUrl);
|
||||||
@@ -72,7 +73,9 @@ export default function DetailVideoUser() {
|
|||||||
color="red"
|
color="red"
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Video yang Anda cari tidak tersedia.
|
<Text fz={{ base: 'sm', md: 'md' }} c="red.9">
|
||||||
|
Video yang Anda cari tidak tersedia.
|
||||||
|
</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconArrowBack size={16} />}
|
leftSection={<IconArrowBack size={16} />}
|
||||||
@@ -91,20 +94,20 @@ export default function DetailVideoUser() {
|
|||||||
return (
|
return (
|
||||||
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
{/* Tombol Kembali */}
|
{/* Tombol Kembali */}
|
||||||
<Box >
|
<Box>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header - Dijadikan Title */}
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
ta="center"
|
ta="center"
|
||||||
fz={{ base: 'xl', md: '2xl' }}
|
|
||||||
fw={700}
|
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
mb="lg"
|
mb="lg"
|
||||||
|
lh={{ base: 1.2, md: 1.25 }}
|
||||||
>
|
>
|
||||||
{data.name || 'Video Galeri Desa'}
|
{data.name || 'Video Galeri Desa'}
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
{/* Konten Utama */}
|
{/* Konten Utama */}
|
||||||
<Card
|
<Card
|
||||||
@@ -118,7 +121,7 @@ export default function DetailVideoUser() {
|
|||||||
{embedUrl ? (
|
{embedUrl ? (
|
||||||
<Box
|
<Box
|
||||||
pos="relative"
|
pos="relative"
|
||||||
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }} // 16:9 aspect ratio
|
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }}
|
||||||
>
|
>
|
||||||
<iframe
|
<iframe
|
||||||
src={embedUrl}
|
src={embedUrl}
|
||||||
@@ -144,7 +147,9 @@ export default function DetailVideoUser() {
|
|||||||
title="Gagal memuat video"
|
title="Gagal memuat video"
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Mohon maaf, video tidak dapat diputar.
|
<Text fz={{ base: 'xs', md: 'sm' }} c="orange.9">
|
||||||
|
Mohon maaf, video tidak dapat diputar.
|
||||||
|
</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
) : (
|
) : (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -153,7 +158,9 @@ export default function DetailVideoUser() {
|
|||||||
title="Tidak ada video"
|
title="Tidak ada video"
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Konten video belum tersedia.
|
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
|
||||||
|
Konten video belum tersedia.
|
||||||
|
</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -163,7 +170,11 @@ export default function DetailVideoUser() {
|
|||||||
<ThemeIcon variant="light" size="sm" radius="xl">
|
<ThemeIcon variant="light" size="sm" radius="xl">
|
||||||
<IconInfoCircle size={14} />
|
<IconInfoCircle size={14} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
c="dimmed"
|
||||||
|
lh={{ base: 1.4, md: 1.5 }}
|
||||||
|
>
|
||||||
Diunggah pada{' '}
|
Diunggah pada{' '}
|
||||||
{new Date(data.createdAt).toLocaleDateString('id-ID', {
|
{new Date(data.createdAt).toLocaleDateString('id-ID', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
@@ -179,8 +190,9 @@ export default function DetailVideoUser() {
|
|||||||
{data.deskripsi && (
|
{data.deskripsi && (
|
||||||
<Paper p="md" bg="gray.0" radius="md">
|
<Paper p="md" bg="gray.0" radius="md">
|
||||||
<Text
|
<Text
|
||||||
fz="md"
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
c="dark"
|
c="dark"
|
||||||
|
ta={"justify"}
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -39,30 +39,38 @@ function PelayananPendudukNonPermanent() {
|
|||||||
) : (
|
) : (
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={{ base: "xl", md: "2xl" }} fw={700} lh={1.3} c="dark">
|
<Title
|
||||||
|
order={1}
|
||||||
|
fz={{ base: 'lg', md: 'xl' }}
|
||||||
|
fw={700}
|
||||||
|
lh={{ base: 1.3, md: 1.3 }}
|
||||||
|
c="dark"
|
||||||
|
>
|
||||||
{data?.name || "Judul belum tersedia"}
|
{data?.name || "Judul belum tersedia"}
|
||||||
</Text>
|
</Title>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
{data?.deskripsi ? (
|
{data?.deskripsi ? (
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "sm", md: "md" }}
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
lh={1.7}
|
lh={{ base: 1.6, md: 1.7 }}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
c="dimmed"
|
c="black"
|
||||||
dangerouslySetInnerHTML={{ __html: data?.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: data?.deskripsi }}
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text fz="sm" c="gray">Deskripsi belum tersedia.</Text>
|
<Text fz="xs" c="gray">
|
||||||
|
Deskripsi belum tersedia.
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider color={colors["blue-button"]} size="sm" />
|
<Divider color={colors["blue-button"]} size="sm" />
|
||||||
|
|
||||||
<Flex justify="space-between" align="center" wrap="wrap" gap="md">
|
<Flex justify="space-between" align="center" wrap="wrap" gap="md">
|
||||||
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">
|
<Text fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.5 }} c="black">
|
||||||
25 Mei 2021 • Darmasaba
|
25 Mei 2021 • Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function PelayananPerizinanBerusaha() {
|
|||||||
return (
|
return (
|
||||||
<Center mih={300}>
|
<Center mih={300}>
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Text fz="lg" fw={500} c="dimmed">
|
<Text fz={{ base: 'md', md: 'lg' }} fw={500} c="dimmed" lh="sm">
|
||||||
Belum ada informasi layanan yang tersedia
|
Belum ada informasi layanan yang tersedia
|
||||||
</Text>
|
</Text>
|
||||||
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
|
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
|
||||||
@@ -67,10 +67,10 @@ function PelayananPerizinanBerusaha() {
|
|||||||
) : (
|
) : (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Box>
|
<Box>
|
||||||
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
|
<Title order={2} fw={700} mb="sm">
|
||||||
Perizinan Berusaha Berbasis Risiko melalui OSS
|
Perizinan Berusaha Berbasis Risiko melalui OSS
|
||||||
</Title>
|
</Title>
|
||||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
<Text fz={{ base: 'sm', md: 'md' }} c="black" lh="sm">
|
||||||
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
|
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -83,13 +83,13 @@ function PelayananPerizinanBerusaha() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>
|
<Title order={3} fw={600} mb="sm">
|
||||||
Alur pendaftaran NIB:
|
Alur pendaftaran NIB:
|
||||||
</Text>
|
</Title>
|
||||||
<Stepper
|
<Stepper
|
||||||
active={active}
|
active={active}
|
||||||
onStepClick={(step) => {
|
onStepClick={(step) => {
|
||||||
if (step <= active) { // Only allow clicking on previous or current steps
|
if (step <= active) {
|
||||||
setActive(step);
|
setActive(step);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -102,28 +102,42 @@ function PelayananPerizinanBerusaha() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StepperStep label="Langkah 1" description="Daftar Akun">
|
<StepperStep label="Langkah 1" description="Daftar Akun">
|
||||||
<Text fz="sm">Membuat akun di portal OSS</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||||
|
Membuat akun di portal OSS
|
||||||
|
</Text>
|
||||||
</StepperStep>
|
</StepperStep>
|
||||||
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
|
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
|
||||||
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||||
|
Lengkapi informasi perusahaan, data pemegang saham, dan alamat
|
||||||
|
</Text>
|
||||||
</StepperStep>
|
</StepperStep>
|
||||||
<StepperStep label="Langkah 3" description="Pilih KBLI">
|
<StepperStep label="Langkah 3" description="Pilih KBLI">
|
||||||
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||||
|
Menentukan kode KBLI sesuai jenis usaha
|
||||||
|
</Text>
|
||||||
</StepperStep>
|
</StepperStep>
|
||||||
<StepperStep label="Langkah 4" description="Unggah Dokumen">
|
<StepperStep label="Langkah 4" description="Unggah Dokumen">
|
||||||
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||||
|
Unggah akta pendirian, surat izin, dan dokumen wajib lainnya
|
||||||
|
</Text>
|
||||||
</StepperStep>
|
</StepperStep>
|
||||||
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
|
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
|
||||||
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||||
|
Menunggu verifikasi dan persetujuan dari pihak berwenang
|
||||||
|
</Text>
|
||||||
</StepperStep>
|
</StepperStep>
|
||||||
<StepperStep label="Langkah 6" description="Terbit NIB">
|
<StepperStep label="Langkah 6" description="Terbit NIB">
|
||||||
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||||
|
Menerima NIB sebagai identitas resmi usaha
|
||||||
|
</Text>
|
||||||
</StepperStep>
|
</StepperStep>
|
||||||
<StepperCompleted>
|
<StepperCompleted>
|
||||||
<Center>
|
<Center>
|
||||||
<Stack align="center" gap="xs">
|
<Stack align="center" gap="xs">
|
||||||
<IconCheck size={40} color="green" />
|
<IconCheck size={40} color="green" />
|
||||||
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text>
|
<Text fz={{ base: 'xs', md: 'sm' }} fw={500} lh="sm">
|
||||||
|
Proses pendaftaran selesai
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
</StepperCompleted>
|
</StepperCompleted>
|
||||||
@@ -159,7 +173,7 @@ function PelayananPerizinanBerusaha() {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Text fz="sm" ta="justify" c="dimmed" mt="md">
|
<Text fz={{ base: 'xs', md: 'sm' }} ta="justify" c="black" lh="sm" mt="md">
|
||||||
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
|
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
|
||||||
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
|
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
|
||||||
oss.go.id
|
oss.go.id
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
|
import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -35,7 +35,7 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
|||||||
<Center py="xl">
|
<Center py="xl">
|
||||||
<Stack align="center" gap="xs">
|
<Stack align="center" gap="xs">
|
||||||
<IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} />
|
<IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} />
|
||||||
<Text c="dimmed" ta="center">
|
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh="sm">
|
||||||
Tidak ada layanan surat keterangan yang ditemukan
|
Tidak ada layanan surat keterangan yang ditemukan
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -48,9 +48,9 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
|||||||
<Group justify="space-between" align="center" mb="md">
|
<Group justify="space-between" align="center" mb="md">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconFileDescription size={28} stroke={1.8} />
|
<IconFileDescription size={28} stroke={1.8} />
|
||||||
<Text fz={{ base: "h4", md: "h2" }} fw={700}>
|
<Title order={2} c="black">
|
||||||
Layanan Surat Keterangan
|
Layanan Surat Keterangan
|
||||||
</Text>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
|
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
|
||||||
<IconInfoCircle size={22} stroke={1.8} />
|
<IconInfoCircle size={22} stroke={1.8} />
|
||||||
@@ -82,15 +82,15 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
|||||||
style={{ borderRadius: 16 }}
|
style={{ borderRadius: 16 }}
|
||||||
/>
|
/>
|
||||||
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
||||||
<Text
|
<Title
|
||||||
|
order={3}
|
||||||
c="white"
|
c="white"
|
||||||
fw={600}
|
|
||||||
fz="lg"
|
|
||||||
ta="center"
|
ta="center"
|
||||||
lineClamp={2}
|
lineClamp={2}
|
||||||
|
lh="sm"
|
||||||
>
|
>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Title>
|
||||||
<Group justify="center">
|
<Group justify="center">
|
||||||
<Button
|
<Button
|
||||||
size="md"
|
size="md"
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ function PelayananTelunjukSaktiDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Title order={2} mb="lg" fz={{ base: 22, md: 28 }} fw={700} style={{ lineHeight: 1.4 }}>
|
<Title order={2} mb="lg" fw={700} style={{ lineHeight: 1.3 }} ta="left">
|
||||||
Layanan Telunjuk Sakti Desa <br />
|
Layanan Telunjuk Sakti Desa
|
||||||
<Text span c="dimmed" fz="lg" fw={400}>
|
<Text span c="black" fz={{ base: 'sm', md: 'md' }} fw={400} style={{ lineHeight: 1.5 }}>
|
||||||
|
{' '}
|
||||||
Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman
|
Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman
|
||||||
</Text>
|
</Text>
|
||||||
</Title>
|
</Title>
|
||||||
@@ -53,7 +54,7 @@ function PelayananTelunjukSaktiDesa() {
|
|||||||
<Skeleton h={400} radius="lg" />
|
<Skeleton h={400} radius="lg" />
|
||||||
) : data.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<Card shadow="sm" radius="lg" withBorder>
|
<Card shadow="sm" radius="lg" withBorder>
|
||||||
<Text c="dimmed" ta="center" py="xl">
|
<Text c="black" ta="center" py="xl" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||||
Belum ada layanan tersedia untuk saat ini
|
Belum ada layanan tersedia untuk saat ini
|
||||||
</Text>
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -72,9 +73,9 @@ function PelayananTelunjukSaktiDesa() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fw={700} fz="lg" lh={1.4}>
|
<Title order={3} fw={700} lh={1.3}>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Title>
|
||||||
<Flex gap="xs" align="center">
|
<Flex gap="xs" align="center">
|
||||||
<IconExternalLink size={18} stroke={1.5} />
|
<IconExternalLink size={18} stroke={1.5} />
|
||||||
<Text
|
<Text
|
||||||
@@ -82,7 +83,7 @@ function PelayananTelunjukSaktiDesa() {
|
|||||||
href={v.link}
|
href={v.link}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
fz="sm"
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
c="blue"
|
c="blue"
|
||||||
td="underline"
|
td="underline"
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
|
|||||||
@@ -1,58 +1,94 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Container, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../../layanan/_com/BackButto';
|
|
||||||
import NewsReader from '@/app/darmasaba/_com/NewsReader';
|
import NewsReader from '@/app/darmasaba/_com/NewsReader';
|
||||||
|
import BackButton from '../../../layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique)
|
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique);
|
||||||
|
const params = useParams();
|
||||||
const params = useParams()
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string)
|
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!detail.data) {
|
if (!detail.data) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Skeleton h={400} />
|
<Skeleton h={400} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Container size="lg" px="md">
|
<Container size="lg" px="md">
|
||||||
<Group>
|
<Group>
|
||||||
<NewsReader />
|
<NewsReader />
|
||||||
</Group>
|
</Group>
|
||||||
<Stack gap="xs" >
|
|
||||||
<Group justify={"space-between"} align={"center"}>
|
<Stack gap="xs">
|
||||||
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
<Group justify="space-between" align="flex-start" wrap="wrap">
|
||||||
{detail.data?.judul}
|
<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>
|
</Text>
|
||||||
<Group justify='end'>
|
</Paper>
|
||||||
<Paper bg={colors['blue-button']} p={5}>
|
</Group>
|
||||||
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
|
||||||
</Paper>
|
<Paper
|
||||||
</Group>
|
bg={colors['white-1']}
|
||||||
</Group>
|
p="md"
|
||||||
<Paper bg={colors["white-1"]} p="md">
|
w="100%"
|
||||||
<Text px="lg" id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
mih={{ base: 200, md: 300 }}
|
||||||
<Text px="lg" fz={"md"} c={colors["blue-button"]} fw="bold" >
|
>
|
||||||
|
<Text
|
||||||
|
px="lg"
|
||||||
|
id="news-content"
|
||||||
|
fz={{ base: 14, md: 16 }}
|
||||||
|
lh={{ base: 1.6, md: 1.6 }}
|
||||||
|
style={{
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{ __html: detail.data?.content }}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
px="lg"
|
||||||
|
fz={{ base: 12, md: 14 }}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fw="bold"
|
||||||
|
lh={{ base: 1.4, md: 1.4 }}
|
||||||
|
mt="md"
|
||||||
|
>
|
||||||
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
|
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric'
|
year: 'numeric',
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Container, Group, Paper, Stack, Text } from '@mantine/core';
|
import { Box, Container, Group, Paper, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconCalendar } from '@tabler/icons-react';
|
import { IconCalendar } from '@tabler/icons-react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../layanan/_com/BackButto';
|
import BackButton from '../../layanan/_com/BackButto';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const unwrappedParams = useParams();
|
const unwrappedParams = useParams();
|
||||||
const kategoriState = useProxy(stateDesaPengumuman);
|
const kategoriState = useProxy(stateDesaPengumuman);
|
||||||
@@ -26,45 +25,82 @@ function Page() {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md" >
|
|
||||||
<Stack align="center" gap="0" >
|
<Container size="lg" px="md">
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Stack align="center" gap="xs">
|
||||||
|
<Title
|
||||||
|
order={1}
|
||||||
|
c={colors["blue-button"]}
|
||||||
|
ta="center"
|
||||||
|
style={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
{categoryName.split('-').map(word =>
|
{categoryName.split('-').map(word =>
|
||||||
word.charAt(0).toUpperCase() + word.slice(1)
|
word.charAt(0).toUpperCase() + word.slice(1)
|
||||||
).join(' ')}
|
).join(' ')}
|
||||||
</Text>
|
</Title>
|
||||||
<Text ta="center" px="md" pb={10}>
|
<Text
|
||||||
|
ta="center"
|
||||||
|
px="md"
|
||||||
|
pb="sm"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={{ base: 1.5, md: 1.6 }}
|
||||||
|
c="dimmed"
|
||||||
|
>
|
||||||
Informasi dan pengumuman resmi terkait {categoryName.split('-').join(' ')}
|
Informasi dan pengumuman resmi terkait {categoryName.split('-').join(' ')}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
{!kategoriState.pengumuman.findMany.data?.length ? (
|
{!kategoriState.pengumuman.findMany.data?.length ? (
|
||||||
<Paper p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
|
<Paper p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
|
||||||
Tidak ada pengumuman yang ditemukan
|
<Text
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
ta="center"
|
||||||
|
c="dimmed"
|
||||||
|
>
|
||||||
|
Tidak ada pengumuman yang ditemukan
|
||||||
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
) : kategoriState.pengumuman.findMany.data?.map((v, k) => {
|
) : (
|
||||||
return (
|
kategoriState.pengumuman.findMany.data?.map((v, k) => (
|
||||||
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
|
<Paper
|
||||||
<Text fz={'h3'}>{v.judul}</Text>
|
mb="md"
|
||||||
<Group style={{ color: 'black' }} pb={20}>
|
key={k}
|
||||||
|
withBorder
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="md"
|
||||||
|
bg={colors["white-1"]}
|
||||||
|
>
|
||||||
|
<Title order={3}>{v.judul}</Title>
|
||||||
|
<Group style={{ color: 'black' }} pb="sm">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconCalendar size={18} />
|
<IconCalendar size={18} />
|
||||||
<Text size="sm">
|
<Text
|
||||||
{v.createdAt ? new Date(v.createdAt).toLocaleDateString('id-ID', {
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
day: 'numeric',
|
lh={{ base: 1.4, md: 1.5 }}
|
||||||
month: 'long',
|
>
|
||||||
year: 'numeric',
|
{v.createdAt
|
||||||
}) : 'No date available'}
|
? new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})
|
||||||
|
: 'No date available'}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<Text ta={'justify'}>
|
<Text
|
||||||
|
ta="justify"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={{ base: 1.6, md: 1.7 }}
|
||||||
|
>
|
||||||
{v.deskripsi}
|
{v.deskripsi}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
))
|
||||||
})}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Center,
|
Center,
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
Flex,
|
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
GridCol,
|
||||||
Group,
|
Group,
|
||||||
@@ -22,7 +21,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
UnstyledButton,
|
UnstyledButton
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconCalendar, IconClock, IconSearch } from '@tabler/icons-react';
|
import { IconCalendar, IconClock, IconSearch } from '@tabler/icons-react';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
@@ -98,10 +97,14 @@ function Page() {
|
|||||||
|
|
||||||
<Container size="lg" px="md">
|
<Container size="lg" px="md">
|
||||||
<Stack align="center" gap="0">
|
<Stack align="center" gap="0">
|
||||||
<Text fz={{ base: '2rem', md: '3.4rem' }} c={colors['blue-button']} fw="bold" ta="center">
|
<Title
|
||||||
|
order={1}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
ta="center"
|
||||||
|
>
|
||||||
Pengumuman Desa Darmasaba
|
Pengumuman Desa Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
<Text ta="center" px="md" pb={10}>
|
<Text ta="center" px="md" pb={10} fz={{ base: 'sm', md: 'md' }} lh="sm">
|
||||||
Informasi dan pengumuman resmi terkait kegiatan dan kebijakan Desa Darmasaba
|
Informasi dan pengumuman resmi terkait kegiatan dan kebijakan Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -126,17 +129,17 @@ function Page() {
|
|||||||
withCloseButton={false}
|
withCloseButton={false}
|
||||||
title={item.CategoryPengumuman?.name || 'Pengumuman'}
|
title={item.CategoryPengumuman?.name || 'Pengumuman'}
|
||||||
>
|
>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="xs">
|
||||||
<Text fz="sm" fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
|
<Text fz={{ base: 'sm', md: 'sm' }} fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
|
||||||
{item.judul}
|
{item.judul}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta="justify" fz="sm" c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text ta="justify" fz={{ base: 'xs', md: 'sm' }} c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Flex pt={20} gap="md" justify="space-between">
|
<Group pt={20} gap="md" justify="space-between">
|
||||||
<Group style={{ color: 'black' }}>
|
<Group style={{ color: 'black' }}>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconCalendar size={18} />
|
<IconCalendar size={18} />
|
||||||
<Text size="sm">
|
<Text fz={{ base: 'xs', md: 'sm' }}>
|
||||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -147,7 +150,7 @@ function Page() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconClock size={18} />
|
<IconClock size={18} />
|
||||||
<Text size="sm">
|
<Text fz={{ base: 'xs', md: 'sm' }}>
|
||||||
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
|
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
@@ -157,11 +160,11 @@ function Page() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
|
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
|
||||||
<Text fs="unset" c={colors['blue-button']} fz="sm">
|
<Text fs="unset" c={colors['blue-button']} fz={{ base: 'xs', md: 'sm' }}>
|
||||||
Baca Selengkapnya
|
Baca Selengkapnya
|
||||||
</Text>
|
</Text>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Flex>
|
</Group>
|
||||||
</Notification>
|
</Notification>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -169,19 +172,19 @@ function Page() {
|
|||||||
|
|
||||||
<Paper p="md">
|
<Paper p="md">
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Text fw="bold" fz="lg" c={colors['blue-button']}>
|
<Title order={3} c={colors['blue-button']}>
|
||||||
Kategori
|
Kategori
|
||||||
</Text>
|
</Title>
|
||||||
{stateDesaPengumuman.category.findMany.data?.map((v: any, k) => {
|
{stateDesaPengumuman.category.findMany.data?.map((v: any, k) => {
|
||||||
const count = v._count?.pengumumans || 0;
|
const count = v._count?.pengumumans || 0;
|
||||||
return (
|
return (
|
||||||
<UnstyledButton component={Link} href={`/darmasaba/desa/pengumuman/${v.name}`} key={k}>
|
<UnstyledButton component={Link} href={`/darmasaba/desa/pengumuman/${v.name}`} key={k}>
|
||||||
<Paper bg={colors['BG-trans']} p={5}>
|
<Paper bg={colors['BG-trans']} p={5}>
|
||||||
<Group px={3} justify="space-between">
|
<Group px={3} justify="space-between">
|
||||||
<Text fz="md" c="black">
|
<Text fz={{ base: 'sm', md: 'md' }} c="black">
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz="md" c="black">
|
<Text fz={{ base: 'sm', md: 'md' }} c="black">
|
||||||
{count}
|
{count}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -200,7 +203,7 @@ function Page() {
|
|||||||
<Divider mb={10} color={colors['blue-button']} />
|
<Divider mb={10} color={colors['blue-button']} />
|
||||||
<Grid>
|
<Grid>
|
||||||
<GridCol span={{ base: 12, md: 8 }}>
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
<Title order={3}>Daftar Pengumuman</Title>
|
<Title order={2}>Daftar Pengumuman</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 4 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -210,6 +213,7 @@ function Page() {
|
|||||||
w="100%"
|
w="100%"
|
||||||
value={searchInput}
|
value={searchInput}
|
||||||
onChange={(e) => setSearchInput(e.target.value)}
|
onChange={(e) => setSearchInput(e.target.value)}
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -223,7 +227,9 @@ function Page() {
|
|||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
) : !state.findMany.data?.length ? (
|
) : !state.findMany.data?.length ? (
|
||||||
<Notification withCloseButton={false} h={100}>
|
<Notification withCloseButton={false} h={100}>
|
||||||
Tidak ada pengumuman yang ditemukan
|
<Text fz={{ base: 'sm', md: 'md' }} ta="center">
|
||||||
|
Tidak ada pengumuman yang ditemukan
|
||||||
|
</Text>
|
||||||
</Notification>
|
</Notification>
|
||||||
) : (
|
) : (
|
||||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" verticalSpacing="lg">
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" verticalSpacing="lg">
|
||||||
@@ -231,26 +237,26 @@ function Page() {
|
|||||||
<Paper key={item.id} p="md" withBorder radius="md" h="100%">
|
<Paper key={item.id} p="md" withBorder radius="md" h="100%">
|
||||||
<Stack h="100%" justify="space-between">
|
<Stack h="100%" justify="space-between">
|
||||||
<div>
|
<div>
|
||||||
<Text fw={600} c={colors['blue-button']} mb={5}>
|
<Text fw={600} c={colors['blue-button']} mb={5} fz={{ base: 'sm', md: 'md' }}>
|
||||||
{item.CategoryPengumuman?.name || 'Pengumuman'}
|
{item.CategoryPengumuman?.name || 'Pengumuman'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz="lg" fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }}>
|
<Text fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }} fz={{ base: 'sm', md: 'lg' }}>
|
||||||
{item.judul}
|
{item.judul}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
lineClamp={4}
|
lineClamp={4}
|
||||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
mb="md"
|
mb="md"
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Group mb="sm" c="dimmed">
|
<Group mb="sm" c="dimmed">
|
||||||
<Group gap={5}>
|
<Group gap={5}>
|
||||||
<IconCalendar size={16} />
|
<IconCalendar size={16} />
|
||||||
<Text size="xs">
|
<Text fz={{ base: 'xs', md: 'xs' }}>
|
||||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
@@ -260,19 +266,19 @@ function Page() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group gap={5}>
|
<Group gap={5}>
|
||||||
<IconClock size={16} />
|
<IconClock size={16} />
|
||||||
<Text size="xs">
|
<Text fz={{ base: 'xs', md: 'xs' }}>
|
||||||
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
|
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
<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>
|
</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>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -289,6 +295,7 @@ function Page() {
|
|||||||
siblings={1}
|
siblings={1}
|
||||||
boundaries={1}
|
boundaries={1}
|
||||||
withEdges
|
withEdges
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../layanan/_com/BackButto';
|
import BackButton from '../../layanan/_com/BackButto';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||||
@@ -35,7 +36,9 @@ function Page() {
|
|||||||
<Center h="80vh">
|
<Center h="80vh">
|
||||||
<Stack align="center" gap="md">
|
<Stack align="center" gap="md">
|
||||||
<Loader size="lg" color="blue" />
|
<Loader size="lg" color="blue" />
|
||||||
<Text c="dimmed" fz="sm">Sedang memuat informasi...</Text>
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
|
||||||
|
Sedang memuat informasi...
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@@ -46,28 +49,31 @@ function Page() {
|
|||||||
<Center h="80vh">
|
<Center h="80vh">
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
|
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
|
||||||
<Title order={3}>Data Tidak Ditemukan</Title>
|
<Title order={3} ta="center">
|
||||||
<Text c="dimmed" fz="sm">Mohon periksa kembali atau coba beberapa saat lagi</Text>
|
Data Tidak Ditemukan
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
|
||||||
|
Mohon periksa kembali atau coba beberapa saat lagi
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: "md", md: 0 }}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: 'md', md: 0 }}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container w={{ base: "100%", md: "60%" }}>
|
<Container w={{ base: '100%', md: '60%' }}>
|
||||||
<Paper radius="2xl" shadow="lg" p="xl" withBorder>
|
<Paper radius="2xl" shadow="lg" p="xl" withBorder>
|
||||||
<Stack gap="lg" align="center">
|
<Stack gap="lg" align="center">
|
||||||
<Title ta="center" fz={{ base: "2rem", md: "3rem" }} c={colors["blue-button"]} fw={800}>
|
<Title order={1} ta="center" c={colors['blue-button']} fw={800}>
|
||||||
{state.findUnique.data?.name}
|
{state.findUnique.data?.name}
|
||||||
</Title>
|
</Title>
|
||||||
<Text ta="center" fw={600} fz={{ base: "md", md: "lg" }} c="dimmed">
|
<Text ta="center" fw={600} fz={{ base: 'md', md: 'lg' }} c="dimmed">
|
||||||
Informasi & Pelayanan Potensi Desa Digital
|
Informasi & Pelayanan Potensi Desa Digital
|
||||||
</Text>
|
</Text>
|
||||||
{/* ✅ Bagian gambar dibuat konsisten tanpa CSS manual */}
|
|
||||||
<Box
|
<Box
|
||||||
w="100%"
|
w="100%"
|
||||||
h={{ base: 220, md: 400 }}
|
h={{ base: 220, md: 400 }}
|
||||||
@@ -87,7 +93,15 @@ function Page() {
|
|||||||
radius="lg"
|
radius="lg"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Text py="md" fz={{ base: "sm", md: "md" }} ta="justify" lh={1.8} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || 'Belum ada deskripsi untuk potensi desa ini.' }} />
|
<Text
|
||||||
|
py="md"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
ta="justify"
|
||||||
|
lh={{ base: 1.6, md: 1.8 }}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: state.findUnique.data?.content || 'Belum ada deskripsi untuk potensi desa ini.',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
import { BackgroundImage, Box, Button, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconEye } from '@tabler/icons-react';
|
import { IconEye } from '@tabler/icons-react';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -41,10 +41,10 @@ function Page() {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
|
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
|
||||||
<Stack gap="sm" maw={600}>
|
<Stack gap="sm" maw={600}>
|
||||||
<Text fz={{ base: "2rem", md: "3rem" }} fw={900} c={colors["blue-button"]} lh={1.2}>
|
<Title order={1} fz={{ base: 28, md: 36 }} lh={1.2} c={colors["blue-button"]}>
|
||||||
Potensi Desa Darmasaba
|
Potensi Desa Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
<Text fz="lg" ta="justify">
|
<Text fz={{ base: 14, md: 16 }} lh={1.6} ta="justify">
|
||||||
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
|
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -58,18 +58,18 @@ function Page() {
|
|||||||
>
|
>
|
||||||
<Flex justify="center" align="center" gap="xl">
|
<Flex justify="center" align="center" gap="xl">
|
||||||
<Box>
|
<Box>
|
||||||
<Text ta="center" fz="2rem" fw={800} c="white">
|
<Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
|
||||||
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
|
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta="center" fz="sm" c="white" fw={500}>
|
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
|
||||||
Potensi
|
Potensi
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text ta="center" fz="2rem" fw={800} c="white">
|
<Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
|
||||||
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0}
|
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta="center" fz="sm" c="white" fw={500}>
|
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
|
||||||
Wisata
|
Wisata
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -98,7 +98,6 @@ function Page() {
|
|||||||
transition: 'transform 0.3s ease'
|
transition: 'transform 0.3s ease'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Overlay with smooth transition */}
|
|
||||||
<Box
|
<Box
|
||||||
pos="absolute"
|
pos="absolute"
|
||||||
inset={0}
|
inset={0}
|
||||||
@@ -112,7 +111,6 @@ function Page() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
||||||
{/* Kategori badge - always visible */}
|
|
||||||
<Group>
|
<Group>
|
||||||
<Paper
|
<Paper
|
||||||
radius="lg"
|
radius="lg"
|
||||||
@@ -121,15 +119,12 @@ function Page() {
|
|||||||
shadow="md"
|
shadow="md"
|
||||||
withBorder
|
withBorder
|
||||||
bg="rgba(255,255,255,0.9)"
|
bg="rgba(255,255,255,0.9)"
|
||||||
style={{
|
style={{ transition: 'all 0.3s ease' }}
|
||||||
transition: 'all 0.3s ease'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text fz="sm" fw={600}>{v.kategori?.nama}</Text>
|
<Text fz={{ base: 11, md: 14 }} fw={600}>{v.kategori?.nama}</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Nama potensi - visible on hover */}
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
opacity: hoveredId === v.id ? 1 : 0,
|
opacity: hoveredId === v.id ? 1 : 0,
|
||||||
@@ -138,19 +133,19 @@ function Page() {
|
|||||||
pointerEvents: hoveredId === v.id ? 'auto' : 'none'
|
pointerEvents: hoveredId === v.id ? 'auto' : 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Title
|
||||||
|
order={3}
|
||||||
fw={800}
|
fw={800}
|
||||||
c="white"
|
c="white"
|
||||||
fz="xl"
|
fz={{ base: 18, md: 20 }}
|
||||||
ta="center"
|
ta="center"
|
||||||
lineClamp={2}
|
lineClamp={2}
|
||||||
lh={1.3}
|
lh={1.3}
|
||||||
>
|
>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Title>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Button - visible on hover */}
|
|
||||||
<Group
|
<Group
|
||||||
justify="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
@@ -169,23 +164,21 @@ function Page() {
|
|||||||
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
|
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
|
||||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
||||||
>
|
>
|
||||||
Lihat Detail
|
<Text c={'white'} fz={{ base: 12, md: 14 }} fw={500}>Lihat Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</BackgroundImage>
|
</BackgroundImage>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Center h={240}>
|
<Stack align="center" gap="xs">
|
||||||
<Stack align="center" gap="xs">
|
<Text fz={{ base: 14, md: 16 }} fw={600} c="dimmed">
|
||||||
<Text fz="lg" fw={600} c="dimmed">
|
Belum ada potensi desa
|
||||||
Belum ada potensi desa
|
</Text>
|
||||||
</Text>
|
<Text fz={{ base: 12, md: 14 }} c="dimmed">
|
||||||
<Text fz="sm" c="dimmed">
|
Data potensi akan tampil di sini setelah tersedia.
|
||||||
Data potensi akan tampil di sini setelah tersedia.
|
</Text>
|
||||||
</Text>
|
</Stack>
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
)}
|
)}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ function DetailPegawaiUser() {
|
|||||||
statePegawai.findUnique.load(params?.id as string);
|
statePegawai.findUnique.load(params?.id as string);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
if (!statePegawai.findUnique.data) {
|
if (!statePegawai.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py="lg">
|
<Stack py="lg">
|
||||||
@@ -41,7 +40,7 @@ function DetailPegawaiUser() {
|
|||||||
<Box px={{ base: 'md', md: 100 }} py="xl">
|
<Box px={{ base: 'md', md: 100 }} py="xl">
|
||||||
{/* Back button */}
|
{/* Back button */}
|
||||||
<Group mb="lg" px={{ base: 'md', md: 100 }}>
|
<Group mb="lg" px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton/>
|
<BackButton />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
@@ -69,11 +68,17 @@ function DetailPegawaiUser() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Nama & Jabatan */}
|
{/* Nama & Jabatan */}
|
||||||
<Stack align="center" gap={2}>
|
<Stack align="center" gap={4}>
|
||||||
<Title order={3} fw={700} c={colors['blue-button']}>
|
{/* Title utama → H2 karena ini judul profil */}
|
||||||
|
<Title order={2} c={colors['blue-button']} lh={1.2}>
|
||||||
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
|
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
|
||||||
</Title>
|
</Title>
|
||||||
<Text fz="sm" c="dimmed">
|
|
||||||
|
<Text
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={1.4}
|
||||||
|
c="dimmed"
|
||||||
|
>
|
||||||
{data.posisi?.nama || 'Posisi tidak tersedia'}
|
{data.posisi?.nama || 'Posisi tidak tersedia'}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -82,7 +87,11 @@ function DetailPegawaiUser() {
|
|||||||
<Divider my="lg" />
|
<Divider my="lg" />
|
||||||
|
|
||||||
{/* Informasi Detail */}
|
{/* Informasi Detail */}
|
||||||
<Stack gap="md">
|
<Stack gap="lg">
|
||||||
|
<Title order={3} lh={1.3}>
|
||||||
|
Informasi Pegawai
|
||||||
|
</Title>
|
||||||
|
|
||||||
<InfoRow label="Email" value={data.email} />
|
<InfoRow label="Email" value={data.email} />
|
||||||
<InfoRow label="Telepon" value={data.telepon} />
|
<InfoRow label="Telepon" value={data.telepon} />
|
||||||
<InfoRow label="Alamat" value={data.alamat} multiline />
|
<InfoRow label="Alamat" value={data.alamat} multiline />
|
||||||
@@ -91,10 +100,10 @@ function DetailPegawaiUser() {
|
|||||||
value={
|
value={
|
||||||
data.tanggalMasuk
|
data.tanggalMasuk
|
||||||
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
|
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})
|
})
|
||||||
: '-'
|
: '-'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -123,11 +132,18 @@ function InfoRow({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw={600} c="dark">
|
<Text
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
fw={600}
|
||||||
|
lh={1.3}
|
||||||
|
c="dark"
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={1.5}
|
||||||
c={valueColor || 'dimmed'}
|
c={valueColor || 'dimmed'}
|
||||||
style={{
|
style={{
|
||||||
whiteSpace: multiline ? 'normal' : 'nowrap',
|
whiteSpace: multiline ? 'normal' : 'nowrap',
|
||||||
|
|||||||
@@ -36,11 +36,12 @@ import { useTransitionRouter } from 'next-view-transitions'
|
|||||||
import { OrganizationChart } from 'primereact/organizationchart'
|
import { OrganizationChart } from 'primereact/organizationchart'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useProxy } from 'valtio/utils'
|
import { useProxy } from 'valtio/utils'
|
||||||
import './struktur.css'
|
|
||||||
import BackButton from '../_com/BackButto'
|
|
||||||
import { useMediaQuery } from '@mantine/hooks'
|
|
||||||
|
|
||||||
export default function StrukturPerangkatDesa() {
|
import './struktur.css'
|
||||||
|
import { useMediaQuery } from '@mantine/hooks'
|
||||||
|
import BackButton from '../_com/BackButto'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@@ -59,10 +60,11 @@ export default function StrukturPerangkatDesa() {
|
|||||||
ta="center"
|
ta="center"
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
fz={{ base: 28, md: 36, lg: 44 }}
|
fz={{ base: 28, md: 36, lg: 44 }}
|
||||||
|
lh={{ base: 1.05, md: 1.03 }}
|
||||||
>
|
>
|
||||||
Struktur Perangkat Desa
|
Struktur Perangkat Desa
|
||||||
</Title>
|
</Title>
|
||||||
<Text ta="center" c="black" maw={800}>
|
<Text ta="center" c="black" maw={800} fz={{ base: 13, md: 15 }} lh={1.45}>
|
||||||
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
|
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
|
||||||
untuk melihat detail atau klik node untuk fokus tampilan.
|
untuk melihat detail atau klik node untuk fokus tampilan.
|
||||||
</Text>
|
</Text>
|
||||||
@@ -105,8 +107,8 @@ function StrukturPerangkatDesaNode() {
|
|||||||
<Center py={48}>
|
<Center py={48}>
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Loader size="lg" />
|
<Loader size="lg" />
|
||||||
<Text fw={600}>Memuat struktur organisasi…</Text>
|
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.2}>Memuat struktur organisasi…</Text>
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" fz={{ base: 12, md: 13 }} lh={1.4}>
|
||||||
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
|
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -132,10 +134,10 @@ function StrukturPerangkatDesaNode() {
|
|||||||
<Center>
|
<Center>
|
||||||
<IconUsers size={56} />
|
<IconUsers size={56} />
|
||||||
</Center>
|
</Center>
|
||||||
<Title order={3} mt="md">
|
<Title order={3} mt="md" fz={{ base: 16, md: 18 }} lh={1.15}>
|
||||||
Data pegawai belum tersedia
|
Data pegawai belum tersedia
|
||||||
</Title>
|
</Title>
|
||||||
<Text c="dimmed" mt="xs">
|
<Text c="dimmed" mt="xs" fz={{ base: 13, md: 14 }} lh={1.4}>
|
||||||
Belum ada data pegawai yang tercatat untuk PPID.
|
Belum ada data pegawai yang tercatat untuk PPID.
|
||||||
</Text>
|
</Text>
|
||||||
<Group justify="center" mt="lg">
|
<Group justify="center" mt="lg">
|
||||||
@@ -232,11 +234,18 @@ function StrukturPerangkatDesaNode() {
|
|||||||
{/* 🔍 Controls */}
|
{/* 🔍 Controls */}
|
||||||
<Paper
|
<Paper
|
||||||
shadow="xs"
|
shadow="xs"
|
||||||
|
w={{
|
||||||
|
base: '100%', // Mobile: 100%
|
||||||
|
sm: '40%', // Tablet: 95%
|
||||||
|
md: '39%', // Desktop: 70%
|
||||||
|
lg: '38%', // Desktop L: 60%
|
||||||
|
xl: '37%', // 4K: 50%
|
||||||
|
'2xl': '36%', // Ultra-wide: 45%
|
||||||
|
}}
|
||||||
p="md"
|
p="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
style={{
|
style={{
|
||||||
background: colors['blue-button'],
|
background: colors['blue-button'], // ⬅️ penting
|
||||||
width: '100%', // ⬅️ penting
|
|
||||||
maxWidth: '100%', // ⬅️ penting
|
maxWidth: '100%', // ⬅️ penting
|
||||||
overflowX: 'auto' // ⬅️ untuk mencegah overflow
|
overflowX: 'auto' // ⬅️ untuk mencegah overflow
|
||||||
}}
|
}}
|
||||||
@@ -269,30 +278,33 @@ function StrukturPerangkatDesaNode() {
|
|||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
minHeight: 'auto',
|
minHeight: 'auto',
|
||||||
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
style={{ width: '100%' }} // 👈 penting
|
||||||
>
|
>
|
||||||
<TabsList
|
<TabsList
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
overflowY: 'hidden', // 👈 tambahkan ini
|
overflowY: 'hidden',
|
||||||
gap: '4px',
|
gap: '4px',
|
||||||
paddingBottom: '4px',
|
paddingBottom: '4px',
|
||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
WebkitOverflowScrolling: 'touch', // 👈 smooth scroll di iOS
|
WebkitOverflowScrolling: 'touch',
|
||||||
scrollbarWidth: 'thin', // 👈 scrollbar tipis di Firefox
|
scrollbarWidth: 'thin',
|
||||||
msOverflowStyle: '-ms-autohiding-scrollbar', // 👈 untuk IE/Edge
|
msOverflowStyle: '-ms-autohiding-scrollbar',
|
||||||
|
maxWidth: '100%',
|
||||||
|
scrollBehavior: 'smooth', // 👈 smooth scroll
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TabsTab
|
<TabsTab
|
||||||
value="zoom-out"
|
value="zoom-out"
|
||||||
onClick={handleZoomOut}
|
onClick={handleZoomOut}
|
||||||
leftSection={<IconZoomOut size={16} />}
|
leftSection={<IconZoomOut size={16} />}
|
||||||
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
|
style={{ flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
Zoom Out
|
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
|
||||||
</TabsTab>
|
</TabsTab>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
@@ -301,7 +313,6 @@ function StrukturPerangkatDesaNode() {
|
|||||||
px={12}
|
px={12}
|
||||||
py={6}
|
py={6}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
@@ -310,10 +321,12 @@ function StrukturPerangkatDesaNode() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
whiteSpace: 'nowrap', // 👈 mencegah text wrap
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Math.round(scale * 100)}%
|
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
|
||||||
|
{Math.round(scale * 100)}%
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TabsTab
|
<TabsTab
|
||||||
@@ -322,7 +335,7 @@ function StrukturPerangkatDesaNode() {
|
|||||||
leftSection={<IconZoomIn size={16} />}
|
leftSection={<IconZoomIn size={16} />}
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
Zoom In
|
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
|
||||||
</TabsTab>
|
</TabsTab>
|
||||||
|
|
||||||
<TabsTab
|
<TabsTab
|
||||||
@@ -330,7 +343,7 @@ function StrukturPerangkatDesaNode() {
|
|||||||
onClick={resetZoom}
|
onClick={resetZoom}
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
Reset
|
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
|
||||||
</TabsTab>
|
</TabsTab>
|
||||||
|
|
||||||
<TabsTab
|
<TabsTab
|
||||||
@@ -345,7 +358,9 @@ function StrukturPerangkatDesaNode() {
|
|||||||
}
|
}
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
|
||||||
|
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
||||||
|
</Text>
|
||||||
</TabsTab>
|
</TabsTab>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -451,17 +466,17 @@ function NodeCard({ node, router }: any) {
|
|||||||
{/* Name */}
|
{/* Name */}
|
||||||
<Text
|
<Text
|
||||||
fw={700}
|
fw={700}
|
||||||
size="sm"
|
|
||||||
ta="center"
|
ta="center"
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
lineClamp={2}
|
lineClamp={2}
|
||||||
|
fz={{ base: 13, md: 15 }}
|
||||||
|
lh={1.2}
|
||||||
style={{
|
style={{
|
||||||
minHeight: 40,
|
minHeight: 40,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
lineHeight: 1.3,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
@@ -469,18 +484,18 @@ function NodeCard({ node, router }: any) {
|
|||||||
|
|
||||||
{/* Title/Position */}
|
{/* Title/Position */}
|
||||||
<Text
|
<Text
|
||||||
size="xs"
|
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={500}
|
fw={500}
|
||||||
lineClamp={2}
|
lineClamp={2}
|
||||||
|
fz={{ base: 12, md: 13 }}
|
||||||
|
lh={1.3}
|
||||||
style={{
|
style={{
|
||||||
minHeight: 32,
|
minHeight: 32,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
lineHeight: 1.2,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
@@ -496,14 +511,14 @@ function NodeCard({ node, router }: any) {
|
|||||||
mt={8}
|
mt={8}
|
||||||
radius="md"
|
radius="md"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(`/darmasaba/desa/profile/struktur-perangkat-desa/${node.data.id}`)
|
router.push(`/darmasaba/desa/profil/struktur-perangkat-desa/${node.data.id}`)
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
height: 32,
|
height: 32,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Lihat Detail
|
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
|
||||||
import colors from '@/con/colors'
|
import colors from '@/con/colors'
|
||||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'
|
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useProxy } from 'valtio/utils'
|
import { useProxy } from 'valtio/utils'
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ function LambangDesa() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack align="center" gap="lg">
|
<Stack align="center" gap="lg">
|
||||||
|
|
||||||
|
{/* HEADER */}
|
||||||
<Box pb="lg">
|
<Box pb="lg">
|
||||||
<Center>
|
<Center>
|
||||||
<Image
|
<Image
|
||||||
@@ -36,17 +38,20 @@ function LambangDesa() {
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
<Text
|
|
||||||
|
{/* TITLE - H1 */}
|
||||||
|
<Title
|
||||||
|
order={1}
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={800}
|
|
||||||
fz={{ base: 28, md: 40 }}
|
|
||||||
mt="sm"
|
mt="sm"
|
||||||
style={{ letterSpacing: '-0.5px' }}
|
style={{ letterSpacing: '-0.5px' }}
|
||||||
>
|
>
|
||||||
Lambang Desa
|
Lambang Desa
|
||||||
</Text>
|
</Title>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* DESKRIPSI */}
|
||||||
<Paper
|
<Paper
|
||||||
p="xl"
|
p="xl"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
@@ -58,15 +63,20 @@ function LambangDesa() {
|
|||||||
borderColor: '#e0e9ff',
|
borderColor: '#e0e9ff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
fz={{ base: 'sm', md: 'md' }} // Body text mobile & desktop
|
||||||
lh={1.8}
|
lh={1.7}
|
||||||
c="dark"
|
c="dark"
|
||||||
ta="justify"
|
ta="justify"
|
||||||
style={{ fontWeight: 400, wordBreak: "break-word", whiteSpace: "normal", }}
|
style={{
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
fontWeight: 400,
|
||||||
/>
|
wordBreak: "break-word",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
|
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconPhoto } from '@tabler/icons-react';
|
import { IconPhoto } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -21,7 +21,9 @@ function MaskotDesa() {
|
|||||||
<Center mih={500}>
|
<Center mih={500}>
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Loader size="lg" color="blue" />
|
<Loader size="lg" color="blue" />
|
||||||
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text>
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
|
||||||
|
Sedang memuat data maskot desa...
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@@ -31,8 +33,21 @@ function MaskotDesa() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Stack align="center" gap="xl">
|
<Stack align="center" gap="xl">
|
||||||
<Stack align="center" gap={10}>
|
<Stack align="center" gap={10}>
|
||||||
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} loading="lazy"/>
|
<Image
|
||||||
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
|
src="/pudak-icon.png"
|
||||||
|
alt="Ikon Desa"
|
||||||
|
w={{ base: 160, md: 240 }}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Page Title */}
|
||||||
|
<Title
|
||||||
|
order={1}
|
||||||
|
ta="center"
|
||||||
|
c={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Maskot Desa
|
||||||
|
</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
@@ -42,48 +57,60 @@ function MaskotDesa() {
|
|||||||
withBorder
|
withBorder
|
||||||
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
|
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
|
||||||
>
|
>
|
||||||
|
{/* Body Description */}
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: 'sm', md: 'lg' }}
|
fz={{ base: 'sm', md: 'lg' }}
|
||||||
lh={1.7}
|
lh={1.7}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
c="dark"
|
c="dark"
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group justify="center" gap="lg" mt="lg">
|
<Group justify="center" gap="lg" mt="lg">
|
||||||
{data.images.length > 0 ? (
|
{data.images.length > 0 ? (
|
||||||
data.images.map((img, index) => (
|
data.images.map((img, index) => (
|
||||||
<Card
|
<Card
|
||||||
key={index}
|
key={index}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
withBorder
|
withBorder
|
||||||
w={220}
|
w={220}
|
||||||
p="sm"
|
p="sm"
|
||||||
style={{
|
style={{
|
||||||
transition: 'transform 200ms ease, box-shadow 200ms ease',
|
transition: 'transform 200ms ease, box-shadow 200ms ease',
|
||||||
}}
|
}}
|
||||||
className="hover:scale-105 hover:shadow-lg"
|
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
|
{img.label}
|
||||||
src={img.image.link}
|
</Text>
|
||||||
alt={img.label}
|
</Card>
|
||||||
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>
|
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Stack align="center" gap="xs" mt="lg">
|
<Stack align="center" gap="xs" mt="lg">
|
||||||
<IconPhoto size={48} stroke={1.5} color="gray" />
|
<IconPhoto size={48} stroke={1.5} color="gray" />
|
||||||
<Text c="dimmed" fz="sm">Belum ada gambar maskot yang ditambahkan</Text>
|
|
||||||
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
|
||||||
|
Belum ada gambar maskot yang ditambahkan
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,35 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { IconSparkles } from '@tabler/icons-react';
|
import { IconSparkles } from '@tabler/icons-react';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
const dataText = [
|
const dataText = [
|
||||||
{
|
{ id: 1, title: "Santun", description: "Pelayanan ramah, penuh empati, sopan, dan beretika." },
|
||||||
id: 1,
|
{ id: 2, title: "Adaptif", description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif." },
|
||||||
title: "Santun",
|
{ id: 3, title: "Inovatif", description: "Berani menciptakan pembaruan dan ide-ide kreatif." },
|
||||||
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
|
{ id: 4, title: "Profesional", description: "Berpengetahuan luas, terampil, dan bertanggung jawab." },
|
||||||
},
|
{ id: 5, title: "Gesit", description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja." },
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "Adaptif",
|
|
||||||
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Inovatif",
|
|
||||||
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Profesional",
|
|
||||||
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: "Gesit",
|
|
||||||
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const letters = ["S", "I", "G", "A", "P"];
|
const letters = ["S", "I", "G", "A", "P"];
|
||||||
@@ -38,11 +18,14 @@ function MotoDesa() {
|
|||||||
return (
|
return (
|
||||||
<Box px={{ base: "md", md: "xl" }}>
|
<Box px={{ base: "md", md: "xl" }}>
|
||||||
<Stack align="center" gap="lg">
|
<Stack align="center" gap="lg">
|
||||||
|
{/* Page Title */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={800}
|
fw={800}
|
||||||
fz={{ base: "2rem", md: "2.8rem" }}
|
fz={{ base: 28, md: 36 }}
|
||||||
|
lh={{ base: 1.2, md: 1.3 }}
|
||||||
style={{
|
style={{
|
||||||
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
|
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
|
||||||
WebkitBackgroundClip: "text",
|
WebkitBackgroundClip: "text",
|
||||||
@@ -50,9 +33,10 @@ function MotoDesa() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Moto Desa Darmasaba
|
Moto Desa Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Letter Icons */}
|
||||||
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
|
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
|
||||||
{letters.map((letter, i) => (
|
{letters.map((letter, i) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -71,7 +55,7 @@ function MotoDesa() {
|
|||||||
backdropFilter: "blur(6px)",
|
backdropFilter: "blur(6px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text c="white" fw={800} fz="xl">
|
<Text c="white" fw={800} fz={{ base: 20, md: 24 }}>
|
||||||
{letter}
|
{letter}
|
||||||
</Text>
|
</Text>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@@ -79,6 +63,7 @@ function MotoDesa() {
|
|||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{/* Values Card */}
|
||||||
<Paper
|
<Paper
|
||||||
radius="lg"
|
radius="lg"
|
||||||
p="xl"
|
p="xl"
|
||||||
@@ -90,19 +75,22 @@ function MotoDesa() {
|
|||||||
>
|
>
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
||||||
{dataText.map((v) => (
|
{dataText.map((v) => (
|
||||||
<motion.div
|
<motion.div key={v.id} whileHover={{ scale: 1.02 }} transition={{ duration: 0.2 }}>
|
||||||
key={v.id}
|
|
||||||
whileHover={{ scale: 1.02 }}
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
>
|
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
|
{/* Section Title */}
|
||||||
<Flex align="center" gap="sm">
|
<Flex align="center" gap="sm">
|
||||||
<IconSparkles size={20} color={colors['blue-button']} />
|
<IconSparkles size={20} color={colors['blue-button']} />
|
||||||
<Text fw={700} fz={{ base: "lg", md: "xl" }} c={colors['blue-button']}>
|
<Title
|
||||||
|
order={3}
|
||||||
|
fw={700}
|
||||||
|
fz={{ base: 20, md: 24 }}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
>
|
||||||
{v.title}
|
{v.title}
|
||||||
</Text>
|
</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text fz={{ base: "sm", md: "md" }} c="gray.7">
|
{/* Body Text */}
|
||||||
|
<Text fz={{ base: 14, md: 16 }} lh={{ base: 1.5, md: 1.6 }} c="gray.7">
|
||||||
{v.description}
|
{v.description}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -111,16 +99,15 @@ function MotoDesa() {
|
|||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Motto Description */}
|
||||||
<Text
|
<Text
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={700}
|
fw={700}
|
||||||
fz={{ base: "md", md: "xl" }}
|
fz={{ base: 15, md: 20 }}
|
||||||
|
lh={{ base: 1.6, md: 1.8 }}
|
||||||
c="blue.8"
|
c="blue.8"
|
||||||
mt="md"
|
mt="md"
|
||||||
style={{
|
style={{ maxWidth: 720 }}
|
||||||
maxWidth: 720,
|
|
||||||
lineHeight: 1.6,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
"Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
|
"Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
|
||||||
<Text span fw={800} c="cyan.6">
|
<Text span fw={800} c="cyan.6">
|
||||||
|
|||||||
@@ -2,44 +2,45 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react';
|
import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function ProfilPerbekel() {
|
function ProfilPerbekel() {
|
||||||
const state = useProxy(stateProfileDesa.profilPerbekel)
|
const state = useProxy(stateProfileDesa.profilPerbekel);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
state.findUnique.load("edit")
|
state.findUnique.load("edit");
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const { data, loading } = state.findUnique
|
const { data, loading } = state.findUnique;
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={20} px="md">
|
<Box py={20} px="md">
|
||||||
<Skeleton h={500} radius="lg" />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px="md">
|
<Box px="md">
|
||||||
|
{/* ===== PAGE TITLE ===== */}
|
||||||
<Stack align="center" gap={0} mb={40}>
|
<Stack align="center" gap={0} mb={40}>
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw="bold"
|
|
||||||
fz={{ base: "2rem", md: "2.8rem" }}
|
|
||||||
style={{ letterSpacing: "0.5px" }}
|
style={{ letterSpacing: "0.5px" }}
|
||||||
>
|
>
|
||||||
Profil Perbekel
|
Profil Perbekel
|
||||||
</Text>
|
</Title>
|
||||||
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
|
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
|
||||||
|
{/* ========== FOTO PERBEKEL ========== */}
|
||||||
<Box>
|
<Box>
|
||||||
<Paper
|
<Paper
|
||||||
bg={colors['white-trans-1']}
|
bg={colors['white-trans-1']}
|
||||||
@@ -60,6 +61,8 @@ function ProfilPerbekel() {
|
|||||||
}}
|
}}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* ===== NAMA DAN JABATAN ===== */}
|
||||||
<Paper
|
<Paper
|
||||||
bg={colors['blue-button']}
|
bg={colors['blue-button']}
|
||||||
px="lg"
|
px="lg"
|
||||||
@@ -67,22 +70,23 @@ function ProfilPerbekel() {
|
|||||||
className="glass3"
|
className="glass3"
|
||||||
py={{ base: 20, md: 50 }}
|
py={{ base: 20, md: 50 }}
|
||||||
>
|
>
|
||||||
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
|
<Title order={3} c={colors['white-1']}>
|
||||||
Perbekel Desa Darmasaba
|
Perbekel Desa Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
<Text
|
|
||||||
|
<Title
|
||||||
|
order={2}
|
||||||
c={colors['white-1']}
|
c={colors['white-1']}
|
||||||
fw="bolder"
|
|
||||||
fz={{ base: "xl", md: "h2" }}
|
|
||||||
mt={8}
|
mt={8}
|
||||||
>
|
>
|
||||||
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
|
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
|
||||||
</Text>
|
</Title>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* ========== BIODATA & PENGALAMAN ========== */}
|
||||||
<Paper
|
<Paper
|
||||||
p="xl"
|
p="xl"
|
||||||
bg={colors['white-trans-1']}
|
bg={colors['white-trans-1']}
|
||||||
@@ -92,34 +96,39 @@ function ProfilPerbekel() {
|
|||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
|
|
||||||
|
{/* ===== BIODATA ===== */}
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={6}>
|
<Stack gap={6}>
|
||||||
<Stack align="center" gap={6}>
|
<Stack align="center" gap={6}>
|
||||||
<IconUser size={22} />
|
<IconUser size={22} />
|
||||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text>
|
<Title order={3}>Biodata</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "1rem", md: "1.2rem" }}
|
fz={{ base: "sm", md: "md" }}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
lh={1.6}
|
lh={1.7}
|
||||||
dangerouslySetInnerHTML={{ __html: data.biodata }}
|
dangerouslySetInnerHTML={{ __html: data.biodata }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word" }}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* ===== PENGALAMAN ===== */}
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={6}>
|
<Stack gap={6}>
|
||||||
<Stack align="center" gap={6}>
|
<Stack align="center" gap={6}>
|
||||||
<IconBriefcase size={22} />
|
<IconBriefcase size={22} />
|
||||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
|
<Title order={3}>Pengalaman</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "1rem", md: "1.2rem" }}
|
fz={{ base: "sm", md: "md" }}
|
||||||
ta="left"
|
ta="left"
|
||||||
lh={1.6}
|
lh={1.7}
|
||||||
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
|
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word" }}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -127,6 +136,7 @@ function ProfilPerbekel() {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
|
{/* ========== ORGANISASI & PROGRAM UNGGULAN ========== */}
|
||||||
<Paper
|
<Paper
|
||||||
p="xl"
|
p="xl"
|
||||||
bg={colors['white-trans-1']}
|
bg={colors['white-trans-1']}
|
||||||
@@ -136,35 +146,41 @@ function ProfilPerbekel() {
|
|||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
|
|
||||||
|
{/* ===== PENGALAMAN ORGANISASI ===== */}
|
||||||
<Box>
|
<Box>
|
||||||
<Stack align="center" gap={6} >
|
<Stack align="center" gap={6}>
|
||||||
<IconUsers size={22} />
|
<IconUsers size={22} />
|
||||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text>
|
<Title order={3}>Pengalaman Organisasi</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "1rem", md: "1.2rem" }}
|
fz={{ base: "sm", md: "md" }}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
lh={1.6}
|
lh={1.7}
|
||||||
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
|
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word" }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* ===== PROGRAM UNGGULAN ===== */}
|
||||||
<Box>
|
<Box>
|
||||||
<Stack align="center" gap={6} mb={6}>
|
<Stack align="center" gap={6} mb={6}>
|
||||||
<IconTargetArrow size={22} />
|
<IconTargetArrow size={22} />
|
||||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Program Kerja Unggulan</Text>
|
<Title order={3}>Program Kerja Unggulan</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Box px={10}>
|
<Box px={10}>
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "1rem", md: "1.2rem" }}
|
fz={{ base: "sm", md: "md" }}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
lh={1.6}
|
lh={1.7}
|
||||||
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
|
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word" }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -26,29 +26,32 @@ function SejarahDesa() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack align="center" gap="xl">
|
<Stack align="center" gap="xl">
|
||||||
|
{/* HEADER ICON + TITLE */}
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Center>
|
<Center>
|
||||||
<Image
|
<Image
|
||||||
src="/darmasaba-icon.png"
|
src="/darmasaba-icon.png"
|
||||||
alt="Ikon Desa Darmasaba"
|
alt="Ikon Desa Darmasaba"
|
||||||
w={{ base: 180, md: 260 }}
|
w={{ base: 160, md: 240 }}
|
||||||
radius="md"
|
radius="md"
|
||||||
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
|
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={700}
|
|
||||||
fz={{ base: '2rem', md: '2.8rem' }}
|
|
||||||
style={{ letterSpacing: '-0.5px' }}
|
style={{ letterSpacing: '-0.5px' }}
|
||||||
>
|
>
|
||||||
Sejarah Desa
|
Sejarah Desa
|
||||||
</Text>
|
</Title>
|
||||||
</Center>
|
</Center>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{/* CONTENT */}
|
||||||
<Paper
|
<Paper
|
||||||
p="xl"
|
p="xl"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
@@ -61,10 +64,14 @@ function SejarahDesa() {
|
|||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: 'md', md: 'lg' }}
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
lh={1.8}
|
lh={1.75}
|
||||||
ta="justify"
|
ta="justify"
|
||||||
style={{ color: '#2a2a2a', wordBreak: "break-word", whiteSpace: "normal" }}
|
c="dark.7"
|
||||||
|
style={{
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
}}
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ function SemuaPerbekel() {
|
|||||||
<Center py="xl">
|
<Center py="xl">
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<IconUser size={48} stroke={1.5} />
|
<IconUser size={48} stroke={1.5} />
|
||||||
<Title fw="bold" order={2}>Belum ada data Perbekel</Title>
|
<Title order={2} ta="center">Belum ada data Perbekel</Title>
|
||||||
<Text c="dimmed" fz="sm" ta="center">Data mantan Perbekel akan muncul di sini ketika sudah tersedia</Text>
|
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.6 }} ta="center">
|
||||||
|
Data mantan Perbekel akan muncul di sini ketika sudah tersedia
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@@ -38,17 +40,20 @@ function SemuaPerbekel() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack align="center" gap="lg">
|
<Stack align="center" gap="lg">
|
||||||
<Box>
|
<Title
|
||||||
<Text
|
order={1}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={900}
|
style={{
|
||||||
fz={{ base: "2rem", md: "2.5rem" }}
|
background: 'linear-gradient(45deg, blue, cyan)',
|
||||||
variant="gradient"
|
WebkitBackgroundClip: 'text',
|
||||||
gradient={{ from: "blue", to: "cyan", deg: 45 }}
|
WebkitTextFillColor: 'transparent',
|
||||||
>
|
}}
|
||||||
Perbekel Dari Masa ke Masa
|
fz={{ base: 28, md: 36 }}
|
||||||
</Text>
|
lh={{ base: 1.2, md: 1.3 }}
|
||||||
</Box>
|
fw={900}
|
||||||
|
>
|
||||||
|
Perbekel Dari Masa ke Masa
|
||||||
|
</Title>
|
||||||
|
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
|
||||||
{data.map((v: any, k: number) => (
|
{data.map((v: any, k: number) => (
|
||||||
@@ -59,9 +64,7 @@ function SemuaPerbekel() {
|
|||||||
withBorder
|
withBorder
|
||||||
p="lg"
|
p="lg"
|
||||||
bg="white"
|
bg="white"
|
||||||
style={{
|
style={{ transition: "all 250ms ease" }}
|
||||||
transition: "all 250ms ease",
|
|
||||||
}}
|
|
||||||
className="hover:shadow-xl hover:scale-[1.02]"
|
className="hover:shadow-xl hover:scale-[1.02]"
|
||||||
>
|
>
|
||||||
<Stack gap="md" align="center">
|
<Stack gap="md" align="center">
|
||||||
@@ -77,17 +80,17 @@ function SemuaPerbekel() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Stack gap={4} align="center">
|
<Stack gap={4} align="center">
|
||||||
<Text fw={700} fz="lg" ta="center">
|
<Title order={3} fz={{ base: 18, md: 20 }} ta="center" fw={700}>
|
||||||
{v.nama}
|
{v.nama}
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
<Text c="dimmed" fz="sm" ta="center">
|
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
|
||||||
{v.daerah}
|
{v.daerah}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text c="blue" fw={600} fz="sm" ta="center">
|
<Text c="blue" fw={600} fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
|
||||||
{v.periode}
|
{v.periode}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -34,60 +34,57 @@ function VisiMisiDesa() {
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* VISI */}
|
||||||
<Paper
|
<Paper
|
||||||
p="xl"
|
p="xl"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
withBorder
|
withBorder
|
||||||
w="100%"
|
w="100%"
|
||||||
style={{
|
style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
|
||||||
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={700}
|
|
||||||
fz={{ base: '2rem', md: '2.5rem' }}
|
|
||||||
mb="md"
|
mb="md"
|
||||||
>
|
>
|
||||||
Visi Desa
|
Visi Desa
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
fz={{ base: 'sm', md: 'md' }} // body text responsive
|
||||||
|
lh={1.7}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={500}
|
|
||||||
lh={1.6}
|
|
||||||
dangerouslySetInnerHTML={{ __html: data.visi }}
|
dangerouslySetInnerHTML={{ __html: data.visi }}
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* MISI */}
|
||||||
<Paper
|
<Paper
|
||||||
p="xl"
|
p="xl"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
withBorder
|
withBorder
|
||||||
w="100%"
|
w="100%"
|
||||||
style={{
|
style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
|
||||||
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw={700}
|
|
||||||
fz={{ base: '2rem', md: '2.5rem' }}
|
|
||||||
mb="md"
|
mb="md"
|
||||||
>
|
>
|
||||||
Misi Desa
|
Misi Desa
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
fz={{ base: 'sm', md: 'md' }} // body text responsive
|
||||||
fw={500}
|
lh={1.7}
|
||||||
lh={1.6}
|
ta="left"
|
||||||
dangerouslySetInnerHTML={{ __html: data.misi }}
|
dangerouslySetInnerHTML={{ __html: data.misi }}
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
import { Box, Flex, Group, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
@@ -30,196 +30,265 @@ function Page() {
|
|||||||
// Hasil akhir
|
// Hasil akhir
|
||||||
const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan;
|
const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan;
|
||||||
|
|
||||||
|
const formatCurrency = (value: number) => {
|
||||||
|
return new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
|
||||||
|
{/* Page Title */}
|
||||||
|
<Title
|
||||||
|
ta="center"
|
||||||
|
c={colors["blue-button"]}
|
||||||
|
fw="bold"
|
||||||
|
order={1}
|
||||||
|
fz={{ base: 28, md: 36 }}
|
||||||
|
>
|
||||||
Pendapatan Asli Desa
|
Pendapatan Asli Desa
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap="lg" justify="center">
|
<Stack gap="lg" justify="center">
|
||||||
<Paper bg={colors['white-1']} p="xl">
|
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }}>
|
||||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
{/* Pendapatan Card */}
|
{/* Pendapatan Card */}
|
||||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
<Box
|
||||||
<Stack gap={"xs"}>
|
p="md"
|
||||||
<Title order={3}>Pendapatan</Title>
|
style={{
|
||||||
{latestApb?.pendapatan?.map((item) => (
|
border: '1px solid #e9ecef',
|
||||||
<Box key={item.id}>
|
borderRadius: '8px',
|
||||||
<Grid>
|
height: '100%'
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
}}
|
||||||
<Text fz="md" fw={500}>{item.name}</Text>
|
>
|
||||||
</GridCol>
|
<Stack gap="md">
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<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
|
<Text
|
||||||
fz="md"
|
fz={{ base: 13, md: 14 }}
|
||||||
fw={500}
|
fw={500}
|
||||||
style={{
|
lh={1.4}
|
||||||
wordBreak: 'break-word',
|
c="black"
|
||||||
whiteSpace: 'normal',
|
style={{ wordBreak: 'break-word' }}
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(item.value)}
|
{item.name} {formatCurrency(item.value)}
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
</Flex>
|
||||||
</Grid>
|
</Box>
|
||||||
</Box>
|
))}
|
||||||
))}
|
</Stack>
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<Box
|
||||||
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
pt="sm"
|
||||||
</GridCol>
|
mt="auto"
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
style={{
|
||||||
<Text style={{
|
borderTop: `2px solid ${colors['blue-button']}`
|
||||||
wordBreak: 'break-word',
|
}}
|
||||||
whiteSpace: 'normal'
|
>
|
||||||
}} fz="xl" fw={700} c={colors['blue-button']}>
|
<Flex direction="column" gap={4}>
|
||||||
{new Intl.NumberFormat('id-ID', {
|
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
|
||||||
style: 'currency',
|
Total Pendapatan
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(totalPendapatan)}
|
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
<Text
|
||||||
</Grid>
|
fz={{ base: 18, md: 22 }}
|
||||||
|
fw={700}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
lh={1.4}
|
||||||
|
>
|
||||||
|
{formatCurrency(totalPendapatan)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Belanja Card */}
|
{/* Belanja Card */}
|
||||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
<Box
|
||||||
<Stack gap={"xs"}>
|
p="md"
|
||||||
<Title order={3}>Belanja</Title>
|
style={{
|
||||||
{latestApb?.belanja?.map((item) => (
|
border: '1px solid #e9ecef',
|
||||||
<Box key={item.id}>
|
borderRadius: '8px',
|
||||||
<Grid>
|
height: '100%'
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
}}
|
||||||
<Text fz="md" fw={500}>{item.name}</Text>
|
>
|
||||||
</GridCol>
|
<Stack gap="md">
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<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
|
<Text
|
||||||
fz="md"
|
fz={{ base: 13, md: 14 }}
|
||||||
fw={500}
|
fw={500}
|
||||||
style={{
|
lh={1.4}
|
||||||
wordBreak: 'break-word',
|
c="black"
|
||||||
whiteSpace: 'normal',
|
style={{ wordBreak: 'break-word' }}
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{new Intl.NumberFormat('id-ID', {
|
{item.name} {formatCurrency(item.value)}
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(item.value)}
|
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
</Group>
|
||||||
</Grid>
|
</Box>
|
||||||
</Box>
|
))}
|
||||||
))}
|
</Stack>
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
<Box
|
||||||
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
|
pt="sm"
|
||||||
</GridCol>
|
mt="auto"
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
style={{
|
||||||
<Text fz="xl" fw={700} c="orange">
|
borderTop: '2px solid orange'
|
||||||
{new Intl.NumberFormat('id-ID', {
|
}}
|
||||||
style: 'currency',
|
>
|
||||||
currency: 'IDR',
|
<Flex direction="column" gap={4}>
|
||||||
minimumFractionDigits: 0
|
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
|
||||||
}).format(totalBelanja)}
|
Total Belanja
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
<Text
|
||||||
</Grid>
|
fz={{ base: 18, md: 22 }}
|
||||||
|
fw={700}
|
||||||
|
c="orange"
|
||||||
|
lh={1.4}
|
||||||
|
>
|
||||||
|
{formatCurrency(totalBelanja)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Pembiayaan Card */}
|
{/* Pembiayaan Card */}
|
||||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
<Box
|
||||||
<Stack gap={"xs"}>
|
p="md"
|
||||||
<Title order={3}>Pembiayaan</Title>
|
style={{
|
||||||
{latestApb?.pembiayaan?.map((item) => (
|
border: '1px solid #e9ecef',
|
||||||
<Box key={item.id}>
|
borderRadius: '8px',
|
||||||
<Grid>
|
height: '100%'
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
}}
|
||||||
<Text fz="md" fw={500}>{item.name}</Text>
|
>
|
||||||
</GridCol>
|
<Stack gap="md">
|
||||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<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
|
<Text
|
||||||
fz="md"
|
fz={{ base: 13, md: 14 }}
|
||||||
fw={500}
|
fw={500}
|
||||||
style={{
|
lh={1.4}
|
||||||
wordBreak: 'break-word',
|
c="black"
|
||||||
whiteSpace: 'normal',
|
style={{ wordBreak: 'break-word' }}
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{new Intl.NumberFormat('id-ID', {
|
{item.name} {formatCurrency(item.value)}
|
||||||
style: 'currency',
|
|
||||||
currency: 'IDR',
|
|
||||||
minimumFractionDigits: 0
|
|
||||||
}).format(item.value)}
|
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
</Group>
|
||||||
</Grid>
|
</Box>
|
||||||
</Box>
|
))}
|
||||||
))}
|
</Stack>
|
||||||
<Grid>
|
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
<Box
|
||||||
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
|
pt="sm"
|
||||||
</GridCol>
|
mt="auto"
|
||||||
<GridCol span={{ base: 12, md: 6 }}>
|
style={{
|
||||||
<Text fz="xl" fw={700} c="green">
|
borderTop: '2px solid green'
|
||||||
{new Intl.NumberFormat('id-ID', {
|
}}
|
||||||
style: 'currency',
|
>
|
||||||
currency: 'IDR',
|
<Flex direction="column" gap={4}>
|
||||||
minimumFractionDigits: 0
|
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
|
||||||
}).format(totalPembiayaan)}
|
Total Pembiayaan
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
<Text
|
||||||
</Grid>
|
fz={{ base: 18, md: 22 }}
|
||||||
|
fw={700}
|
||||||
|
c="green"
|
||||||
|
lh={1.4}
|
||||||
|
>
|
||||||
|
{formatCurrency(totalPembiayaan)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* 🔽 Tambahan Ringkasan Anggaran */}
|
{/* Ringkasan Anggaran */}
|
||||||
<Paper bg={colors['white-1']} p="xl" shadow="sm" withBorder>
|
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="sm" withBorder>
|
||||||
<Title order={3} mb="md">Ringkasan Anggaran</Title>
|
<Title order={3} mb="md" fz={{ base: 18, md: 20 }}>
|
||||||
|
Ringkasan Anggaran
|
||||||
|
</Title>
|
||||||
<Table striped highlightOnHover withTableBorder>
|
<Table striped highlightOnHover withTableBorder>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th>Keterangan</Table.Th>
|
<Table.Th>
|
||||||
<Table.Th ta={"right"}>Jumlah</Table.Th>
|
<Text fz={{ base: 13, md: 14 }} fw={600}>Keterangan</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th ta="right">
|
||||||
|
<Text fz={{ base: 13, md: 14 }} fw={600}>Jumlah</Text>
|
||||||
|
</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td>Total Pendapatan</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td align="right">
|
<Text fz={{ base: 13, md: 14 }} lh={1.4}>Total Pendapatan</Text>
|
||||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPendapatan)}
|
</Table.Td>
|
||||||
|
<Table.Td ta="right">
|
||||||
|
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4}>
|
||||||
|
{formatCurrency(totalPendapatan)}
|
||||||
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td>Total Belanja</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td align="right" c="orange">
|
<Text fz={{ base: 13, md: 14 }} lh={1.4} c="orange">Total Belanja</Text>
|
||||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalBelanja)}
|
</Table.Td>
|
||||||
|
<Table.Td ta="right">
|
||||||
|
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="orange">
|
||||||
|
{formatCurrency(totalBelanja)}
|
||||||
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td>Total Pembiayaan</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td align="right" c="green">
|
<Text fz={{ base: 13, md: 14 }} lh={1.4} c="green">Total Pembiayaan</Text>
|
||||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPembiayaan)}
|
</Table.Td>
|
||||||
|
<Table.Td ta="right">
|
||||||
|
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="green">
|
||||||
|
{formatCurrency(totalPembiayaan)}
|
||||||
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
<Table.Tr>
|
<Table.Tr style={{ backgroundColor: '#f8f9fa' }}>
|
||||||
<Table.Td><b>Sisa Anggaran</b></Table.Td>
|
<Table.Td>
|
||||||
<Table.Td align="right" c={sisaAnggaran >= 0 ? "blue" : "red"}>
|
<Text fz={{ base: 14, md: 15 }} fw={700} lh={1.4}>Sisa Anggaran</Text>
|
||||||
<b>
|
</Table.Td>
|
||||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(sisaAnggaran)}
|
<Table.Td ta="right">
|
||||||
</b>
|
<Text
|
||||||
|
fz={{ base: 14, md: 15 }}
|
||||||
|
fw={700}
|
||||||
|
c={sisaAnggaran >= 0 ? colors['blue-button'] : "red"}
|
||||||
|
lh={1.4}
|
||||||
|
>
|
||||||
|
{formatCurrency(sisaAnggaran)}
|
||||||
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
|
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react';
|
import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
@@ -14,7 +14,7 @@ function Page() {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const state = useProxy(pasarDesaState.pasarDesa)
|
const state = useProxy(pasarDesaState.pasarDesa)
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -28,7 +28,6 @@ function Page() {
|
|||||||
pasarDesaState.kategoriProduk.findManyAll.load()
|
pasarDesaState.kategoriProduk.findManyAll.load()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Filter data based on selected category
|
|
||||||
const filteredData = selectedCategory
|
const filteredData = selectedCategory
|
||||||
? data?.filter(item =>
|
? data?.filter(item =>
|
||||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||||
@@ -39,7 +38,6 @@ function Page() {
|
|||||||
load(page, 4, debouncedSearch, selectedCategory || undefined)
|
load(page, 4, debouncedSearch, selectedCategory || undefined)
|
||||||
}, [page, debouncedSearch, selectedCategory])
|
}, [page, debouncedSearch, selectedCategory])
|
||||||
|
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
@@ -49,21 +47,22 @@ function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Title order={1} c={colors["blue-button"]} fw="bold">
|
||||||
Pasar Desa
|
Pasar Desa
|
||||||
</Text>
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius="lg"
|
||||||
placeholder='Cari Produk'
|
placeholder="Cari Produk"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
@@ -71,22 +70,15 @@ function Page() {
|
|||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
|
||||||
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan
|
<Text px={{ base: 'md', md: 100 }} pt={20} ta="justify" fz={{ base: 'sm', md: 'md' }}>
|
||||||
</Text>
|
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan dan memperkenalkan produk mereka.
|
||||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
|
||||||
dan memperkenalkan produk mereka.
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="lg">
|
||||||
<SimpleGrid
|
<SimpleGrid pb={30} cols={{ base: 1, md: 2 }}>
|
||||||
pb={30}
|
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Pilih Kategori"
|
placeholder="Pilih Kategori"
|
||||||
@@ -103,50 +95,58 @@ function Page() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 4 }}>
|
<SimpleGrid cols={{ base: 1, md: 4 }}>
|
||||||
{filteredData?.map((v, k) => {
|
{filteredData?.map((v, k) => (
|
||||||
return (
|
<Stack key={k}>
|
||||||
<Stack key={k}>
|
<motion.div
|
||||||
<motion.div
|
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
|
||||||
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileTap={{ scale: 0.8 }}
|
||||||
whileTap={{ scale: 0.8 }}
|
>
|
||||||
>
|
<Paper p="lg">
|
||||||
<Paper p={'lg'}>
|
<Image
|
||||||
<Image
|
radius="lg"
|
||||||
radius={'lg'}
|
src={v.image?.link || '/placeholder-product.jpg'}
|
||||||
src={v.image?.link || '/placeholder-product.jpg'}
|
alt={v.nama}
|
||||||
alt={v.nama}
|
h={200}
|
||||||
h={200}
|
w="100%"
|
||||||
w='100%'
|
style={{ objectFit: 'cover' }}
|
||||||
style={{ objectFit: 'cover' }}
|
loading="lazy"
|
||||||
loading="lazy"
|
/>
|
||||||
/>
|
<Text py="sm" fw="bold" fz={{ base: 'md', md: 'lg' }}>
|
||||||
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text>
|
{v.nama}
|
||||||
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text>
|
</Text>
|
||||||
<Flex py={10} gap={'md'}>
|
<Text fz={{ base: 'sm', md: 'md' }}>
|
||||||
<IconStarFilled size={20} color='#EBCB09' />
|
Rp {v.harga.toLocaleString('id-ID')}
|
||||||
<Text fz={'sm'} ml={2}>{v.rating}</Text>
|
</Text>
|
||||||
</Flex>
|
<Flex py="sm" gap="md">
|
||||||
<Flex justify={'space-between'} align={'center'}>
|
<IconStarFilled size={20} color="#EBCB09" />
|
||||||
<Box>
|
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
|
||||||
<Flex gap={'md'} align={'center'}>
|
{v.rating}
|
||||||
<IconMapPinFilled size={20} color='red' />
|
</Text>
|
||||||
<Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text>
|
</Flex>
|
||||||
</Flex>
|
<Flex justify="space-between" align="center">
|
||||||
</Box>
|
<Box>
|
||||||
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
|
<Flex gap="md" align="center">
|
||||||
</Flex>
|
<IconMapPinFilled size={20} color="red" />
|
||||||
</Paper>
|
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
|
||||||
</motion.div>
|
{v.alamatUsaha}
|
||||||
</Stack>
|
</Text>
|
||||||
)
|
</Flex>
|
||||||
})}
|
</Box>
|
||||||
|
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
|
||||||
|
</Flex>
|
||||||
|
</Paper>
|
||||||
|
</motion.div>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => load(newPage)}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
my="md"
|
my="md"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
|
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -32,10 +32,9 @@ interface ProgramKemiskinanData {
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const state = useProxy(programKemiskinanState)
|
const state = useProxy(programKemiskinanState)
|
||||||
|
|
||||||
// 🔧 Get valid statistics data with proper type checking
|
|
||||||
const statistikData = state.findMany.data
|
const statistikData = state.findMany.data
|
||||||
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
|
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
|
||||||
return !!item?.statistik &&
|
return !!item?.statistik &&
|
||||||
@@ -43,11 +42,11 @@ function Page() {
|
|||||||
item.statistik.jumlah !== undefined;
|
item.statistik.jumlah !== undefined;
|
||||||
})
|
})
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
tahun: Number(item.statistik.tahun) || 0, // Ensure tahun is a number
|
tahun: Number(item.statistik.tahun) || 0,
|
||||||
jumlah: Number(item.statistik.jumlah) || 0, // Ensure jumlah is a number
|
jumlah: Number(item.statistik.jumlah) || 0,
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.tahun - b.tahun)
|
.sort((a, b) => a.tahun - b.tahun)
|
||||||
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah)); // Remove any invalid entries
|
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -74,12 +73,18 @@ function Page() {
|
|||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Grid align='center'>
|
<Grid align='center'>
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Title
|
||||||
|
order={1}
|
||||||
|
c={colors["blue-button"]}
|
||||||
|
fw={"bold"}
|
||||||
|
fz={{ base: '28px', md: '32px' }}
|
||||||
|
lh={{ base: '1.2', md: '1.25' }}
|
||||||
|
>
|
||||||
Program Kemiskinan
|
Program Kemiskinan
|
||||||
</Text>
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -92,7 +97,15 @@ function Page() {
|
|||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Text fz={'h4'}>Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat</Text>
|
<Text
|
||||||
|
fz={{ base: '14px', md: '16px' }}
|
||||||
|
lh={{ base: '1.5', md: '1.6' }}
|
||||||
|
c="black"
|
||||||
|
ta={{ base: 'left', md: 'left' }}
|
||||||
|
pt={20}
|
||||||
|
>
|
||||||
|
Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'} justify='center'>
|
<Stack gap={'lg'} justify='center'>
|
||||||
@@ -106,8 +119,22 @@ function Page() {
|
|||||||
{state.findMany.data.map(v => {
|
{state.findMany.data.map(v => {
|
||||||
return (
|
return (
|
||||||
<Paper p={'xl'} key={v.id}>
|
<Paper p={'xl'} key={v.id}>
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
|
<Title
|
||||||
<Text fz={'lg'} c={'black'} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
|
order={3}
|
||||||
|
fw={'bold'}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fz={{ base: '18px', md: '20px' }}
|
||||||
|
lh={{ base: '1.3', md: '1.35' }}
|
||||||
|
>
|
||||||
|
{v.nama}
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
fz={{ base: '14px', md: '16px' }}
|
||||||
|
lh={{ base: '1.5', md: '1.6' }}
|
||||||
|
c={'black'}
|
||||||
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||||
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -124,7 +151,16 @@ function Page() {
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
<Paper p={'xl'}>
|
<Paper p={'xl'}>
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text>
|
<Title
|
||||||
|
order={3}
|
||||||
|
fw={'bold'}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fz={{ base: '18px', md: '20px' }}
|
||||||
|
lh={{ base: '1.3', md: '1.35' }}
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Statistik Kemiskinan Masyarakat
|
||||||
|
</Title>
|
||||||
<Box style={{ width: '100%', height: 'auto' }}>
|
<Box style={{ width: '100%', height: 'auto' }}>
|
||||||
{statistikData.length > 0 ? (
|
{statistikData.length > 0 ? (
|
||||||
<Box w="100%" style={{ overflowX: 'auto' }}>
|
<Box w="100%" style={{ overflowX: 'auto' }}>
|
||||||
@@ -162,7 +198,11 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
|
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
|
||||||
<Text c="dimmed">
|
<Text
|
||||||
|
fz={{ base: '12px', md: '14px' }}
|
||||||
|
c="dimmed"
|
||||||
|
lh={{ base: '1.4', md: '1.5' }}
|
||||||
|
>
|
||||||
{state.findMany.loading
|
{state.findMany.loading
|
||||||
? 'Memuat data statistik...'
|
? 'Memuat data statistik...'
|
||||||
: 'Belum ada data statistik yang tersedia atau data tidak valid'}
|
: 'Belum ada data statistik yang tersedia atau data tidak valid'}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
Loader,
|
Loader,
|
||||||
Paper,
|
Paper,
|
||||||
Stack,
|
Stack,
|
||||||
|
Tabs,
|
||||||
|
TabsList,
|
||||||
|
TabsTab,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
@@ -33,6 +36,8 @@ import { OrganizationChart } from 'primereact/organizationchart'
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useProxy } from 'valtio/utils'
|
import { useProxy } from 'valtio/utils'
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto'
|
import BackButton from '../../desa/layanan/_com/BackButto'
|
||||||
|
import { useMediaQuery } from '@mantine/hooks'
|
||||||
|
import { useTransitionRouter } from 'next-view-transitions'
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
@@ -51,11 +56,11 @@ export default function Page() {
|
|||||||
order={1}
|
order={1}
|
||||||
ta="center"
|
ta="center"
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
fz={{ base: 28, md: 36, lg: 44 }}
|
fz={{ base: 28, md: 36 }}
|
||||||
>
|
>
|
||||||
Struktur Organisasi & SK Pengurus BumDes
|
Struktur Organisasi & SK Pengurus BumDes
|
||||||
</Title>
|
</Title>
|
||||||
<Text ta="center" c="black" maw={800}>
|
<Text ta="center" c="black" maw={800} fz={{ base: 14, md: 16 }} lh={1.6}>
|
||||||
Gambaran visual peran dan pengurus yang ditugaskan. Gunakan kontrol
|
Gambaran visual peran dan pengurus yang ditugaskan. Gunakan kontrol
|
||||||
di bawah untuk mencari, memperbesar, atau melihat lebih jelas.
|
di bawah untuk mencari, memperbesar, atau melihat lebih jelas.
|
||||||
</Text>
|
</Text>
|
||||||
@@ -70,13 +75,14 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StrukturOrganisasiBumDes() {
|
function StrukturOrganisasiBumDes() {
|
||||||
|
const router = useTransitionRouter()
|
||||||
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
|
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
|
||||||
const chartContainerRef = useRef<HTMLDivElement>(null)
|
const chartContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const [scale, setScale] = useState(1)
|
const [scale, setScale] = useState(1)
|
||||||
const [isFullscreen, setFullscreen] = useState(false)
|
const [isFullscreen, setFullscreen] = useState(false)
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const debouncedSearch = useRef(
|
const debouncedSearch = useRef(
|
||||||
debounce((value: string) => setSearchQuery(value), 400)
|
debounce((value: string) => setSearchQuery(value), 1000)
|
||||||
).current
|
).current
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -92,8 +98,10 @@ function StrukturOrganisasiBumDes() {
|
|||||||
<Center py={48}>
|
<Center py={48}>
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Loader size="lg" />
|
<Loader size="lg" />
|
||||||
<Text fw={600}>Memuat struktur organisasi…</Text>
|
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.4}>
|
||||||
<Text c="dimmed" size="sm">
|
Memuat struktur organisasi…
|
||||||
|
</Text>
|
||||||
|
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={1.4}>
|
||||||
Mengambil data pengurus dan posisi. Mohon tunggu sebentar.
|
Mengambil data pengurus dan posisi. Mohon tunggu sebentar.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -119,10 +127,10 @@ function StrukturOrganisasiBumDes() {
|
|||||||
<Center>
|
<Center>
|
||||||
<IconUsers size={56} />
|
<IconUsers size={56} />
|
||||||
</Center>
|
</Center>
|
||||||
<Title order={3} mt="md">
|
<Title order={3} mt="md" ta="center">
|
||||||
Data pengurus belum tersedia
|
Data pengurus belum tersedia
|
||||||
</Title>
|
</Title>
|
||||||
<Text c="dimmed" mt="xs">
|
<Text c="dimmed" mt="xs" fz={{ base: 12, md: 14 }} lh={1.4}>
|
||||||
Belum ada data pengurus yang tercatat untuk BumDes.
|
Belum ada data pengurus yang tercatat untuk BumDes.
|
||||||
</Text>
|
</Text>
|
||||||
<Group justify="center" mt="lg">
|
<Group justify="center" mt="lg">
|
||||||
@@ -218,155 +226,300 @@ function StrukturOrganisasiBumDes() {
|
|||||||
return (
|
return (
|
||||||
<Stack align="center" mt="xl">
|
<Stack align="center" mt="xl">
|
||||||
{/* 🧭 Kontrol atas */}
|
{/* 🧭 Kontrol atas */}
|
||||||
<Paper shadow="xs" p="md" radius="md" bg={colors['blue-button']}>
|
<Paper
|
||||||
<Group gap="sm" wrap="wrap" justify="center">
|
shadow="xs"
|
||||||
<TextInput
|
w={{
|
||||||
placeholder="Cari nama atau jabatan..."
|
base: '100%', // Mobile: 100%
|
||||||
leftSection={<IconSearch size={16} />}
|
sm: '40%', // Tablet: 95%
|
||||||
onChange={(e) => debouncedSearch(e.target.value)}
|
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={{
|
styles={{
|
||||||
input: {
|
panel: { display: 'none' },
|
||||||
minWidth: 250,
|
tab: {
|
||||||
|
color: colors['blue-button'],
|
||||||
|
backgroundColor: colors['blue-button-2'],
|
||||||
|
border: 'none',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
padding: '6px 12px',
|
||||||
|
minHeight: 'auto',
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
style={{ width: '100%' }} // 👈 penting
|
||||||
<Group gap="xs">
|
>
|
||||||
<Button
|
<TabsList
|
||||||
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={{
|
style={{
|
||||||
fontSize: 14,
|
display: 'flex',
|
||||||
fontWeight: 700,
|
overflowX: 'auto',
|
||||||
borderRadius: '8px',
|
overflowY: 'hidden',
|
||||||
minWidth: 70,
|
gap: '4px',
|
||||||
textAlign: 'center',
|
paddingBottom: '4px',
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
WebkitOverflowScrolling: 'touch',
|
||||||
|
scrollbarWidth: 'thin',
|
||||||
|
msOverflowStyle: '-ms-autohiding-scrollbar',
|
||||||
|
maxWidth: '100%',
|
||||||
|
scrollBehavior: 'smooth', // 👈 smooth scroll
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Math.round(scale * 100)}%
|
<TabsTab
|
||||||
</Box>
|
value="zoom-out"
|
||||||
<Button
|
onClick={handleZoomOut}
|
||||||
variant="light"
|
leftSection={<IconZoomOut size={16} />}
|
||||||
bg={colors['blue-button-2']}
|
style={{ flexShrink: 0 }}
|
||||||
c={colors['blue-button']}
|
>
|
||||||
size="sm"
|
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
|
||||||
onClick={handleZoomIn}
|
</TabsTab>
|
||||||
leftSection={<IconZoomIn size={16} />}
|
|
||||||
>
|
<Box
|
||||||
Zoom In
|
bg={colors['blue-button-2']}
|
||||||
</Button>
|
c={colors['blue-button']}
|
||||||
<Button
|
px={12}
|
||||||
variant="light"
|
py={6}
|
||||||
bg={colors['blue-button-2']}
|
style={{
|
||||||
c={colors['blue-button']}
|
fontWeight: 700,
|
||||||
size="sm"
|
borderRadius: '6px',
|
||||||
onClick={resetZoom}
|
minWidth: 60,
|
||||||
>
|
textAlign: 'center',
|
||||||
Reset
|
display: 'flex',
|
||||||
</Button>
|
alignItems: 'center',
|
||||||
<Button
|
justifyContent: 'center',
|
||||||
variant="light"
|
flexShrink: 0,
|
||||||
bg={colors['blue-button-2']}
|
whiteSpace: 'nowrap',
|
||||||
c={colors['blue-button']}
|
}}
|
||||||
size="sm"
|
>
|
||||||
onClick={toggleFullscreen}
|
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
|
||||||
leftSection={
|
{Math.round(scale * 100)}%
|
||||||
isFullscreen ? (
|
</Text>
|
||||||
<IconArrowsMinimize size={16} />
|
</Box>
|
||||||
) : (
|
|
||||||
<IconArrowsMaximize size={16} />
|
<TabsTab
|
||||||
)
|
value="zoom-in"
|
||||||
}
|
onClick={handleZoomIn}
|
||||||
>
|
leftSection={<IconZoomIn size={16} />}
|
||||||
Fullscreen
|
style={{ flexShrink: 0 }}
|
||||||
</Button>
|
>
|
||||||
</Group>
|
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
|
||||||
</Group>
|
</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>
|
</Paper>
|
||||||
|
|
||||||
{/* 📊 Chart Container */}
|
{/* 🧩 Chart Container */}
|
||||||
<Center style={{ width: '100%' }}>
|
<Center style={{ width: '100%' }}>
|
||||||
<Box
|
<Box
|
||||||
ref={chartContainerRef}
|
ref={chartContainerRef}
|
||||||
style={{
|
style={{
|
||||||
overflowX: 'auto',
|
borderRadius: '12px',
|
||||||
overflowY: 'auto',
|
overflowX: 'auto',
|
||||||
width: '100%',
|
overflowY: 'auto',
|
||||||
padding: '32px 16px',
|
width: '100%',
|
||||||
transition: 'transform 0.2s ease',
|
maxWidth: '100%',
|
||||||
transform: `scale(${scale})`,
|
padding: '32px 16px',
|
||||||
transformOrigin: 'center top',
|
transition: 'transform 0.2s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OrganizationChart
|
<Box style={{
|
||||||
value={chartData}
|
|
||||||
nodeTemplate={(node) => <NodeCard node={node} />}
|
|
||||||
className="p-organizationchart p-organizationchart-horizontal"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Center>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NodeCard({ node }: any) {
|
transform: `scale(${scale})`,
|
||||||
const imageSrc = node?.data?.image || '/img/default.png'
|
transformOrigin: 'center top',
|
||||||
const name = node?.data?.name || 'Tanpa Nama'
|
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
|
||||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
|
||||||
const description = node?.data?.description || ''
|
}}>
|
||||||
|
<OrganizationChart
|
||||||
return (
|
value={chartData}
|
||||||
<Transition mounted transition="pop" duration={300}>
|
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
|
||||||
{(styles) => (
|
className="p-organizationchart p-organizationchart-horizontal"
|
||||||
<Card
|
/>
|
||||||
shadow="md"
|
</Box>
|
||||||
radius="xl"
|
</Box>
|
||||||
withBorder
|
</Center>
|
||||||
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>
|
|
||||||
</Stack>
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ function Page() {
|
|||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" mt={4} >
|
<Text px={{ base: 'md', md: 100 }} pt={20} ta={"justify"} fz="md" mt={4} >
|
||||||
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ function Page() {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} fz="lg" >Judul</Text>
|
<Text fw={600} fz="lg" >Judul</Text>
|
||||||
<Text fz="sm" c="dimmed">{data.judul || '-'}</Text>
|
<Text fz="sm" c="black">{data.judul || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} fz="lg" >Tanggal</Text>
|
<Text fw={600} fz="lg" >Tanggal</Text>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text fz="sm" c="black">
|
||||||
{data.tanggalWaktu
|
{data.tanggalWaktu
|
||||||
? new Date(data.tanggalWaktu).toLocaleString('id-ID', { dateStyle: 'full', timeStyle: 'short' })
|
? new Date(data.tanggalWaktu).toLocaleString('id-ID', { dateStyle: 'full', timeStyle: 'short' })
|
||||||
: '-'}
|
: '-'}
|
||||||
@@ -89,7 +89,7 @@ function Page() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} fz="lg" >Lokasi</Text>
|
<Text fw={600} fz="lg" >Lokasi</Text>
|
||||||
<Text fz="sm" c="dimmed">{data.lokasi || '-'}</Text>
|
<Text fz="sm" c="black">{data.lokasi || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
@@ -120,7 +120,7 @@ function Page() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} fz="lg" >Kronologi</Text>
|
<Text fw={600} fz="lg" >Kronologi</Text>
|
||||||
<Text fz="sm" c="dimmed" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
|
<Text fz="sm" c="black" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
@@ -136,11 +136,11 @@ function Page() {
|
|||||||
radius="md"
|
radius="md"
|
||||||
shadow="xs"
|
shadow="xs"
|
||||||
withBorder
|
withBorder
|
||||||
bg="dark.5"
|
bg={colors['blue-button-1']}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz="sm"
|
||||||
c="dimmed"
|
c="black"
|
||||||
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
|
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
|
import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
|
||||||
@@ -8,11 +8,10 @@ import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(tipsKeamananState)
|
const state = useProxy(tipsKeamananState);
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
@@ -22,84 +21,114 @@ function Page() {
|
|||||||
} = state.findMany;
|
} = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, debouncedSearch)
|
load(page, 3, debouncedSearch);
|
||||||
}, [page, debouncedSearch])
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Title
|
||||||
|
order={1}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
style={{ lineHeight: '1.2' }}
|
||||||
|
>
|
||||||
Tips Keamanan
|
Tips Keamanan
|
||||||
</Text>
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius="lg"
|
||||||
placeholder='Cari Tips'
|
placeholder="Cari Tips"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
w={{ base: "50%", md: "100%" }}
|
w={{ base: '50%', md: '100%' }}
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
|
||||||
|
<Text
|
||||||
|
px={{ base: 'md', md: 100 }}
|
||||||
|
ta="justify"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={{ base: '1.5', md: '1.6' }}
|
||||||
|
mt="sm"
|
||||||
|
>
|
||||||
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal).
|
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal).
|
||||||
</Text>
|
</Text>
|
||||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
<Text
|
||||||
|
px={{ base: 'md', md: 100 }}
|
||||||
|
ta="justify"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh={{ base: '1.5', md: '1.6' }}
|
||||||
|
mt="xs"
|
||||||
|
>
|
||||||
Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<Stack gap="lg">
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
pb={10}
|
pb="10"
|
||||||
cols={{
|
cols={{ base: 1, md: 3 }}
|
||||||
base: 1,
|
>
|
||||||
md: 3,
|
{data.map((v, k) => (
|
||||||
}}>
|
<Paper radius={10} key={k} bg={colors['white-trans-1']}>
|
||||||
{data.map((v, k) => {
|
<Stack gap="xs">
|
||||||
return (
|
<Center p="10">
|
||||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
<Image
|
||||||
<Stack gap={'xs'}>
|
src={v.image?.link}
|
||||||
<Center p={10}>
|
radius={10}
|
||||||
<Image src={v.image?.link} radius={10} loading="lazy"
|
loading="lazy"
|
||||||
alt='' />
|
alt=""
|
||||||
</Center>
|
/>
|
||||||
<Box px={'xl'}>
|
</Center>
|
||||||
<Box pb={20}>
|
<Box px="xl">
|
||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
<Box pb="20">
|
||||||
{v.judul}
|
<Title
|
||||||
</Text>
|
order={3}
|
||||||
<Box>
|
c={colors['blue-button']}
|
||||||
<Text pb={10} fz={"md"} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
style={{ lineHeight: '1.3' }}
|
||||||
</Box>
|
>
|
||||||
|
{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>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Box>
|
||||||
</Paper>
|
</Stack>
|
||||||
)
|
</Paper>
|
||||||
})}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => load(newPage)}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
my="md"
|
my="md"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Card, Divider, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
|
import { Box, Button, Card, Divider, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
|
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -17,9 +17,8 @@ function ArtikelKesehatanPage() {
|
|||||||
|
|
||||||
if (!state.findMany.data) {
|
if (!state.findMany.data) {
|
||||||
return (
|
return (
|
||||||
<Box py="xl" ta="center">
|
<Box py="lg">
|
||||||
<Loader size="lg" color={colors['blue-button']} />
|
<Skeleton h={500} radius="lg" />
|
||||||
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
|
import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -17,12 +17,8 @@ function FasilitasKesehatanPage() {
|
|||||||
|
|
||||||
if (!state.findMany.data) {
|
if (!state.findMany.data) {
|
||||||
return (
|
return (
|
||||||
<Box py="xl" px="md">
|
<Box py="lg">
|
||||||
<Stack gap="md">
|
<Skeleton h={500} radius="lg" />
|
||||||
<Skeleton height={80} radius="lg" />
|
|
||||||
<Skeleton height={80} radius="lg" />
|
|
||||||
<Skeleton height={80} radius="lg" />
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -31,14 +27,20 @@ function FasilitasKesehatanPage() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%">
|
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%">
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
<Title
|
||||||
|
order={1}
|
||||||
|
ta="center"
|
||||||
|
fw={700}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
style={{ lineHeight: '1.2' }}
|
||||||
|
>
|
||||||
Fasilitas Kesehatan
|
Fasilitas Kesehatan
|
||||||
</Text>
|
</Title>
|
||||||
<Divider size="sm" color={colors['blue-button']} />
|
<Divider size="sm" color={colors['blue-button']} />
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{state.findMany.data.length === 0 ? (
|
{state.findMany.data.length === 0 ? (
|
||||||
<Box py="xl" ta="center">
|
<Box py="xl" ta="center">
|
||||||
<Text fz="lg" c="dimmed">
|
<Text size="lg" c="dimmed" lh="1.5">
|
||||||
Belum ada fasilitas kesehatan yang tersedia
|
Belum ada fasilitas kesehatan yang tersedia
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -65,22 +67,20 @@ function FasilitasKesehatanPage() {
|
|||||||
>
|
>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Text fw={700} fz="lg" c={colors['blue-button']}>
|
<Title order={3} fw={700} c={colors['blue-button']} lh="1.3" />
|
||||||
{item.name}
|
<Badge color="blue" radius="sm" variant="light" size="xs">
|
||||||
</Text>
|
|
||||||
<Badge color="blue" radius="sm" variant="light" fz="xs">
|
|
||||||
Aktif
|
Aktif
|
||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconMapPin size={18} stroke={1.5} />
|
<IconMapPin size={18} stroke={1.5} />
|
||||||
<Text fz="sm">
|
<Text size="sm" lh="1.5">
|
||||||
{item.informasiumum.alamat}
|
{item.informasiumum.alamat}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconClock size={18} stroke={1.5} />
|
<IconClock size={18} stroke={1.5} />
|
||||||
<Text fz="sm">
|
<Text size="sm" lh="1.5">
|
||||||
{item.informasiumum.jamOperasional}
|
{item.informasiumum.jamOperasional}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import { Box, Center, ColorSwatch, Flex, Paper, SimpleGrid, Skeleton, Stack, Tex
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
// import { useRouter } from 'next/navigation';
|
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -17,7 +15,6 @@ import GrafikPenyakit from './grafik-penyakit/page';
|
|||||||
import JadwalKegiatan from './jadwal-kegiatan-page/page';
|
import JadwalKegiatan from './jadwal-kegiatan-page/page';
|
||||||
import ArtikelKesehatanPage from './artikel-kesehatan-page/page';
|
import ArtikelKesehatanPage from './artikel-kesehatan-page/page';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
type DataTahunan = {
|
type DataTahunan = {
|
||||||
tahun: string;
|
tahun: string;
|
||||||
@@ -31,7 +28,6 @@ function Page() {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Count occurrences per year
|
|
||||||
const countByYear = (data: any[], dateField: string) => {
|
const countByYear = (data: any[], dateField: string) => {
|
||||||
const counts: Record<string, number> = {};
|
const counts: Record<string, number> = {};
|
||||||
data?.forEach(item => {
|
data?.forEach(item => {
|
||||||
@@ -43,28 +39,23 @@ function Page() {
|
|||||||
|
|
||||||
const statePersentase = useProxy(persentasekelahiran);
|
const statePersentase = useProxy(persentasekelahiran);
|
||||||
const [chartData, setChartData] = useState<DataTahunan[]>([]);
|
const [chartData, setChartData] = useState<DataTahunan[]>([]);
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)');
|
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data
|
statePersentase.kelahiran.findMany.load(1, 1000);
|
||||||
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data
|
statePersentase.kematian.findMany.load(1, 1000);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
|
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
|
||||||
// Count kelahiran and kematian by year
|
|
||||||
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
|
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
|
||||||
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
|
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
|
||||||
|
|
||||||
// Get all unique years
|
|
||||||
const allYears = new Set([
|
const allYears = new Set([
|
||||||
...Object.keys(kelahiranByYear),
|
...Object.keys(kelahiranByYear),
|
||||||
...Object.keys(kematianByYear)
|
...Object.keys(kematianByYear)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create data structure for the chart
|
|
||||||
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
|
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
|
||||||
acc[year] = {
|
acc[year] = {
|
||||||
tahun: year,
|
tahun: year,
|
||||||
@@ -93,32 +84,44 @@ function Page() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
|
||||||
|
{/* Page Title */}
|
||||||
|
<Title
|
||||||
|
order={1}
|
||||||
|
ta="center"
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fw="bold"
|
||||||
|
lh={1.2}
|
||||||
|
>
|
||||||
Data Kesehatan Masyarakat Puskesmas Darmasaba
|
Data Kesehatan Masyarakat Puskesmas Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<Stack gap="lg">
|
||||||
{/* Bar Chart Kematian Kelahiran */}
|
{/* Bar Chart Kematian Kelahiran */}
|
||||||
<Box>
|
<Box>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
<Paper p="xl" bg={colors['white-trans-1']}>
|
||||||
<Box pb={30}>
|
<Box pb={30}>
|
||||||
<Title order={2} mb="md">Data Kematian dan Kelahiran</Title>
|
<Title order={2} mb="md" ta="center">
|
||||||
|
Data Kematian dan Kelahiran
|
||||||
|
</Title>
|
||||||
|
|
||||||
{chartData.length === 0 ? (
|
{chartData.length === 0 ? (
|
||||||
<Text c="dimmed" ta="center" py="xl">
|
<Text c="dimmed" ta="center" py="xl" size="md">
|
||||||
Belum ada data yang tersedia untuk ditampilkan
|
Belum ada data yang tersedia untuk ditampilkan
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Main Chart */}
|
|
||||||
<Center>
|
<Center>
|
||||||
<Box h={400}>
|
<Box h={400}>
|
||||||
<Box style={{
|
<Box style={{
|
||||||
width: isMobile ? '90vw' : isTablet ? '700px' : '800px',
|
width: isMobile ? '90vw' : '800px',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
margin: '0 auto'
|
margin: '0 auto'
|
||||||
}}>
|
}}>
|
||||||
@@ -137,16 +140,21 @@ function Page() {
|
|||||||
</Center>
|
</Center>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
|
||||||
|
<Flex pb={30} justify="center" gap="xl" align="center" wrap="wrap">
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
<Flex gap={{ base: 'xs', md: 'sm' }} align="center">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text>
|
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
|
||||||
<ColorSwatch color="#EF3E3E" size={30} />
|
Angka Kematian
|
||||||
|
</Text>
|
||||||
|
<ColorSwatch color="#EF3E3E" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
<Flex gap={{ base: 'xs', md: 'sm' }} align="center">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran</Text>
|
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
|
||||||
|
Angka Kelahiran
|
||||||
|
</Text>
|
||||||
<ColorSwatch color="#3290CA" size={30} />
|
<ColorSwatch color="#3290CA" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -154,20 +162,13 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<GrafikPenyakit />
|
<GrafikPenyakit />
|
||||||
{/* Artikel Kesehatan */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 3,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Fasilitas Kesehatan */}
|
|
||||||
<FasilitasKesehatan />
|
<FasilitasKesehatan />
|
||||||
{/* Jadwal Kegiatan */}
|
|
||||||
<JadwalKegiatan />
|
<JadwalKegiatan />
|
||||||
{/* Artikel Kesehatan */}
|
|
||||||
<ArtikelKesehatanPage />
|
<ArtikelKesehatanPage />
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -51,10 +51,15 @@ function DetailInfoWabahPenyakitUser() {
|
|||||||
shadow="sm"
|
shadow="sm"
|
||||||
>
|
>
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* Judul */}
|
{/* Judul — H1 */}
|
||||||
<Text fz="xl" fw="bold" c={colors['blue-button']} ta="center">
|
<Title
|
||||||
|
order={1}
|
||||||
|
fw="bold"
|
||||||
|
c={colors['blue-button']}
|
||||||
|
ta="center"
|
||||||
|
>
|
||||||
{data.name || 'Kontak Darurat'}
|
{data.name || 'Kontak Darurat'}
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
{/* Gambar */}
|
{/* Gambar */}
|
||||||
{data.image?.link && (
|
{data.image?.link && (
|
||||||
@@ -70,10 +75,16 @@ function DetailInfoWabahPenyakitUser() {
|
|||||||
|
|
||||||
{/* Deskripsi */}
|
{/* Deskripsi */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
{/* Section Title — H2 */}
|
||||||
|
<Title order={2} fw="bold" fz={{ base: 'md', md: 'lg' }} lh="1.4">
|
||||||
|
Deskripsi
|
||||||
|
</Title>
|
||||||
<Box pl={20}>
|
<Box pl={20}>
|
||||||
<Text
|
<Text
|
||||||
fz="md"
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
lh="1.6"
|
||||||
|
c="dimmed"
|
||||||
|
ta={"justify"}
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Button, Center, Flex, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react';
|
import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -53,15 +64,14 @@ export default function DetailPosyanduUser() {
|
|||||||
mx="auto"
|
mx="auto"
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{/* Header */}
|
{/* Header — Dijadikan Title */}
|
||||||
<Text
|
<Title
|
||||||
ta="center"
|
ta="center"
|
||||||
fz={{ base: '1.8rem', md: '2.2rem' }}
|
order={1}
|
||||||
fw={700}
|
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
>
|
>
|
||||||
{data.name || 'Posyandu Desa'}
|
{data.name || 'Posyandu Desa'}
|
||||||
</Text>
|
</Title>
|
||||||
|
|
||||||
{/* Gambar */}
|
{/* Gambar */}
|
||||||
{data.image?.link ? (
|
{data.image?.link ? (
|
||||||
@@ -78,7 +88,7 @@ export default function DetailPosyanduUser() {
|
|||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<Center>
|
<Center>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
|
||||||
Tidak ada gambar
|
Tidak ada gambar
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -88,7 +98,11 @@ export default function DetailPosyanduUser() {
|
|||||||
<Stack gap="sm" mt="md">
|
<Stack gap="sm" mt="md">
|
||||||
<Flex align="center" gap="xs">
|
<Flex align="center" gap="xs">
|
||||||
<IconPhone size={18} stroke={1.5} />
|
<IconPhone size={18} stroke={1.5} />
|
||||||
<Text fz="sm" c="dimmed">
|
<Text
|
||||||
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
|
c="dimmed"
|
||||||
|
lh={{ base: 1.5, md: 1.6 }}
|
||||||
|
>
|
||||||
{data.nomor || 'Nomor tidak tersedia'}
|
{data.nomor || 'Nomor tidak tersedia'}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -96,8 +110,9 @@ export default function DetailPosyanduUser() {
|
|||||||
<Flex align="center" gap="xs">
|
<Flex align="center" gap="xs">
|
||||||
<IconCalendar size={18} stroke={1.5} />
|
<IconCalendar size={18} stroke={1.5} />
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
|
lh={{ base: 1.5, md: 1.6 }}
|
||||||
dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }}
|
dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }}
|
||||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
@@ -106,9 +121,9 @@ export default function DetailPosyanduUser() {
|
|||||||
<Flex align="center" gap="xs">
|
<Flex align="center" gap="xs">
|
||||||
<IconInfoCircle size={18} stroke={1.5} />
|
<IconInfoCircle size={18} stroke={1.5} />
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz={{ base: 'xs', md: 'sm' }}
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
lh={1.6}
|
lh={{ base: 1.5, md: 1.6 }}
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
|
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from "@mantine/core";
|
import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||||
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
|
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
|
||||||
import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
|
import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -12,8 +12,8 @@ import { useTransitionRouter } from "next-view-transitions";
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
const state = useProxy(posyandustate);
|
const state = useProxy(posyandustate);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
@@ -35,14 +35,13 @@ export default function Page() {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
|
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
ta="left"
|
ta="left"
|
||||||
fz={{ base: "1.8rem", md: "2.5rem" }}
|
|
||||||
c={colors["blue-button"]}
|
c={colors["blue-button"]}
|
||||||
fw="bold"
|
|
||||||
>
|
>
|
||||||
Posyandu Desa Darmasaba
|
Posyandu Desa Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Cari posyandu berdasarkan nama..."
|
placeholder="Cari posyandu berdasarkan nama..."
|
||||||
aria-label="Pencarian Posyandu"
|
aria-label="Pencarian Posyandu"
|
||||||
@@ -55,6 +54,7 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Title c={"dimmed"} order={3} ta={"center"}>Belum ada posyandu yang terdaftar</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -64,14 +64,13 @@ export default function Page() {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
|
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
|
||||||
<Text
|
<Title
|
||||||
|
order={1}
|
||||||
ta="left"
|
ta="left"
|
||||||
fz={{ base: "1.8rem", md: "2.5rem" }}
|
|
||||||
c={colors["blue-button"]}
|
c={colors["blue-button"]}
|
||||||
fw="bold"
|
|
||||||
>
|
>
|
||||||
Posyandu Desa Darmasaba
|
Posyandu Desa Darmasaba
|
||||||
</Text>
|
</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Cari posyandu berdasarkan nama..."
|
placeholder="Cari posyandu berdasarkan nama..."
|
||||||
aria-label="Pencarian Posyandu"
|
aria-label="Pencarian Posyandu"
|
||||||
@@ -116,9 +115,9 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Text c={colors["blue-button"]} fw="bold" fz="lg" lineClamp={1}>
|
<Title order={3} c={colors["blue-button"]} fw="bold" lineClamp={1}>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Title>
|
||||||
<Badge color="blue" variant="light" size="sm" radius="sm">
|
<Badge color="blue" variant="light" size="sm" radius="sm">
|
||||||
Aktif
|
Aktif
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -137,7 +136,7 @@ export default function Page() {
|
|||||||
<Flex align="flex-start" gap="xs">
|
<Flex align="flex-start" gap="xs">
|
||||||
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
<Text fz={{ base: "xs", md: "sm" }} c="dimmed" lh={1.4}>
|
||||||
{v.nomor || "Tidak tersedia"}
|
{v.nomor || "Tidak tersedia"}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -146,7 +145,7 @@ export default function Page() {
|
|||||||
<Flex align="flex-start" gap="xs">
|
<Flex align="flex-start" gap="xs">
|
||||||
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
<Text fz={{ base: "xs", md: "sm" }} c="dimmed" lh={1.4}>
|
||||||
<strong>Jadwal:</strong>{" "}
|
<strong>Jadwal:</strong>{" "}
|
||||||
<span
|
<span
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
@@ -159,7 +158,7 @@ export default function Page() {
|
|||||||
<Flex align="flex-start" gap="xs">
|
<Flex align="flex-start" gap="xs">
|
||||||
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz={{ base: "xs", md: "sm" }}
|
||||||
lh={1.5}
|
lh={1.5}
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||||
@@ -168,7 +167,9 @@ export default function Page() {
|
|||||||
truncate="end"
|
truncate="end"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>Detail</Button>
|
<Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
@@ -191,11 +192,11 @@ export default function Page() {
|
|||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Flex align="center" gap="xs">
|
<Flex align="center" gap="xs">
|
||||||
<IconInfoCircle size={22} color={colors["blue-button"]} />
|
<IconInfoCircle size={22} color={colors["blue-button"]} />
|
||||||
<Text fz="lg" fw="bold" c={colors["blue-button"]}>
|
<Title order={2} c={colors["blue-button"]}>
|
||||||
Layanan Utama Posyandu
|
Layanan Utama Posyandu
|
||||||
</Text>
|
</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
<List spacing="xs" size="sm" center>
|
<List spacing="xs" fz={{ base: "xs", md: "sm" }} center>
|
||||||
<ListItem>Penimbangan bayi dan balita</ListItem>
|
<ListItem>Penimbangan bayi dan balita</ListItem>
|
||||||
<ListItem>Pemantauan status gizi</ListItem>
|
<ListItem>Pemantauan status gizi</ListItem>
|
||||||
<ListItem>Imunisasi dasar lengkap</ListItem>
|
<ListItem>Imunisasi dasar lengkap</ListItem>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ function StrukturOrganisasiPPID() {
|
|||||||
const debouncedSearch = useRef(
|
const debouncedSearch = useRef(
|
||||||
debounce((value: string) => {
|
debounce((value: string) => {
|
||||||
setSearchQuery(value)
|
setSearchQuery(value)
|
||||||
}, 400)
|
}, 1000)
|
||||||
).current
|
).current
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text } from "@mantine/core";
|
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text, Title } from "@mantine/core";
|
||||||
import { useMediaQuery } from "@mantine/hooks";
|
import { useMediaQuery } from "@mantine/hooks";
|
||||||
import { IconArrowRight, IconAward } from "@tabler/icons-react";
|
import { IconArrowRight, IconAward } from "@tabler/icons-react";
|
||||||
import { useTransitionRouter } from "next-view-transitions";
|
import { useTransitionRouter } from "next-view-transitions";
|
||||||
@@ -20,11 +20,21 @@ export default function Page() {
|
|||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconAward size={40} color={colors["blue-button"]} />
|
<IconAward size={40} color={colors["blue-button"]} />
|
||||||
<Text fz={{ base: "2rem", md: "3.2rem" }} fw={800} variant="gradient" gradient={{ from: "#1C6EA4", to: "#69BFF8" }}>
|
<Title
|
||||||
|
order={1}
|
||||||
|
fw={800}
|
||||||
|
c={colors["blue-button"]}
|
||||||
|
ta="center"
|
||||||
|
>
|
||||||
Penghargaan Desa
|
Penghargaan Desa
|
||||||
</Text>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Text fz="lg" c="dimmed" ta="center">
|
<Text
|
||||||
|
fz={{ base: "sm", md: "md" }}
|
||||||
|
lh={{ base: "1.5", md: "1.6" }}
|
||||||
|
c="black"
|
||||||
|
ta="center"
|
||||||
|
>
|
||||||
Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan.
|
Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -61,10 +71,8 @@ function Slider() {
|
|||||||
const data = state.findMany.data || [];
|
const data = state.findMany.data || [];
|
||||||
const loading = state.findMany.loading;
|
const loading = state.findMany.loading;
|
||||||
|
|
||||||
// Triple data untuk infinite loop (desktop only)
|
|
||||||
const slidesData = mobile ? data : [...data, ...data, ...data];
|
const slidesData = mobile ? data : [...data, ...data, ...data];
|
||||||
|
|
||||||
// Auto-scroll animation untuk desktop
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading || !containerRef.current || data.length === 0 || mobile) return;
|
if (loading || !containerRef.current || data.length === 0 || mobile) return;
|
||||||
|
|
||||||
@@ -72,7 +80,6 @@ function Slider() {
|
|||||||
const slideWidth = container.scrollWidth / slidesData.length;
|
const slideWidth = container.scrollWidth / slidesData.length;
|
||||||
const originalLength = data.length;
|
const originalLength = data.length;
|
||||||
|
|
||||||
// Start dari middle set
|
|
||||||
scrollPosRef.current = slideWidth * originalLength;
|
scrollPosRef.current = slideWidth * originalLength;
|
||||||
container.scrollLeft = scrollPosRef.current;
|
container.scrollLeft = scrollPosRef.current;
|
||||||
|
|
||||||
@@ -88,7 +95,6 @@ function Slider() {
|
|||||||
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
|
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
|
||||||
scrollPosRef.current += speed;
|
scrollPosRef.current += speed;
|
||||||
|
|
||||||
// Reset untuk infinite loop
|
|
||||||
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
|
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
|
||||||
scrollPosRef.current -= slideWidth * originalLength;
|
scrollPosRef.current -= slideWidth * originalLength;
|
||||||
}
|
}
|
||||||
@@ -100,7 +106,6 @@ function Slider() {
|
|||||||
} else {
|
} else {
|
||||||
scrollPosRef.current = container.scrollLeft;
|
scrollPosRef.current = container.scrollLeft;
|
||||||
|
|
||||||
// Momentum untuk drag release
|
|
||||||
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
||||||
scrollPosRef.current += velocityRef.current;
|
scrollPosRef.current += velocityRef.current;
|
||||||
velocityRef.current *= VELOCITY_DECAY;
|
velocityRef.current *= VELOCITY_DECAY;
|
||||||
@@ -185,7 +190,7 @@ function Slider() {
|
|||||||
return (
|
return (
|
||||||
<Stack align="center" py="xl">
|
<Stack align="center" py="xl">
|
||||||
<IconAward size={56} color={colors["blue-button"]} />
|
<IconAward size={56} color={colors["blue-button"]} />
|
||||||
<Text fz="lg" fw={600} c="dimmed">
|
<Text fz={{ base: "sm", md: "md" }} fw={600} c="dimmed" ta="center">
|
||||||
Belum ada penghargaan yang ditambahkan
|
Belum ada penghargaan yang ditambahkan
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -213,7 +218,6 @@ function Slider() {
|
|||||||
msOverflowStyle: "none",
|
msOverflowStyle: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Blur edges - hanya untuk desktop */}
|
|
||||||
{!mobile && (
|
{!mobile && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@@ -291,8 +295,8 @@ function Slider() {
|
|||||||
style={{ borderRadius: 16 }}
|
style={{ borderRadius: 16 }}
|
||||||
/>
|
/>
|
||||||
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
|
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
|
||||||
<Text
|
<Title
|
||||||
fz={{ base: "lg", sm: "xl", md: "1.5rem" }}
|
order={3}
|
||||||
fw={700}
|
fw={700}
|
||||||
ta="center"
|
ta="center"
|
||||||
c="white"
|
c="white"
|
||||||
@@ -300,7 +304,7 @@ function Slider() {
|
|||||||
style={{ textShadow: "0 2px 8px rgba(0,0,0,0.8)" }}
|
style={{ textShadow: "0 2px 8px rgba(0,0,0,0.8)" }}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Title>
|
||||||
<Group justify="center">
|
<Group justify="center">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}
|
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
|||||||
<Tooltip label="Profil Saya" position="bottom" withArrow>
|
<Tooltip label="Profil Saya" position="bottom" withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
next.push("/admin/landing-page/profile/program-inovasi")
|
next.push("/admin/landing-page/profil/program-inovasi")
|
||||||
}}
|
}}
|
||||||
color={colors["blue-button"]}
|
color={colors["blue-button"]}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function Potensi() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
/* CARD LIST */
|
/* CARD LIST */
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2 }}>
|
<SimpleGrid cols={{ base: 1, sm: 2 }} px={{base: 'md', md: 80}}>
|
||||||
{_.take(data, 4).map((v, k) => (
|
{_.take(data, 4).map((v, k) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={k}
|
key={k}
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ const getDetailUrl = (item: { type?: string; id: string | number;[key: string]:
|
|||||||
sdgsdesa: () => '/darmasaba/sdgs-desa',
|
sdgsdesa: () => '/darmasaba/sdgs-desa',
|
||||||
apbdes: () => '/darmasaba/apbdes',
|
apbdes: () => '/darmasaba/apbdes',
|
||||||
prestasidesa: () => '/darmasaba/prestasi-desa',
|
prestasidesa: () => '/darmasaba/prestasi-desa',
|
||||||
pejabatdesa: () => '/darmasaba/ppid/profile-ppid',
|
pejabatdesa: () => '/darmasaba/ppid/profil-ppid',
|
||||||
strukturppid: () => '/darmasaba/ppid/struktur-ppid',
|
strukturppid: () => '/darmasaba/ppid/struktur-ppid',
|
||||||
visimisippid: () => '/darmasaba/ppid/visi-misi',
|
visimisippid: () => '/darmasaba/ppid/visi-misi',
|
||||||
dasarhukumppid: () => '/darmasaba/ppid/dasar-hukum',
|
dasarhukumppid: () => '/darmasaba/ppid/dasar-hukum',
|
||||||
profileppid: () => '/darmasaba/ppid/profile',
|
profileppid: () => '/darmasaba/ppid/profil',
|
||||||
daftarinformasipublik: () => '/darmasaba/ppid/daftar-informasi-publik',
|
daftarinformasipublik: () => '/darmasaba/ppid/daftar-informasi-publik',
|
||||||
perbekeldarmasaba: () => '/darmasaba/desa/profile',
|
perbekeldarmasaba: () => '/darmasaba/desa/profil',
|
||||||
berita: (id, kategori) => `/darmasaba/desa/berita/${kategori}/${id}`,
|
berita: (id, kategori) => `/darmasaba/desa/berita/${kategori}/${id}`,
|
||||||
pengumuman: (id, kategori) => `/darmasaba/desa/pengumuman/${kategori}/${id}`,
|
pengumuman: (id, kategori) => `/darmasaba/desa/pengumuman/${kategori}/${id}`,
|
||||||
sejarahdesa: () => '/darmasaba/desa/profile',
|
sejarahdesa: () => '/darmasaba/desa/profil',
|
||||||
visimisidesa: () => '/darmasaba/desa/profile',
|
visimisidesa: () => '/darmasaba/desa/profil',
|
||||||
lambangdesa: () => '/darmasaba/desa/profile',
|
lambangdesa: () => '/darmasaba/desa/profil',
|
||||||
maskotdesa: () => '/darmasaba/desa/profile',
|
maskotdesa: () => '/darmasaba/desa/profil',
|
||||||
profilperbekel: () => '/darmasaba/desa/profile',
|
profilperbekel: () => '/darmasaba/desa/profil',
|
||||||
potensi: () => '/darmasaba/desa/potensi-desa',
|
potensi: () => '/darmasaba/desa/potensi-desa',
|
||||||
galleryFoto: () => '/darmasaba/desa/gallery/foto',
|
galleryFoto: () => '/darmasaba/desa/gallery/foto',
|
||||||
galleryVideo: () => '/darmasaba/desa/gallery/video',
|
galleryVideo: () => '/darmasaba/desa/gallery/video',
|
||||||
|
|||||||
Reference in New Issue
Block a user