Fix Menu Lingkungan Darmasaba User

This commit is contained in:
2025-08-26 17:49:33 +08:00
parent b21e1f0c2e
commit 3a726a3334
36 changed files with 2509 additions and 1977 deletions

View File

@@ -1,207 +1,118 @@
'use client'
import colors from '@/con/colors';
import { ActionIcon, Anchor, Box, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title, useMantineTheme } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
function Footer() {
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
return (
<>
<Stack bg={colors["blue-button"]}>
<Box w={mobile ? "100%" : "100%"} p={"xl"} h={{ base: 2500, md: 1100 }} >
<Center>
<Paper w={"100%"}>
<Box component="footer" py="xl">
<Container size="lg">
<Stack gap="xl">
<Box>
<Title fz={"md"} order={2} fw={700} mb="md">Komitmen Dalam Pelayanan</Title>
<Stack gap="sm">
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>1. Transparansi:</Text>
<Text fz={"sm"}>
Kami berkomitmen untuk mengelola dana desa secara terbuka, sehingga masyarakat dapat
mengetahui penggunaan anggaran secara jelas dan bertanggung jawab.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>2. Profesionalisme:</Text>
<Text fz={"sm"}>
Setiap layanan desa akan dilakukan dengan profesional, cepat, dan tanpa diskriminasi,
demi memastikan kepuasan masyarakat.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>3. Partisipatif:</Text>
<Text fz={"sm"}>
Kami percaya bahwa partisipasi aktif masyarakat adalah kunci keberhasilan pembangunan desa.
Oleh karena itu, kami akan terus melibatkan warga dalam setiap proses pengambilan keputusan.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>4. Inovasi:</Text>
<Text fz={"sm"}>
Kami berkomitmen untuk terus berinovasi dalam memberikan solusi bagi permasalahan desa,
termasuk melalui pemanfaatan teknologi untuk mempermudah akses layanan.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>5. Berkeadilan:</Text>
<Text fz={"sm"}>
Setiap kebijakan dan program desa akan dirancang untuk memberikan manfaat yang merata
bagi seluruh lapisan masyarakat, tanpa memandang status sosial atau ekonomi.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>6. Pemberdayaan:</Text>
<Text fz={"sm"}>
Kami berkomitmen untuk memberdayakan masyarakat melalui pelatihan, pendampingan,
dan dukungan terhadap usaha-usaha lokal agar desa semakin mandiri.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>7. Ramah Lingkungan:</Text>
<Text fz={"sm"}>
Seluruh kegiatan pembangunan dan pelayanan desa akan memperhatikan keberlanjutan lingkungan,
demi menjaga keseimbangan alam dan kenyamanan hidup warga.
</Text>
</Group>
</Stack>
</Box>
<Divider />
<Box>
<Title fz={"md"} order={2} fw={700} mb="md">Tujuan Akhir</Title>
<Text fz={"sm"} mb="sm">
Dengan visi, misi dan komitmen ini, kami bertekad untuk menjadikan desa sebagai tempat tinggal
yang nyaman, aman dan sejahtera bagi seluruh warganya.
</Text>
<Text fz={"sm"} mb="sm">
Kami percaya bahwa kemajuan desa dimulai dari kerjasama antara pemerintah desa dan masyarakat,
serta didukung oleh tata kelola yang baik dan berorientasi pada kepentingan bersama. Jika ada
masukan untuk lembaga desa, silahkan hubungi pada nomor pengaduan di bawah, terima kasih.
</Text>
</Box>
<Group justify='apart' align="center">
<Text ta={"center"} fz={"sm"} fw={700} size="lg" style={{ fontStyle: 'italic' }}>{"Desa Kuat, Masyarakat Sejahtera!"}</Text>
<Box
style={{
width: 80,
height: 80,
position: 'relative'
}}
>
<ActionIcon size={80} radius={"xl"} variant='transparent'>
<Image src="/api/img/chatbot-removebg-preview.png" alt="Logo Desa" width={80} height={80} />
</ActionIcon>
</Box>
</Group>
</Stack>
</Container>
</Box>
</Paper>
</Center>
<Box py={20} >
<SimpleGrid
p={20}
cols={{
base: 2,
sm: 4,
}}
style={{
color: "white"
}}
>
<Box p={mobile ? 30 : 30} style={{color: "white"}}>
<Stack justify='space-between'>
<Text fz={"md"} fw={"bold"} c={"white"}>Tentang Darmasaba</Text>
<Text fz={"xs"} c={"white"}>Desa Darmasaba adalah desa
budaya yang kaya akan tradisi dan
nilai-nilai luhur masyarakat Bali.</Text>
<Stack bg="linear-gradient(180deg, #1C6EA4, #124170)" c="white">
<Box w="100%" p="xl" h={{ base: 1800, md: 1100 }}>
<Center>
<Paper w="100%" bg="transparent" shadow="md" radius="lg" p="xl">
<Box component="footer">
<Container size="lg">
<Stack gap="xl">
<Box>
<Flex gap={"md"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color='white' size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color='white' size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color='white' size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color='white' size={"30"} />
</ActionIcon>
</Flex>
<Title fz="lg" order={2} fw={700} mb="md" c="white">Komitmen Layanan Kami</Title>
<Stack gap="sm">
{[
{ title: "Transparansi", text: "Pengelolaan dana desa dilakukan secara terbuka agar masyarakat dapat memahami dan memantau penggunaan anggaran." },
{ title: "Profesionalisme", text: "Layanan desa diberikan secara cepat, adil, dan profesional demi kepuasan masyarakat." },
{ title: "Partisipasi", text: "Masyarakat dilibatkan aktif dalam pengambilan keputusan demi pembangunan desa yang berhasil." },
{ title: "Inovasi", text: "Kami terus berinovasi, termasuk melalui teknologi, agar layanan semakin mudah diakses." },
{ title: "Keadilan", text: "Kebijakan dan program disusun untuk memberi manfaat yang merata bagi seluruh warga." },
{ title: "Pemberdayaan", text: "Masyarakat didukung melalui pelatihan, pendampingan, dan pengembangan usaha lokal." },
{ title: "Ramah Lingkungan", text: "Seluruh kegiatan pembangunan memperhatikan keberlanjutan demi menjaga alam dan kesehatan warga." }
].map((item, i) => (
<Group key={i} align="flex-start" gap="xs">
<Text fz="sm" c="#F3F2EC" fw={700}>{i + 1}. {item.title}:</Text>
<Text fz="sm" c="#F3F2EC">{item.text}</Text>
</Group>
))}
</Stack>
</Box>
<Divider color="white" opacity={0.2} />
<Box>
<Title fz="lg" order={2} fw={700} mb="md" c="white">Visi Kami</Title>
<Text fz="sm" mb="sm" c="#F3F2EC">
Dengan visi ini, kami berkomitmen menjadikan desa sebagai tempat yang aman, sejahtera, dan nyaman bagi seluruh warga.
</Text>
<Text fz="sm" mb="sm" c="#F3F2EC">
Kami percaya kemajuan dimulai dari kerja sama antara pemerintah desa dan masyarakat, didukung tata kelola yang baik demi kepentingan bersama. Saran maupun keluhan dapat disampaikan melalui kontak di bawah ini.
</Text>
</Box>
<Group justify="apart" align="center" mt="lg">
<Text c="#F3F2EC" ta="center" fz="md" fw={700} style={{ fontStyle: 'italic' }}>&quot;Desa Kuat, Warga Sejahtera!&quot;</Text>
<ActionIcon size={80} radius="xl" variant="transparent">
<Image src="/api/img/chatbot-removebg-preview.png" alt="Logo Desa" width={80} height={80} />
</ActionIcon>
</Group>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between' gap={"xs"}>
<Text fz={"md"} fw={"bold"} c={"white"}>Layanan</Text>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Administrasi Kependudukan</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Pelayanan Sosial</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Pengaduan Masyarakat</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Informasi Publik</Text>
</Anchor>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between' gap={"xs"}>
<Text fz={"md"} fw={"bold"} c={"white"}>Tautan Penting</Text>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Portal Badung</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>E-Government</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Transparansi</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Unduhan</Text>
</Anchor>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between'>
<Text fz={"md"} fw={"bold"} c={"white"}>Newsletter</Text>
<Text fz={"xs"} c={"white"}>Dapatkan informasi terbaru
tentang kegiatan dan program
desa</Text>
</Container>
</Box>
</Paper>
</Center>
<Box py={40}>
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="xl">
<Box>
<Stack gap="sm">
<Text c="white" fz="md" fw={700}>Tentang Darmasaba</Text>
<Text fz="xs" c="#F3F2EC">
Darmasaba adalah desa budaya yang kaya akan tradisi dan nilai-nilai warisan Bali.
</Text>
<Flex gap="md" mt="sm" c="#F3F2EC">
<ActionIcon variant="subtle" color="white"><IconBrandFacebook size={22} /></ActionIcon>
<ActionIcon variant="subtle" color="white"><IconBrandInstagram size={22} /></ActionIcon>
<ActionIcon variant="subtle" color="white"><IconBrandTwitter size={22} /></ActionIcon>
<ActionIcon variant="subtle" color="white"><IconBrandWhatsapp size={22} /></ActionIcon>
</Flex>
</Stack>
</Box>
<Box>
<Stack gap="xs">
<Text c="white" fz="md" fw={700}>Layanan Desa</Text>
<Anchor c="#F3F2EC" fz="xs">Administrasi Kependudukan</Anchor>
<Anchor c="#F3F2EC" fz="xs">Layanan Sosial</Anchor>
<Anchor c="#F3F2EC" fz="xs">Pengaduan Masyarakat</Anchor>
<Anchor c="#F3F2EC" fz="xs">Informasi Publik</Anchor>
</Stack>
</Box>
<Box>
<Stack gap="xs">
<Text c="white" fz="md" fw={700}>Tautan Penting</Text>
<Anchor c="#F3F2EC" fz="xs">Portal Badung</Anchor>
<Anchor c="#F3F2EC" fz="xs">E-Government</Anchor>
<Anchor c="#F3F2EC" fz="xs">Transparansi</Anchor>
<Anchor c="#F3F2EC" fz="xs">Unduhan</Anchor>
</Stack>
</Box>
<Box>
<Stack gap="sm">
<Text c="white" fz="md" fw={700}>Berlangganan Info</Text>
<Text c="#F3F2EC" fz="xs">Dapatkan kabar terbaru tentang program dan kegiatan desa langsung ke email Anda.</Text>
<Group wrap="nowrap">
<TextInput
placeholder='Alamat email anda'
rightSection={<IconAt color={colors["blue-button"]} />}
w="70%"
placeholder="Masukkan email Anda"
rightSection={<IconAt size={16} />}
/>
</Stack>
</Box>
</SimpleGrid>
</Box>
<Divider py={15} />
<Text ta={"center"} c={"white"} p={20}>
© 2024 Desa Darmasaba. Hak Cipta Dilindungi.
</Text>
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
</Group>
</Stack>
</Box>
</SimpleGrid>
</Box>
</Stack>
</>
<Divider opacity={0.2} my="md" />
<Text ta="center" fz="xs" c="white">© 2025 Desa Darmasaba. Hak cipta dilindungi.</Text>
</Box>
</Stack>
);
}

View File

@@ -1,23 +1,27 @@
import stateNav from "@/state/state-nav";
import { Container, Stack, TextInput } from "@mantine/core";
import { Container, Stack, TextInput, Tooltip } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
export function NavbarSearch() {
return <Container w={{
base: '100%',
md: '80%',
}} fluid py={"xl"}
onMouseLeave={stateNav.clear}
return (
<Container
w={{ base: "100%", md: "80%" }}
fluid
py="xl"
onMouseLeave={stateNav.clear}
>
<Stack pt={"xl"}>
<TextInput
autoFocus
styles={{
input: {
borderRadius: "xl",
color: "black",
// backgroundColor: "rgba(255, 255, 255, 0.3)"
}
}} size="lg" variant="transparent" placeholder="Cari" />
</Stack>
<Stack pt="xl">
<Tooltip label="Type to search across the site" position="bottom-start" withArrow>
<TextInput
autoFocus
size="lg"
variant="filled"
radius="xl"
placeholder="Search anything..."
leftSection={<IconSearch size={20} />}
/>
</Tooltip>
</Stack>
</Container>
}
);
}

