Sinkronisasi UI Admin & User Menu Landing Page, Submenu Profile, SDGSDesa

This commit is contained in:
2025-08-04 10:29:13 +08:00
parent 54312e9486
commit 1cdff53c56
17 changed files with 490 additions and 72 deletions

View File

@@ -0,0 +1,114 @@
[
{
"id": "cmdsjzdl30002vneknuvo4irv",
"name": "Desa Tanpa Kemiskinan",
"jumlah": "52.62",
"imageId": ""
},
{
"id": "cmdskargd0005vnek0mu2ofk9",
"name": "Desa Tanpa Kelaparan",
"jumlah": "35.75",
"imageId": ""
},
{
"id": "cmdskbvl0008vnek5dmieatb",
"name": "Desa Sehat Dan Sejahtera",
"jumlah": "77.37",
"imageId": ""
},
{
"id": "cmdskcx91000bvneko7tuaoqa",
"name": "Pendidikan Desa Berkualitas",
"jumlah": "34.11",
"imageId": ""
},
{
"id": "cmdskjare000evnek1hglu0x8",
"name": "Keterlibatan Perempuan Desa",
"jumlah": "45.70",
"imageId": ""
},
{
"id": "cmdskqcpc0002vnvnqjkqgm92",
"name": "Desa Layak Air Bersih Dan Sanitasi",
"jumlah": "48.54",
"imageId": ""
},
{
"id": "cmdsktl3x0005vnvne15seefw",
"name": "Desa Berenergi Bersih Dan Terbarukan",
"jumlah": "99.64",
"imageId": ""
},
{
"id": "cmdskuncw0008vnvcsdqoeog",
"name": "Pertumbuhan Ekonomi Desa Merata",
"jumlah": "40.92",
"imageId": ""
},
{
"id": "cmdskw83j000bvvn9szqrea6",
"name": "Infrastruktur Dan Inovasi Desa Sesuai Kebutuhan",
"jumlah": "35.37",
"imageId": ""
},
{
"id": "cmdskwrq7000envnvy0c5nbgf",
"name": "Desa Tanpa Kesenjangan",
"jumlah": "35.47",
"imageId": ""
},
{
"id": "cmdskxivx000hnvnvsx520gv1",
"name": "Kawasan Pemukiman Desa Aman Dan Nyaman",
"jumlah": "40.35",
"imageId": ""
},
{
"id": "cmdskzg4c000kvnnkiv61gkt",
"name": "Konsumsi Dan Produksi Desa Sadar Lingkungan",
"jumlah": "16.67",
"imageId": ""
},
{
"id": "cmdsl07lk000nvnnvnrepsdy5m",
"name": "Desa Tanggap Perubahan Iklim",
"jumlah": "0.00",
"imageId": ""
},
{
"id": "cmdsl10rq000qvnvnlch9c1yv",
"name": "Desa Peduli Lingkungan Laut",
"jumlah": "50.00",
"imageId": ""
},
{
"id": "cmdsl1mc2000tvnvn357n8usi",
"name": "Desa Peduli Lingkungan Darat",
"jumlah": "0.00",
"imageId": ""
},
{
"id": "cmdsl2bx3000wvnvntshi4gnj",
"name": "Desa Damai Berkeadilan",
"jumlah": "78.65",
"imageId": ""
},
{
"id": "cmdsl2yz3000zvnvnmf60ok7q",
"name": "Kemitraan Untuk Pembangunan Desa",
"jumlah": "20.00",
"imageId": ""
},
{
"id": "cmdsl492h0012vnvnmckm3n2x",
"name": "Kelembagaan Desa Dinamis Dan Budaya Desa Adaptif",
"jumlah": "47.22",
"imageId": ""
}
]

View File

