Fix QC Kak Inno 17 Okt 25, Fix QC Kak Ayu 17 Okt 25, & Fix Qc Pak Jun 17 Okt 25
This commit is contained in:
@@ -132,7 +132,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<Tooltip label="Keluar" position="bottom" withArrow>
|
<Tooltip label="Keluar" position="bottom" withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push("/login");
|
router.push("/darmasaba");
|
||||||
}}
|
}}
|
||||||
color={colors["blue-button"]}
|
color={colors["blue-button"]}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
|
|||||||
@@ -28,23 +28,37 @@ const searchState = proxy({
|
|||||||
|
|
||||||
searchState.loading = true;
|
searchState.loading = true;
|
||||||
|
|
||||||
const res = await ApiFetch.api.search.findMany.get({
|
try {
|
||||||
query: {
|
const res = await ApiFetch.api.search.findMany.get({
|
||||||
query: searchState.query,
|
query: {
|
||||||
page: searchState.page,
|
query: searchState.query,
|
||||||
limit: searchState.limit,
|
page: searchState.page,
|
||||||
type: searchState.type,
|
limit: searchState.limit,
|
||||||
},
|
type: searchState.type,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (searchState.page === 1) {
|
console.log("Search API Response:", res);
|
||||||
searchState.results = res.data?.data || [];
|
const rawItems = res.data?.data || [];
|
||||||
} else {
|
const parsedItems = structuredClone(rawItems); // ✅ penting!
|
||||||
searchState.results.push(...(res.data?.data || []));
|
|
||||||
|
console.log("✅ Parsed items:", parsedItems);
|
||||||
|
|
||||||
|
if (searchState.page === 1) {
|
||||||
|
searchState.results = parsedItems;
|
||||||
|
} else {
|
||||||
|
searchState.results.push(...parsedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Search results render:", searchState.results);
|
||||||
|
|
||||||
|
|
||||||
|
searchState.nextPage = res.data?.nextPage || null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Search fetch error:", error);
|
||||||
|
} finally {
|
||||||
|
searchState.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchState.nextPage = res.data?.nextPage || null;
|
|
||||||
searchState.loading = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async next() {
|
async next() {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function Page() {
|
|||||||
p={10}
|
p={10}
|
||||||
mb={50}
|
mb={50}
|
||||||
h={400}
|
h={400}
|
||||||
w={150}
|
w={Math.max(data.length * 120, 800)} // auto lebar sesuai jumlah data
|
||||||
data={data.map((item) => ({
|
data={data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
Pekerjaan: item.pekerjaan,
|
Pekerjaan: item.pekerjaan,
|
||||||
|
|||||||
@@ -121,7 +121,12 @@ function Page() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text fz="sm" c="dimmed">
|
||||||
Diposting: {v.createdAt.toLocaleDateString()}
|
Diposting: {new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
|
||||||
</Text>
|
</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text fz="sm" lh={1.5} lineClamp={3} truncate="end">
|
<Text fz="sm" lh={1.5} lineClamp={3} truncate="end">
|
||||||
|
|||||||
120
src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx
Normal file
120
src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Button, Center, Flex, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import posyanduState from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
|
||||||
|
|
||||||
|
export default function DetailPosyanduUser() {
|
||||||
|
const statePosyandu = useProxy(posyanduState);
|
||||||
|
const params = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePosyandu.findUnique.load(params?.id as string);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!statePosyandu.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
|
<Skeleton height={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = statePosyandu.findUnique.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack pos="relative" bg={colors.Bg} py="xl" px={{ base: 'md', md: 100 }} gap="xl">
|
||||||
|
{/* Tombol Kembali */}
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}
|
||||||
|
mb="sm"
|
||||||
|
c={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
p="xl"
|
||||||
|
radius="lg"
|
||||||
|
shadow="md"
|
||||||
|
bg={colors['white-trans-1']}
|
||||||
|
maw={800}
|
||||||
|
mx="auto"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Header */}
|
||||||
|
<Text
|
||||||
|
ta="center"
|
||||||
|
fz={{ base: '1.8rem', md: '2.2rem' }}
|
||||||
|
fw={700}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
>
|
||||||
|
{data.name || 'Posyandu Desa'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Gambar */}
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Center>
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt={`Gambar ${data.name}`}
|
||||||
|
w="100%"
|
||||||
|
h={300}
|
||||||
|
radius="md"
|
||||||
|
fit="cover"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<Center>
|
||||||
|
<Text fz="sm" c="dimmed">
|
||||||
|
Tidak ada gambar
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Info utama */}
|
||||||
|
<Stack gap="sm" mt="md">
|
||||||
|
<Flex align="flex-start" gap="xs">
|
||||||
|
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
|
<Text fz="sm" c="dimmed">
|
||||||
|
{data.nomor || 'Nomor tidak tersedia'}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex align="flex-start" gap="xs">
|
||||||
|
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
|
<Text
|
||||||
|
fz="sm"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }}
|
||||||
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex align="flex-start" gap="xs">
|
||||||
|
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
|
<Text
|
||||||
|
fz="sm"
|
||||||
|
c="dimmed"
|
||||||
|
lh={1.6}
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
'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, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, TextInput } from "@mantine/core";
|
import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from "@mantine/core";
|
||||||
import { 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";
|
||||||
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 { useDebouncedValue } from "@mantine/hooks";
|
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, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||||
|
const router = useTransitionRouter()
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
@@ -133,33 +134,41 @@ export default function Page() {
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
<Flex align="center" gap="xs">
|
<Flex align="flex-start" gap="xs">
|
||||||
<IconPhone size={18} stroke={1.5} />
|
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
<Text fz="sm" c="dimmed">
|
<Box>
|
||||||
{v.nomor || "Tidak tersedia"}
|
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||||
</Text>
|
{v.nomor || "Tidak tersedia"}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex align="center" gap="xs">
|
|
||||||
<IconCalendar size={18} stroke={1.5} />
|
<Flex align="flex-start" gap="xs">
|
||||||
<Text fz="sm" c="dimmed">
|
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
Jadwal:{" "}
|
<Box>
|
||||||
<span style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} />
|
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||||
</Text>
|
<strong>Jadwal:</strong>{" "}
|
||||||
|
<span
|
||||||
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Spoiler
|
|
||||||
key={`spoiler-${v.id}`}
|
<Flex align="flex-start" gap="xs">
|
||||||
maxHeight={70}
|
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||||
showLabel="Lihat selengkapnya"
|
|
||||||
hideLabel="Sembunyikan"
|
|
||||||
transitionDuration={300}
|
|
||||||
>
|
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz="sm"
|
||||||
lh={1.5}
|
lh={1.5}
|
||||||
|
c="dimmed"
|
||||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
|
lineClamp={3}
|
||||||
|
truncate="end"
|
||||||
/>
|
/>
|
||||||
</Spoiler>
|
</Flex>
|
||||||
|
<Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>Detail</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -43,15 +43,48 @@ export default function Page() {
|
|||||||
const loadingGrid = state.findMany.loading;
|
const loadingGrid = state.findMany.loading;
|
||||||
const loadingFeatured = featured.loading;
|
const loadingFeatured = featured.loading;
|
||||||
|
|
||||||
|
// Load featured data once on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!featured.data && !loadingFeatured) {
|
let mounted = true;
|
||||||
gotongRoyongState.kegiatanDesa.findFirst.load();
|
|
||||||
|
const loadFeatured = async () => {
|
||||||
|
try {
|
||||||
|
if (!featured.data && !loadingFeatured) {
|
||||||
|
await gotongRoyongState.kegiatanDesa.findFirst.load();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading featured data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
loadFeatured();
|
||||||
}
|
}
|
||||||
}, [featured.data, loadingFeatured]);
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}, []); // Empty dependency array to run only once on mount
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const limit = 3;
|
let mounted = true;
|
||||||
state.findMany.load(page, limit, search);
|
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const limit = 3;
|
||||||
|
await state.findMany.load(page, limit, search);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
const handlePageChange = (newPage: number) => {
|
const handlePageChange = (newPage: number) => {
|
||||||
@@ -59,7 +92,9 @@ export default function Page() {
|
|||||||
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');
|
else url.delete('page');
|
||||||
router.replace(`?${url.toString()}`);
|
|
||||||
|
// Use push instead of replace to keep browser history
|
||||||
|
router.push(`?${url.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const featuredData = featured.data;
|
const featuredData = featured.data;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Flex, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { Icon, IconChartLine, IconClipboardTextFilled, IconLeaf, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
|
import { Icon, IconChartLine, IconClipboardTextFilled, IconLeaf, IconRecycle, IconRoute, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
|
||||||
import React, { useState } from 'react';
|
import React, { 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';
|
||||||
@@ -122,20 +122,28 @@ function Page() {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{data2?.map((v, k) => (
|
{data2?.map((v, k) => (
|
||||||
<Paper key={k} p="md" withBorder radius="md">
|
<Paper key={k} p="md" withBorder radius="md">
|
||||||
<Text fw="bold" fz="lg">{v.namaTempatMaps}</Text>
|
<Group justify='space-between'>
|
||||||
<Text c="dimmed" fz="sm" mb="sm">{v.alamat}</Text>
|
<Box>
|
||||||
{v.lat && v.lng ? (
|
<Text fw="bold" fz="lg">{v.namaTempatMaps}</Text>
|
||||||
<a
|
<Text c="dimmed" fz="sm" mb="sm">{v.alamat}</Text>
|
||||||
href={`https://www.google.com/maps/dir/?api=1&destination=${v.lat},${v.lng}`}
|
</Box>
|
||||||
target="_blank"
|
<Box>
|
||||||
rel="noopener noreferrer"
|
<IconRoute color={colors['blue-button']} size={30} />
|
||||||
style={{ color: colors['blue-button'], textDecoration: 'none' }}
|
<Text fw={"bold"} fz="sm" c={colors['blue-button']}>Rute</Text>
|
||||||
>
|
</Box>
|
||||||
<Text fz="sm">📌 Buka di Google Maps</Text>
|
</Group>
|
||||||
</a>
|
{v.lat && v.lng ? (
|
||||||
) : (
|
<a
|
||||||
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
|
href={`https://www.google.com/maps/dir/?api=1&destination=${v.lat},${v.lng}`}
|
||||||
)}
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ color: colors['blue-button'], textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
<Text fz="sm">📌 Lihat Peta Lebih Besar</Text>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ function Page() {
|
|||||||
cursor={{ fill: 'var(--mantine-color-gray-1)' }}
|
cursor={{ fill: 'var(--mantine-color-gray-1)' }}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah Pendidikan" radius={[8, 8, 0, 0]} />
|
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah Penduduk dengan Pendidikan" radius={[8, 8, 0, 0]} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import pendidikanNonFormalState from '@/app/admin/(dashboard)/_state/pendidikan/pendidikan-non-formal';
|
import pendidikanNonFormalState from '@/app/admin/(dashboard)/_state/pendidikan/pendidikan-non-formal';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
import { Box, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { IconMapPin, IconTarget, IconBook2 } from '@tabler/icons-react';
|
import { IconMapPin, IconTarget, IconBook2 } from '@tabler/icons-react';
|
||||||
@@ -59,13 +59,17 @@ function Page() {
|
|||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Tooltip label="Fokus utama program" withArrow>
|
<Group align="center" gap={8} wrap="nowrap">
|
||||||
<Title order={2} fw="bold" c={colors['blue-button']} mb="xs" flex="center">
|
<Tooltip label="Fokus utama program" withArrow>
|
||||||
<IconTarget size={28} style={{ marginRight: 8 }} />
|
<Box display="flex" style={{ alignItems: "center" }}>
|
||||||
|
<IconTarget color={colors['blue-button']} size={26} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<Text fw={700} fz="xl" c={colors['blue-button']}>
|
||||||
{stateTujuanPendidikanNonFormal.findById.data?.judul}
|
{stateTujuanPendidikanNonFormal.findById.data?.judul}
|
||||||
</Title>
|
</Text>
|
||||||
</Tooltip>
|
</Group>
|
||||||
<Text fz="md" lh={1.7} c="dark" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: stateTujuanPendidikanNonFormal.findById.data?.deskripsi }} />
|
<Text fz="md" lh={1.7} c="dark" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: stateTujuanPendidikanNonFormal.findById.data?.deskripsi }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Paper
|
<Paper
|
||||||
@@ -76,13 +80,17 @@ function Page() {
|
|||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Tooltip label="Lokasi pelaksanaan kegiatan" withArrow>
|
<Group align="center" gap={8} wrap="nowrap">
|
||||||
<Title order={2} fw="bold" c={colors['blue-button']} mb="xs" flex="center">
|
<Tooltip label="Lokasi pelaksanaan kegiatan" withArrow>
|
||||||
<IconMapPin size={28} style={{ marginRight: 8 }} />
|
<Box display="flex" style={{ alignItems: "center" }}>
|
||||||
|
<IconMapPin color={colors['blue-button']} size={26} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<Text fw={700} fz="xl" c={colors['blue-button']}>
|
||||||
{stateTempatKegiatan.findById.data?.judul}
|
{stateTempatKegiatan.findById.data?.judul}
|
||||||
</Title>
|
</Text>
|
||||||
</Tooltip>
|
</Group>
|
||||||
<Text fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} lh={1.7} c="dark" dangerouslySetInnerHTML={{ __html: stateTempatKegiatan.findById.data?.deskripsi }} />
|
<Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} lh={1.7} c="dark" dangerouslySetInnerHTML={{ __html: stateTempatKegiatan.findById.data?.deskripsi }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
@@ -95,13 +103,17 @@ function Page() {
|
|||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Tooltip label="Ragam jenis program yang tersedia" withArrow>
|
<Group align="center" gap={8} wrap="nowrap">
|
||||||
<Title order={2} fw="bold" c={colors['blue-button']} mb="xs" flex="center">
|
<Tooltip label="Ragam jenis program yang tersedia" withArrow>
|
||||||
<IconBook2 size={28} style={{ marginRight: 8 }} />
|
<Box display="flex" style={{ alignItems: "center" }}>
|
||||||
|
<IconBook2 color={colors['blue-button']} size={26} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<Text fw={700} fz="xl" c={colors['blue-button']}>
|
||||||
{stateJenisProgram.findById.data?.judul}
|
{stateJenisProgram.findById.data?.judul}
|
||||||
</Title>
|
</Text>
|
||||||
</Tooltip>
|
</Group>
|
||||||
<Text fz="md" lh={1.7} c="dark" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: stateJenisProgram.findById.data?.deskripsi }} />
|
<Text fz="md" lh={1.7} c="dark" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: stateJenisProgram.findById.data?.deskripsi }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default function JenisInformasiSelector({ onChange }: {
|
|||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Select
|
<Select
|
||||||
placeholder='pilih jenis informasi'
|
placeholder='Pilih jenis informasi'
|
||||||
label='Jenis Informasi'
|
label='Jenis Informasi'
|
||||||
data={data.map((item) => ({
|
data={data.map((item) => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function MemperolehInformasi({ onChange }: {
|
|||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Select
|
<Select
|
||||||
placeholder='pilih cara memperoleh informasi'
|
placeholder='Pilih cara memperoleh informasi'
|
||||||
label={"Cara Memperoleh Informasi"}
|
label={"Cara Memperoleh Informasi"}
|
||||||
data={data.map((item) => ({
|
data={data.map((item) => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function MemperolehSalinan({ onChange }: {
|
|||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Select
|
<Select
|
||||||
placeholder='pilih cara memperoleh salinan informasi'
|
placeholder='Pilih cara memperoleh salinan informasi'
|
||||||
label={'Cara Memperoleh Salinan Informasi'}
|
label={'Cara Memperoleh Salinan Informasi'}
|
||||||
data={data.map((item) => ({
|
data={data.map((item) => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ function Page() {
|
|||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Alamat Email"
|
label="Alamat Email"
|
||||||
placeholder="contoh: nama@email.com"
|
placeholder="Contoh: nama@email.com"
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
type="email"
|
type="email"
|
||||||
@@ -190,7 +190,7 @@ function Page() {
|
|||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Nomor Telepon"
|
label="Nomor Telepon"
|
||||||
placeholder="contoh: 0812-3456-7890"
|
placeholder="Contoh: 0812-3456-7890"
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ function Page() {
|
|||||||
<Box key={item.id} px={{ base: "md", md: 100 }}>
|
<Box key={item.id} px={{ base: "md", md: 100 }}>
|
||||||
<Paper p="xl" bg={colors['white-trans-1']} radius="lg" shadow="xl">
|
<Paper p="xl" bg={colors['white-trans-1']} radius="lg" shadow="xl">
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Flex align="center" gap={40} justify="center">
|
<Center>
|
||||||
<Image loading='lazy' src="/darmasaba-icon.png" h={{ base: 70, md: 120 }} alt="Logo Desa" />
|
<Image loading='lazy' src="/darmasaba-icon.png" h={{ base: 70, md: 120 }} w={{ base: 70, md: 120 }} alt="Logo Desa" />
|
||||||
<Text fz={{ base: "1.5rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
|
</Center>
|
||||||
Pejabat Pengelola Informasi Publik
|
<Text ta="center" fz={{ base: "1.2rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
|
||||||
|
Pejabat Pengelola Informasi dan Dokumentasi
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Divider my="lg" />
|
<Divider my="lg" />
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ import GlobalSearch from "./globalSearch";
|
|||||||
export function NavbarSearch() {
|
export function NavbarSearch() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isNavigatingRef = useRef(false);
|
||||||
|
|
||||||
// Close when clicking outside
|
// Close when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
// Only close if clicking outside both the search input and results
|
|
||||||
if (
|
// Jangan close jika klik di search result item (biar handleSelect yang urus)
|
||||||
containerRef.current &&
|
if (target.closest('.search-result-item')) {
|
||||||
!containerRef.current.contains(target) &&
|
return;
|
||||||
!target.closest('.search-result-item') // Add a class to your search result items
|
}
|
||||||
) {
|
|
||||||
|
// Close jika klik di luar container
|
||||||
|
if (containerRef.current && !containerRef.current.contains(target)) {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
stateNav.clear();
|
stateNav.clear();
|
||||||
}
|
}
|
||||||
@@ -29,6 +32,13 @@ export function NavbarSearch() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Reset navigation flag saat component unmount atau route change
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
isNavigatingRef.current = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import searchState, { debouncedFetch } from '@/app/api/[[...slugs]]/_lib/search/searchState';
|
import searchState, { debouncedFetch } from '@/app/api/[[...slugs]]/_lib/search/searchState';
|
||||||
import { Box, Center, Loader, Popover, Text, TextInput } from '@mantine/core';
|
import { Box, Center, Loader, Popover, Text, TextInput } from '@mantine/core';
|
||||||
import { IconX } from '@tabler/icons-react';
|
import { IconX } from '@tabler/icons-react';
|
||||||
@@ -10,36 +11,85 @@ import getDetailUrl from './searchUrl';
|
|||||||
export default function GlobalSearch() {
|
export default function GlobalSearch() {
|
||||||
const snap = useSnapshot(searchState);
|
const snap = useSnapshot(searchState);
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
const [isNavigating, setIsNavigating] = useState(false);
|
||||||
|
|
||||||
// buka popover saat ada query
|
// Buka popover saat ada query
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOpened(!!snap.query);
|
setOpened(!!snap.query);
|
||||||
}, [snap.query]);
|
}, [snap.query]);
|
||||||
|
|
||||||
// infinite scroll
|
// Infinite scroll handler
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const bottom = window.innerHeight + window.scrollY >= document.body.offsetHeight - 200;
|
const nearBottom = window.innerHeight + window.scrollY >= document.body.offsetHeight - 200;
|
||||||
if (bottom && !snap.loading) searchState.next();
|
if (nearBottom && !snap.loading) searchState.next();
|
||||||
};
|
};
|
||||||
window.addEventListener('scroll', handleScroll);
|
window.addEventListener('scroll', handleScroll);
|
||||||
return () => window.removeEventListener('scroll', handleScroll);
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
}, [snap.loading]);
|
}, [snap.loading]);
|
||||||
|
|
||||||
const handleSelect = async (e: React.MouseEvent, item: any) => {
|
const handleSelect = async (e: React.MouseEvent, item: any) => {
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
const url = getDetailUrl(item);
|
if (isNavigating) return;
|
||||||
if (!url) return;
|
setIsNavigating(true);
|
||||||
|
|
||||||
// Immediately close the search dropdown
|
try {
|
||||||
|
// 🔥 pastikan objek udah “dikeluarkan” dari Proxy valtio
|
||||||
|
const rawItem = JSON.parse(JSON.stringify(item));
|
||||||
|
|
||||||
|
// 🔥 pastikan type-nya string murni
|
||||||
|
const type = String(rawItem.type || '').trim().toLowerCase();
|
||||||
|
|
||||||
|
// 🔥 panggil getDetailUrl pakai type yang fix
|
||||||
|
let url = getDetailUrl({ ...rawItem, type });
|
||||||
|
|
||||||
|
// kalau hasil undefined atau default, fallback ke link eksternal
|
||||||
|
if (!url || url === '/darmasaba') {
|
||||||
|
if (rawItem.link && rawItem.link.startsWith('http')) {
|
||||||
|
url = rawItem.link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
console.warn('URL tidak ditemukan untuk item:', rawItem);
|
||||||
|
setIsNavigating(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Navigating to:', url);
|
||||||
|
|
||||||
|
// tutup popover dulu
|
||||||
|
setOpened(false);
|
||||||
|
searchState.query = '';
|
||||||
|
searchState.results = [];
|
||||||
|
searchState.loading = false;
|
||||||
|
|
||||||
|
// kasih delay biar UI nutup dulu
|
||||||
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
|
||||||
|
// navigasi
|
||||||
|
if (url.startsWith('http')) {
|
||||||
|
window.location.href = url;
|
||||||
|
} else {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error saat navigasi:', err);
|
||||||
|
setIsNavigating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchState.query = '';
|
||||||
|
searchState.results = [];
|
||||||
|
searchState.page = 1;
|
||||||
|
searchState.nextPage = null;
|
||||||
setOpened(false);
|
setOpened(false);
|
||||||
searchState.results = []; // Clear results immediately
|
setIsNavigating(false);
|
||||||
searchState.loading = false;
|
|
||||||
|
|
||||||
// Use window.location for navigation to ensure full page reload
|
|
||||||
window.location.href = url;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,13 +97,7 @@ export default function GlobalSearch() {
|
|||||||
<Popover
|
<Popover
|
||||||
opened={opened && !!snap.query}
|
opened={opened && !!snap.query}
|
||||||
onChange={(isOpen) => {
|
onChange={(isOpen) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) clearSearch();
|
||||||
// Clear search state when popover is closed
|
|
||||||
searchState.query = '';
|
|
||||||
searchState.results = [];
|
|
||||||
searchState.page = 1;
|
|
||||||
searchState.nextPage = null;
|
|
||||||
}
|
|
||||||
setOpened(isOpen);
|
setOpened(isOpen);
|
||||||
}}
|
}}
|
||||||
width="target"
|
width="target"
|
||||||
@@ -61,10 +105,14 @@ export default function GlobalSearch() {
|
|||||||
shadow="md"
|
shadow="md"
|
||||||
withinPortal
|
withinPortal
|
||||||
radius="md"
|
radius="md"
|
||||||
zIndex={1000} // Add this line to ensure it appears above other elements
|
zIndex={2000}
|
||||||
|
closeOnClickOutside={true}
|
||||||
|
closeOnEscape={true}
|
||||||
styles={{
|
styles={{
|
||||||
dropdown: {
|
dropdown: {
|
||||||
zIndex: 1000, // Add this to ensure the dropdown appears above other elements
|
zIndex: 2000,
|
||||||
|
borderRadius: 12,
|
||||||
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -83,13 +131,7 @@ export default function GlobalSearch() {
|
|||||||
<IconX
|
<IconX
|
||||||
size={16}
|
size={16}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={clearSearch}
|
||||||
searchState.query = '';
|
|
||||||
searchState.results = [];
|
|
||||||
searchState.page = 1;
|
|
||||||
searchState.nextPage = null;
|
|
||||||
setOpened(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
@@ -101,34 +143,32 @@ export default function GlobalSearch() {
|
|||||||
style={{
|
style={{
|
||||||
maxHeight: 350,
|
maxHeight: 350,
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
borderRadius: 12,
|
backgroundColor: '#fff',
|
||||||
zIndex: 1000, // Add this line to ensure dropdown stays above other elements
|
border: '1px solid #eee',
|
||||||
position: 'relative', // Add this to contain child elements
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{snap.results.length > 0 ? (
|
{[...snap.results].length > 0 ? (
|
||||||
snap.results.map((item, i) => (
|
[...snap.results].map((item: any, i: number) => (
|
||||||
<Box
|
<Box
|
||||||
key={i}
|
key={i}
|
||||||
p="sm"
|
p="sm"
|
||||||
className="search-result-item" // Add this class
|
className="search-result-item" // Add class untuk prevent close
|
||||||
style={{
|
style={{
|
||||||
borderBottom: '1px solid #eee',
|
borderBottom: '1px solid #f1f1f1',
|
||||||
cursor: 'pointer',
|
cursor: isNavigating ? 'wait' : 'pointer',
|
||||||
|
background: 'white',
|
||||||
transition: 'background 0.2s',
|
transition: 'background 0.2s',
|
||||||
position: 'relative', // Add this
|
opacity: isNavigating ? 0.6 : 1,
|
||||||
zIndex: 1, // Add this to ensure proper stacking context
|
|
||||||
backgroundColor: 'white', // Ensure background is set
|
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.background = '#f7f7f7')}
|
onMouseEnter={(e) => !isNavigating && (e.currentTarget.style.background = '#f9f9f9')}
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
|
onMouseLeave={(e) => (e.currentTarget.style.background = 'white')}
|
||||||
onClick={(e) => handleSelect(e, item)} // Pass the event here
|
onClick={(e) => handleSelect(e, item)}
|
||||||
>
|
>
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500} lineClamp={1}>
|
||||||
{item.judul || item.namaPasar || item.nama || item.name}
|
{item.name ?? item.nama ?? item.namaPasar ?? item.judul ?? '(Tanpa nama)'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed" lineClamp={1}>
|
||||||
dari modul: {item.type}
|
dari modul: {item.type || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ function Apbdes() {
|
|||||||
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text ta={"center"} fz={{ base: '2.4rem', sm: '4rem' }} fw="bold" lh={1.2}>
|
<Text ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"center"} fz={{ base: '1rem', sm: '1.3rem' }} c="dimmed">
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
{textHeading.des}
|
{textHeading.des}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -156,9 +156,9 @@ function Kepuasan() {
|
|||||||
<Stack p="sm">
|
<Stack p="sm">
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||||
<Center>
|
<Center>
|
||||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||||
</Center>
|
</Center>
|
||||||
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||||
<Center mt={10}>
|
<Center mt={10}>
|
||||||
<Button
|
<Button
|
||||||
radius={"lg"}
|
radius={"lg"}
|
||||||
|
|||||||
@@ -29,42 +29,42 @@ function ModuleItem({ data }: { data: ProgramInovasiItem }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div whileHover={{ scale: 1.03 }}>
|
<motion.div whileHover={{ scale: 1.03 }}>
|
||||||
<Paper
|
<Paper
|
||||||
onClick={() => router.push(`/darmasaba/program-inovasi/${data.id}`)}
|
onClick={() => router.push(`/darmasaba/program-inovasi/${data.id}`)}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="cursor-pointer transition-all"
|
className="cursor-pointer transition-all"
|
||||||
bg={isDark ? "dark.6" : "white"}
|
bg={isDark ? "dark.6" : "white"}
|
||||||
>
|
>
|
||||||
<Center h={160}>
|
<Center h={160}>
|
||||||
{data.image?.link ? (
|
{data.image?.link ? (
|
||||||
<Image
|
<Image
|
||||||
src={data.image.link}
|
src={data.image.link}
|
||||||
alt={data.name}
|
alt={data.name}
|
||||||
radius="md"
|
radius="md"
|
||||||
fit="cover"
|
fit="contain"
|
||||||
h={140}
|
h={140}
|
||||||
w="100%"
|
w="100%"
|
||||||
loading="lazy"
|
style={{ objectPosition: "center" }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Stack align="center" gap="xs">
|
<Stack align="center" gap="xs">
|
||||||
<IconPhotoOff size={38} stroke={1.5} />
|
<IconPhotoOff size={38} stroke={1.5} />
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
Belum ada gambar
|
Belum ada gambar
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Center>
|
</Center>
|
||||||
<Box mt="md">
|
<Box mt="md">
|
||||||
<Text fw={600} ta="center" size="md">
|
<Text fw={600} ta="center" size="md">
|
||||||
{data.name}
|
{data.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -110,11 +110,11 @@ function ModuleView() {
|
|||||||
viewport: { paddingRight: 8 }, // kasih jarak biar scroll nggak dempet
|
viewport: { paddingRight: 8 }, // kasih jarak biar scroll nggak dempet
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" mt="lg">
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" mt="lg">
|
||||||
{listImageState.findMany.data?.map((item) => (
|
{listImageState.findMany.data?.map((item) => (
|
||||||
<ModuleItem key={item.id} data={item} />
|
<ModuleItem key={item.id} data={item} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,20 +30,41 @@ export default function ProfileView({ data }: ProfileViewProps) {
|
|||||||
justify="end"
|
justify="end"
|
||||||
align="end"
|
align="end"
|
||||||
pos="relative"
|
pos="relative"
|
||||||
w={{ base: '100%', md: '40%' }}
|
w={{
|
||||||
px="xl"
|
base: '100%', // mobile: full width
|
||||||
|
xs: '100%', // small mobile
|
||||||
|
sm: '85%', // tablet: 85%
|
||||||
|
md: '60%', // laptop: 60%
|
||||||
|
lg: '55%', // laptop large: 55%
|
||||||
|
xl: '50%' // extra large (4K): 50%
|
||||||
|
}}
|
||||||
|
px={{ base: 'md', sm: 'lg', md: 'xl', xl: '2xl' }}
|
||||||
|
h={{ base: 'auto', sm: '500px', md: '600px', lg: '650px', xl: '700px' }}
|
||||||
>
|
>
|
||||||
{data.image?.link ? (
|
{data.image?.link ? (
|
||||||
<Image
|
<Box
|
||||||
src={data.image.link}
|
pos="relative"
|
||||||
alt={data.name || 'Foto profil'}
|
w="100%"
|
||||||
fit="contain"
|
h="100%"
|
||||||
radius="lg"
|
|
||||||
loading="lazy"
|
|
||||||
style={{
|
style={{
|
||||||
objectPosition: 'bottom center',
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt={data.name || 'Foto profil'}
|
||||||
|
fit="contain"
|
||||||
|
radius="lg"
|
||||||
|
loading="lazy"
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
style={{
|
||||||
|
objectPosition: 'center bottom',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Stack align="center" gap="xs" w="100%" py="xl">
|
<Stack align="center" gap="xs" w="100%" py="xl">
|
||||||
<IconUserCircle size={96} stroke={1.5} />
|
<IconUserCircle size={96} stroke={1.5} />
|
||||||
@@ -53,32 +74,43 @@ export default function ProfileView({ data }: ProfileViewProps) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Box nama dan jabatan - sedikit overlap dengan gambar */}
|
{/* Box nama dan jabatan - responsive positioning */}
|
||||||
<Box
|
<Box
|
||||||
pos="absolute"
|
pos="absolute"
|
||||||
bottom={-20} // bikin naik sedikit ke gambar
|
bottom={{ base: -30, sm: -25, md: -20 }}
|
||||||
right={0}
|
right={0}
|
||||||
w="100%"
|
w={{ base: '95%', sm: '100%' }}
|
||||||
p={{ base: 'xs', md: 'md' }}
|
px={{ base: 'xs', sm: 'sm', md: 'md' }}
|
||||||
style={{ pointerEvents: 'none' }} // biar ga ganggu klik di gambar
|
style={{ pointerEvents: 'none' }}
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
px="lg"
|
px={{ base: 'md', sm: 'lg' }}
|
||||||
py="sm"
|
py={{ base: 'xs', sm: 'sm' }}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
withBorder
|
withBorder
|
||||||
style={{
|
style={{
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
||||||
backdropFilter: 'blur(6px)',
|
backdropFilter: 'blur(6px)',
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip label="Jabatan Resmi" withArrow>
|
<Tooltip label="Jabatan Resmi" withArrow>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text
|
||||||
|
fz={{ base: 'xs', sm: 'sm' }}
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={1}
|
||||||
|
>
|
||||||
{data.position || 'Tidak ada jabatan'}
|
{data.position || 'Tidak ada jabatan'}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Text c={colors['blue-button']} fw={700} fz="xl" mt={4}>
|
<Text
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fw={700}
|
||||||
|
fz={{ base: 'lg', sm: 'xl' }}
|
||||||
|
mt={4}
|
||||||
|
lineClamp={2}
|
||||||
|
>
|
||||||
{data.name}
|
{data.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { Prisma } from "@prisma/client";
|
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Skeleton,
|
Center,
|
||||||
Flex,
|
Flex,
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
GridCol,
|
||||||
|
Group,
|
||||||
Image,
|
Image,
|
||||||
Paper,
|
Paper,
|
||||||
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Center,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Badge,
|
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
import { IconCalendarTime, IconInfoCircle } from "@tabler/icons-react";
|
import { IconCalendarTime, IconInfoCircle } from "@tabler/icons-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ModuleView from "./ModuleView";
|
import ModuleView from "./ModuleView";
|
||||||
import SosmedView from "./SosmedView";
|
|
||||||
import ProfileView from "./ProfileView";
|
import ProfileView from "./ProfileView";
|
||||||
|
import SosmedView from "./SosmedView";
|
||||||
|
|
||||||
const getDayOfWeek = () => {
|
const getDayOfWeek = () => {
|
||||||
const days = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"];
|
const days = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"];
|
||||||
@@ -126,17 +127,15 @@ function LandingPage() {
|
|||||||
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
<Flex gap="md" wrap="wrap">
|
<Flex gap="md" wrap="wrap">
|
||||||
|
<Group>
|
||||||
|
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
||||||
|
<Image loading="lazy" src="/darmasaba-icon.png" alt="Logo Darmasaba" fit="contain" />
|
||||||
|
</Box>
|
||||||
|
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
||||||
|
<Image loading="lazy" src="/pudak-icon.png" alt="Logo Pudak" fit="contain" />
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
<Grid w="100%">
|
<Grid w="100%">
|
||||||
<Grid.Col span={{ base: 3, sm: 2 }}>
|
|
||||||
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
|
||||||
<Image loading="lazy" src="/darmasaba-icon.png" alt="Logo Darmasaba" fit="contain" />
|
|
||||||
</Box>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={{ base: 9, sm: 10 }}>
|
|
||||||
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
|
||||||
<Image loading="lazy" src="/pudak-icon.png" alt="Logo Pudak" fit="contain" />
|
|
||||||
</Box>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={12}>
|
<Grid.Col span={12}>
|
||||||
<Paper
|
<Paper
|
||||||
bg={colors["blue-button"]}
|
bg={colors["blue-button"]}
|
||||||
|
|||||||
@@ -31,16 +31,12 @@ function Layanan() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
|
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
|
||||||
<Container w={{ base: "100%", md: "50%" }} p={"xl"}>
|
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||||
<Stack align="center" gap={"0"}>
|
<Stack align="center" gap={"0"}>
|
||||||
<Text fz={"3.4rem"} fw={"bold"}>
|
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{textHeading.des}
|
{textHeading.des}
|
||||||
</Text>
|
</Text>
|
||||||
<Box p={"md"}>
|
<Box p={"md"}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
BackgroundImage,
|
BackgroundImage,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
@@ -49,14 +50,14 @@ function Potensi() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="sm" gap="4rem">
|
<Stack p="sm" gap="4rem">
|
||||||
<Box>
|
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }} fw={700} c={colors["blue-button"]}>
|
<Text ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"center"} fz={{ base: "1.4rem", md: "1.6rem" }} c="black">
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
{textHeading.des}
|
{textHeading.des}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Container>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Stack align="center" justify="center" h={300}>
|
<Stack align="center" justify="center" h={300}>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function SDGS() {
|
|||||||
SDGs Desa
|
SDGs Desa
|
||||||
</Title>
|
</Title>
|
||||||
</Center>
|
</Center>
|
||||||
<Text fz={{ base: "1rem", md: "1.2rem" }} ta="center" c="dimmed" mt="md" maw={820} mx="auto">
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
|
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -1,91 +1,92 @@
|
|||||||
const getDetailUrl = (item: { type?: string; id: string | number; [key: string]: unknown }) => {
|
const getDetailUrl = (item: { type?: string; id: string | number; [key: string]: unknown }) => {
|
||||||
const { type, id, kategori } = item;
|
const { type, id, kategori } = item;
|
||||||
const typeUrlMap: Record<string, string> = {
|
const map: Record<string, (id: string | number, kategori?: string) => string> = {
|
||||||
programinovasi: `/darmasaba/program-inovasi/${id}`,
|
programinovasi: (id) => `/darmasaba/program-inovasi/${id}`,
|
||||||
desaantikorupsi: '/darmasaba/desa-anti-korupsi',
|
desaantikorupsi: () => '/darmasaba/desa-anti-korupsi',
|
||||||
sdgsdesa: '/darmasaba/sdgs-desa',
|
sdgsdesa: () => '/darmasaba/sdgs-desa',
|
||||||
apbdes: '/darmasaba/apbdes',
|
apbdes: () => '/darmasaba/apbdes',
|
||||||
prestasidesa: '/darmasaba/prestasi-desa',
|
prestasidesa: () => '/darmasaba/prestasi-desa',
|
||||||
pejabatdesa: '/darmasaba/profile/pejabat-desa',
|
pejabatdesa: () => '/darmasaba/ppid/profile-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/profile',
|
||||||
daftarinformasipublik: '/darmasaba/ppid/daftar-informasi-publik',
|
daftarinformasipublik: () => '/darmasaba/ppid/daftar-informasi-publik',
|
||||||
perbekeldarmasaba: '/darmasaba/desa/profile',
|
perbekeldarmasaba: () => '/darmasaba/desa/profile',
|
||||||
berita: `/darmasaba/desa/berita/${kategori}/${id}`,
|
berita: (id, kategori) => `/darmasaba/desa/berita/${kategori}/${id}`,
|
||||||
pengumuman: `/darmasaba/desa/pengumuman/${kategori}/${id}`,
|
pengumuman: (id, kategori) => `/darmasaba/desa/pengumuman/${kategori}/${id}`,
|
||||||
sejarahdesa: '/darmasaba/desa/profile',
|
sejarahdesa: () => '/darmasaba/desa/profile',
|
||||||
visimisidesa: '/darmasaba/desa/profile',
|
visimisidesa: () => '/darmasaba/desa/profile',
|
||||||
lambangdesa: '/darmasaba/desa/profile',
|
lambangdesa: () => '/darmasaba/desa/profile',
|
||||||
maskotdesa: '/darmasaba/desa/profile',
|
maskotdesa: () => '/darmasaba/desa/profile',
|
||||||
profilperbekel: '/darmasaba/desa/profile',
|
profilperbekel: () => '/darmasaba/desa/profile',
|
||||||
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',
|
||||||
pelayananSuratKeterangan: '/darmasaba/desa/layanan',
|
pelayananSuratKeterangan: () => '/darmasaba/desa/layanan',
|
||||||
pelayananPerizinanBerusaha: '/darmasaba/desa/layanan',
|
pelayananPerizinanBerusaha: () => '/darmasaba/desa/layanan',
|
||||||
pelayananTelunjukSaktiDesa: '/darmasaba/desa/layanan',
|
pelayananTelunjukSaktiDesa: () => '/darmasaba/desa/layanan',
|
||||||
pelayananPendudukNonPermanent: '/darmasaba/desa/layanan',
|
pelayananPendudukNonPermanent: () => '/darmasaba/desa/layanan',
|
||||||
penghargaan: '/darmasaba/desa/penghargaan',
|
penghargaan: () => '/darmasaba/desa/penghargaan',
|
||||||
posyandu: '/darmasaba/kesehatan/posyandu',
|
posyandu: (id) => `/darmasaba/kesehatan/posyandu/${id}`,
|
||||||
fasilitasKesehatan: '/darmasaba/kesehatan/data-kesehatan-warga',
|
fasilitasKesehatan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||||
jadwalKegiatan: '/darmasaba/kesehatan/data-kesehatan-warga',
|
jadwalKegiatan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||||
artikelKesehatan: '/darmasaba/kesehatan/data-kesehatan-warga',
|
artikelKesehatan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||||
puskesmas: '/darmasaba/kesehatan/puskesmas',
|
puskesmas: () => '/darmasaba/kesehatan/puskesmas',
|
||||||
programKesehatan: '/darmasaba/kesehatan/program-kesehatan',
|
programKesehatan: () => '/darmasaba/kesehatan/program-kesehatan',
|
||||||
penangananDarurat: '/darmasaba/kesehatan/penanganan-darurat',
|
penangananDarurat: () => '/darmasaba/kesehatan/penanganan-darurat',
|
||||||
kontakDarurat: '/darmasaba/kesehatan/kontak-darurat',
|
kontakDarurat: () => '/darmasaba/kesehatan/kontak-darurat',
|
||||||
infoWabahPenyakit: '/darmasaba/kesehatan/info-wabah-penyakit',
|
infoWabahPenyakit: () => '/darmasaba/kesehatan/info-wabah-penyakit',
|
||||||
keamananLingkungan: '/darmasaba/keamanan/keamanan-lingkungan-pecalang-patwal',
|
keamananLingkungan: () => '/darmasaba/keamanan/keamanan-lingkungan-pecalang-patwal',
|
||||||
polsekTerdekat: '/darmasaba/keamanan/polsek-terdekat',
|
polsekTerdekat: () => '/darmasaba/keamanan/polsek-terdekat',
|
||||||
kontakDaruratKeamanan: '/darmasaba/keamanan/kontak-darurat',
|
kontakDaruratKeamanan: () => '/darmasaba/keamanan/kontak-darurat',
|
||||||
pencegahanKriminalitas: '/darmasaba/keamanan/pencegahan-kriminalitas',
|
pencegahanKriminalitas: () => '/darmasaba/keamanan/pencegahan-kriminalitas',
|
||||||
laporanPublik: '/darmasaba/keamanan/laporan-publik',
|
laporanPublik: () => '/darmasaba/keamanan/laporan-publik',
|
||||||
tipsKeamanan: '/darmasaba/keamanan/tips-keamanan',
|
tipsKeamanan: () => '/darmasaba/keamanan/tips-keamanan',
|
||||||
pasarDesa: '/darmasaba/ekonomi/pasar-desa',
|
pasarDesa: () => '/darmasaba/ekonomi/pasar-desa',
|
||||||
lowonganKerjaLokal: '/darmasaba/ekonomi/lowongan-kerja-lokal',
|
lowonganKerjaLokal: () => '/darmasaba/ekonomi/lowongan-kerja-lokal',
|
||||||
strukturOrganisasi: '/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa',
|
strukturOrganisasi: () => '/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa',
|
||||||
jumlahPendudukUsiaKerjaYangMenganggurUsia: '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
jumlahPendudukUsiaKerjaYangMenganggurUsia: () => '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
||||||
jumlahPendudukUsiaKerjaYangMenganggurPendidikan: '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
jumlahPendudukUsiaKerjaYangMenganggurPendidikan: () => '/darmasaba/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur',
|
||||||
jumlahPendudukMiskin: '/darmasaba/ekonomi/jumlah-penduduk-miskin',
|
jumlahPendudukMiskin: () => '/darmasaba/ekonomi/jumlah-penduduk-miskin',
|
||||||
programKemiskinan: '/darmasaba/ekonomi/program-kemiskinan',
|
programKemiskinan: () => '/darmasaba/ekonomi/program-kemiskinan',
|
||||||
sektorUnggulanDesa: '/darmasaba/ekonomi/sektor-unggulan-desa',
|
sektorUnggulanDesa: () => '/darmasaba/ekonomi/sektor-unggulan-desa',
|
||||||
demografiPekerjaan: '/darmasaba/ekonomi/demografi-pekerjaan',
|
demografiPekerjaan: () => '/darmasaba/ekonomi/demografi-pekerjaan',
|
||||||
desaDigital: '/darmasaba/inovasi/desa-digital-smart-village',
|
desaDigital: () => '/darmasaba/inovasi/desa-digital-smart-village',
|
||||||
programKreatif: '/darmasaba/inovasi/program-kreatif-desa',
|
programKreatif: () => '/darmasaba/inovasi/program-kreatif-desa',
|
||||||
kolaborasiInovasi: '/darmasaba/inovasi/kolaborasi-inovasi',
|
kolaborasiInovasi: () => '/darmasaba/inovasi/kolaborasi-inovasi',
|
||||||
mitraKolaborasi: '/darmasaba/inovasi/kolaborasi-inovasi',
|
mitraKolaborasi: () => '/darmasaba/inovasi/kolaborasi-inovasi',
|
||||||
infoTekno: '/darmasaba/inovasi/info-teknologi-tepat-guna',
|
infoTekno: () => '/darmasaba/inovasi/info-teknologi-tepat-guna',
|
||||||
pengelolaanSampah: '/darmasaba/lingkungan/pengelolaan-sampah-bank-sampah',
|
pengelolaanSampah: () => '/darmasaba/lingkungan/pengelolaan-sampah-bank-sampah',
|
||||||
keteranganBankSampahTerdekat: '/darmasaba/lingkungan/pengelolaan-sampah-bank-sampah',
|
keteranganBankSampahTerdekat: () => '/darmasaba/lingkungan/pengelolaan-sampah-bank-sampah',
|
||||||
programPenghijauan: '/darmasaba/lingkungan/program-penghijauan',
|
programPenghijauan: () => '/darmasaba/lingkungan/program-penghijauan',
|
||||||
dataLingkunganDesa: '/darmasaba/lingkungan/data-lingkungan-desa',
|
dataLingkunganDesa: () => '/darmasaba/lingkungan/data-lingkungan-desa',
|
||||||
gotongRoyong: '/darmasaba/lingkungan/gotong-royong',
|
gotongRoyong: (id, kategori) => `/darmasaba/lingkungan/gotong-royong/${kategori}/${id}`,
|
||||||
tujuanEdukasiLingkungan: '/darmasaba/lingkungan/edukasi-lingkungan',
|
tujuanEdukasiLingkungan: () => '/darmasaba/lingkungan/edukasi-lingkungan',
|
||||||
materiEdukasiLingkungan: '/darmasaba/lingkungan/edukasi-lingkungan',
|
materiEdukasiLingkungan: () => '/darmasaba/lingkungan/edukasi-lingkungan',
|
||||||
contohEdukasiLingkungan: '/darmasaba/lingkungan/edukasi-lingkungan',
|
contohEdukasiLingkungan: () => '/darmasaba/lingkungan/edukasi-lingkungan',
|
||||||
filosofiTriHita: '/darmasaba/lingkungan/konservasi-adat-bali',
|
filosofiTriHita: () => '/darmasaba/lingkungan/konservasi-adat-bali',
|
||||||
bentukKonservasiBerdasarkanAdat: '/darmasaba/lingkungan/konservasi-adat-bali',
|
bentukKonservasiBerdasarkanAdat: () => '/darmasaba/lingkungan/konservasi-adat-bali',
|
||||||
nilaiKonservasiAdat: '/darmasaba/lingkungan/konservasi-adat-bali',
|
nilaiKonservasiAdat: () => '/darmasaba/lingkungan/konservasi-adat-bali',
|
||||||
jenjangPendidikan: '/darmasaba/pendidikan/info-sekolah/semua',
|
jenjangPendidikan: () => '/darmasaba/pendidikan/info-sekolah/semua',
|
||||||
lembaga: '/darmasaba/pendidikan/info-sekolah/semua/lembaga',
|
lembaga: () => '/darmasaba/pendidikan/info-sekolah/semua/lembaga',
|
||||||
siswa: '/darmasaba/pendidikan/info-sekolah/semua/siswa',
|
siswa: () => '/darmasaba/pendidikan/info-sekolah/semua/siswa',
|
||||||
pengajar: '/darmasaba/pendidikan/info-sekolah/semua/pengajar',
|
pengajar: () => '/darmasaba/pendidikan/info-sekolah/semua/pengajar',
|
||||||
keunggulanProgram: '/darmasaba/pendidikan/beasiswa-desa',
|
keunggulanProgram: () => '/darmasaba/pendidikan/beasiswa-desa',
|
||||||
tujuanProgram: '/darmasaba/pendidikan/program-pendidikan-anak',
|
tujuanProgram: () => '/darmasaba/pendidikan/program-pendidikan-anak',
|
||||||
programUnggulan: '/darmasaba/pendidikan/program-pendidikan-anak',
|
programUnggulan: () => '/darmasaba/pendidikan/program-pendidikan-anak',
|
||||||
lokasiJadwalBimbinganBelajarDesa: '/darmasaba/pendidikan/bimbingan-belajar-desa',
|
lokasiJadwalBimbinganBelajarDesa: () => '/darmasaba/pendidikan/bimbingan-belajar-desa',
|
||||||
fasilitasBimbinganBelajarDesa: '/darmasaba/pendidikan/bimbingan-belajar-desa',
|
fasilitasBimbinganBelajarDesa: () => '/darmasaba/pendidikan/bimbingan-belajar-desa',
|
||||||
tujuanPendidikanNonFormal: '/darmasaba/pendidikan/pendidikan-non-formal',
|
tujuanPendidikanNonFormal: () => '/darmasaba/pendidikan/pendidikan-non-formal',
|
||||||
tempatKegiatan: '/darmasaba/pendidikan/pendidikan-non-formal',
|
tempatKegiatan: () => '/darmasaba/pendidikan/pendidikan-non-formal',
|
||||||
jenisProgramYangDiselenggarakan: '/darmasaba/pendidikan/pendidikan-non-formal',
|
jenisProgramYangDiselenggarakan: () => '/darmasaba/pendidikan/pendidikan-non-formal',
|
||||||
dataPerpustakaan: '/darmasaba/pendidikan/perpustakaan-digital/semua',
|
dataPerpustakaan: () => '/darmasaba/pendidikan/perpustakaan-digital/semua',
|
||||||
dataPendidikan: '/darmasaba/pendidikan/data-pendidikan',
|
dataPendidikan: () => '/darmasaba/pendidikan/data-pendidikan',
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return typeUrlMap[type || ''] || '/darmasaba';
|
if (type && map[type]) return map[type](id, kategori as string | undefined);
|
||||||
|
return '/darmasaba';
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getDetailUrl;
|
export default getDetailUrl;
|
||||||
|
|||||||
Reference in New Issue
Block a user