View File

@@ -2,78 +2,96 @@
import colors from "@/con/colors";
import navbarListMenu from "@/con/navbar-list-menu";
import stateNav from "@/state/state-nav";
import { ActionIcon, Box, Burger, Group, Image, Stack, Text } from "@mantine/core";
import { ActionIcon, Box, Burger, Group, Image, Paper, ScrollArea, Stack, Text, Tooltip } from "@mantine/core";
import { IconSquareArrowRight } from "@tabler/icons-react";
import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation';
import { motion } from "framer-motion";
import { useRouter } from "next/navigation";
import { useSnapshot } from "valtio";
import { MenuItem } from "../../../../types/menu-item";
import { NavbarMainMenu } from "./NavbarMainMenu";
export function Navbar() {
const { item, isSearch, mobileOpen } = useSnapshot(stateNav);
const router = useRouter()
const router = useRouter();
return (
<Box>
<Box
<Paper
radius="0"
className="glass2"
w={"100%"}
pos={"fixed"}
w="100%"
pos="fixed"
top={0}
style={{
zIndex: 100,
overflow: "scroll"
}}
style={{ zIndex: 100 }}
>
<NavbarMainMenu listNavbar={navbarListMenu} />
<Stack hiddenFrom="sm" bg={colors.grey[2]}>
<Group justify="space-between">
<ActionIcon variant="transparent" onClick={() => {
router.push("/darmasaba")
stateNav.mobileOpen = false
}}
size={80} radius={"xl"}
>
<Image src="/darmasaba-icon.png" alt="Logo Desa" width={50} height={50} />
</ActionIcon>
<Burger onClick={() => stateNav.mobileOpen = !stateNav.mobileOpen} color={colors["blue-button"]} opened={mobileOpen} />
</Group>
{mobileOpen && <motion.div
initial={{ x: 300 }}
animate={{ x: 0 }}
transition={{ duration: 0.1 }}
style={{
height: "100vh",
overflow: "scroll"
}}
>
<NavbarMobile listNavbar={navbarListMenu} />
</motion.div>}
</Stack>
</Box>
<Stack hiddenFrom="sm" bg={colors.grey[2]} px="md" py="sm">
<Group justify="space-between">
<ActionIcon
variant="transparent"
size="xl"
radius="xl"
onClick={() => {
router.push("/darmasaba");
stateNav.mobileOpen = false;
}}
>
<Tooltip label="Go to homepage" position="bottom" withArrow>
<Image src="/darmasaba-icon.png" alt="Village Logo" width={48} height={48} />
</Tooltip>
</ActionIcon>
<Tooltip label={mobileOpen ? "Close menu" : "Open menu"} position="bottom" withArrow>
<Burger
opened={mobileOpen}
color={colors["blue-button"]}
onClick={() => (stateNav.mobileOpen = !stateNav.mobileOpen)}
size="sm"
/>
</Tooltip>
</Group>
{mobileOpen && (
<motion.div
initial={{ x: 300 }}
animate={{ x: 0 }}
transition={{ duration: 0.2 }}
style={{ height: "100vh" }}
>
<NavbarMobile listNavbar={navbarListMenu} />
</motion.div>
)}
</Stack>
</Paper>
{(item || isSearch) && <Box className="glass" />}
</Box>
);
}
function NavbarMobile({ listNavbar }: { listNavbar: MenuItem[] }) {
const router = useRouter()
return <Stack p={"md"} style={{ backgroundColor: "rgba(255, 255, 255, 0.3)" }}>
{listNavbar.map((item, k) => {
return <Stack key={k}>
<Group justify="space-between" onClick={() => {
router.push(item.href)
stateNav.mobileOpen = false
}}>
<Text c="dark.9"
style={{ fontWeight: "bold" }}
>{item.name}</Text>
<IconSquareArrowRight />
</Group>
{item.children && <NavbarMobile listNavbar={item.children} />}
const router = useRouter();
return (
<ScrollArea h="100vh" offsetScrollbars>
<Stack p="lg" gap="md" style={{ backgroundColor: "rgba(255, 255, 255, 0.25)" }}>
{listNavbar.map((item, k) => (
<Stack key={k} gap={4}>
<Group
justify="space-between"
align="center"
onClick={() => {
router.push(item.href);
stateNav.mobileOpen = false;
}}
style={{ cursor: "pointer" }}
>
<Text c="dark.9" fw={600} fz="lg">
{item.name}
</Text>
<IconSquareArrowRight size={20} />
</Group>
{item.children && <NavbarMobile listNavbar={item.children} />}
</Stack>
))}
</Stack>
})}
</Stack>
</ScrollArea>
);
}