@@ -171,8 +171,8 @@ model SDGSDesa {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String @unique
jumlah String jumlah String
image FileStorage @relation(fields: [imageId], references: [id]) image FileStorage? @relation(fields: [imageId], references: [id])
imageId String imageId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -52,19 +53,38 @@ const sdgsDesa = proxy({
}, },
}, },
findMany: { findMany: {
data: null as Array< data: null as any[] | null,
Prisma.SDGSDesaGetPayload<{ page: 1,
include: { totalPages: 1,
image: true; total: 0,
}; loading: false,
}> load: async (page = 1, limit = 10) => { // Change to arrow function
> | null, sdgsDesa.findMany.loading = true; // Use the full path to access the property
async load() { sdgsDesa.findMany.page = page;
const res = await ApiFetch.api.landingpage.sdgsdesa[ try {
"find-many" const res = await ApiFetch.api.landingpage.sdgsdesa[
].get(); "findMany"
if (res.status === 200) { ].get({
sdgsDesa.findMany.data = res.data?.data ?? []; query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
sdgsDesa.findMany.data = res.data.data || [];
sdgsDesa.findMany.total = res.data.total || 0;
sdgsDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
sdgsDesa.findMany.data = [];
sdgsDesa.findMany.total = 0;
sdgsDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading media sosial:", error);
sdgsDesa.findMany.data = [];
sdgsDesa.findMany.total = 0;
sdgsDesa.findMany.totalPages = 1;
} finally {
sdgsDesa.findMany.loading = false;
} }
}, },
}, },

View File

@@ -68,7 +68,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton height={300} /> <Skeleton height={550} />
</Stack> </Stack>
); );
} }

View File

@@ -59,7 +59,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton height={300} /> <Skeleton height={550} />
</Stack> </Stack>
); );
} }

View File

@@ -57,7 +57,7 @@ function ListMediaSosial({ search }: { search: string }) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton height={300} /> <Skeleton height={550} />
</Stack> </Stack>
); );
} }

View File

@@ -57,7 +57,7 @@ function ListProgramInovasi({ search }: { search: string }) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton height={300} /> <Skeleton height={550} />
</Stack> </Stack>
); );
} }

View File

