Fix Menu Lingkungan Darmasaba User
This commit is contained in:
@@ -1,121 +1,136 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconDownload } from '@tabler/icons-react';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
|
||||
import colors from '@/con/colors'
|
||||
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, Loader, SimpleGrid, Stack, Text } from '@mantine/core'
|
||||
import { IconDownload } from '@tabler/icons-react'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
|
||||
function Apbdes() {
|
||||
const state = useProxy(apbdes);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const state = useProxy(apbdes)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const textHeading = {
|
||||
title: "APBDes",
|
||||
des: "Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab"
|
||||
title: 'APBDes',
|
||||
des: 'Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola desa yang bersih, terbuka, dan bertanggung jawab.'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findMany.load();
|
||||
setLoading(true)
|
||||
await state.findMany.load()
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
console.error('Error loading data:', error)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
loadData();
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
const data = (state.findMany.data || []).slice(0, 3);
|
||||
const data = (state.findMany.data || []).slice(0, 3)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack p={"sm"} gap={"4rem"} bg={colors.Bg}>
|
||||
<Box
|
||||
w={{
|
||||
base: '100%',
|
||||
sm: '60%',
|
||||
}}
|
||||
>
|
||||
<Stack gap={0}>
|
||||
<Text fz={"4.4rem"} fw={"bold"}>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text fz={"1.4rem"}>
|
||||
{textHeading.des}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
sm: 3,
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Center>
|
||||
<Text fz={"2.4rem"}>Memuat Data...</Text>
|
||||
</Center>
|
||||
) : (
|
||||
data.map((v, k) => {
|
||||
return (
|
||||
<BackgroundImage
|
||||
key={k}
|
||||
src={v.image?.link || ''}
|
||||
h={350}
|
||||
radius={16}
|
||||
pos={"relative"}
|
||||
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
||||
<Box w={{ base: '100%', sm: '70%' }}>
|
||||
<Stack gap="sm">
|
||||
<Text fz={{ base: '2.4rem', sm: '4rem' }} fw="bold" lh={1.2}>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text fz={{ base: '1rem', sm: '1.3rem' }} c="dimmed">
|
||||
{textHeading.des}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="lg">
|
||||
{loading ? (
|
||||
<Center mih={200}>
|
||||
<Loader size="lg" color="blue" />
|
||||
</Center>
|
||||
) : data.length === 0 ? (
|
||||
<Center mih={200}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz="lg" c="dimmed">
|
||||
Belum ada data APBDes yang tersedia
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
Data akan ditampilkan di sini setelah diunggah
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
data.map((v, k) => (
|
||||
<BackgroundImage
|
||||
key={k}
|
||||
src={v.image?.link || ''}
|
||||
h={360}
|
||||
radius="xl"
|
||||
pos="relative"
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
<Box
|
||||
pos="absolute"
|
||||
inset={0}
|
||||
bg="rgba(0,0,0,0.55)"
|
||||
style={{ backdropFilter: 'blur(4px)' }}
|
||||
/>
|
||||
<Stack justify="space-between" h="100%" p="xl" pos="relative">
|
||||
<Text
|
||||
c="white"
|
||||
fw={600}
|
||||
fz="lg"
|
||||
ta="center"
|
||||
lineClamp={2}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
zIndex: 0
|
||||
}}
|
||||
pos={"absolute"}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
bg={colors.trans.dark[2]}
|
||||
/>
|
||||
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
|
||||
<Box p={"lg"}>
|
||||
<Text
|
||||
c={"white"}
|
||||
size={"1.5rem"}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
}}>{v.name}</Text>
|
||||
</Box>
|
||||
<Text
|
||||
fw={"bold"}
|
||||
c={"white"}
|
||||
size={"3.5rem"}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
}}>{v.jumlah}</Text>
|
||||
<Group justify="center">
|
||||
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]} component={Link} href={v.file?.link || ''}>
|
||||
<Flex gap={"md"}>
|
||||
<IconDownload size={20} />
|
||||
<Text fz={"sm"} c={"white"}>Download</Text>
|
||||
</Flex>
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</SimpleGrid>
|
||||
<Group pb={80} justify='center'>
|
||||
<Button component={Link} href="/darmasaba/apbdes" radius={"lg"} bg={colors["blue-button"]} fz={"h4"}>Lihat Semua</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text
|
||||
fw="bold"
|
||||
c="white"
|
||||
fz="3rem"
|
||||
ta="center"
|
||||
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
|
||||
>
|
||||
{v.jumlah}
|
||||
</Text>
|
||||
<Group justify="center">
|
||||
<ActionIcon
|
||||
component={Link}
|
||||
href={v.file?.link || ''}
|
||||
radius="xl"
|
||||
size="lg"
|
||||
variant="gradient"
|
||||
gradient={{ from: '#1C6EA4', to: '#1C6EA4' }}
|
||||
>
|
||||
<Flex align="center" gap="xs" px="md" py={6}>
|
||||
<IconDownload size={18} color="white" />
|
||||
</Flex>
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
))
|
||||
)}
|
||||
</SimpleGrid>
|
||||
|
||||
<Group pb={80} justify="center">
|
||||
<Button
|
||||
component={Link}
|
||||
href="/darmasaba/apbdes"
|
||||
radius="xl"
|
||||
size="lg"
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
>
|
||||
Lihat Semua Data
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default Apbdes;
|
||||
export default Apbdes
|
||||
|
||||
@@ -160,7 +160,12 @@ function Kepuasan() {
|
||||
</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>
|
||||
<Center mt={10}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
<Button
|
||||
radius={"lg"}
|
||||
onClick={open}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
|
||||
@@ -1,64 +1,81 @@
|
||||
import profileLandingPageState from "@/app/admin/(dashboard)/_state/landing-page/profile";
|
||||
import { Center, Image, Paper, SimpleGrid, Text } from "@mantine/core";
|
||||
import { Box, Center, Image, Paper, SimpleGrid, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { motion } from "framer-motion";
|
||||
import { useTransitionRouter } from "next-view-transitions";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { IconPhotoOff } from "@tabler/icons-react";
|
||||
|
||||
type ProgramInovasiItem = Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>;
|
||||
|
||||
function ModuleItem({ data }: { data: ProgramInovasiItem }) {
|
||||
const router = useTransitionRouter();
|
||||
return (
|
||||
<Paper
|
||||
onClick={() => {
|
||||
router.push(`/${data.name}`);
|
||||
}}
|
||||
p={"md"}
|
||||
bg={"white"}
|
||||
radius={"32"}
|
||||
pos={"relative"}
|
||||
>
|
||||
<Center h={"100%"}>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
<motion.div whileHover={{ scale: 1.04 }}>
|
||||
<Tooltip label={`Lihat ${data.name}`} withArrow>
|
||||
<Paper
|
||||
onClick={() => router.push(`/${data.name}`)}
|
||||
p="xl"
|
||||
radius="2xl"
|
||||
bg="white"
|
||||
className="cursor-pointer transition-all shadow-md hover:shadow-xl"
|
||||
>
|
||||
{data.image?.link ? (
|
||||
<Image src={data.image.link} alt="icon"
|
||||
fit="contain"
|
||||
sizes="100%"
|
||||
loading="lazy"
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
objectPosition: "center"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text>
|
||||
-
|
||||
</Text>
|
||||
)}
|
||||
</motion.div>
|
||||
</Center>
|
||||
</Paper>
|
||||
<Center h={180}>
|
||||
{data.image?.link ? (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.name}
|
||||
fit="contain"
|
||||
radius="lg"
|
||||
loading="lazy"
|
||||
style={{ objectFit: "contain", objectPosition: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<Stack align="center" gap="xs">
|
||||
<IconPhotoOff size={40} stroke={1.5} />
|
||||
<Text size="sm" c="dimmed">
|
||||
Belum ada gambar
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Center>
|
||||
<Box mt="md">
|
||||
<Text fw={600} ta="center" size="lg" c="black">
|
||||
{data.name}
|
||||
</Text>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Tooltip>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModuleView() {
|
||||
const listImageState = useProxy(profileLandingPageState.programInovasi)
|
||||
const listImageState = useProxy(profileLandingPageState.programInovasi);
|
||||
|
||||
useShallowEffect(() => {
|
||||
listImageState.findMany.load()
|
||||
}, [])
|
||||
listImageState.findMany.load();
|
||||
}, []);
|
||||
|
||||
if (!listImageState.findMany.loading && !listImageState.findMany.data?.length) {
|
||||
return (
|
||||
<Center h={320}>
|
||||
<Stack align="center" gap="sm">
|
||||
<IconPhotoOff size={54} stroke={1.5} />
|
||||
<Text size="lg" fw={600} c="white">
|
||||
Belum ada program inovasi
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
Tambahkan program inovasi untuk ditampilkan di sini
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 2,
|
||||
md: 3,
|
||||
}}
|
||||
>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
|
||||
{listImageState.findMany.data?.map((item) => (
|
||||
<ModuleItem key={item.id} data={item} />
|
||||
))}
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Card, Image, Stack, Text } from '@mantine/core';
|
||||
import { Box, Card, Image, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { IconUserCircle } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import colors from '@/con/colors';
|
||||
|
||||
interface ProfileViewProps {
|
||||
data: Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null;
|
||||
}
|
||||
|
||||
function ProfileView({ data }: ProfileViewProps) {
|
||||
export default function ProfileView({ data }: ProfileViewProps) {
|
||||
if (!data) {
|
||||
return <div>No profile data available</div>;
|
||||
return (
|
||||
<Card radius="2xl" className="glass3" py="xl" px="lg" withBorder>
|
||||
<Stack align="center" gap="sm">
|
||||
<IconUserCircle size={72} stroke={1.4} />
|
||||
<Text fw={500} c="dimmed">
|
||||
Profil belum tersedia
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
Data pejabat desa akan muncul di sini
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -17,42 +30,38 @@ function ProfileView({ data }: ProfileViewProps) {
|
||||
justify="end"
|
||||
align="end"
|
||||
pos="relative"
|
||||
w={{
|
||||
base: "100%",
|
||||
md: "40%",
|
||||
}}
|
||||
w={{ base: '100%', md: '40%' }}
|
||||
px="xl"
|
||||
>
|
||||
{data.image?.link ? (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.name || "Profile image"}
|
||||
sizes="100%"
|
||||
fit="contain"
|
||||
alt={data.name || 'Foto profil'}
|
||||
fit="cover"
|
||||
radius="lg"
|
||||
/>
|
||||
): (
|
||||
<Text>
|
||||
-
|
||||
</Text>
|
||||
) : (
|
||||
<Stack align="center" gap="xs" w="100%" py="xl">
|
||||
<IconUserCircle size={96} stroke={1.5} />
|
||||
<Text c="dimmed" fz="sm">
|
||||
Belum ada foto
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
<Box
|
||||
pos="absolute"
|
||||
bottom={0}
|
||||
p={{
|
||||
base: "xs",
|
||||
md: "md",
|
||||
}}
|
||||
>
|
||||
<Box pos="absolute" bottom={0} w="100%" p={{ base: 'xs', md: 'md' }}>
|
||||
<Card
|
||||
px="lg"
|
||||
radius="32"
|
||||
radius="2xl"
|
||||
withBorder
|
||||
className="glass3"
|
||||
style={{
|
||||
border: `1px solid white`,
|
||||
}}
|
||||
style={{ border: '1px solid rgba(255,255,255,0.15)' }}
|
||||
>
|
||||
<Text>{data.position}</Text>
|
||||
<Text c={colors["blue-button"]} fw="bolder" fz="1rem">
|
||||
<Tooltip label="Jabatan Resmi" withArrow>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{data.position || 'Tidak ada jabatan'}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Text c={colors['blue-button']} fw={700} fz="xl" mt={4}>
|
||||
{data.name}
|
||||
</Text>
|
||||
</Card>
|
||||
@@ -60,5 +69,3 @@ function ProfileView({ data }: ProfileViewProps) {
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileView;
|
||||
@@ -1,35 +1,79 @@
|
||||
import { ActionIcon, Flex, Image, Text } from "@mantine/core";
|
||||
import { ActionIcon, Card, Flex, Image, Text, Tooltip } from "@mantine/core";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { useTransitionRouter } from "next-view-transitions";
|
||||
import { IconBrandInstagram, IconBrandFacebook, IconBrandTwitter, IconWorld } from "@tabler/icons-react";
|
||||
|
||||
|
||||
|
||||
function SosmedView({data} : {data : Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]}) {
|
||||
function SosmedView({
|
||||
data,
|
||||
}: {
|
||||
data: Prisma.MediaSosialGetPayload<{ include: { image: true } }>[];
|
||||
}) {
|
||||
const router = useTransitionRouter();
|
||||
|
||||
const fallbackIcon = (platform?: string) => {
|
||||
switch (platform?.toLowerCase()) {
|
||||
case "instagram":
|
||||
return <IconBrandInstagram size={22} />;
|
||||
case "facebook":
|
||||
return <IconBrandFacebook size={22} />;
|
||||
case "twitter":
|
||||
return <IconBrandTwitter size={22} />;
|
||||
default:
|
||||
return <IconWorld size={22} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex gap={"md"} justify={"center"} align={"center"}>
|
||||
{data?.map((item, k) => {
|
||||
return (
|
||||
<Flex gap="lg" justify="center" align="center" wrap="wrap">
|
||||
{data && data.length > 0 ? (
|
||||
data.map((item, k) => (
|
||||
<Tooltip
|
||||
key={k}
|
||||
label={item.name || "Tautan Sosial"}
|
||||
withArrow
|
||||
position="top"
|
||||
transitionProps={{ transition: "pop", duration: 150 }}
|
||||
>
|
||||
<ActionIcon
|
||||
variant="transparent"
|
||||
key={k}
|
||||
w={32}
|
||||
h={32}
|
||||
pos={"relative"}
|
||||
onClick={() => {
|
||||
router.push(item.iconUrl || "");
|
||||
variant="light"
|
||||
radius="xl"
|
||||
size="xl"
|
||||
onClick={() => item.iconUrl && router.push(item.iconUrl)}
|
||||
style={{
|
||||
transition: "all 0.3s ease",
|
||||
boxShadow: "0 0 12px rgba(28, 110, 164, 0.6)",
|
||||
}}
|
||||
>
|
||||
{item.image?.link ? (
|
||||
<Image src={item.image.link} alt="icon" loading="lazy" />
|
||||
<Image
|
||||
src={item.image.link}
|
||||
alt={item.name || "ikon"}
|
||||
w={24}
|
||||
h={24}
|
||||
fit="contain"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Text>
|
||||
none
|
||||
</Text>
|
||||
fallbackIcon(item.name)
|
||||
)}
|
||||
</ActionIcon>
|
||||
);
|
||||
})}
|
||||
</Tooltip>
|
||||
))
|
||||
) : (
|
||||
<Card
|
||||
shadow="md"
|
||||
radius="xl"
|
||||
p="lg"
|
||||
withBorder
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #1C6EA4 0%, #000 100%)",
|
||||
}}
|
||||
>
|
||||
<Text ta="center" c="dimmed" size="sm">
|
||||
Belum ada media sosial yang terhubung
|
||||
</Text>
|
||||
</Card>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,79 +11,77 @@ import {
|
||||
Image,
|
||||
Paper,
|
||||
Stack,
|
||||
Text
|
||||
Text,
|
||||
Center,
|
||||
Tooltip,
|
||||
Badge,
|
||||
} from "@mantine/core";
|
||||
import { IconCalendarTime, IconInfoCircle } from "@tabler/icons-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ModuleView from "./ModuleView";
|
||||
import SosmedView from "./SosmedView";
|
||||
import ProfileView from "./ProfileView";
|
||||
|
||||
const getDayOfWeek = () => {
|
||||
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
|
||||
const days = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"];
|
||||
const today = new Date();
|
||||
return days[today.getDay()];
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentTime = () => {
|
||||
const now = new Date();
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, "0");
|
||||
const minutes = String(now.getMinutes()).padStart(2, "0");
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
};
|
||||
|
||||
const isWorkingHours = (currentTime: string): boolean => {
|
||||
const [openTime, closeTime] = ['08:00', '16:00'];
|
||||
|
||||
const [openTime, closeTime] = ["08:00", "16:00"];
|
||||
const compareTimes = (time1: string, time2: string) => {
|
||||
const [hour1, minute1] = time1.split(':').map(Number);
|
||||
const [hour2, minute2] = time2.split(':').map(Number);
|
||||
|
||||
const [hour1, minute1] = time1.split(":").map(Number);
|
||||
const [hour2, minute2] = time2.split(":").map(Number);
|
||||
if (hour1 < hour2) return true;
|
||||
if (hour1 > hour2) return false;
|
||||
return minute1 <= minute2;
|
||||
};
|
||||
return compareTimes(currentTime, closeTime) && !compareTimes(currentTime, openTime);
|
||||
}
|
||||
};
|
||||
|
||||
const getWorkStatus = (day: string, currentTime: string): { status: string; message: string } => {
|
||||
const workingDays = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat'];
|
||||
|
||||
const workingDays = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat"];
|
||||
if (!workingDays.includes(day)) {
|
||||
return {
|
||||
status: 'Tutup',
|
||||
message: 'Sabtu - Minggu'
|
||||
}
|
||||
return { status: "Tutup", message: "Libur Akhir Pekan" };
|
||||
}
|
||||
const isOpen = isWorkingHours(currentTime)
|
||||
return isOpen ? { status: 'Buka', message: '08:00 - 16:00' } : { status: 'Tutup', message: '08:00 - 16:00' };
|
||||
}
|
||||
|
||||
const isOpen = isWorkingHours(currentTime);
|
||||
return isOpen
|
||||
? { status: "Buka", message: "08:00 - 16:00" }
|
||||
: { status: "Tutup", message: "08:00 - 16:00" };
|
||||
};
|
||||
|
||||
function LandingPage() {
|
||||
const [socialMedia, setSocialMedia] = useState<Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]>([]);
|
||||
const [profile, setProfile] = useState<Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null>(null);
|
||||
const [socialMedia, setSocialMedia] = useState<
|
||||
Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]
|
||||
>([]);
|
||||
const [profile, setProfile] = useState<
|
||||
Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null
|
||||
>(null);
|
||||
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 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
|
||||
} catch {
|
||||
setSocialMedia([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -92,22 +90,22 @@ function LandingPage() {
|
||||
const fetchProfile = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/landingpage/pejabatdesa/edit`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
const result = await response.json();
|
||||
setProfile(result.data || null); // Handle single object response
|
||||
} catch (error) {
|
||||
console.error('Error fetching profile:', error);
|
||||
setProfile(result.data || null);
|
||||
} catch {
|
||||
setProfile(null);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSocialMedia();
|
||||
fetchProfile();
|
||||
}, []);
|
||||
|
||||
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>
|
||||
({ status: '', message: '' });
|
||||
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>({
|
||||
status: "",
|
||||
message: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const updateWorkStatus = () => {
|
||||
@@ -115,212 +113,110 @@ function LandingPage() {
|
||||
const time = getCurrentTime();
|
||||
const status = getWorkStatus(day, time);
|
||||
setWorkStatus(status);
|
||||
}
|
||||
};
|
||||
updateWorkStatus();
|
||||
const intervalId = setInterval(updateWorkStatus, 60 * 1000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack bg={colors["Bg"]}>
|
||||
<Flex
|
||||
gap={"md"}
|
||||
wrap={{
|
||||
base: "wrap",
|
||||
md: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
gap={"xl"}
|
||||
w={{
|
||||
base: "100%",
|
||||
md: "60%",
|
||||
}}
|
||||
py={{
|
||||
base: "xs",
|
||||
md: "40",
|
||||
}}
|
||||
px={{
|
||||
base: "xs",
|
||||
md: "100",
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
radius={"32"}
|
||||
bg={colors.grey[1]}
|
||||
p={{
|
||||
base: "xs",
|
||||
md: "32",
|
||||
}}
|
||||
>
|
||||
<Stack gap={42}>
|
||||
<Flex
|
||||
gap={"md"}
|
||||
wrap={{
|
||||
base: "wrap",
|
||||
md: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
>
|
||||
<Grid.Col span={{
|
||||
base: 3,
|
||||
lg: 2,
|
||||
md: 3,
|
||||
}}>
|
||||
<Box
|
||||
pos={"relative"}
|
||||
bg={"white"}
|
||||
w={{
|
||||
base: 64,
|
||||
md: 72,
|
||||
}}
|
||||
h={{
|
||||
base: 64,
|
||||
md: 72,
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 24,
|
||||
}}
|
||||
p={"sm"}
|
||||
>
|
||||
<Image
|
||||
src={"/darmasaba-icon.png"}
|
||||
alt="icon"
|
||||
sizes="100%"
|
||||
/>
|
||||
<Stack bg={colors.Bg} p="md" gap="xl">
|
||||
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
|
||||
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
|
||||
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
||||
<Stack gap="xl">
|
||||
<Flex gap="md" wrap="wrap">
|
||||
<Grid w="100%">
|
||||
<Grid.Col span={{ base: 3, sm: 2 }}>
|
||||
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
||||
<Image src="/darmasaba-icon.png" alt="Logo Darmasaba" fit="contain" />
|
||||
</Box>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{
|
||||
base: 3,
|
||||
lg: 2,
|
||||
md: 3,
|
||||
}}>
|
||||
<Box
|
||||
pos={"relative"}
|
||||
w={{
|
||||
base: 64,
|
||||
md: 72,
|
||||
}}
|
||||
h={{
|
||||
base: 64,
|
||||
md: 72,
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 24,
|
||||
}}
|
||||
p={"sm"}
|
||||
bg={"white"}
|
||||
>
|
||||
<Image
|
||||
src={"/pudak-icon.png"}
|
||||
alt="icon"
|
||||
sizes={"100%"}
|
||||
fit="contain"
|
||||
/>
|
||||
<Grid.Col span={{ base: 9, sm: 10 }}>
|
||||
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
||||
<Image src="/pudak-icon.png" alt="Logo Pudak" fit="contain" />
|
||||
</Box>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{
|
||||
base: 12,
|
||||
lg: 12,
|
||||
md: 12,
|
||||
}}>
|
||||
<Grid.Col span={12}>
|
||||
<Paper
|
||||
pos={"relative"}
|
||||
bg={colors["blue-button"]}
|
||||
p={10}
|
||||
w={{ base: "100%", sm: "auto", md: "auto" }}
|
||||
flex={{ base: "1", sm: "1", md: "1" }}
|
||||
p="md"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
style={{ position: "relative", overflow: "hidden" }}
|
||||
>
|
||||
<Grid
|
||||
>
|
||||
<GridCol span={{
|
||||
base: 12,
|
||||
lg: 6,
|
||||
md: 6,
|
||||
}}>
|
||||
<Box>
|
||||
<Text c={colors["white-1"]} fz={"sm"}>
|
||||
Jadwal Kerja
|
||||
</Text>
|
||||
<Paper
|
||||
w={{ base: "100%", sm: "100%", md: "auto" }}
|
||||
p={5}
|
||||
bg={colors["white-1"]}
|
||||
>
|
||||
<Flex justify={"space-between"} align={"center"}>
|
||||
<Paper
|
||||
w={{ base: "100%", sm: "100%", md: "auto" }}
|
||||
p={5}
|
||||
bg={colors["white-1"]}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Stack gap="xs">
|
||||
<Flex align="center" gap="xs">
|
||||
<IconCalendarTime size={16} color="white" />
|
||||
<Text c="white" fz="sm">Jam Operasional</Text>
|
||||
</Flex>
|
||||
<Paper p="sm" radius="md" bg="white">
|
||||
<Tooltip label="Status saat ini berdasarkan jam operasional kantor">
|
||||
<Badge
|
||||
color={workStatus.status === "Buka" ? "green" : "red"}
|
||||
radius="sm"
|
||||
variant="filled"
|
||||
>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" c={workStatus.status === 'Buka' ? "black" : "red"}>
|
||||
{workStatus.status}
|
||||
</Text>
|
||||
<Text fw="bold" fz="lg" >
|
||||
{workStatus.message}
|
||||
</Text>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Flex>
|
||||
{workStatus.status}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
<Text fw="bold" fz="lg">{workStatus.message}</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
{/* Edit yang ini */}
|
||||
<GridCol span={{ base: 12, lg: 6, md: 6 }}>
|
||||
<Box>
|
||||
<Text c={colors["white-1"]} fz={"sm"}>
|
||||
{new Intl.DateTimeFormat('id-ID', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
}).format(new Date())}
|
||||
</Text>
|
||||
<Paper bg={colors["white-1"]} p={10}>
|
||||
<Text fz="sm" >
|
||||
Status
|
||||
</Text>
|
||||
<Text fw="bold" fz="lg" >
|
||||
{workStatus.status === 'Buka' ? 'Operasional' : 'Tutup'}
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Stack gap="xs">
|
||||
<Flex align="center" gap="xs">
|
||||
<IconInfoCircle size={16} color="white" />
|
||||
<Text c="white" fz="sm">Hari Ini</Text>
|
||||
</Flex>
|
||||
<Paper p="sm" radius="md" bg="white">
|
||||
<Text fz="sm">Status Kantor</Text>
|
||||
<Text fw="bold" fz="lg">
|
||||
{workStatus.status === "Buka" ? "Sedang Beroperasi" : "Tidak Beroperasi"}
|
||||
</Text>
|
||||
</Paper>
|
||||
|
||||
</Box>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid.Col>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Flex>
|
||||
|
||||
<ModuleView />
|
||||
|
||||
{isLoading ? (
|
||||
<Skeleton height={32} width="100%" />
|
||||
) : socialMedia.length > 0 ? (
|
||||
<SosmedView data={socialMedia} />
|
||||
) : (
|
||||
<div>No social media links available</div>
|
||||
<Center>
|
||||
<Text c="dimmed">Belum ada tautan media sosial yang tersedia</Text>
|
||||
</Center>
|
||||
)}
|
||||
<Text c={colors.trans.dark[2]} style={{
|
||||
textAlign: "center"
|
||||
}} >Sampaikan saran dan masukan guna kemajuan dalam pembangunan. Semua lebih mudah melalui fitur interaktif</Text>
|
||||
|
||||
<Text ta="center" c={colors.trans.dark[2]}>
|
||||
Bagikan ide, kritik, atau saran Anda untuk mendukung pembangunan desa.
|
||||
Semua lebih mudah dengan fitur interaktif yang kami sediakan.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
</Stack>
|
||||
|
||||
{isLoading ? (
|
||||
<Skeleton height={32} width="100%" />
|
||||
<Skeleton height={300} width="100%" radius="lg" />
|
||||
) : profile ? (
|
||||
<ProfileView data={profile} />
|
||||
) : (
|
||||
<div>No profile available</div>
|
||||
<Center w="100%">
|
||||
<Text c="dimmed">Informasi profil belum tersedia</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Flex>
|
||||
</Stack >
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client';
|
||||
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
||||
import colors from "@/con/colors";
|
||||
import { Stack, Box, Container, Button, Text } from "@mantine/core";
|
||||
import { useTransitionRouter } from 'next-view-transitions'
|
||||
import { Stack, Box, Container, Button, Text, Loader, Paper } from "@mantine/core";
|
||||
import { IconAward, IconArrowRight } from "@tabler/icons-react";
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useEffect, useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
|
||||
function Penghargaan() {
|
||||
const router = useTransitionRouter()
|
||||
const state = useProxy(penghargaanState)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useTransitionRouter();
|
||||
const state = useProxy(penghargaanState);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
await state.findMany.load()
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
setLoading(true);
|
||||
await state.findMany.load();
|
||||
} finally {
|
||||
setLoading(false)
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}, [])
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const data = state.findMany.data?.slice(0, 3);
|
||||
|
||||
const data = state.findMany.data?.slice(0, 3)
|
||||
return (
|
||||
<Stack pos={"relative"} h={720}>
|
||||
<Stack pos="relative" h={720}>
|
||||
<video
|
||||
width="320"
|
||||
height="240"
|
||||
@@ -51,47 +50,68 @@ function Penghargaan() {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
background: "rgba(0,0,0,0.6)",
|
||||
background: "linear-gradient(to bottom, rgba(0,0,0,0.6), rgba(0,0,0,0.85))",
|
||||
}}
|
||||
>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720}>
|
||||
<Stack justify="center" align="center">
|
||||
<Container w={{ base: "100%", md: "80%" }} h={720} p="xl">
|
||||
<Stack justify="center" align="center" gap="xl" h="100%">
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
}}
|
||||
fw={"bold"}
|
||||
fz={"2.4rem"}
|
||||
c={"white"}
|
||||
fw={900}
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
ta="center"
|
||||
variant="gradient"
|
||||
gradient={{ from: "cyan", to: "blue", deg: 60 }}
|
||||
>
|
||||
Penghargaan
|
||||
Penghargaan & Prestasi Desa
|
||||
</Text>
|
||||
|
||||
{loading ? (
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
}}
|
||||
fw={"bold"}
|
||||
fz={"2.4rem"}
|
||||
c={"white"}
|
||||
>
|
||||
Memuat Data...
|
||||
</Text>
|
||||
) : (
|
||||
data?.map((v, k) => {
|
||||
return (
|
||||
<Box key={k}>
|
||||
<Stack align="center" gap={0}>
|
||||
<Text fz={"1.4rem"} c={"white"}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader color="blue" size="lg" />
|
||||
<Text c="gray.3" fz="lg">Sedang memuat data penghargaan...</Text>
|
||||
</Stack>
|
||||
) : data && data.length > 0 ? (
|
||||
<Stack gap="md" w="100%" maw={600}>
|
||||
{data.map((v, k) => (
|
||||
<Paper
|
||||
key={k}
|
||||
withBorder
|
||||
radius="xl"
|
||||
p="lg"
|
||||
shadow="xl"
|
||||
style={{
|
||||
background: "rgba(255,255,255,0.07)",
|
||||
backdropFilter: "blur(12px)",
|
||||
transition: "all 0.3s ease",
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap="xs">
|
||||
<IconAward size={40} color="var(--mantine-color-blue-4)" />
|
||||
<Text fz="lg" fw={700} c="white" ta="center">
|
||||
{v.name}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack align="center" gap="xs">
|
||||
<IconAward size={48} color="var(--mantine-color-gray-5)" />
|
||||
<Text c="gray.4" fz="lg" ta="center">
|
||||
Belum ada penghargaan yang tercatat
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
<Button color={colors["blue-button"]} onClick={() => router.push("/darmasaba/penghargaan")} variant="white" radius={100}>
|
||||
Selanjutnya
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
radius="xl"
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170", deg: 45 }}
|
||||
rightSection={<IconArrowRight size={20} />}
|
||||
onClick={() => router.push("/darmasaba/penghargaan")}
|
||||
>
|
||||
Lihat Semua Penghargaan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
@@ -100,4 +120,4 @@ function Penghargaan() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Penghargaan;
|
||||
export default Penghargaan;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
"use client";
|
||||
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
|
||||
import colors from "@/con/colors";
|
||||
@@ -9,118 +8,128 @@ import {
|
||||
Button,
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { IconArrowRight, IconInfoCircle } from "@tabler/icons-react";
|
||||
import _ from "lodash";
|
||||
import { motion } from "motion/react";
|
||||
import { useTransitionRouter } from "next-view-transitions";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
|
||||
|
||||
|
||||
const textHeading = {
|
||||
title: "Potensi",
|
||||
des: "Tidak hanya untuk warga desa, fitur ini juga dapat digunakan oleh pemerintah desa untuk merencanakan program pengembangan berbasis potensi lokal",
|
||||
title: "Potensi Desa",
|
||||
des: "Jelajahi berbagai potensi dan peluang yang dimiliki desa. Fitur ini membantu warga maupun pemerintah desa dalam merencanakan dan mengembangkan program berbasis kekuatan lokal.",
|
||||
};
|
||||
|
||||
function Potensi() {
|
||||
const router = useTransitionRouter()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const state = useProxy(potensiDesaState.potensiDesa)
|
||||
const router = useTransitionRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const state = useProxy(potensiDesaState.potensiDesa);
|
||||
|
||||
useEffect(()=> {
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findMany.load()
|
||||
await state.findMany.load();
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
console.error("Gagal memuat data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}, [])
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const data = (state.findMany.data || []).slice(0, 4);
|
||||
|
||||
return (
|
||||
<Stack p={"sm"} gap={"4rem"}>
|
||||
<Box
|
||||
w={{
|
||||
base: "100%",
|
||||
sm: "60%",
|
||||
}}
|
||||
>
|
||||
<Text fz={"4.4rem"}>{textHeading.title}</Text>
|
||||
<Text size={"1.4rem"}>{textHeading.des}</Text>
|
||||
<Stack p="sm" gap="4rem">
|
||||
<Box w={{ base: "100%", sm: "60%" }}>
|
||||
<Text fz="4.4rem" fw={700} c={colors["blue-button"]}>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text size="1.4rem" c="black">
|
||||
{textHeading.des}
|
||||
</Text>
|
||||
</Box>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
sm: 2,
|
||||
}}
|
||||
>
|
||||
{_.take(data, 4).map((v, k) => (
|
||||
<motion.div
|
||||
key={k}
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.8 }}
|
||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
||||
>
|
||||
<BackgroundImage
|
||||
src={v.image?.link}
|
||||
h={320}
|
||||
|
||||
{loading ? (
|
||||
<Stack align="center" justify="center" h={300}>
|
||||
<Loader size="lg" color={colors["blue-button"]} />
|
||||
<Text c="gray.4">Sedang memuat potensi desa...</Text>
|
||||
</Stack>
|
||||
) : data.length === 0 ? (
|
||||
<Stack align="center" justify="center" h={300} gap="xs">
|
||||
<IconInfoCircle size={48} color={colors["blue-button"]} />
|
||||
<Text fw={600} c="gray.3">
|
||||
Belum ada potensi tersedia
|
||||
</Text>
|
||||
<Text size="sm" c="gray.5">
|
||||
Silakan cek kembali nanti untuk pembaruan terbaru.
|
||||
</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }}>
|
||||
{_.take(data, 4).map((v, k) => (
|
||||
<motion.div
|
||||
key={k}
|
||||
radius={16}
|
||||
pos={"relative"}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
zIndex: 0,
|
||||
}}
|
||||
pos={"absolute"}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
bg={colors.trans.dark[2]}
|
||||
/>
|
||||
<Stack
|
||||
justify="end"
|
||||
h={"100%"}
|
||||
p={"md"}
|
||||
align="start"
|
||||
pos={"absolute"}
|
||||
style={{
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Text fw={"bold"} c={"gray.1"} size={"2.4rem"}>
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text
|
||||
lineClamp={2}
|
||||
style={{
|
||||
textAlign: "justify",
|
||||
}}
|
||||
c={colors["white-1"]}
|
||||
<BackgroundImage src={v.image?.link} h={320} radius={20} pos="relative">
|
||||
<Box
|
||||
pos="absolute"
|
||||
w="100%"
|
||||
h="100%"
|
||||
bg={colors.trans.dark[2]}
|
||||
style={{ borderRadius: 20, zIndex: 0 }}
|
||||
/>
|
||||
<Stack
|
||||
justify="end"
|
||||
h="100%"
|
||||
p="md"
|
||||
align="start"
|
||||
pos="absolute"
|
||||
style={{ zIndex: 1 }}
|
||||
>
|
||||
{v.deskripsi}
|
||||
</Text>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
</motion.div>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Tooltip label={v.name} position="top-start">
|
||||
<Text fw={700} c="white" size="2.2rem" truncate>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Text lineClamp={2} c="gray.2" size="sm">
|
||||
{v.deskripsi}
|
||||
</Text>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
</motion.div>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
<Stack align="center">
|
||||
<Group>
|
||||
<Button onClick={()=> router.push("/darmasaba/desa/potensi")} color={colors["blue-button"]} variant="outline" radius={100} size="md">
|
||||
Selengkapnya
|
||||
<Button
|
||||
onClick={() => router.push("/darmasaba/desa/potensi")}
|
||||
color={colors["blue-button"]}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170", }}
|
||||
radius="xl"
|
||||
size="md"
|
||||
rightSection={<IconArrowRight size={18} />}
|
||||
>
|
||||
Lihat Semua Potensi
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Divider />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -2,104 +2,124 @@
|
||||
'use client'
|
||||
import prestasiState from "@/app/admin/(dashboard)/_state/landing-page/prestasi-desa";
|
||||
import colors from "@/con/colors";
|
||||
import { BackgroundImage, Box, Button, Center, Container, Group, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { BackgroundImage, Box, Button, Center, Container, Group, Loader, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { IconTrophy } from "@tabler/icons-react";
|
||||
|
||||
function Prestasi() {
|
||||
const state = useProxy(prestasiState.prestasiDesa);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter()
|
||||
const state = useProxy(prestasiState.prestasiDesa);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
prestasiState.kategoriPrestasi.findMany.load()
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findMany.load();
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData();
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
prestasiState.kategoriPrestasi.findMany.load();
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findMany.load();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const data = (state.findMany.data || []).slice(0, 3);
|
||||
return (
|
||||
<>
|
||||
<Stack p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Text ta={"center"} fz={"3.4rem"}>Prestasi Desa</Text>
|
||||
<Text fz={"1.4rem"} ta={"center"}>Kami bangga dengan apa yang telah dicapai desa kita hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama.</Text>
|
||||
<Center pt={20}>
|
||||
<Button radius={"lg"} fz={"h4"} color={colors["blue-button"]} component={Link} href="/darmasaba/prestasi-desa">Selengkapnya</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box py={50}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
sm: 3
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Center>
|
||||
<Text fz={"2.4rem"}>Memuat Data...</Text>
|
||||
</Center>
|
||||
) : (
|
||||
data.map((v, k) => {
|
||||
return (
|
||||
<BackgroundImage
|
||||
key={k}
|
||||
src={v.image?.link || ''}
|
||||
radius={16}
|
||||
pos={"relative"}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
zIndex: 0
|
||||
}}
|
||||
pos={"absolute"}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
bg={colors.trans.dark[2]}
|
||||
/>
|
||||
<Stack justify="space-between" h={"100%"} gap={0} p={"lg"} pos={"relative"}>
|
||||
<Box p={"lg"}>
|
||||
<Text
|
||||
c={"white"}
|
||||
fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }}
|
||||
ta={"center"}
|
||||
>
|
||||
{v.kategori.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
fw={"bold"}
|
||||
c={"white"}
|
||||
fz={{ base: "2rem", md: "2.5rem", lg: "2.7rem", xl: "3rem" }}
|
||||
ta={"center"}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
<Group justify="center">
|
||||
<Button onClick={() => router.push(`/darmasaba/prestasi-desa/${v.id}`)} px={46} radius={"100"} size="md" bg={colors["blue-button"]}>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
const data = (state.findMany.data || []).slice(0, 3);
|
||||
|
||||
return (
|
||||
<Stack p="sm" bg="linear-gradient(180deg, #ffffff 0%, #f8fbff 100%)">
|
||||
<Container w={{ base: "100%", md: "80%" }} p="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<Group gap="xs">
|
||||
<IconTrophy size={36} color={colors["blue-button"]} />
|
||||
<Text ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
||||
Prestasi Desa
|
||||
</Text>
|
||||
</Group>
|
||||
<Text fz={{ base: "1rem", md: "1.3rem" }} ta="center" c="dimmed" maw={700}>
|
||||
Kami bangga dengan pencapaian desa hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama.
|
||||
</Text>
|
||||
<Button
|
||||
radius="xl"
|
||||
size="lg"
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
component={Link}
|
||||
href="/darmasaba/prestasi-desa"
|
||||
>
|
||||
Lihat Semua Prestasi
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
<Box py={50}>
|
||||
{loading ? (
|
||||
<Center mih={200}>
|
||||
<Loader color={colors["blue-button"]} size="xl" />
|
||||
</Center>
|
||||
) : data.length === 0 ? (
|
||||
<Center mih={200}>
|
||||
<Stack align="center" gap="xs">
|
||||
<IconTrophy size={48} color="gray" />
|
||||
<Text fz="1.2rem" fw={500} c="dimmed">
|
||||
Belum ada prestasi yang ditampilkan
|
||||
</Text>
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
</Center>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
|
||||
{data.map((v, k) => (
|
||||
<BackgroundImage
|
||||
key={k}
|
||||
src={v.image?.link || ""}
|
||||
radius="xl"
|
||||
pos="relative"
|
||||
>
|
||||
<Box
|
||||
pos="absolute"
|
||||
inset={0}
|
||||
bg="linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.7) 100%)"
|
||||
style={{ borderRadius: "1rem" }}
|
||||
/>
|
||||
<Stack justify="space-between" h="100%" pos="relative" p="lg">
|
||||
<Box>
|
||||
<Text
|
||||
c="white"
|
||||
fz={{ base: "1rem", md: "1.25rem" }}
|
||||
ta="center"
|
||||
fw={500}
|
||||
>
|
||||
{v.kategori.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
fw={700}
|
||||
c="white"
|
||||
fz={{ base: "1.5rem", md: "2rem", lg: "2.5rem" }}
|
||||
ta="center"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
<Group justify="center">
|
||||
<Button
|
||||
onClick={() => router.push(`/darmasaba/prestasi-desa/${v.id}`)}
|
||||
radius="xl"
|
||||
size="md"
|
||||
color={colors["blue-button"]}
|
||||
>
|
||||
Detail Prestasi
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
export default Prestasi;
|
||||
|
||||
export default Prestasi;
|
||||
|
||||
@@ -1,143 +1,142 @@
|
||||
'use client'
|
||||
import colors from "@/con/colors";
|
||||
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react"
|
||||
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core"
|
||||
import { useMediaQuery } from "@mantine/hooks"
|
||||
import { Prisma } from "@prisma/client"
|
||||
import Link from "next/link"
|
||||
import { IconMoodSad } from "@tabler/icons-react"
|
||||
|
||||
export default function SDGS() {
|
||||
const theme = useMantineTheme();
|
||||
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
||||
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[] | null>(null);
|
||||
const theme = useMantineTheme()
|
||||
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;
|
||||
}
|
||||
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()
|
||||
let data = []
|
||||
if (Array.isArray(result.data)) data = result.data
|
||||
else if (Array.isArray(result)) data = result
|
||||
else {
|
||||
setSdgsDesa([])
|
||||
return
|
||||
}
|
||||
const top3Sdgs = [...data].sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah)).slice(0, 3)
|
||||
setSdgsDesa(top3Sdgs)
|
||||
} catch {
|
||||
setSdgsDesa([])
|
||||
}
|
||||
}
|
||||
fetchSdgsDesa()
|
||||
}, [])
|
||||
|
||||
// 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);
|
||||
return (
|
||||
<Stack p="sm">
|
||||
<Container w={{ base: "100%", md: "80%" }} p="xl">
|
||||
<Center>
|
||||
<Title order={1} fz={{ base: "2.2rem", md: "3.4rem" }} fw={900}>
|
||||
SDGs Desa
|
||||
</Title>
|
||||
</Center>
|
||||
<Text fz={{ base: "1rem", md: "1.3rem" }} ta="center" c="dimmed" mt="md" maw={800} mx="auto">
|
||||
SDGs Desa adalah penerapan 17 Tujuan Pembangunan Berkelanjutan di tingkat desa. Fokus pada pengentasan kemiskinan,
|
||||
pendidikan, kesehatan, kesetaraan gender, dan pelestarian lingkungan untuk menciptakan desa yang maju, inklusif, dan berkelanjutan.
|
||||
</Text>
|
||||
|
||||
setSdgsDesa(top3Sdgs);
|
||||
} catch (error) {
|
||||
console.error('Error fetching sdgs desa:', error);
|
||||
setSdgsDesa([]);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSdgsDesa();
|
||||
}, []);
|
||||
return (
|
||||
<Stack p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Center>
|
||||
<Text fz={"3.4rem"}>SDGs Desa</Text>
|
||||
</Center>
|
||||
<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>
|
||||
<Box py={50}>
|
||||
<Paper p={{ base: 'md', md: 'xl' }} bg={colors.Bg} radius="lg" shadow="sm">
|
||||
{sdgsDesa && sdgsDesa.length > 0 ? (
|
||||
<SimpleGrid
|
||||
cols={{ base: 1, sm: 3 }}
|
||||
spacing="xl"
|
||||
verticalSpacing="xl"
|
||||
>
|
||||
{sdgsDesa.map((item) => (
|
||||
<Box
|
||||
key={item.id}
|
||||
p="md"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
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>
|
||||
<Center>
|
||||
<Button component={Link} href={"/darmasaba/sdgs-desa"} radius={"lg"} fz={"1.2rem"} mt={20} bg={colors["blue-button"]}>Selengkapnya</Button>
|
||||
<Box py={50}>
|
||||
<Paper
|
||||
p={{ base: "md", md: "xl" }}
|
||||
radius="xl"
|
||||
withBorder
|
||||
shadow="lg"
|
||||
style={{
|
||||
background: "linear-gradient(145deg, #FAF6E9, #FFFDF6)",
|
||||
border: "1px solid rgba(255,255,255,0.05)",
|
||||
}}
|
||||
>
|
||||
{sdgsDesa && sdgsDesa.length > 0 ? (
|
||||
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xl" verticalSpacing="xl">
|
||||
{sdgsDesa.map((item) => (
|
||||
<Paper
|
||||
key={item.id}
|
||||
p="lg"
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
style={{
|
||||
background: "linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02))",
|
||||
border: "1px solid rgba(255,255,255,0.08)",
|
||||
transition: "all 0.3s ease",
|
||||
}}
|
||||
withBorder
|
||||
>
|
||||
<Center mb="lg">
|
||||
<Box
|
||||
p="md"
|
||||
style={{
|
||||
background: "rgba(255,255,255,0.06)",
|
||||
backdropFilter: "blur(6px)",
|
||||
width: mobile ? 140 : 160,
|
||||
height: mobile ? 140 : 160,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "1rem",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={item.image?.link ? item.image.link : "/placeholder-sdgs.png"}
|
||||
alt={item.name}
|
||||
w={mobile ? 90 : 110}
|
||||
h={mobile ? 90 : 110}
|
||||
fit="contain"
|
||||
/>
|
||||
</Box>
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
<Text ta="center" fz={{ base: "lg", md: "xl" }} fw={700} mb="xs">
|
||||
{item.name}
|
||||
</Text>
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
style={{
|
||||
fontSize: mobile ? "2.3rem" : "3rem",
|
||||
fontWeight: 900,
|
||||
letterSpacing: "-1px",
|
||||
}}
|
||||
>
|
||||
{item.jumlah}
|
||||
</Title>
|
||||
</Paper>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : (
|
||||
<Center mih={200} style={{ flexDirection: "column" }}>
|
||||
<IconMoodSad size={48} stroke={1.5} style={{ marginBottom: "1rem" }} />
|
||||
<Text fz="lg" c="dimmed">
|
||||
Belum ada data SDGs Desa
|
||||
</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
<Center>
|
||||
<Button
|
||||
component={Link}
|
||||
href="/darmasaba/sdgs-desa"
|
||||
radius="xl"
|
||||
size="lg"
|
||||
mt={30}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
>
|
||||
Lihat Semua SDGs Desa
|
||||
</Button>
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user