View File

@@ -2,7 +2,7 @@
import colors from "@/con/colors"
import stateNav from "@/state/state-nav"
import { ActionIcon, Button, Container, Flex, Image, Stack } from "@mantine/core"
import { ActionIcon, Button, Container, Flex, Image, Stack, Tooltip } from "@mantine/core"
import { useHover } from "@mantine/hooks"
import { IconSearch, IconUser } from "@tabler/icons-react"
import { useTransitionRouter } from 'next-view-transitions'
@@ -12,68 +12,91 @@ import { NavbarSearch } from "./NavBarSearch"
import { NavbarSubMenu } from "./NavbarSubMenu"
import { useRouter } from "next/navigation"
export function NavbarMainMenu({ listNavbar }: {
listNavbar: MenuItem[]
}) {
const { item, isSearch } = useSnapshot(stateNav)
const router = useTransitionRouter()
const next = useRouter()
return <Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
<Container pos={"relative"} w={{
base: '100%',
md: '80%',
}} fluid>
<Flex align={"center"} justify={"space-between"} wrap={{
base: "wrap",
md: "nowrap"
}}>
<ActionIcon radius={"100"} variant="transparent" onClick={() => {
router.push("/darmasaba")
stateNav.clear()
export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
const { item, isSearch } = useSnapshot(stateNav)
const router = useTransitionRouter()
const next = useRouter()
}} >
<Image radius={"100"} src={"/assets/images/darmasaba-icon.png"} alt="icon" w={24} h={24} loading="lazy" />
</ActionIcon>
{listNavbar.map((item, k) => {
return <MenuItemCom key={k} item={item} />
})}
<ActionIcon variant="transparent" c={isSearch ? 'grey' : colors["blue-button"]}
onClick={() => {
stateNav.item = null
stateNav.isSearch = !stateNav.isSearch
}}
>
{/* TODO: add icon search */}
<IconSearch size={"1.5rem"} />
</ActionIcon>
<ActionIcon onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")
}} color={colors["blue-button"]} radius={'xl'}>
<IconUser size={24} />
</ActionIcon>
</Flex>
</Container>
{item && <NavbarSubMenu item={item as MenuItem[]} />}
{isSearch && <NavbarSearch />}
return (
<Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
<Container pos="relative" w={{ base: '100%', md: '80%' }} fluid>
<Flex align="center" justify="space-between" wrap={{ base: "wrap", md: "nowrap" }}>
<Tooltip label="Go to Homepage" position="bottom" withArrow>
<ActionIcon
radius="xl"
variant="transparent"
onClick={() => {
router.push("/darmasaba")
stateNav.clear()
}}
>
<Image
radius="xl"
src="/assets/images/darmasaba-icon.png"
alt="Darmasaba Logo"
w={28}
h={28}
loading="lazy"
/>
</ActionIcon>
</Tooltip>
{listNavbar.map((item, k) => (
<MenuItemCom key={k} item={item} />
))}
<Tooltip label="Search content" position="bottom" withArrow>
<ActionIcon
variant="transparent"
c={isSearch ? 'gray' : colors["blue-button"]}
onClick={() => {
stateNav.item = null
stateNav.isSearch = !stateNav.isSearch
}}
radius="xl"
>
<IconSearch size="1.5rem" />
</ActionIcon>
</Tooltip>
<Tooltip label="My Profile" position="bottom" withArrow>
<ActionIcon
onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")
}}
color={colors["blue-button"]}
radius="xl"
variant="light"
>
<IconUser size={22} />
</ActionIcon>
</Tooltip>
</Flex>
</Container>
{item && <NavbarSubMenu item={item as MenuItem[]} />}
{isSearch && <NavbarSearch />}
</Stack>
)
}
function MenuItemCom({ item, }: { item: MenuItem }) {
const { ref, hovered } = useHover()
const router = useTransitionRouter()
function MenuItemCom({ item }: { item: MenuItem }) {
const { ref, hovered } = useHover()
const router = useTransitionRouter()
return <Button
ref={ref}
color={hovered ? "grey" : colors["blue-button"]}
onMouseEnter={() => {
stateNav.item = item.children || null
stateNav.isSearch = false
}}
variant="transparent"
onClick={() => {
router.push(item.href)
stateNav.clear()
}}
>{item.name}</Button>
}
return (
<Button
ref={ref}
color={hovered ? "gray" : colors["blue-button"]}
onMouseEnter={() => {
stateNav.item = item.children || null
stateNav.isSearch = false
}}
variant="subtle"
radius="xl"
onClick={() => {
router.push(item.href)
stateNav.clear()
}}
fw={500}
>
{item.name}
</Button>
)
}