@@ -163,7 +163,7 @@ function EditKolaborasiInovasi() {
<iframe <iframe
src={previewImage} src={previewImage}
width="100%" width="100%"
height="500px" height="250px"
style={{ border: "1px solid #ccc", borderRadius: "8px" }} style={{ border: "1px solid #ccc", borderRadius: "8px" }}
/> />
) : ( ) : (

View File

@@ -110,7 +110,7 @@ function CreateSDGsDesa() {
<iframe <iframe
src={previewImage} src={previewImage}
width="100%" width="100%"
height="500px" height="250px"
style={{ border: "1px solid #ccc", borderRadius: "8px" }} style={{ border: "1px solid #ccc", borderRadius: "8px" }}
/> />
) : ( ) : (

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
import sdgsDesa from '../../_state/landing-page/sdgs-desa'; import sdgsDesa from '../../_state/landing-page/sdgs-desa';
@@ -30,24 +30,61 @@ function SdgsDesa() {
function ListSdgsDesa({ search }: { search: string }) { function ListSdgsDesa({ search }: { search: string }) {
const listState = useProxy(sdgsDesa) const listState = useProxy(sdgsDesa)
const router = useRouter(); const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany;
useEffect(() => { useEffect(() => {
listState.findMany.load() load(page, 10)
}, []) }, [])
const filteredData = (listState.findMany.data || []).filter(item => { const filteredData = useMemo(() => {
const keyword = search.toLowerCase(); if (!data) return [];
return ( return data.filter(item => {
item.name.toLowerCase().includes(keyword) || const keyword = search.toLowerCase();
item.jumlah.toLowerCase().includes(keyword) return (
) item.name?.toLowerCase().includes(keyword) ||
}); item.jumlah?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
if (!listState.findMany.data) { // Handle loading state
if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={10}>
<Skeleton h={500} /> <Skeleton height={550} />
</Stack> </Stack>
) );
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Desa Anti Korupsi'
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama SDGs Desa</TableTh>
<TableTh>Jumlah SDGs Desa</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
} }
return ( return (
@@ -90,6 +127,18 @@ function ListSdgsDesa({ search }: { search: string }) {
</Box> </Box>
</Stack> </Stack>
</Paper> </Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box> </Box>
) )
} }

View File

@@ -16,7 +16,7 @@ async function sdgsDesaFindMany(context: Context) {
}, },
skip, skip,
take: limit, take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu orderBy: { jumlah: "desc" }, // opsional, kalau mau urut berdasarkan waktu
}), }),
prisma.sDGSDesa.count({ prisma.sDGSDesa.count({
where: { isActive: true }, where: { isActive: true },

View File

@@ -11,7 +11,7 @@ const SDGSDesa = new Elysia({
}) })
// ✅ Find all // ✅ Find all
.get("/find-many", sdgsDesaFindMany) .get("/findMany", sdgsDesaFindMany)
// ✅ Find by ID // ✅ Find by ID
.get("/:id", sdgsDesaFindUnique) .get("/:id", sdgsDesaFindUnique)

View File

@@ -1,11 +1,43 @@
import { Box, Center, Container, Image, Stack, Text } from '@mantine/core'; 'use client'
import { Box, Center, Container, Image, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Prisma } from '@prisma/client';
import { useEffect, useState } from 'react';
import BackButton from '../../(pages)/desa/layanan/_com/BackButto'; import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
function Page() { function Page() {
return ( const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Ensure the data is an array before setting it
let data = [];
if (Array.isArray(result.data)) {
data = result.data;
} else if (Array.isArray(result)) {
// In case the API returns the array directly
data = result;
} else {
console.error('Invalid data format:', result);
}
setSdgsDesa(data);
} catch (error) {
console.error('Error fetching sdgs desa:', error);
} finally {
setLoading(false);
}
};
fetchSdgsDesa();
}, []);
return (
<Stack pos={"relative"} py={"xl"} gap={22}> <Stack pos={"relative"} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box> <Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}> <Container w={{ base: "100%", md: "50%" }}>
@@ -28,12 +60,70 @@ function Page() {
> >
SDGs Desa sebagaimana dijabarkan dalam Permendesa Nomor 21 tahun 2020 terdiri dari 18 tujuan yang harus dicapai pada tahun 2030. Tujuan-tujuan tersebut mencakup berbagai aspek kehidupan masyarakat desa, mulai dari pengentasan kemiskinan, peningkatan kesehatan dan pendidikan, kesetaraan gender, pertumbuhan ekonomi, infrastruktur, hingga kelestarian lingkungan. Adapun SDGs Desa terdiri dari tujuan-tujuan sebagai berikut: SDGs Desa sebagaimana dijabarkan dalam Permendesa Nomor 21 tahun 2020 terdiri dari 18 tujuan yang harus dicapai pada tahun 2030. Tujuan-tujuan tersebut mencakup berbagai aspek kehidupan masyarakat desa, mulai dari pengentasan kemiskinan, peningkatan kesehatan dan pendidikan, kesetaraan gender, pertumbuhan ekonomi, infrastruktur, hingga kelestarian lingkungan. Adapun SDGs Desa terdiri dari tujuan-tujuan sebagai berikut:
</Text> </Text>
<Center>
<Image src={"/api/img/sdgsdesa-18-removebg.png"} alt='' w={800} />
</Center>
</Box > </Box >
<Box py={20}> <Box py={20} px={{ base: "md", md: 100 }}>
<Box pos="relative" style={{ minHeight: 200 }}>
<LoadingOverlay visible={loading} overlayProps={{ blur: 2 }} />
{!loading && sdgsDesa.length > 0 ? (
<SimpleGrid
cols={{ base: 1, sm: 2, md: 3, lg: 4 }}
spacing="xl"
verticalSpacing="xl"
>
{sdgsDesa.map((item) => (
<Paper
key={item.id}
p="md"
radius="md"
shadow="sm"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '100%',
transition: 'transform 0.2s',
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: '0 8px 16px rgba(0,0,0,0.1)'
}
}}
>
<Box
p="md"
style={{
backgroundColor: '#f8f9fa',
borderRadius: '8px',
width: '100%',
display: 'flex',
justifyContent: 'center',
marginBottom: '1rem',
}}
>
<Image
src={item.image?.link || '/placeholder-sdgs.png'}
alt={item.name}
width={120}
height={120}
fit="contain"
/>
</Box>
<Stack gap="xs" style={{ width: '100%' }}>
<Title order={4} ta="center" c={"dimmed"} fw={600} lineClamp={2} style={{ minHeight: '3rem' }}>
{item.name}
</Title>
<Text ta="center" fw={"bold"} c={colors['blue-button']} fz={"h2"} lineClamp={3} style={{ minHeight: '4.5rem' }}>
{item.jumlah}
</Text>
</Stack>
</Paper>
))}
</SimpleGrid>
) : !loading ? (
<Center style={{ minHeight: 200 }}>
<Text>Tidak ada data SDGs Desa yang tersedia</Text>
</Center>
) : null}
</Box>
</Box> </Box>
</Stack > </Stack >
); );

View File