View File

@@ -1,20 +1,22 @@
"use client";
import stateNav from "@/state/state-nav";
import { Button, Container, Stack } from "@mantine/core";
import _ from "lodash";
import { Button, Container, Stack, Text } from "@mantine/core";
import { motion } from "motion/react";
import { IconArrowRight } from "@tabler/icons-react";
import { MenuItem } from "../../../../types/menu-item";
import { useTransitionRouter } from 'next-view-transitions'
import { useTransitionRouter } from "next-view-transitions";
import colors from "@/con/colors";
export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
const router = useTransitionRouter()
const router = useTransitionRouter();
return (
<motion.div
key={_.uniqueId()}
initial={{ opacity: 0.5 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
key={Math.random().toString(36).slice(2)}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<Container
key={stateNav.item?.[0]?.id}
@@ -22,32 +24,50 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
stateNav.item = null;
stateNav.isSearch = false;
}}
w={{
base: "100%",
md: "80%",
}}
w={{ base: "100%", md: "80%" }}
fluid
py="xl"
>
<Stack gap={0} align="start" py={"xl"}>
{item &&
item.map((item, k) => {
return (
<Button
key={k}
fz={"lg"}
color="dark.9"
variant="transparent"
onClick={() => {
router.push(item.href)
stateNav.item = null
stateNav.isSearch = false
}}
>
{item.name}
</Button>
);
})}
</Stack>
{item && item.length > 0 ? (
<Stack gap="xs" align="stretch">
{item.map((link, index) => (
<Button
key={index}
variant="subtle"
justify="space-between"
size="lg"
radius="md"
color="gray.0"
onClick={() => {
router.push(link.href);
stateNav.item = null;
stateNav.isSearch = false;
}}
rightSection={<IconArrowRight size={18} />}
styles={(theme) => ({
root: {
background: "transparent",
color: colors['blue-button'],
fontWeight: 500,
transition: "all 0.2s ease",
"&:hover": {
background: theme.colors.gray[8],
boxShadow: `0 0 12px ${theme.colors.blue[6]}55`,
},
},
})}
>
{link.name}
</Button>
))}
</Stack>
) : (
<Stack align="center" py="xl">
<Text c="dimmed" size="sm">
No submenu available
</Text>
</Stack>
)}
</Container>
</motion.div>
);

View File

@@ -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

View File

@@ -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"}>

View File

@@ -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} />
))}

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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>
)
}