@@ -112,7 +112,7 @@ function Footer() {
</Box> </Box>
</Paper> </Paper>
</Center> </Center>
<Box py={20}> <Box py={20} >
<SimpleGrid <SimpleGrid
p={20} p={20}
cols={{ cols={{
@@ -123,7 +123,7 @@ function Footer() {
color: "white" color: "white"
}} }}
> >
<Box p={mobile ? 30 : 30}> <Box p={mobile ? 30 : 30} style={{color: "white"}}>
<Stack justify='space-between'> <Stack justify='space-between'>
<Text fz={"md"} fw={"bold"}>Tentang Darmasaba</Text> <Text fz={"md"} fw={"bold"}>Tentang Darmasaba</Text>
<Text fz={"xs"} >Desa Darmasaba adalah desa <Text fz={"xs"} >Desa Darmasaba adalah desa

View File

@@ -1,11 +1,14 @@
import images from "@/con/images";
import { ActionIcon, Flex, Image } from "@mantine/core"; import { ActionIcon, Flex, Image } from "@mantine/core";
import { Prisma } from "@prisma/client";
import { useTransitionRouter } from "next-view-transitions";
function SosmedView() {
const listSosmed = Object.values(images.sosmed);
function SosmedView({data} : {data : Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]}) {
const router = useTransitionRouter();
return ( return (
<Flex gap={"md"} justify={"center"} align={"center"}> <Flex gap={"md"} justify={"center"} align={"center"}>
{listSosmed.map((item, k) => { {data?.map((item, k) => {
return ( return (
<ActionIcon <ActionIcon
variant="transparent" variant="transparent"
@@ -13,8 +16,11 @@ function SosmedView() {
w={32} w={32}
h={32} h={32}
pos={"relative"} pos={"relative"}
onClick={() => {
router.push(item.iconUrl || "");
}}
> >
<Image src={item} alt="icon" loading="lazy" /> <Image src={item.image?.link || ""} alt="icon" loading="lazy" />
</ActionIcon> </ActionIcon>
); );
})} })}

View File

@@ -1,8 +1,10 @@
"use client"; "use client";
import colors from "@/con/colors"; import colors from "@/con/colors";
import { Prisma } from "@prisma/client";
import { import {
Box, Box,
Card, Card,
Skeleton,
Flex, Flex,
Grid, Grid,
GridCol, GridCol,
@@ -11,9 +13,9 @@ import {
Stack, Stack,
Text Text
} from "@mantine/core"; } from "@mantine/core";
import { useEffect, useState } from "react";
import ModuleView from "./ModuleView"; import ModuleView from "./ModuleView";
import SosmedView from "./SosmedView"; import SosmedView from "./SosmedView";
import { useEffect, useState } from "react";
const getDayOfWeek = () => { const getDayOfWeek = () => {
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu']; const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
@@ -56,6 +58,38 @@ const getWorkStatus = (day: string, currentTime: string): { status: string; mess
function LandingPage() { function LandingPage() {
const [socialMedia, setSocialMedia] = useState<Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchSocialMedia = async () => {
try {
const response = await fetch('/api/landingpage/mediasosial/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Ensure the data is an array before setting it
if (Array.isArray(result.data)) {
setSocialMedia(result.data);
} else if (Array.isArray(result)) {
// In case the API returns the array directly
setSocialMedia(result);
} else {
console.error('Unexpected API response format:', result);
setSocialMedia([]);
}
} catch (error) {
console.error('Error fetching social media:', error);
setSocialMedia([]); // Ensure we always have an array
} finally {
setIsLoading(false);
}
};
fetchSocialMedia();
}, []);
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }> const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>
({ status: '', message: '' }); ({ status: '', message: '' });
@@ -248,7 +282,13 @@ function LandingPage() {
</Flex> </Flex>
<ModuleView /> <ModuleView />
<SosmedView /> {isLoading ? (
<Skeleton height={32} width="100%" />
) : socialMedia.length > 0 ? (
<SosmedView data={socialMedia} />
) : (
<div>No social media links available</div>
)}
<Text c={colors.trans.dark[2]} style={{ <Text c={colors.trans.dark[2]} style={{
textAlign: "center" textAlign: "center"
}} >Sampaikan saran dan masukan guna kemajuan dalam pembangunan. Semua lebih mudah melalui fitur interaktif</Text> }} >Sampaikan saran dan masukan guna kemajuan dalam pembangunan. Semua lebih mudah melalui fitur interaktif</Text>

View File

@@ -1,12 +1,51 @@
'use client' 'use client'
import colors from "@/con/colors"; import colors from "@/con/colors";
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, useMantineTheme } from "@mantine/core"; import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks"; import { useMediaQuery } from "@mantine/hooks";
import { Prisma } from "@prisma/client";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useState } from "react";
export default function SDGS() { export default function SDGS() {
const theme = useMantineTheme(); const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`); const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[] | null>(null);
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Ensure the data is an array before setting it
let data = [];
if (Array.isArray(result.data)) {
data = result.data;
} else if (Array.isArray(result)) {
// In case the API returns the array directly
data = result;
} else {
console.error('Unexpected API response format:', result);
setSdgsDesa([]);
return;
}
// Sort by jumlah in descending order and take top 3
const top3Sdgs = [...data]
.sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah))
.slice(0, 3);
setSdgsDesa(top3Sdgs);
} catch (error) {
console.error('Error fetching sdgs desa:', error);
setSdgsDesa([]);
}
};
fetchSdgsDesa();
}, []);
return ( return (
<Stack p={"sm"}> <Stack p={"sm"}>
<Container w={{ base: "100%", md: "80%" }} p={"xl"}> <Container w={{ base: "100%", md: "80%" }} p={"xl"}>
@@ -16,22 +55,82 @@ export default function SDGS() {
<Text fz={"1.4rem"} ta={"center"}>SDGs Desa adalah upaya menerapkan 17 Tujuan Pembangunan Berkelanjutan di tingkat desa. <Text fz={"1.4rem"} ta={"center"}>SDGs Desa adalah upaya menerapkan 17 Tujuan Pembangunan Berkelanjutan di tingkat desa.
Dengan fokus pada pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, dan pelestarian lingkungan, kami berkomitmen untuk menciptakan desa yang lebih baik bagi semua</Text> Dengan fokus pada pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, dan pelestarian lingkungan, kami berkomitmen untuk menciptakan desa yang lebih baik bagi semua</Text>
<Box py={50}> <Box py={50}>
<Paper p={"lg"} bg={colors.Bg}> <Paper p={{ base: 'md', md: 'xl' }} bg={colors.Bg} radius="lg" shadow="sm">
<SimpleGrid {sdgsDesa && sdgsDesa.length > 0 ? (
cols={{ <SimpleGrid
base: 1, cols={{ base: 1, sm: 3 }}
sm: 3, spacing="xl"
}}> verticalSpacing="xl"
<Center> >
<Image src={"/api/img/sgdesa-1.png"} alt="" w={mobile ? 250 : 200} /> {sdgsDesa.map((item) => (
</Center> <Box
<Center> key={item.id}
<Image src={"/api/img/sgdesa-2.png"} alt="" w={mobile ? 250 : 220} /> p="md"
</Center> style={{
<Center> display: 'flex',
<Image src={"/api/img/sgdesa-3.png"} alt="" w={mobile ? 250 : 190} /> flexDirection: 'column',
</Center> alignItems: 'center',
</SimpleGrid> justifyContent: 'center',
height: '100%',
transition: 'transform 0.2s',
'&:hover': {
transform: 'translateY(-5px)'
}
}}
>
<Box
p="md"
style={{
backgroundColor: 'white',
width: mobile ? 150 : 180,
height: mobile ? 150 : 180,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '1.5rem',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
}}
>
<Image
src={item.image?.link ? item.image.link : '/placeholder-sdgs.png'}
alt={item.name}
width={mobile ? 100 : 120}
height={mobile ? 100 : 120}
style={{
objectFit: 'contain',
maxWidth: '100%',
maxHeight: '100%'
}}
/>
</Box>
<Text
ta="center"
fz={{ base: 'lg', md: 'xl' }}
fw={700}
mb="xs"
style={{ lineHeight: 1.2 }}
>
{item.name}
</Text>
<Title
order={2}
ta="center"
c={colors['blue-button']}
style={{
fontSize: mobile ? '2.5rem' : '3rem',
lineHeight: 1,
margin: '0.5rem 0',
fontWeight: 800
}}
>
{item.jumlah}
</Title>
</Box>
))}
</SimpleGrid>
) : (
<Text>Tidak ada data SDGs Desa</Text>
)}
</Paper> </Paper>
<Center> <Center>
<Button component={Link} href={"/darmasaba/sdgs-desa"} radius={"lg"} fz={"1.2rem"} mt={20} bg={colors["blue-button"]}>Selengkapnya</Button> <Button component={Link} href={"/darmasaba/sdgs-desa"} radius={"lg"} fz={"1.2rem"} mt={20} bg={colors["blue-button"]}>Selengkapnya</Button>