Fix Menu Lingkungan Darmasaba User
This commit is contained in:
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal file
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
|
||||||
|
import { LatLngExpression } from 'leaflet';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import L from 'leaflet';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
type MarkerData = {
|
||||||
|
position: [number, number];
|
||||||
|
popup: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
center: [number, number];
|
||||||
|
markers: MarkerData[];
|
||||||
|
zoom?: number;
|
||||||
|
scrollWheelZoom?: boolean;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LeafletMultiMarkerMap({
|
||||||
|
center,
|
||||||
|
markers,
|
||||||
|
zoom = 13,
|
||||||
|
scrollWheelZoom = true,
|
||||||
|
className = '',
|
||||||
|
style = { height: '100%', width: '100%', zIndex: 0 },
|
||||||
|
}: Props) {
|
||||||
|
// Fix for default marker icons in Next.js
|
||||||
|
useEffect(() => {
|
||||||
|
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||||
|
L.Icon.Default.mergeOptions({
|
||||||
|
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
|
||||||
|
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
|
||||||
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} style={style}>
|
||||||
|
<MapContainer
|
||||||
|
center={center as LatLngExpression}
|
||||||
|
zoom={zoom}
|
||||||
|
scrollWheelZoom={scrollWheelZoom}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
|
<TileLayer
|
||||||
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
/>
|
||||||
|
{markers.map((marker, index) => (
|
||||||
|
<Marker key={index} position={marker.position as LatLngExpression}>
|
||||||
|
<Popup>{marker.popup}</Popup>
|
||||||
|
</Marker>
|
||||||
|
))}
|
||||||
|
</MapContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -56,13 +56,17 @@ const dataLingkunganDesaState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
|
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
|
||||||
dataLingkunganDesaState.findMany.page = page;
|
dataLingkunganDesaState.findMany.page = page;
|
||||||
|
dataLingkunganDesaState.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
|
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -52,15 +52,19 @@ const pengelolaanSampah = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
|
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
|
||||||
pengelolaanSampah.findMany.page = page;
|
pengelolaanSampah.findMany.page = page;
|
||||||
|
pengelolaanSampah.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
|
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -56,13 +56,17 @@ const programPenghijauanState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
|
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
|
||||||
programPenghijauanState.findMany.page = page;
|
programPenghijauanState.findMany.page = page;
|
||||||
|
programPenghijauanState.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
|
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -42,18 +42,10 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [page])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
|
||||||
item.jumlah.toLowerCase().includes(keyword) ||
|
|
||||||
item.icon.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconMap: Record<string, React.FC<any>> = {
|
const iconMap: Record<string, React.FC<any>> = {
|
||||||
ekowisata: IconLeaf,
|
ekowisata: IconLeaf,
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
|||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
||||||
<TableTd style={{ width: '35%', wordWrap: 'break-word' }} dangerouslySetInnerHTML={{ __html: item.judul }}></TableTd>
|
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>
|
||||||
|
<Text lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.judul }}/>
|
||||||
|
</TableTd>
|
||||||
<TableTd style={{ width: '10%' }}>
|
<TableTd style={{ width: '10%' }}>
|
||||||
{iconMap[item.icon] && (
|
{iconMap[item.icon] && (
|
||||||
<Box title={item.icon}>
|
<Box title={item.icon}>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
@@ -6,17 +7,28 @@ export default async function dataLingkunganDesaFindMany(context: Context) {
|
|||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ name: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.dataLingkunganDesa.findMany({
|
prisma.dataLingkunganDesa.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.dataLingkunganDesa.count({
|
prisma.dataLingkunganDesa.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
@@ -5,18 +6,28 @@ import { Context } from "elysia";
|
|||||||
export default async function pengelolaanSampahFindMany(context: Context) {
|
export default async function pengelolaanSampahFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ name: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.pengelolaanSampah.findMany({
|
prisma.pengelolaanSampah.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.pengelolaanSampah.count({
|
prisma.pengelolaanSampah.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
@@ -6,17 +7,26 @@ export default async function programPenghijauanFindMany(context: Context) {
|
|||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ name: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ judul: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.programPenghijauan.findMany({
|
prisma.programPenghijauan.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.programPenghijauan.count({
|
prisma.programPenghijauan.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import { useParams } from 'next/navigation';
|
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../_com/BackButto';
|
import BackButton from '../_com/BackButto';
|
||||||
@@ -34,46 +34,44 @@ function Page() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.suratKeterangan.findUnique.load(id);
|
await state.suratKeterangan.findUnique.load(id);
|
||||||
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
|
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
|
||||||
setData(result);
|
setData(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Terjadi kesalahan saat memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="100vh" bg={colors.Bg}>
|
||||||
<Skeleton height={500} />
|
<Skeleton height={500} radius="md" animate />
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="100vh" bg={colors.Bg}>
|
||||||
<Text>Data tidak ditemukan</Text>
|
<Text fz="xl" c="dimmed">Maaf, data layanan tidak ditemukan</Text>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container w={{ base: "100%", md: "50%" }}>
|
<Container w={{ base: "100%", md: "60%" }}>
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }}
|
fz={{ base: "2rem", md: "2.5rem", lg: "3rem" }}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw="bold"
|
fw="bold"
|
||||||
>
|
>
|
||||||
@@ -81,19 +79,32 @@ function Page() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={0}>
|
<Stack gap="md">
|
||||||
<Text
|
<Text
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
fz={{ base: "h4", md: "h2" }}
|
fz={{ base: "md", md: "lg" }}
|
||||||
pb={20}
|
c="dark.7"
|
||||||
|
ta="justify"
|
||||||
/>
|
/>
|
||||||
{data.image2?.link && (
|
{data.image2?.link && (
|
||||||
<Center>
|
<Center>
|
||||||
<Image src={data.image2.link} alt={data.name} />
|
<Image
|
||||||
|
src={data.image2.link}
|
||||||
|
alt={data.name}
|
||||||
|
radius="md"
|
||||||
|
maw={500}
|
||||||
|
mx="auto"
|
||||||
|
style={{ boxShadow: '0 0 20px rgba(28,110,164,0.2)' }}
|
||||||
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
<Group justify='center' mt="md">
|
<Group justify="center" mt="md">
|
||||||
<Button radius="lg" fz="h4" bg={colors['blue-button']}>
|
<Button
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
||||||
|
>
|
||||||
Ajukan Permohonan
|
Ajukan Permohonan
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -5,112 +5,130 @@ import { IconArrowRight } from '@tabler/icons-react';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
px={{ base: 20, md: 100 }}
|
px={{ base: 20, md: 100 }}
|
||||||
cols={{
|
cols={{ base: 1, md: 2 }}
|
||||||
base: 1,
|
spacing="xl"
|
||||||
md: 2
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Content 1 */}
|
<Box pt={{ base: 0, md: 35 }}>
|
||||||
<Box pt={{base: 0, md: 35}}>
|
<Text c={colors['blue-button']} fz={{ base: 'h4', md: 'h3' }}>
|
||||||
<Text c={colors["blue-button"]} fz={{ base: "h4", md: "h3" }} >
|
Community Safety & Crime Prevention
|
||||||
Info Keamanan dan Pencegahan Kriminalitas
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
|
||||||
Kontak Darurat
|
Emergency Contacts
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
|
<Button
|
||||||
Lihat Detail
|
rightSection={<IconArrowRight />}
|
||||||
|
mt={20}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
c={colors['white-1']}
|
||||||
|
>
|
||||||
|
View Details
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Content 2 */}
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
<Paper p="lg" radius="xl" shadow="md">
|
||||||
<Paper p={"lg"}>
|
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
|
||||||
<Text c={colors["blue-button"]} fw={'bold'} fz={{ base: "h4", md: "h3" }} >
|
How to Keep Your Neighborhood Safe
|
||||||
Tips menjaga keamanan lingkungan
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h5", md: "h4" }} c={colors["blue-button"]} >
|
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
|
||||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
Practical safety habits everyone can apply daily to reduce risks.
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
|
<Button
|
||||||
Lihat Detail
|
rightSection={<IconArrowRight />}
|
||||||
|
mt={20}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
c={colors['white-1']}
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Paper p={"lg"}>
|
<Paper p="lg" radius="xl" shadow="md">
|
||||||
<Text c={colors["blue-button"]} fw={'bold'} fz={{ base: "h4", md: "h3" }} >
|
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
|
||||||
Mengenali tanda-tanda kegiatan kriminal
|
Recognizing Criminal Activities
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h5", md: "h4" }} c={colors["blue-button"]} >
|
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
|
||||||
the printing and typesetting industry. the printing and typesetting industry.
|
Key warning signs and behavior patterns you should stay aware of.
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
|
<Button
|
||||||
Lihat Detail
|
rightSection={<IconArrowRight />}
|
||||||
|
mt={20}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
c={colors['white-1']}
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
{/* Content 3 */}
|
<Paper p="xl" radius="xl" shadow="lg">
|
||||||
<Paper p={'xl'}>
|
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} c={colors["blue-button"]} fw={"bold"}>
|
Ongoing Security Programs
|
||||||
Program Keamanan Rutin
|
|
||||||
</Text>
|
</Text>
|
||||||
<Stack pt={30} gap={'lg'}>
|
<Stack pt={30} gap="lg">
|
||||||
<Box>
|
{['Night Patrol', 'Neighborhood Watch', 'Emergency Preparedness'].map((program, i) => (
|
||||||
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
|
<Paper key={i} p="md" bg={colors['blue-button']} radius="md" shadow="sm">
|
||||||
<Flex align={'center'} justify={'space-between'}>
|
<Flex align="center" justify="space-between">
|
||||||
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
|
<Text fz="h3" c={colors['white-1']}>
|
||||||
<IconArrowRight size={30} color={colors['white-1']} />
|
{program}
|
||||||
|
</Text>
|
||||||
|
<IconArrowRight size={28} color={colors['white-1']} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
))}
|
||||||
<Box>
|
|
||||||
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
|
|
||||||
<Flex align={'center'} justify={'space-between'}>
|
|
||||||
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
|
|
||||||
<IconArrowRight size={30} color={colors['white-1']} />
|
|
||||||
</Flex>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
|
|
||||||
<Flex align={'center'} justify={'space-between'}>
|
|
||||||
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
|
|
||||||
<IconArrowRight size={30} color={colors['white-1']} />
|
|
||||||
</Flex>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box pt={25}>
|
<Box pt={25}>
|
||||||
<Button fullWidth rightSection={<IconArrowRight size={20} color={colors['white-1']} />}>Lihat program lainnya</Button>
|
<Button
|
||||||
|
fullWidth
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
|
||||||
|
>
|
||||||
|
Explore More Programs
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
{/* Content 4 */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<Paper p={'xl'} >
|
<Paper p="xl" radius="xl" shadow="lg">
|
||||||
<Box style={{ maxWidth: "560px", width: "100%", aspectRatio: "16/9" }}>
|
<Box style={{ maxWidth: 560, width: '100%', aspectRatio: '16/9' }}>
|
||||||
<iframe width="100%"
|
<iframe
|
||||||
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
src="https://www.youtube.com/embed/p5OkdBgasWA?si=6lFKXeE9LN5zcJQq" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" ></iframe>
|
src="https://www.youtube.com/embed/p5OkdBgasWA?si=6lFKXeE9LN5zcJQq"
|
||||||
|
title="Community Safety Patrol"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Text py={10} fz={{ base: "h3", md: "h2" }} fw={'bold'} c={colors['blue-button']}>
|
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
|
||||||
Patroli Malam Darmasaba
|
Darmasaba Night Patrol
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={'h4'} >
|
<Text fz="h4" c={colors['blue-button']}>
|
||||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
A closer look at how the community works together to maintain safety.
|
||||||
</Text>
|
</Text>
|
||||||
<Box pt={10}>
|
<Box pt={10}>
|
||||||
<Button bg={colors['blue-button']} rightSection={<IconArrowRight size={20} color={colors['white-1']} />}>
|
<Button
|
||||||
Lihat Video Lainnya
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
|
||||||
|
>
|
||||||
|
Watch More Videos
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,85 +1,126 @@
|
|||||||
|
'use client'
|
||||||
|
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Badge, Box, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Tooltip } from '@mantine/core';
|
||||||
import { IconChristmasTree, IconDroplet, IconHome, IconLeaf, IconTrash } from '@tabler/icons-react';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { Icon, IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDroplet, IconHome, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent, IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
icon: <IconLeaf size={100} color={colors['blue-button']} />,
|
|
||||||
title: 'Luas Lahan Hijau',
|
|
||||||
jumlah: '± 25 hektar',
|
|
||||||
deskripsi: 'tersebar di area persawahan, kebun, dan taman desa.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
icon: <IconHome size={100} color={colors['blue-button']} />,
|
|
||||||
title: 'Jumlah Rumah Tangga',
|
|
||||||
jumlah: '± 1.500 rumah tangga',
|
|
||||||
deskripsi: 'yang mayoritas sudah memiliki fasilitas pengelolaan sampah mandiri.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
icon: <IconDroplet size={100} color={colors['blue-button']} />,
|
|
||||||
title: 'Jumlah Sungai dan Saluran Air',
|
|
||||||
jumlah: '± 3 Sungai Besar',
|
|
||||||
deskripsi: 'dan beberapa saluran irigasi tradisional (subak) yang masih aktif digunakan.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
icon: <IconChristmasTree size={100} color={colors['blue-button']} />,
|
|
||||||
title: 'Program Penghijauan',
|
|
||||||
jumlah: '± 1000 Pohon',
|
|
||||||
deskripsi: 'Dilaksanakan secara berkala melalui kegiatan menanam pohon di area umum dan perbukitan.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
icon: <IconTrash size={100} color={colors['blue-button']} />,
|
|
||||||
title: 'Pengelolaan Sampah',
|
|
||||||
jumlah: '± 5 Bank Sampah',
|
|
||||||
deskripsi: 'Didukung oleh Bank Sampah dan sistem pemilahan sampah rumah tangga.'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(dataLingkunganDesaState.findMany)
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
load,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
} = state
|
||||||
|
|
||||||
|
const iconMap: Record<string, Icon> = {
|
||||||
|
ekowisata: IconLeaf,
|
||||||
|
kompetisi: IconTrophy,
|
||||||
|
wisata: IconTent,
|
||||||
|
ekonomi: IconChartLine,
|
||||||
|
sampah: IconRecycle,
|
||||||
|
truck: IconTruckFilled,
|
||||||
|
scale: IconScale,
|
||||||
|
clipboard: IconClipboardTextFilled,
|
||||||
|
trash: IconTrashFilled,
|
||||||
|
lingkunganSehat: IconHomeEco,
|
||||||
|
sumberOksigen: IconChristmasTreeFilled,
|
||||||
|
ekonomiBerkelanjutan: IconTrendingUp,
|
||||||
|
mencegahBencana: IconShieldFilled,
|
||||||
|
rumah: IconHome,
|
||||||
|
pohon: IconTree,
|
||||||
|
air: IconDroplet
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 6, debouncedSearch)
|
||||||
|
}, [page, debouncedSearch])
|
||||||
|
|
||||||
|
if (state.loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="28px">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Group justify="space-between" align='center' mt="md">
|
||||||
Data Lingkungan Desa
|
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||||
|
Data Lingkungan Desa
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
radius="xl"
|
||||||
|
w={'30%'}
|
||||||
|
placeholder="Cari Data Lingkungan Desa"
|
||||||
|
leftSection={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Text fz="lg" c={'black'}>
|
||||||
|
Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya. Fokus utama meliputi penghijauan, pengelolaan sampah, dan perlindungan kawasan hijau.
|
||||||
</Text>
|
</Text>
|
||||||
<Text px={20} ta={'center'} fz={'h4'}>Desa Darmasaba memiliki lingkungan yang terus dijaga dan dikembangkan demi kesejahteraan warganya. Upaya pelestarian lingkungan difokuskan pada penghijauan, pengelolaan sampah, serta perlindungan kawasan hijau.</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
cols={{
|
{data.map((item) => (
|
||||||
base: 1,
|
<Paper
|
||||||
md: 2,
|
key={item.id}
|
||||||
}}>
|
p="lg"
|
||||||
{data.map((v, k) => {
|
bg={colors['white-trans-2']}
|
||||||
return (
|
radius="md"
|
||||||
<Box key={k}>
|
shadow="xl"
|
||||||
<Paper p={20} bg={colors['white-trans-1']}>
|
style={{ transition: 'all 0.3s', '&:hover': { transform: 'translateY(-5px)', boxShadow: `0 0 20px ${colors['blue-button']}` } }}
|
||||||
<Text fw={'bold'} c={colors['blue-button']} fz={{ base: "lg", md: "xl" }} >
|
>
|
||||||
{v.title}
|
<Stack align="center" gap="md">
|
||||||
</Text>
|
<Tooltip label={item.name} position="top" withArrow>
|
||||||
<Box>
|
<Center>
|
||||||
{v.icon}
|
{iconMap[item.icon] ? (
|
||||||
<Text c={colors['blue-button']} fz={'h4'} fw={'bold'}>
|
React.createElement(iconMap[item.icon], {
|
||||||
{v.jumlah}
|
size: 55,
|
||||||
</Text>
|
color: colors['blue-button'],
|
||||||
</Box>
|
style: {
|
||||||
<Text fz={{ base: "lg", md: "xl" }} >
|
transition: 'all 0.3s',
|
||||||
{v.deskripsi}
|
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'
|
||||||
</Text>
|
}
|
||||||
|
})
|
||||||
</Paper>
|
) : null}
|
||||||
</Box>
|
</Center>
|
||||||
)
|
</Tooltip>
|
||||||
})}
|
<Text fw="bold" fz="xl" c={colors['blue-button']}>
|
||||||
|
± {item.jumlah}
|
||||||
|
</Text>
|
||||||
|
<Text ta="center" fz="md" c={"black"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
<Badge variant="gradient" gradient={{ from: '#1C6EA4', to: '#69C0FF' }} size="sm">
|
||||||
|
{item.name}
|
||||||
|
</Badge>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
radius="xl"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,93 +1,79 @@
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Box, Center, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'Filosofi Tri Hita Karana',
|
title: 'Filosofi Tri Hita Karana',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
listDeskripsi: (
|
||||||
<ListItem>
|
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||||
Parahyangan: Hubungan manusia dengan Tuhan
|
<ListItem>Parahyangan: Hubungan manusia dengan Tuhan yang dijaga penuh kesadaran spiritual</ListItem>
|
||||||
</ListItem>
|
<ListItem>Pawongan: Harmoni dan kerja sama antar manusia dalam masyarakat</ListItem>
|
||||||
<ListItem>
|
<ListItem>Palemahan: Pelestarian lingkungan dan hubungan manusia dengan alam</ListItem>
|
||||||
Pawongan: Hubungan antar manusia
|
</List>
|
||||||
</ListItem>
|
),
|
||||||
<ListItem>
|
|
||||||
Palemahan: Hubungan manusia dengan alam
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'Bentuk Konservasi Berdasarkan Adat',
|
title: 'Bentuk Konservasi Berdasarkan Adat',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
listDeskripsi: (
|
||||||
<ListItem>
|
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||||
Pelestarian Hutan Adat seperti Alas Pala Sangeh atau Wana Kerthi
|
<ListItem>Pelestarian Hutan Adat seperti Alas Pala Sangeh dan Wana Kerthi</ListItem>
|
||||||
</ListItem>
|
<ListItem>Subak: Sistem irigasi tradisional yang menekankan kebersamaan dan keberlanjutan</ListItem>
|
||||||
<ListItem>
|
<ListItem>Hari Raya Tumpek Uduh: Perayaan untuk menghormati pohon dan tumbuhan</ListItem>
|
||||||
Subak: Sistem pengelolaan irigasi tradisional yang menjunjung kebersamaan dan keberlanjutan
|
<ListItem>Perarem & Awig-Awig: Aturan adat untuk menjaga lingkungan dari kerusakan</ListItem>
|
||||||
</ListItem>
|
<ListItem>Ritual penyucian alam seperti Melasti dan Piodalan Segara</ListItem>
|
||||||
<ListItem>
|
</List>
|
||||||
Hari Raya Tumpek Uduh: Perayaan khusus untuk menghormati pohon dan tumbuhan
|
),
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Perarem dan Awig-Awig: Aturan adat desa yang mengatur larangan menebang pohon sembarangan, membuang limbah ke sungai, dll.
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Ritual penyucian alam seperti Melasti, Piodalan Segara, dan lainnya
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Nilai Konservasi Adat',
|
title: 'Nilai Konservasi Adat',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
listDeskripsi: (
|
||||||
<ListItem>
|
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||||
Menjaga keseimbangan ekosistem
|
<ListItem>Menjaga keseimbangan ekosistem dan lingkungan hidup</ListItem>
|
||||||
</ListItem>
|
<ListItem>Melestarikan spiritualitas lokal dan kesucian alam</ListItem>
|
||||||
<ListItem>
|
<ListItem>Meningkatkan kesadaran kolektif untuk hidup selaras dengan alam</ListItem>
|
||||||
Melestarikan spiritualitas lokal dan kesucian alam
|
<ListItem>Menjamin keberlanjutan sumber daya alam untuk generasi mendatang</ListItem>
|
||||||
</ListItem>
|
</List>
|
||||||
<ListItem>
|
),
|
||||||
Menumbuhkan kesadaran kolektif untuk hidup selaras dengan lingkungan
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Menjaga keberlangsungan sumber daya alam untuk generasi mendatang
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="24">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
<Box px={{ base: 'md', md: 100 }} pb={30}>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta="center" fz={{ base: '2xl', md: '3rem' }} c={colors['blue-button']} fw="bold">
|
||||||
Konservasi Adat Bali
|
Konservasi Adat Bali
|
||||||
</Text>
|
</Text>
|
||||||
<Text px={20} ta={'center'} fz={'h4'}>
|
<Text px={20} ta="center" fz="lg" c="dimmed">
|
||||||
Konservasi Adat Bali adalah upaya pelestarian lingkungan yang berpijak pada kearifan lokal masyarakat Bali, di mana alam dan budaya dianggap sebagai satu kesatuan yang harus dijaga secara harmonis.
|
Pelestarian lingkungan di Bali yang berpijak pada kearifan lokal, menjaga harmoni antara alam, budaya, dan manusia.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
cols={{
|
{data.map((item) => (
|
||||||
base: 1,
|
<Paper
|
||||||
md: 3,
|
key={item.id}
|
||||||
}}>
|
p="lg"
|
||||||
{data.map((v, k) => {
|
bg="linear-gradient(145deg, #DFE3E8FF 0%, #EFF1F4FF 100%)"
|
||||||
return (
|
style={{ borderRadius: 16, boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)' }}
|
||||||
<Box key={k}>
|
>
|
||||||
<Paper h={{ base: 0, md: 450 }} p={20} bg={colors['white-trans-1']}>
|
<Stack gap="md" px={20}>
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.title}</Text>
|
<Center>
|
||||||
{v.listDeskripsi}
|
<Text fz="xl" fw="bold" c="black">
|
||||||
</Paper>
|
{item.title}
|
||||||
</Box>
|
</Text>
|
||||||
)
|
</Center>
|
||||||
})}
|
{item.listDeskripsi}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,63 +1,60 @@
|
|||||||
|
'use client'
|
||||||
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Flex, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Flex, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { IconClipboardTextFilled, IconMapPin, IconRecycle, IconScale, IconSearch, IconTrashFilled, IconTruckFilled } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { Icon, IconChartLine, IconClipboardTextFilled, IconLeaf, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
// Dynamically import the map component to avoid SSR issues with Leaflet
|
||||||
|
const LeafletMultiMarkerMap = dynamic(
|
||||||
|
() => import('@/app/admin/(dashboard)/_com/LeafletMultiMarkerMap'),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
icon: <IconRecycle size={50} color={colors['blue-button']} />,
|
|
||||||
deskripsi: '1. Pilah sampah sesuai jenisnya'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
icon: <IconTruckFilled size={50} color={colors["blue-button"]} />,
|
|
||||||
deskripsi: '2. Bawa sampah ke Bank Sampah'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
icon: <IconScale size={50} color={colors["blue-button"]} />,
|
|
||||||
deskripsi: '3. Timbang sampah di Bank Sampah'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
icon: <IconClipboardTextFilled size={50} color={colors["blue-button"]} />,
|
|
||||||
deskripsi: '4. Catat hasil timbangan di buku tabungan'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
icon: <IconTrashFilled size={50} color={colors["blue-button"]} />,
|
|
||||||
deskripsi: '5. Sampah didaur ulang oleh petugas Bank Sampah'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const bankSampah = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
icon: <IconMapPin size={50} color={colors['blue-button']} />,
|
|
||||||
deskripsi: 'Bank Sampah Sarana Gathi',
|
|
||||||
alamat: 'Jl. Ahmad Yani Utara No.453, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
icon: <IconMapPin size={50} color={colors['blue-button']} />,
|
|
||||||
deskripsi: 'Bank Sampah BALI WASTU LESTARI',
|
|
||||||
alamat: 'Jl. Ahmad Yani Utara Gg. Garuda No.1, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
icon: <IconMapPin size={50} color={colors['blue-button']} />,
|
|
||||||
deskripsi: 'Bank Sampah Jempiring Sari',
|
|
||||||
alamat: 'Jl. Gn. Lebah I No.9, Tegal Harum, Kec. Denpasar Bar., Kota Denpasar, Bali 80119'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
icon: <IconMapPin size={50} color={colors['blue-button']} />,
|
|
||||||
deskripsi: 'Bank Sampah Sarana Gathi',
|
|
||||||
alamat: 'Jl. Ahmad Yani Utara No.453, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(pengelolaanSampahState.pengelolaanSampah)
|
||||||
|
const state2 = useProxy(pengelolaanSampahState.keteranganSampah)
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
load
|
||||||
|
} = state.findMany
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: data2,
|
||||||
|
load: load2
|
||||||
|
} = state2.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load()
|
||||||
|
load2()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const iconMap: Record<string, Icon> = {
|
||||||
|
ekowisata: IconLeaf,
|
||||||
|
kompetisi: IconTrophy,
|
||||||
|
wisata: IconTent,
|
||||||
|
ekonomi: IconChartLine,
|
||||||
|
sampah: IconRecycle,
|
||||||
|
truck: IconTruckFilled,
|
||||||
|
scale: IconScale,
|
||||||
|
clipboard: IconClipboardTextFilled,
|
||||||
|
trash: IconTrashFilled,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.findMany.loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
@@ -84,10 +81,10 @@ function Page() {
|
|||||||
<Box key={k} px={28}>
|
<Box key={k} px={28}>
|
||||||
<Paper p={20} bg={colors['white-trans-1']}>
|
<Paper p={20} bg={colors['white-trans-1']}>
|
||||||
<Flex gap={20} align={'center'}>
|
<Flex gap={20} align={'center'}>
|
||||||
<Box>
|
<Box style={{ alignContent: 'center', alignItems: 'center' }}>
|
||||||
{v.icon}
|
{k + 1} {iconMap[v.icon] ? React.createElement(iconMap[v.icon]) : null}
|
||||||
</Box>
|
</Box>
|
||||||
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>{v.deskripsi}</Text>
|
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>{v.name}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -96,7 +93,7 @@ function Page() {
|
|||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||||
Keterangan Bank Sampah Terdekat
|
Keterangan Bank Sampah Terdekat
|
||||||
</Text>
|
</Text>
|
||||||
@@ -107,66 +104,64 @@ function Page() {
|
|||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
placeholder='Cari Bank Sampah Terdekat'
|
placeholder='Cari Bank Sampah Terdekat'
|
||||||
/>
|
/>
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
base: 1,
|
{/* Left side - List of bank locations */}
|
||||||
md: 2,
|
|
||||||
}}>
|
|
||||||
<Box>
|
<Box>
|
||||||
<SimpleGrid
|
<Paper p="md" bg={colors['white-trans-1']} radius="lg">
|
||||||
cols={{
|
<Text fz="xl" fw="bold" mb="md">Daftar Bank Sampah</Text>
|
||||||
base: 1,
|
<Stack gap="md">
|
||||||
md: 1,
|
{data2?.map((v, k) => (
|
||||||
}}
|
<Paper key={k} p="md" withBorder radius="md">
|
||||||
>
|
<Text fw="bold" fz="lg">{v.namaTempatMaps}</Text>
|
||||||
{bankSampah.map((v, k) => {
|
<Text c="dimmed" fz="sm" mb="sm">{v.alamat}</Text>
|
||||||
return (
|
{v.lat && v.lng ? (
|
||||||
<Box key={k} px={20}>
|
<a
|
||||||
<Paper p={20} bg={colors['white-trans-1']} radius={'lg'}>
|
href={`https://www.google.com/maps/dir/?api=1&destination=${v.lat},${v.lng}`}
|
||||||
<Flex gap={20} align={'center'}>
|
target="_blank"
|
||||||
<Box>
|
rel="noopener noreferrer"
|
||||||
{v.icon}
|
style={{ color: colors['blue-button'], textDecoration: 'none' }}
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Text fz="sm">📌 Buka di Google Maps</Text>
|
||||||
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>
|
</a>
|
||||||
{v.deskripsi}
|
) : (
|
||||||
</Text>
|
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
|
||||||
<Text fz={{ base: "md", md: "lg" }} c={'black'}>
|
)}
|
||||||
{v.alamat}
|
</Paper>
|
||||||
</Text>
|
))}
|
||||||
</Box>
|
</Stack>
|
||||||
</Flex>
|
</Paper>
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box style={{
|
|
||||||
position: 'relative',
|
{/* Right side - Single map showing all locations */}
|
||||||
width: '100%',
|
<Box style={{ position: 'sticky', top: '20px' }}>
|
||||||
paddingBottom: '90.5%', // Aspek rasio 16:9 (atau gunakan '100%' untuk aspek rasio 1:1)
|
<Paper p="md" bg={colors['white-trans-1']} radius="lg" h="100%">
|
||||||
height: 0,
|
<Text fz="xl" fw="bold" mb="md">Peta Lokasi</Text>
|
||||||
overflow: 'hidden'
|
{data2?.some(v => v.lat && v.lng) ? (
|
||||||
}}>
|
<Box style={{ height: '600px', width: '100%', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<iframe
|
<LeafletMultiMarkerMap
|
||||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d31558.578355337635!2d115.18413781150647!3d-8.613053599999985!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23ff3a9d0f0ab%3A0xb6bb54a820adbae6!2sBank%20Sampah%20Sarana%20Gathi!5e0!3m2!1sid!2sid!4v1743994947623!5m2!1sid!2sid"
|
center={[
|
||||||
style={{
|
data2[0]?.lat || -8.3405,
|
||||||
position: 'absolute',
|
data2[0]?.lng || 115.0920
|
||||||
top: 0,
|
]}
|
||||||
left: 0,
|
markers={data2
|
||||||
width: '100%',
|
.filter(v => v.lat && v.lng)
|
||||||
height: '100%',
|
.map(v => ({
|
||||||
border: 0
|
position: [v.lat, v.lng],
|
||||||
}}
|
popup: v.namaTempatMaps
|
||||||
loading="lazy"
|
}))}
|
||||||
allowFullScreen
|
/>
|
||||||
></iframe>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed" fz="sm">Tidak ada koordinat yang tersedia</Text>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +1,131 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconChristmasTreeFilled, IconHomeEco, IconShieldFilled, IconTrendingUp } from '@tabler/icons-react';
|
import { IconSearch, IconLeaf, IconTrophy, IconTent, IconChartLine, IconRecycle, IconTruckFilled, IconScale, IconClipboardTextFilled, IconTrashFilled, IconHomeEco, IconChristmasTreeFilled, IconTrendingUp, IconShieldFilled } from '@tabler/icons-react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
deskripsi: 'Lingkungan Sehat',
|
|
||||||
icon: <IconHomeEco size={80} color={colors['blue-button']} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
deskripsi: 'Sumber Oksigen',
|
|
||||||
icon: <IconChristmasTreeFilled size={80} color={colors['blue-button']} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
deskripsi: 'Ekonomi Berkelanjutan',
|
|
||||||
icon: <IconTrendingUp size={80} color={colors['blue-button']} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
deskripsi: 'Mencegah Bencana',
|
|
||||||
icon: <IconShieldFilled size={80} color={colors['blue-button']} />,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(programPenghijauanState);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 500);
|
||||||
|
const { data, load, page, totalPages, loading } = state.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, debouncedSearch);
|
||||||
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
|
const iconMap: Record<string, any> = {
|
||||||
|
ekowisata: IconLeaf,
|
||||||
|
kompetisi: IconTrophy,
|
||||||
|
wisata: IconTent,
|
||||||
|
ekonomi: IconChartLine,
|
||||||
|
sampah: IconRecycle,
|
||||||
|
truck: IconTruckFilled,
|
||||||
|
scale: IconScale,
|
||||||
|
clipboard: IconClipboardTextFilled,
|
||||||
|
trash: IconTrashFilled,
|
||||||
|
lingkunganSehat: IconHomeEco,
|
||||||
|
sumberOksigen: IconChristmasTreeFilled,
|
||||||
|
ekonomiBerkelanjutan: IconTrendingUp,
|
||||||
|
mencegahBencana: IconShieldFilled,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={20}>
|
||||||
|
<Skeleton h={500} radius="xl" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Box>
|
||||||
Program Penghijauan Desa
|
<Group justify="space-between" align='center' mt="md">
|
||||||
|
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||||
|
Program Penghijauan Desa
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
radius="xl"
|
||||||
|
w={'30%'}
|
||||||
|
placeholder="Cari program atau kegiatan"
|
||||||
|
leftSection={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
<Text c="dimmed" fz={{ base: 'sm', md: 'lg' }} mt="sm">
|
||||||
|
Mari berpartisipasi menanam dan merawat pohon untuk menciptakan lingkungan hijau, sehat, dan seimbang bagi seluruh warga desa.
|
||||||
</Text>
|
</Text>
|
||||||
<Text px={20} ta={'center'} fz={'h4'}>Program Penghijauan Desa bertujuan untuk meningkatkan kesadaran masyarakat akan pentingnya lingkungan hijau melalui penanaman pohon dan perawatan tanaman.</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={60}>
|
<Box px={{ base: 'md', md: 100 }} pb={60}>
|
||||||
<Text c={colors['blue-button']} fw={'bold'} py={10} px={28} fz={{ base: "lg", md: "xl" }} ta={"justify"}>
|
<Title order={2} c={colors['blue-button']} fw="bold" py={10} px={28} fz={{ base: 'lg', md: 'xl' }}>
|
||||||
Manfaat Program Penghijauan
|
Manfaat Program
|
||||||
</Text>
|
</Title>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 4 }} spacing="lg" mt="md">
|
||||||
cols={{
|
{data.map((v) => (
|
||||||
base: 1,
|
<Paper
|
||||||
md: 4
|
key={v.id}
|
||||||
}}>
|
p="xl"
|
||||||
{data.map((v, k) => {
|
radius="xl"
|
||||||
return (
|
bg={colors['white-trans-1']}
|
||||||
<Box key={k}>
|
withBorder
|
||||||
<Paper p={20} bg={colors['white-trans-1']}>
|
style={{
|
||||||
<Stack flex={5}>
|
backdropFilter: 'blur(10px)',
|
||||||
<Center>
|
border: `1px solid rgba(255,255,255,0.2)`,
|
||||||
{v.icon}
|
transition: 'transform 0.3s, box-shadow 0.3s',
|
||||||
</Center>
|
cursor: 'pointer',
|
||||||
<Box>
|
}}
|
||||||
<Text fz={{ base: "lg", md: "xl" }} ta={'center'} c={colors['blue-button']} fw={'bold'}>{v.deskripsi}</Text>
|
onMouseEnter={(e) => {
|
||||||
</Box>
|
const el = e.currentTarget;
|
||||||
<Group justify='center'>
|
el.style.transform = 'translateY(-8px)';
|
||||||
<Button bg={colors['blue-button']}>Detail</Button>
|
el.style.boxShadow = '0 15px 30px rgba(28,110,164,0.5)';
|
||||||
</Group>
|
}}
|
||||||
</Stack>
|
onMouseLeave={(e) => {
|
||||||
</Paper>
|
const el = e.currentTarget;
|
||||||
</Box>
|
el.style.transform = 'translateY(0)';
|
||||||
)
|
el.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
||||||
})}
|
}}
|
||||||
|
>
|
||||||
|
<Stack align="center" gap="sm">
|
||||||
|
<Center>
|
||||||
|
{iconMap[v.icon] && React.createElement(iconMap[v.icon], { size: 50, stroke: 1.5, color: colors['blue-button'] })}
|
||||||
|
</Center>
|
||||||
|
<Tooltip label={v.judul} withArrow position="bottom">
|
||||||
|
<Text fz={{ base: 'md', md: 'lg' }} ta="center" c={colors['blue-button']} fw="bold">
|
||||||
|
{v.name}
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
<Text fz="sm" ta="center" c="dimmed">
|
||||||
|
{v.judul}
|
||||||
|
</Text>
|
||||||
|
<Button variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="sm" radius="xl" mt="sm">
|
||||||
|
Pelajari Lebih Lanjut
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
radius="xl"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,119 +3,132 @@ import stateProfilePPID from '@/app/admin/(dashboard)/_state/ppid/profile_ppid/p
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Divider, Flex, Image, List, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Center, Divider, Flex, Image, List, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconBuildingCommunity, IconTargetArrow, IconTimeline, IconUser } from '@tabler/icons-react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const allList = useProxy(stateProfilePPID)
|
const allList = useProxy(stateProfilePPID)
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
allList.profile.load("edit") // Assuming "1" is your default ID, adjust as needed
|
allList.profile.load("edit")
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!allList.profile.data) return <Stack bg={colors.Bg} py={"xl"} gap={"22"}>
|
if (!allList.profile.data) return (
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Stack bg={colors.Bg} py="xl" gap="22">
|
||||||
<Skeleton style={{backgroundColor: colors.Bg}} h={40} />
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
</Box>
|
<Skeleton h={40} />
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
</Box>
|
||||||
<Skeleton h={80} />
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
</Box>
|
<Skeleton h={80} />
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
</Box>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
{Array.from({ length: 10 }).map((v, k) =>
|
<Paper p="xl" bg={colors['white-trans-1']}>
|
||||||
<Skeleton key={k} h={40} />
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
)}
|
<Skeleton key={i} h={40} mb="sm" />
|
||||||
</Paper>
|
))}
|
||||||
</Box>
|
</Paper>
|
||||||
</Stack>
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
|
||||||
const dataArray = Array.isArray(allList.profile.data)
|
const dataArray = Array.isArray(allList.profile.data)
|
||||||
? allList.profile.data
|
? allList.profile.data
|
||||||
: [allList.profile.data];
|
: [allList.profile.data]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta="center" fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }} c={colors["blue-button"]} fw="bold">
|
||||||
Profil Singkat PPID Desa Darmasaba
|
PPID Desa Darmasaba Profile
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{dataArray.map((item) => (
|
{dataArray.map((item) => (
|
||||||
<Box key={item.id} px={{ base: "md", md: 100 }}>
|
<Box key={item.id} px={{ base: "md", md: 100 }}>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
<Paper p="xl" bg={colors['white-trans-1']} radius="lg" shadow="xl">
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Flex align={"center"} gap={50}>
|
<Flex align="center" gap={40} justify="center">
|
||||||
<Image src={"/darmasaba-icon.png"} h={{ base: 80, md: 150 }} alt='' />
|
<Image src="/darmasaba-icon.png" h={{ base: 70, md: 120 }} alt="Village logo" />
|
||||||
<Text fz={{ base: "1.4rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw={'bold'}>PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA </Text>
|
<Text fz={{ base: "1.5rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
|
||||||
|
Public Information Officer
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider my={"md"} />
|
<Divider my="lg" />
|
||||||
{/* biodata perbekel */}
|
|
||||||
<Box px={{ base: 0, md: 50 }} pb={30}>
|
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 1,
|
|
||||||
lg: 1,
|
|
||||||
xl: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box px={{ base: 0, md: 50 }}>
|
|
||||||
<Paper bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
|
||||||
<Stack gap={0}>
|
|
||||||
<Center>
|
|
||||||
<Image pt={{ base: 0, md: 90 }} src={item.imageUrl ? `${item.imageUrl}?t=${Date.now()}` : "/api/img/perbekel.png"} w={{ base: 250, md: 355 }} alt='' />
|
|
||||||
</Center>
|
|
||||||
<Paper
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
py={30}
|
|
||||||
className="glass3"
|
|
||||||
px={{ base: 20, md: 20 }}
|
|
||||||
|
|
||||||
>
|
<Box px={{ base: 0, md: 50 }} pb={40}>
|
||||||
<Text ta={"center"} c={colors['white-1']} fw={"bolder"} fz={{ base: "1.5rem", md: "2.125rem", lg: "2.25rem", xl: "1.5rem" }}>
|
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
|
||||||
|
<Box px={{ base: 0, md: 50 }}>
|
||||||
|
<Paper bg={colors['white-trans-1']} radius="lg" shadow="sm">
|
||||||
|
<Stack gap="md">
|
||||||
|
<Center>
|
||||||
|
<Image
|
||||||
|
src={item.imageUrl ? `${item.imageUrl}?t=${Date.now()}` : "/api/img/perbekel.png"}
|
||||||
|
w={{ base: 220, md: 330 }}
|
||||||
|
alt="Leader photo"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Paper bg={colors['blue-button']} py={25} radius="lg" className="glass3">
|
||||||
|
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.5rem", md: "2rem" }}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Box>
|
<Stack gap="xl">
|
||||||
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Biodata</Text>
|
<Box>
|
||||||
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.biodata }} />
|
<Flex align="center" gap="sm" mb="sm">
|
||||||
</Box>
|
<IconUser size={28} />
|
||||||
<Box>
|
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Biography</Text>
|
||||||
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Riwayat Karir</Text>
|
</Flex>
|
||||||
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} dangerouslySetInnerHTML={{ __html: item.riwayat }} />
|
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.biodata }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex align="center" gap="sm" mb="sm">
|
||||||
|
<IconTimeline size={28} />
|
||||||
|
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Career History</Text>
|
||||||
|
</Flex>
|
||||||
|
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} dangerouslySetInnerHTML={{ __html: item.riwayat }} />
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
<Box pb={30}>
|
|
||||||
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Pengalaman Organisasi</Text>
|
<Box pb={40}>
|
||||||
<List>
|
<Flex align="center" gap="sm" mb="sm">
|
||||||
|
<IconBuildingCommunity size={28} />
|
||||||
|
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Organizational Experience</Text>
|
||||||
|
</Flex>
|
||||||
|
<List spacing="xs" size="sm">
|
||||||
<Box px={20}>
|
<Box px={20}>
|
||||||
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.pengalaman }} />
|
<Text fz={{ base: "1rem", md: "1.125rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.pengalaman }} />
|
||||||
</Box>
|
</Box>
|
||||||
</List>
|
</List>
|
||||||
</Box>
|
</Box>
|
||||||
<Box pb={20}>
|
|
||||||
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Program Kerja Unggulan</Text>
|
<Box>
|
||||||
<List>
|
<Flex align="center" gap="sm" mb="sm">
|
||||||
|
<IconTargetArrow size={28} />
|
||||||
|
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Flagship Programs</Text>
|
||||||
|
</Flex>
|
||||||
|
<List spacing="xs" size="sm">
|
||||||
<Box px={20}>
|
<Box px={20}>
|
||||||
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.unggulan }} />
|
<Text fz={{ base: "1rem", md: "1.125rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.unggulan }} />
|
||||||
</Box>
|
</Box>
|
||||||
</List>
|
</List>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page
|
||||||
|
|||||||
@@ -1,121 +1,395 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
// /* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
// /* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import colors from '@/con/colors';
|
// 'use client'
|
||||||
import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
// import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||||
import { OrganizationChart } from 'primereact/organizationchart';
|
// import colors from '@/con/colors';
|
||||||
import { useEffect } from 'react';
|
// import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useProxy } from 'valtio/utils';
|
// import { OrganizationChart } from 'primereact/organizationchart';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
// import { useEffect } from 'react';
|
||||||
|
// import { useProxy } from 'valtio/utils';
|
||||||
|
// import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
// function Page() {
|
||||||
return (
|
// return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
// <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
// <Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
// <BackButton />
|
||||||
</Box>
|
// </Box>
|
||||||
<Title ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>Struktur PPID</Title>
|
// <Title ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>Struktur PPID</Title>
|
||||||
<StrukturOrganisasiPPID />
|
// <StrukturOrganisasiPPID />
|
||||||
|
|
||||||
</Stack>
|
// </Stack>
|
||||||
);
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function StrukturOrganisasiPPID() {
|
||||||
|
// const stateOrganisasi = useProxy(stateStrukturPPID.pegawai)
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// stateOrganisasi.findMany.load()
|
||||||
|
// }, [])
|
||||||
|
|
||||||
|
// if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) {
|
||||||
|
// return (
|
||||||
|
// <Stack py={10}>
|
||||||
|
// <Skeleton h={500} />
|
||||||
|
// </Stack>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Step 1: Group pegawai berdasarkan posisiId
|
||||||
|
// const posisiMap = new Map<string, any>();
|
||||||
|
|
||||||
|
// for (const pegawai of stateOrganisasi.findMany.data) {
|
||||||
|
// const posisiId = pegawai.posisi.id;
|
||||||
|
// if (!posisiMap.has(posisiId)) {
|
||||||
|
// posisiMap.set(posisiId, {
|
||||||
|
// ...pegawai.posisi,
|
||||||
|
// pegawaiList: [],
|
||||||
|
// children: []
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// // Step 2: Buat struktur pohon berdasarkan parentId
|
||||||
|
// const root: any[] = [];
|
||||||
|
|
||||||
|
// posisiMap.forEach((posisi) => {
|
||||||
|
// if (posisi.parentId) {
|
||||||
|
// const parent = posisiMap.get(posisi.parentId);
|
||||||
|
// if (parent) {
|
||||||
|
// parent.children.push(posisi);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// root.push(posisi);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Step 3: Ubah struktur ke format OrganizationChart
|
||||||
|
// function toOrgChartFormat(node: any): any {
|
||||||
|
// return {
|
||||||
|
// expanded: true,
|
||||||
|
// type: 'person',
|
||||||
|
// styleClass: 'p-person',
|
||||||
|
// data: {
|
||||||
|
// name: node.pegawaiList?.[0]?.namaLengkap || 'Tidak ada pegawai',
|
||||||
|
// status: node.nama,
|
||||||
|
// image: node.pegawaiList?.[0]?.image?.link || '/img/default.png'
|
||||||
|
// },
|
||||||
|
// children: node.children.map(toOrgChartFormat)
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// const chartData = root.map(toOrgChartFormat);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Box py={10}>
|
||||||
|
// <Paper bg={colors.grey} p="md" style={{ overflowX: 'auto' }}>
|
||||||
|
// <OrganizationChart style={{ color: colors['blue-button'] }} value={chartData} nodeTemplate={nodeTemplate} />
|
||||||
|
// </Paper>
|
||||||
|
// </Box>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// function nodeTemplate(node: any) {
|
||||||
|
// const imageSrc = node?.data?.image || '/img/default.png';
|
||||||
|
// const name = node?.data?.name || 'Tanpa Nama';
|
||||||
|
// const status = node?.data?.status || 'Tidak ada deskripsi';
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Stack pos={"relative"} py={"xl"} gap={"22"}>
|
||||||
|
// <Stack align="center" gap={4}>
|
||||||
|
// <Image
|
||||||
|
// src={imageSrc}
|
||||||
|
// alt={name}
|
||||||
|
// radius="xl"
|
||||||
|
// w={120}
|
||||||
|
// h={120}
|
||||||
|
// fit="cover"
|
||||||
|
// />
|
||||||
|
// <Text fw={600} ta="center">{name}</Text>
|
||||||
|
// <Text size="sm" c="dimmed" ta="center">{status}</Text>
|
||||||
|
// </Stack>
|
||||||
|
// </Stack>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default Page;
|
||||||
|
|
||||||
|
'use client'
|
||||||
|
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Center,
|
||||||
|
Container,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Loader,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Transition,
|
||||||
|
} from '@mantine/core'
|
||||||
|
import { IconRefresh, IconSearch, IconUsers } from '@tabler/icons-react'
|
||||||
|
import { OrganizationChart } from 'primereact/organizationchart'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useProxy } from 'valtio/utils'
|
||||||
|
import BackButton from '../../desa/layanan/_com/BackButto'
|
||||||
|
import colors from '@/con/colors'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
minHeight: '100vh',
|
||||||
|
background: colors['Bg'],
|
||||||
|
color: '#E6F0FF',
|
||||||
|
paddingBottom: 48,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container size="xl" py="xl">
|
||||||
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<BackButton />
|
||||||
|
</Box>
|
||||||
|
<Stack align="center" gap="xl" mt="xl">
|
||||||
|
<Title
|
||||||
|
order={1}
|
||||||
|
ta="center"
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fz={{ base: 28, md: 36, lg: 44 }}
|
||||||
|
|
||||||
|
>
|
||||||
|
Struktur Organisasi PPID
|
||||||
|
</Title>
|
||||||
|
<Text ta="center" c="black" maw={800}>
|
||||||
|
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
|
||||||
|
untuk melihat detail atau klik node untuk fokus tampilan.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Box mt="lg">
|
||||||
|
<StrukturOrganisasiPPID />
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function StrukturOrganisasiPPID() {
|
function StrukturOrganisasiPPID() {
|
||||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai)
|
const stateOrganisasi: any = useProxy(stateStrukturPPID.pegawai)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateOrganisasi.findMany.load()
|
void stateOrganisasi.findMany.load()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) {
|
const isLoading =
|
||||||
|
!stateOrganisasi.findMany.data &&
|
||||||
|
stateOrganisasi.findMany.loading !== false
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Center py={48}>
|
||||||
<Skeleton h={500} />
|
<Stack align="center" gap="sm">
|
||||||
</Stack>
|
<Loader size="lg" />
|
||||||
);
|
<Text fw={600}>Memuat struktur organisasi…</Text>
|
||||||
|
<Text c="dimmed" size="sm">
|
||||||
|
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Group pegawai berdasarkan posisiId
|
if (
|
||||||
const posisiMap = new Map<string, any>();
|
!stateOrganisasi.findMany.data ||
|
||||||
|
stateOrganisasi.findMany.data.length === 0
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Center py={40}>
|
||||||
|
<Stack align="center" gap="md">
|
||||||
|
<Paper
|
||||||
|
radius="md"
|
||||||
|
p="xl"
|
||||||
|
style={{
|
||||||
|
width: 560,
|
||||||
|
background: 'rgba(28,110,164,0.2)',
|
||||||
|
border: `1px solid rgba(255,255,255,0.1)`,
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Center>
|
||||||
|
<IconUsers size={56} />
|
||||||
|
</Center>
|
||||||
|
<Title order={3} mt="md">
|
||||||
|
Data pegawai belum tersedia
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" mt="xs">
|
||||||
|
Belum ada data pegawai yang tercatat untuk PPID. Silakan coba
|
||||||
|
muat ulang atau periksa sumber data.
|
||||||
|
</Text>
|
||||||
|
<Group justify="center" mt="lg">
|
||||||
|
<Button
|
||||||
|
leftSection={<IconRefresh size={16} />}
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: 'indigo', to: 'cyan' }}
|
||||||
|
onClick={() => stateOrganisasi.findMany.load()}
|
||||||
|
>
|
||||||
|
Muat Ulang
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconSearch size={16} />}
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() =>
|
||||||
|
stateOrganisasi.findMany.load({ query: { q: '' } })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Cari Pegawai
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const posisiMap = new Map<string, any>()
|
||||||
for (const pegawai of stateOrganisasi.findMany.data) {
|
for (const pegawai of stateOrganisasi.findMany.data) {
|
||||||
const posisiId = pegawai.posisi.id;
|
const posisiId = pegawai.posisi.id
|
||||||
if (!posisiMap.has(posisiId)) {
|
if (!posisiMap.has(posisiId)) {
|
||||||
posisiMap.set(posisiId, {
|
posisiMap.set(posisiId, {
|
||||||
...pegawai.posisi,
|
...pegawai.posisi,
|
||||||
pegawaiList: [],
|
pegawaiList: [],
|
||||||
children: []
|
children: [],
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
|
posisiMap.get(posisiId)!.pegawaiList.push(pegawai)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const root: any[] = []
|
||||||
// Step 2: Buat struktur pohon berdasarkan parentId
|
|
||||||
const root: any[] = [];
|
|
||||||
|
|
||||||
posisiMap.forEach((posisi) => {
|
posisiMap.forEach((posisi) => {
|
||||||
if (posisi.parentId) {
|
if (posisi.parentId) {
|
||||||
const parent = posisiMap.get(posisi.parentId);
|
const parent = posisiMap.get(posisi.parentId)
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.children.push(posisi);
|
parent.children.push(posisi)
|
||||||
|
} else {
|
||||||
|
root.push(posisi)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
root.push(posisi);
|
root.push(posisi)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Step 3: Ubah struktur ke format OrganizationChart
|
|
||||||
function toOrgChartFormat(node: any): any {
|
function toOrgChartFormat(node: any): any {
|
||||||
return {
|
return {
|
||||||
expanded: true,
|
expanded: true,
|
||||||
type: 'person',
|
type: 'person',
|
||||||
styleClass: 'p-person',
|
styleClass: 'p-person',
|
||||||
data: {
|
data: {
|
||||||
name: node.pegawaiList?.[0]?.namaLengkap || 'Tidak ada pegawai',
|
name: node.pegawaiList?.[0]?.namaLengkap || 'Belum ditugaskan',
|
||||||
status: node.nama,
|
title: node.nama || 'Tanpa jabatan',
|
||||||
image: node.pegawaiList?.[0]?.image?.link || '/img/default.png'
|
image: node.pegawaiList?.[0]?.image?.link || '/img/default.png',
|
||||||
|
description: node.deskripsi || '',
|
||||||
|
positionId: node.id || null,
|
||||||
},
|
},
|
||||||
children: node.children.map(toOrgChartFormat)
|
children: node.children?.map(toOrgChartFormat) || [],
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chartData = root.map(toOrgChartFormat)
|
||||||
const chartData = root.map(toOrgChartFormat);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={16} >
|
||||||
<Paper bg={colors.grey} p="md" style={{ overflowX: 'auto' }}>
|
<Paper
|
||||||
<OrganizationChart style={{ color: colors['blue-button'] }} value={chartData} nodeTemplate={nodeTemplate} />
|
radius="md"
|
||||||
|
p="md"
|
||||||
|
style={{
|
||||||
|
background: 'rgba(28,110,164,0.2)',
|
||||||
|
border: `1px solid rgba(255,255,255,0.1)`,
|
||||||
|
overflowX: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OrganizationChart
|
||||||
|
value={chartData}
|
||||||
|
nodeTemplate={nodeTemplate}
|
||||||
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function nodeTemplate(node: any) {
|
function nodeTemplate(node: any) {
|
||||||
const imageSrc = node?.data?.image || '/img/default.png';
|
const imageSrc = node?.data?.image || '/img/default.png'
|
||||||
const name = node?.data?.name || 'Tanpa Nama';
|
const name = node?.data?.name || 'Tanpa Nama'
|
||||||
const status = node?.data?.status || 'Tidak ada deskripsi';
|
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||||
|
const description = node?.data?.description || ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} py={"xl"} gap={"22"}>
|
<Transition mounted transition="pop" duration={240}>
|
||||||
<Stack align="center" gap={4}>
|
{(styles) => (
|
||||||
<Image
|
<Card
|
||||||
src={imageSrc}
|
radius="lg"
|
||||||
alt={name}
|
withBorder
|
||||||
radius="xl"
|
style={{
|
||||||
w={120}
|
...styles,
|
||||||
h={120}
|
width: 260,
|
||||||
fit="cover"
|
padding: 16,
|
||||||
/>
|
background: 'rgba(28,110,164,0.3)',
|
||||||
<Text fw={600} ta="center">{name}</Text>
|
borderColor: 'rgba(255,255,255,0.15)',
|
||||||
<Text size="sm" c="dimmed" ta="center">{status}</Text>
|
display: 'flex',
|
||||||
</Stack>
|
flexDirection: 'column',
|
||||||
</Stack>
|
alignItems: 'center',
|
||||||
);
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={imageSrc}
|
||||||
|
alt={name}
|
||||||
|
radius="md"
|
||||||
|
width={120}
|
||||||
|
height={120}
|
||||||
|
fit="cover"
|
||||||
|
style={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
border: '2px solid rgba(255,255,255,0.2)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text fw={700}>{name}</Text>
|
||||||
|
<Text size="sm" c="dimmed" mt={4}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text size="xs" c="dimmed" mt={8} lineClamp={3}>
|
||||||
|
{description || 'Belum ada deskripsi.'}
|
||||||
|
</Text>
|
||||||
|
<Tooltip label="Lihat detail" withArrow position="bottom">
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
size="xs"
|
||||||
|
mt="md"
|
||||||
|
onClick={() => {
|
||||||
|
const id = node?.data?.positionId
|
||||||
|
if (id && (window as any).scrollTo) {
|
||||||
|
;(window as any).scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,112 +1,97 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
|
||||||
import { ActionIcon, BackgroundImage, Box, Center, Container, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
|
import colors from '@/con/colors'
|
||||||
import { IconDownload } from '@tabler/icons-react';
|
import { ActionIcon, BackgroundImage, Box, Center, Container, Group, Loader, SimpleGrid, Stack, Text, Title } from '@mantine/core'
|
||||||
import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
|
import { IconDownload } from '@tabler/icons-react'
|
||||||
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
|
import { Link } from 'next-view-transitions'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useEffect, useState } from 'react'
|
||||||
import { useEffect, useState } from 'react';
|
import { useProxy } from 'valtio/utils'
|
||||||
import { Link } from 'next-view-transitions';
|
import BackButton from '../../(pages)/desa/layanan/_com/BackButto'
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(apbdes);
|
const state = useProxy(apbdes)
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true)
|
||||||
await state.findMany.load();
|
await state.findMany.load()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadData();
|
loadData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const data = state.findMany.data || [];
|
const data = state.findMany.data || []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
|
||||||
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Container w={{ base: "100%", md: "50%" }}>
|
<BackButton />
|
||||||
<Stack align="center" gap={0}>
|
</Box>
|
||||||
<Text fz={"3.4rem"} fw={"bold"}>
|
<Container w={{ base: '100%', md: '60%' }}>
|
||||||
APBDes
|
<Stack align="center" gap="sm">
|
||||||
</Text>
|
<Title order={1} fz={{ base: '2.4rem', md: '3.2rem' }} fw="bold" ta="center">
|
||||||
<Text
|
Anggaran Pendapatan & Belanja Desa (APBDes)
|
||||||
py={10}
|
</Title>
|
||||||
ta={"justify"}
|
<Text fz="md" c="dimmed" ta="center">
|
||||||
>
|
Laporan transparansi APBDes Desa Darmasaba sebagai bentuk keterbukaan dan akuntabilitas pengelolaan anggaran desa.
|
||||||
Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab. Adapun APBDes sebagai berikut:
|
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
<SimpleGrid
|
{loading ? (
|
||||||
px={{ base: "md", md: 100 }}
|
<Center mih={200}>
|
||||||
cols={{
|
<Stack align="center" gap="sm">
|
||||||
base: 1,
|
<Loader size="lg" color="blue" />
|
||||||
sm: 3,
|
<Text fz="lg" c="dimmed">Sedang memuat data APBDes...</Text>
|
||||||
}}
|
</Stack>
|
||||||
>
|
</Center>
|
||||||
{loading ? (
|
) : data.length === 0 ? (
|
||||||
<Center>
|
<Center mih={200}>
|
||||||
<Text fz={"2.4rem"}>Memuat Data...</Text>
|
<Stack align="center" gap="xs">
|
||||||
</Center>
|
<Text fz="xl" fw={600} c="dimmed">Belum ada data APBDes tersedia</Text>
|
||||||
) : (
|
<Text fz="sm" c="dimmed">Data akan ditampilkan jika sudah diunggah oleh admin desa</Text>
|
||||||
data.map((v, k) => {
|
</Stack>
|
||||||
return (
|
</Center>
|
||||||
<BackgroundImage
|
) : (
|
||||||
key={k}
|
<SimpleGrid px={{ base: 'md', md: 100 }} cols={{ base: 1, sm: 2, md: 3 }} spacing="xl">
|
||||||
src={v.image?.link || ''}
|
{data.map((v: any, k: number) => (
|
||||||
h={350}
|
<BackgroundImage key={k} src={v.image?.link || ''} h={360} radius="xl" pos="relative">
|
||||||
radius={16}
|
<Box pos="absolute" inset={0} bg="rgba(0,0,0,0.45)" style={{ borderRadius: 16 }} />
|
||||||
pos={"relative"}
|
<Stack justify="space-between" h="100%" p="lg" pos="relative">
|
||||||
>
|
<Box>
|
||||||
<Box
|
<Text fz="lg" fw={600} c="white" ta="center">
|
||||||
style={{
|
{v.name}
|
||||||
borderRadius: 16,
|
</Text>
|
||||||
zIndex: 0
|
</Box>
|
||||||
}}
|
<Text fz="2.6rem" fw="bold" c="white" ta="center">
|
||||||
pos={"absolute"}
|
{v.jumlah}
|
||||||
w={"100%"}
|
</Text>
|
||||||
h={"100%"}
|
<Group justify="center">
|
||||||
bg={colors.trans.dark[2]}
|
<ActionIcon
|
||||||
/>
|
component={Link}
|
||||||
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
|
href={v.file?.link || '#'}
|
||||||
<Box p={"lg"}>
|
radius="xl"
|
||||||
<Text
|
size="lg"
|
||||||
c={"white"}
|
bg={colors['blue-button']}
|
||||||
size={"1.5rem"}
|
variant="filled"
|
||||||
style={{
|
>
|
||||||
textAlign: "center",
|
<IconDownload size={20} color="white" />
|
||||||
}}>{v.name}</Text>
|
</ActionIcon>
|
||||||
</Box>
|
</Group>
|
||||||
<Text
|
</Stack>
|
||||||
fw={"bold"}
|
</BackgroundImage>
|
||||||
c={"white"}
|
))}
|
||||||
size={"3.5rem"}
|
</SimpleGrid>
|
||||||
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>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page
|
||||||
|
|||||||
@@ -1,137 +1,136 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client';
|
'use client';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text, ActionIcon } from '@mantine/core';
|
|
||||||
import { IconFile } from '@tabler/icons-react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Flex,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
ActionIcon,
|
||||||
|
Loader,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconFile, IconInbox } from '@tabler/icons-react';
|
||||||
|
|
||||||
function Lokal() {
|
function Lokal() {
|
||||||
const [selectedKategori, setSelectedKategori] = useState<string>('PENGUATAN TATA LAKSANA');
|
const [selectedKategori, setSelectedKategori] = useState<string>('PENGUATAN TATA LAKSANA');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const state = useProxy(korupsiState);
|
const state = useProxy(korupsiState);
|
||||||
|
|
||||||
// Load data on component mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.desaAntikorupsi.findMany.load(1, 100); // Load first 100 items
|
await state.desaAntikorupsi.findMany.load(1, 100);
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Get data from state
|
|
||||||
const data = state.desaAntikorupsi.findMany.data || [];
|
const data = state.desaAntikorupsi.findMany.data || [];
|
||||||
|
const categories = [...new Set(data.filter(i => i.kategori?.name).map(i => i.kategori.name))];
|
||||||
// Debug: Log the complete data structure
|
const filteredData =
|
||||||
console.log('Complete data:', JSON.parse(JSON.stringify(data)));
|
selectedKategori === 'PENGUATAN TATA LAKSANA'
|
||||||
|
? data
|
||||||
// Get unique categories
|
: data.filter(i => i.kategori?.name === selectedKategori);
|
||||||
const categories = [...new Set(
|
|
||||||
data
|
|
||||||
.filter(item => item.kategori?.name) // Only include items with a category name
|
|
||||||
.map(item => item.kategori.name)
|
|
||||||
)];
|
|
||||||
|
|
||||||
// Filter data based on selected category
|
|
||||||
const filteredData = selectedKategori === 'PENGUATAN TATA LAKSANA'
|
|
||||||
? data
|
|
||||||
: data.filter(item => item.kategori?.name === selectedKategori);
|
|
||||||
|
|
||||||
// Debug: Log filtered data
|
|
||||||
console.log('Filtered data:', JSON.parse(JSON.stringify(filteredData)));
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container w={{ base: "100%", md: "80%" }}>
|
|
||||||
<Stack align="center" gap={0}>
|
<Container w={{ base: '100%', md: '80%' }}>
|
||||||
<Text fz={"3.4rem"} fw={"bold"}>
|
<Stack align="center" gap={4}>
|
||||||
|
<Text fz={{ base: 32, md: 44 }} fw={700} ta="center" c={colors['blue-button']}>
|
||||||
Desa Anti Korupsi
|
Desa Anti Korupsi
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text ta="center" fz="md" c="dimmed" maw={700}>
|
||||||
py={10}
|
Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola
|
||||||
ta={"justify"}
|
terbuka dengan melibatkan warga untuk mengawasi anggaran sehingga tepat sasaran sesuai
|
||||||
>
|
kebutuhan.
|
||||||
Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan. Adapun beberapa jenis tata penguatan :
|
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
<Container size="lg" px={{ base: "md", md: "xl" }}>
|
|
||||||
{/* Category Filter Buttons */}
|
<Container size="lg" px={{ base: 'md', md: 'xl' }}>
|
||||||
<Flex gap="md" justify="center" mb="xl" wrap="wrap">
|
<Flex gap="sm" justify="center" mb="xl" wrap="wrap">
|
||||||
{categories.map((kategori) => (
|
{categories.map(kategori => (
|
||||||
<Button
|
<Button
|
||||||
color={selectedKategori === kategori ? colors['blue-button'] : 'gray'}
|
|
||||||
key={kategori}
|
key={kategori}
|
||||||
variant={selectedKategori === kategori ? 'filled' : 'outline'}
|
|
||||||
onClick={() => setSelectedKategori(kategori)}
|
onClick={() => setSelectedKategori(kategori)}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
radius="xl"
|
||||||
|
variant={selectedKategori === kategori ? 'filled' : 'outline'}
|
||||||
|
color={selectedKategori === kategori ? colors['blue-button'] : 'gray'}
|
||||||
>
|
>
|
||||||
{kategori}
|
{kategori}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* Loading State */}
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Text ta="center">Memuat data...</Text>
|
<Flex justify="center" align="center" h={200}>
|
||||||
|
<Loader color={colors['blue-button']} size="lg" />
|
||||||
|
</Flex>
|
||||||
|
) : filteredData.length === 0 ? (
|
||||||
|
<Flex direction="column" align="center" justify="center" gap="sm" py="xl">
|
||||||
|
<IconInbox size={48} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Text fz="lg" fw={500} c="dimmed">
|
||||||
|
Belum ada data untuk kategori ini
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
|
||||||
cols={{ base: 1, sm: 2, lg: 3 }}
|
{filteredData.map(item => {
|
||||||
spacing="xl"
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
verticalSpacing="xl"
|
|
||||||
>
|
|
||||||
{filteredData.map((item) => {
|
|
||||||
console.log('Item data:', item);
|
|
||||||
console.log('All item properties:', Object.keys(item).map(key => `${key}: ${item[key]}`).join(', '));
|
|
||||||
|
|
||||||
const handleDownload = async (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!item?.file?.link) return;
|
if (!item?.file?.link) return;
|
||||||
|
const url = item.file.link.startsWith('http')
|
||||||
try {
|
? item.file.link
|
||||||
const fileUrl = item.file.link.startsWith('http')
|
: `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${
|
||||||
? item.file.link
|
item.file.link
|
||||||
: `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${item.file.link}`;
|
}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
window.open(fileUrl, '_blank');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error opening file:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper key={item.id} p="lg" shadow="sm" radius="md" withBorder>
|
<Paper
|
||||||
|
key={item.id}
|
||||||
|
p="lg"
|
||||||
|
shadow="md"
|
||||||
|
radius="xl"
|
||||||
|
withBorder
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #ffffff, #f8fbff)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Stack h="100%" justify="space-between">
|
<Stack h="100%" justify="space-between">
|
||||||
<div>
|
<Text fz="lg" fw={600} c={colors['blue-button']} lineClamp={2}>
|
||||||
<Text fz="lg" fw={600} mb="sm" c={colors["blue-button"]}>
|
{item.name}
|
||||||
{item.name}
|
</Text>
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{item?.file && (
|
{item?.file && (
|
||||||
<ActionIcon
|
<Tooltip label="Unduh file" withArrow>
|
||||||
variant="filled"
|
<ActionIcon
|
||||||
color={colors["blue-button"]}
|
variant="filled"
|
||||||
size="lg"
|
color={colors['blue-button']}
|
||||||
onClick={handleDownload}
|
size="lg"
|
||||||
style={{ marginTop: 10 }}
|
radius="xl"
|
||||||
title="Unduh File"
|
onClick={handleDownload}
|
||||||
>
|
>
|
||||||
<IconFile size={20} />
|
<IconFile size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -141,9 +140,7 @@ function Lokal() {
|
|||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Lokal;
|
export default Lokal;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Container, Text, Image, ActionIcon, Box, Divider, Flex, Center, Skeleton } from '@mantine/core';
|
import { Stack, Container, Text, Image, ActionIcon, Box, Divider, Flex, Center, Skeleton, Paper, Tooltip } from '@mantine/core';
|
||||||
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
@@ -23,77 +22,99 @@ function Page() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.findUnique.load(id);
|
await state.findUnique.load(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [id])
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h={"70vh"}>
|
||||||
<Skeleton height={500} />
|
<Stack align="center" gap="sm">
|
||||||
|
<Skeleton height={40} width={200} radius="xl" />
|
||||||
|
<Skeleton height={300} width={300} radius="md" />
|
||||||
|
<Skeleton height={20} width="80%" radius="xl" />
|
||||||
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h={"70vh"}>
|
||||||
<Text>Data tidak ditemukan</Text>
|
<Paper withBorder shadow="md" p="xl" radius="lg">
|
||||||
|
<Text ta="center" fz="lg" fw="bold" c="dimmed">
|
||||||
|
Data penghargaan tidak tersedia
|
||||||
|
</Text>
|
||||||
|
<Text ta="center" fz="sm" c="dimmed" mt="sm">
|
||||||
|
Silakan kembali dan pilih penghargaan lainnya
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container w={{ base: "100%", md: "50%" }}>
|
<Container w={{ base: "100%", md: "60%" }}>
|
||||||
<Stack align="center" gap={0}>
|
<Stack align="center" gap="md">
|
||||||
<Text
|
<Text ta="center" fw="bold" fz={{ base: "1.8rem", md: "2.3rem" }} lh={1.3}>
|
||||||
ta={"center"}
|
|
||||||
fw={"bold"}
|
|
||||||
fz={"2.3rem"}
|
|
||||||
>
|
|
||||||
{state.findUnique.data?.name}
|
{state.findUnique.data?.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Image py={20} src={state.findUnique.data?.image?.link || ''} alt='' />
|
<Image
|
||||||
|
src={state.findUnique.data?.image?.link || ''}
|
||||||
|
alt="Gambar penghargaan"
|
||||||
|
radius="lg"
|
||||||
|
fit="contain"
|
||||||
|
mah={400}
|
||||||
|
fallbackSrc="https://placehold.co/600x400?text=Tidak+Ada+Gambar"
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Text
|
<Text
|
||||||
pb={20}
|
pb={20}
|
||||||
ta={"justify"}
|
ta="justify"
|
||||||
fw={"bold"}
|
fz="md"
|
||||||
|
lh={1.6}
|
||||||
dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || '' }}
|
dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || '' }}
|
||||||
/>
|
/>
|
||||||
<Box py={20}>
|
<Box py={20}>
|
||||||
<Divider color={colors['blue-button']} />
|
<Divider color={colors['blue-button']} />
|
||||||
<Flex justify={"space-between"} py={20}>
|
<Flex direction={{ base: "column", sm: "row" }} justify="space-between" align={{ base: "start", sm: "center" }} gap="sm" py={20}>
|
||||||
<Text fz={"sm"}>{new Date(state.findUnique.data?.createdAt).toLocaleDateString()}</Text>
|
<Text fz="sm" c="dimmed">
|
||||||
<Box>
|
Diterbitkan: {new Date(state.findUnique.data?.createdAt).toLocaleDateString('id-ID')}
|
||||||
<Flex gap={"lg"}>
|
</Text>
|
||||||
<ActionIcon variant='transparent'>
|
<Flex gap="md">
|
||||||
<IconBrandFacebook color={colors['blue-button']} size={"30"} />
|
<Tooltip label="Bagikan ke Facebook" withArrow>
|
||||||
|
<ActionIcon variant="light" radius="xl" size="lg" color="blue">
|
||||||
|
<IconBrandFacebook size={22} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon variant='transparent'>
|
</Tooltip>
|
||||||
<IconBrandInstagram color={colors['blue-button']} size={"30"} />
|
<Tooltip label="Bagikan ke Instagram" withArrow>
|
||||||
|
<ActionIcon variant="light" radius="xl" size="lg" color="pink">
|
||||||
|
<IconBrandInstagram size={22} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon variant='transparent'>
|
</Tooltip>
|
||||||
<IconBrandTwitter color={colors['blue-button']} size={"30"} />
|
<Tooltip label="Bagikan ke Twitter" withArrow>
|
||||||
|
<ActionIcon variant="light" radius="xl" size="lg" color="cyan">
|
||||||
|
<IconBrandTwitter size={22} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon variant='transparent'>
|
</Tooltip>
|
||||||
<IconBrandWhatsapp color={colors['blue-button']} size={"30"} />
|
<Tooltip label="Bagikan ke WhatsApp" withArrow>
|
||||||
|
<ActionIcon variant="light" radius="xl" size="lg" color="green">
|
||||||
|
<IconBrandWhatsapp size={22} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider color={colors['blue-button']} pb={50} />
|
<Divider color={colors['blue-button']} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -3,109 +3,131 @@
|
|||||||
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { Carousel, CarouselSlide } from "@mantine/carousel";
|
import { Carousel, CarouselSlide } from "@mantine/carousel";
|
||||||
import { Box, Button, Container, Group, Paper, Stack, Text, useMantineTheme } from "@mantine/core";
|
import { Box, Button, Container, Group, Paper, Stack, Text, useMantineTheme, Skeleton } from "@mantine/core";
|
||||||
import { useMediaQuery } from "@mantine/hooks";
|
import { useMediaQuery } from "@mantine/hooks";
|
||||||
import Autoplay from "embla-carousel-autoplay";
|
import Autoplay from "embla-carousel-autoplay";
|
||||||
|
import { IconAward, IconArrowRight } from "@tabler/icons-react";
|
||||||
import { useTransitionRouter } from "next-view-transitions";
|
import { useTransitionRouter } from "next-view-transitions";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
import BackButton from "../../(pages)/desa/layanan/_com/BackButto";
|
import BackButton from "../../(pages)/desa/layanan/_com/BackButto";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={22}>
|
<Stack pos="relative" bg={colors.grey[1]} py="xl" gap={32}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container w={{ base: "100%", md: "50%" }}>
|
<Container w={{ base: "100%", md: "60%" }}>
|
||||||
<Stack align="center" gap={0}>
|
<Stack align="center" gap="sm">
|
||||||
<Text fz={"3.4rem"} fw={"bold"}>
|
<Group gap="xs">
|
||||||
Penghargaan
|
<IconAward size={40} color={colors["blue-button"]} />
|
||||||
</Text>
|
<Text fz={{ base: "2rem", md: "3.2rem" }} fw={800} variant="gradient" gradient={{ from: "#1C6EA4", to: "#69BFF8" }}>
|
||||||
<Text
|
Penghargaan Desa
|
||||||
py={10}
|
</Text>
|
||||||
ta={"justify"}
|
</Group>
|
||||||
>
|
<Text fz="lg" c="dimmed" ta="center">
|
||||||
Desa Darmasaba telah berhasil meraih berbagai penghargaan bergengsi yang membuktikan dedikasi dan kerja keras seluruh elemen masyarakat dalam membangun desa yang maju dan berkelanjutan. Berikut ini adalah macam-macam penghargaan yang telah diraih oleh Desa Darmasaba:
|
Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan.
|
||||||
</Text>
|
</Text>
|
||||||
<Slider />
|
<Slider />
|
||||||
</Stack>
|
|
||||||
</Container>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
</Container>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Slider() {
|
function Slider() {
|
||||||
const height = 720;
|
const height = 500;
|
||||||
const width = 1200;
|
const width = 1200;
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
||||||
const autoplay = useRef(Autoplay({ delay: 2000 }));
|
const autoplay = useRef(Autoplay({ delay: 3000 }));
|
||||||
const state = useProxy(penghargaanState);
|
const state = useProxy(penghargaanState);
|
||||||
const roter = useTransitionRouter()
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
state.findMany.load();
|
||||||
try {
|
}, []);
|
||||||
await state.findMany.load();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadData();
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const data = state.findMany.data || [];
|
const data = state.findMany.data || [];
|
||||||
|
const loading = state.findMany.loading;
|
||||||
const slides = data.map((item) => (
|
|
||||||
|
|
||||||
<CarouselSlide key={item.id}>
|
|
||||||
<Paper h={"100%"} pos={"relative"} style={{
|
|
||||||
backgroundImage: `url(${item.image?.link}) `,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundPosition: "center",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
}}>
|
|
||||||
<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 fz={"1.5rem"} ta={"center"} c={"white"}>{item.name}</Text>
|
|
||||||
</Box>
|
|
||||||
<Group justify="center" >
|
|
||||||
<Button onClick={() => roter.push(`/darmasaba/penghargaan/${item.id}`)} px={46} radius={"100"} size="md" bg={colors["blue-button"]}>
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</CarouselSlide>
|
|
||||||
|
|
||||||
));
|
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Carousel
|
<Group justify="center" py="xl">
|
||||||
c={"white"}
|
<Skeleton w={300} h={200} radius="lg" />
|
||||||
py={50}
|
<Skeleton w={300} h={200} radius="lg" visibleFrom="sm" />
|
||||||
plugins={[autoplay.current]}
|
<Skeleton w={300} h={200} radius="lg" visibleFrom="md" />
|
||||||
onMouseEnter={autoplay.current.stop}
|
</Group>
|
||||||
onMouseLeave={autoplay.current.reset}
|
|
||||||
w={{ base: 500, md: 800, lg: 900, xl: width }}
|
|
||||||
height={height}
|
|
||||||
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
|
|
||||||
slideGap={{ base: "xl", sm: "md" }}
|
|
||||||
loop
|
|
||||||
align="start"
|
|
||||||
slidesToScroll={mobile ? 1 : 2}
|
|
||||||
>
|
|
||||||
{slides}
|
|
||||||
</Carousel>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!loading && data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Stack align="center" py="xl">
|
||||||
|
<IconAward size={56} color={colors["blue-button"]} />
|
||||||
|
<Text fz="lg" fw={600} c="dimmed">
|
||||||
|
Belum ada penghargaan yang ditambahkan
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const slides = data.map((item) => (
|
||||||
|
<CarouselSlide key={item.id}>
|
||||||
|
<Paper
|
||||||
|
h="100%"
|
||||||
|
radius="lg"
|
||||||
|
shadow="md"
|
||||||
|
pos="relative"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${item.image?.link})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
pos="absolute"
|
||||||
|
inset={0}
|
||||||
|
bg="linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0.3))"
|
||||||
|
style={{ borderRadius: 16 }}
|
||||||
|
/>
|
||||||
|
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
|
||||||
|
<Text fz="xl" fw={700} ta="center" c="white">
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
<Group justify="center">
|
||||||
|
<Button
|
||||||
|
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
rightSection={<IconArrowRight size={18} />}
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: "#1C6EA4", to: "#69BFF8" }}
|
||||||
|
>
|
||||||
|
Lihat Detail
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</CarouselSlide>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Carousel
|
||||||
|
py="xl"
|
||||||
|
plugins={[autoplay.current]}
|
||||||
|
onMouseEnter={autoplay.current.stop}
|
||||||
|
onMouseLeave={autoplay.current.reset}
|
||||||
|
w={{ base: "100%", sm: "90%", md: "80%", lg: width }}
|
||||||
|
h={height}
|
||||||
|
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
|
||||||
|
slideGap="md"
|
||||||
|
loop
|
||||||
|
align="start"
|
||||||
|
slidesToScroll={mobile ? 1 : 2}
|
||||||
|
>
|
||||||
|
{slides}
|
||||||
|
</Carousel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,207 +1,118 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
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 { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
const theme = useMantineTheme();
|
|
||||||
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack bg="linear-gradient(180deg, #1C6EA4, #124170)" c="white">
|
||||||
<Stack bg={colors["blue-button"]}>
|
<Box w="100%" p="xl" h={{ base: 1800, md: 1100 }}>
|
||||||
<Box w={mobile ? "100%" : "100%"} p={"xl"} h={{ base: 2500, md: 1100 }} >
|
<Center>
|
||||||
<Center>
|
<Paper w="100%" bg="transparent" shadow="md" radius="lg" p="xl">
|
||||||
<Paper w={"100%"}>
|
<Box component="footer">
|
||||||
<Box component="footer" py="xl">
|
<Container size="lg">
|
||||||
<Container size="lg">
|
<Stack gap="xl">
|
||||||
<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>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={"md"}>
|
<Title fz="lg" order={2} fw={700} mb="md" c="white">Komitmen Layanan Kami</Title>
|
||||||
<ActionIcon variant='transparent'>
|
<Stack gap="sm">
|
||||||
<IconBrandFacebook color='white' size={"30"} />
|
{[
|
||||||
</ActionIcon>
|
{ title: "Transparansi", text: "Pengelolaan dana desa dilakukan secara terbuka agar masyarakat dapat memahami dan memantau penggunaan anggaran." },
|
||||||
<ActionIcon variant='transparent'>
|
{ title: "Profesionalisme", text: "Layanan desa diberikan secara cepat, adil, dan profesional demi kepuasan masyarakat." },
|
||||||
<IconBrandInstagram color='white' size={"30"} />
|
{ title: "Partisipasi", text: "Masyarakat dilibatkan aktif dalam pengambilan keputusan demi pembangunan desa yang berhasil." },
|
||||||
</ActionIcon>
|
{ title: "Inovasi", text: "Kami terus berinovasi, termasuk melalui teknologi, agar layanan semakin mudah diakses." },
|
||||||
<ActionIcon variant='transparent'>
|
{ title: "Keadilan", text: "Kebijakan dan program disusun untuk memberi manfaat yang merata bagi seluruh warga." },
|
||||||
<IconBrandTwitter color='white' size={"30"} />
|
{ title: "Pemberdayaan", text: "Masyarakat didukung melalui pelatihan, pendampingan, dan pengembangan usaha lokal." },
|
||||||
</ActionIcon>
|
{ title: "Ramah Lingkungan", text: "Seluruh kegiatan pembangunan memperhatikan keberlanjutan demi menjaga alam dan kesehatan warga." }
|
||||||
<ActionIcon variant='transparent'>
|
].map((item, i) => (
|
||||||
<IconBrandWhatsapp color='white' size={"30"} />
|
<Group key={i} align="flex-start" gap="xs">
|
||||||
</ActionIcon>
|
<Text fz="sm" c="#F3F2EC" fw={700}>{i + 1}. {item.title}:</Text>
|
||||||
</Flex>
|
<Text fz="sm" c="#F3F2EC">{item.text}</Text>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
</Box>
|
</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' }}>"Desa Kuat, Warga Sejahtera!"</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>
|
</Stack>
|
||||||
</Box>
|
</Container>
|
||||||
<Box p={mobile ? 30 : 30}>
|
</Box>
|
||||||
<Stack justify='space-between' gap={"xs"}>
|
</Paper>
|
||||||
<Text fz={"md"} fw={"bold"} c={"white"}>Layanan</Text>
|
</Center>
|
||||||
<Anchor c={"white"} variant='transparent'>
|
|
||||||
<Text fz={"xs"} c={"white"}>Administrasi Kependudukan</Text>
|
<Box py={40}>
|
||||||
</Anchor>
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="xl">
|
||||||
<Anchor c={"white"} variant='transparent'>
|
<Box>
|
||||||
<Text fz={"xs"} c={"white"}>Pelayanan Sosial</Text>
|
<Stack gap="sm">
|
||||||
</Anchor>
|
<Text c="white" fz="md" fw={700}>Tentang Darmasaba</Text>
|
||||||
<Anchor c={"white"} variant='transparent'>
|
<Text fz="xs" c="#F3F2EC">
|
||||||
<Text fz={"xs"} c={"white"}>Pengaduan Masyarakat</Text>
|
Darmasaba adalah desa budaya yang kaya akan tradisi dan nilai-nilai warisan Bali.
|
||||||
</Anchor>
|
</Text>
|
||||||
<Anchor c={"white"} variant='transparent'>
|
<Flex gap="md" mt="sm" c="#F3F2EC">
|
||||||
<Text fz={"xs"} c={"white"}>Informasi Publik</Text>
|
<ActionIcon variant="subtle" color="white"><IconBrandFacebook size={22} /></ActionIcon>
|
||||||
</Anchor>
|
<ActionIcon variant="subtle" color="white"><IconBrandInstagram size={22} /></ActionIcon>
|
||||||
</Stack>
|
<ActionIcon variant="subtle" color="white"><IconBrandTwitter size={22} /></ActionIcon>
|
||||||
</Box>
|
<ActionIcon variant="subtle" color="white"><IconBrandWhatsapp size={22} /></ActionIcon>
|
||||||
<Box p={mobile ? 30 : 30}>
|
</Flex>
|
||||||
<Stack justify='space-between' gap={"xs"}>
|
</Stack>
|
||||||
<Text fz={"md"} fw={"bold"} c={"white"}>Tautan Penting</Text>
|
</Box>
|
||||||
<Anchor c={"white"} variant='transparent'>
|
|
||||||
<Text fz={"xs"} c={"white"}>Portal Badung</Text>
|
<Box>
|
||||||
</Anchor>
|
<Stack gap="xs">
|
||||||
<Anchor c={"white"} variant='transparent'>
|
<Text c="white" fz="md" fw={700}>Layanan Desa</Text>
|
||||||
<Text fz={"xs"} c={"white"}>E-Government</Text>
|
<Anchor c="#F3F2EC" fz="xs">Administrasi Kependudukan</Anchor>
|
||||||
</Anchor>
|
<Anchor c="#F3F2EC" fz="xs">Layanan Sosial</Anchor>
|
||||||
<Anchor c={"white"} variant='transparent'>
|
<Anchor c="#F3F2EC" fz="xs">Pengaduan Masyarakat</Anchor>
|
||||||
<Text fz={"xs"} c={"white"}>Transparansi</Text>
|
<Anchor c="#F3F2EC" fz="xs">Informasi Publik</Anchor>
|
||||||
</Anchor>
|
</Stack>
|
||||||
<Anchor c={"white"} variant='transparent'>
|
</Box>
|
||||||
<Text fz={"xs"} c={"white"}>Unduhan</Text>
|
|
||||||
</Anchor>
|
<Box>
|
||||||
</Stack>
|
<Stack gap="xs">
|
||||||
</Box>
|
<Text c="white" fz="md" fw={700}>Tautan Penting</Text>
|
||||||
<Box p={mobile ? 30 : 30}>
|
<Anchor c="#F3F2EC" fz="xs">Portal Badung</Anchor>
|
||||||
<Stack justify='space-between'>
|
<Anchor c="#F3F2EC" fz="xs">E-Government</Anchor>
|
||||||
<Text fz={"md"} fw={"bold"} c={"white"}>Newsletter</Text>
|
<Anchor c="#F3F2EC" fz="xs">Transparansi</Anchor>
|
||||||
<Text fz={"xs"} c={"white"}>Dapatkan informasi terbaru
|
<Anchor c="#F3F2EC" fz="xs">Unduhan</Anchor>
|
||||||
tentang kegiatan dan program
|
</Stack>
|
||||||
desa</Text>
|
</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
|
<TextInput
|
||||||
placeholder='Alamat email anda'
|
w="70%"
|
||||||
rightSection={<IconAt color={colors["blue-button"]} />}
|
placeholder="Masukkan email Anda"
|
||||||
|
rightSection={<IconAt size={16} />}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
|
||||||
</Box>
|
</Group>
|
||||||
</SimpleGrid>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider py={15} />
|
</SimpleGrid>
|
||||||
<Text ta={"center"} c={"white"} p={20}>
|
|
||||||
© 2024 Desa Darmasaba. Hak Cipta Dilindungi.
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
|
||||||
</>
|
<Divider opacity={0.2} my="md" />
|
||||||
|
<Text ta="center" fz="xs" c="white">© 2025 Desa Darmasaba. Hak cipta dilindungi.</Text>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
import stateNav from "@/state/state-nav";
|
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() {
|
export function NavbarSearch() {
|
||||||
return <Container w={{
|
return (
|
||||||
base: '100%',
|
<Container
|
||||||
md: '80%',
|
w={{ base: "100%", md: "80%" }}
|
||||||
}} fluid py={"xl"}
|
fluid
|
||||||
onMouseLeave={stateNav.clear}
|
py="xl"
|
||||||
|
onMouseLeave={stateNav.clear}
|
||||||
>
|
>
|
||||||
<Stack pt={"xl"}>
|
<Stack pt="xl">
|
||||||
<TextInput
|
<Tooltip label="Type to search across the site" position="bottom-start" withArrow>
|
||||||
autoFocus
|
<TextInput
|
||||||
styles={{
|
autoFocus
|
||||||
input: {
|
size="lg"
|
||||||
borderRadius: "xl",
|
variant="filled"
|
||||||
color: "black",
|
radius="xl"
|
||||||
// backgroundColor: "rgba(255, 255, 255, 0.3)"
|
placeholder="Search anything..."
|
||||||
}
|
leftSection={<IconSearch size={20} />}
|
||||||
}} size="lg" variant="transparent" placeholder="Cari" />
|
/>
|
||||||
</Stack>
|
</Tooltip>
|
||||||
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,78 +2,96 @@
|
|||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import navbarListMenu from "@/con/navbar-list-menu";
|
import navbarListMenu from "@/con/navbar-list-menu";
|
||||||
import stateNav from "@/state/state-nav";
|
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 { IconSquareArrowRight } from "@tabler/icons-react";
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from "framer-motion";
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from "next/navigation";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import { MenuItem } from "../../../../types/menu-item";
|
import { MenuItem } from "../../../../types/menu-item";
|
||||||
import { NavbarMainMenu } from "./NavbarMainMenu";
|
import { NavbarMainMenu } from "./NavbarMainMenu";
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const { item, isSearch, mobileOpen } = useSnapshot(stateNav);
|
const { item, isSearch, mobileOpen } = useSnapshot(stateNav);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box
|
<Paper
|
||||||
|
radius="0"
|
||||||
className="glass2"
|
className="glass2"
|
||||||
w={"100%"}
|
w="100%"
|
||||||
pos={"fixed"}
|
pos="fixed"
|
||||||
top={0}
|
top={0}
|
||||||
style={{
|
style={{ zIndex: 100 }}
|
||||||
zIndex: 100,
|
|
||||||
overflow: "scroll"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<NavbarMainMenu listNavbar={navbarListMenu} />
|
<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" />}
|
{(item || isSearch) && <Box className="glass" />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavbarMobile({ listNavbar }: { listNavbar: MenuItem[] }) {
|
function NavbarMobile({ listNavbar }: { listNavbar: MenuItem[] }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
return <Stack p={"md"} style={{ backgroundColor: "rgba(255, 255, 255, 0.3)" }}>
|
return (
|
||||||
{listNavbar.map((item, k) => {
|
<ScrollArea h="100vh" offsetScrollbars>
|
||||||
return <Stack key={k}>
|
<Stack p="lg" gap="md" style={{ backgroundColor: "rgba(255, 255, 255, 0.25)" }}>
|
||||||
<Group justify="space-between" onClick={() => {
|
{listNavbar.map((item, k) => (
|
||||||
router.push(item.href)
|
<Stack key={k} gap={4}>
|
||||||
stateNav.mobileOpen = false
|
<Group
|
||||||
}}>
|
justify="space-between"
|
||||||
<Text c="dark.9"
|
align="center"
|
||||||
style={{ fontWeight: "bold" }}
|
onClick={() => {
|
||||||
>{item.name}</Text>
|
router.push(item.href);
|
||||||
<IconSquareArrowRight />
|
stateNav.mobileOpen = false;
|
||||||
</Group>
|
}}
|
||||||
{item.children && <NavbarMobile listNavbar={item.children} />}
|
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>
|
||||||
</Stack>
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import colors from "@/con/colors"
|
import colors from "@/con/colors"
|
||||||
import stateNav from "@/state/state-nav"
|
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 { useHover } from "@mantine/hooks"
|
||||||
import { IconSearch, IconUser } from "@tabler/icons-react"
|
import { IconSearch, IconUser } from "@tabler/icons-react"
|
||||||
import { useTransitionRouter } from 'next-view-transitions'
|
import { useTransitionRouter } from 'next-view-transitions'
|
||||||
@@ -12,68 +12,91 @@ import { NavbarSearch } from "./NavBarSearch"
|
|||||||
import { NavbarSubMenu } from "./NavbarSubMenu"
|
import { NavbarSubMenu } from "./NavbarSubMenu"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
|
||||||
export function NavbarMainMenu({ listNavbar }: {
|
export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
||||||
listNavbar: MenuItem[]
|
const { item, isSearch } = useSnapshot(stateNav)
|
||||||
}) {
|
const router = useTransitionRouter()
|
||||||
const { item, isSearch } = useSnapshot(stateNav)
|
const next = useRouter()
|
||||||
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()
|
|
||||||
|
|
||||||
}} >
|
return (
|
||||||
<Image radius={"100"} src={"/assets/images/darmasaba-icon.png"} alt="icon" w={24} h={24} loading="lazy" />
|
<Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
|
||||||
</ActionIcon>
|
<Container pos="relative" w={{ base: '100%', md: '80%' }} fluid>
|
||||||
{listNavbar.map((item, k) => {
|
<Flex align="center" justify="space-between" wrap={{ base: "wrap", md: "nowrap" }}>
|
||||||
return <MenuItemCom key={k} item={item} />
|
<Tooltip label="Go to Homepage" position="bottom" withArrow>
|
||||||
})}
|
<ActionIcon
|
||||||
<ActionIcon variant="transparent" c={isSearch ? 'grey' : colors["blue-button"]}
|
radius="xl"
|
||||||
onClick={() => {
|
variant="transparent"
|
||||||
stateNav.item = null
|
onClick={() => {
|
||||||
stateNav.isSearch = !stateNav.isSearch
|
router.push("/darmasaba")
|
||||||
}}
|
stateNav.clear()
|
||||||
>
|
}}
|
||||||
{/* TODO: add icon search */}
|
>
|
||||||
<IconSearch size={"1.5rem"} />
|
<Image
|
||||||
</ActionIcon>
|
radius="xl"
|
||||||
<ActionIcon onClick={() => {
|
src="/assets/images/darmasaba-icon.png"
|
||||||
next.push("/admin/landing-page/profile/program-inovasi")
|
alt="Darmasaba Logo"
|
||||||
}} color={colors["blue-button"]} radius={'xl'}>
|
w={28}
|
||||||
<IconUser size={24} />
|
h={28}
|
||||||
</ActionIcon>
|
loading="lazy"
|
||||||
</Flex>
|
/>
|
||||||
</Container>
|
</ActionIcon>
|
||||||
{item && <NavbarSubMenu item={item as MenuItem[]} />}
|
</Tooltip>
|
||||||
{isSearch && <NavbarSearch />}
|
{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>
|
</Stack>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenuItemCom({ item, }: { item: MenuItem }) {
|
function MenuItemCom({ item }: { item: MenuItem }) {
|
||||||
const { ref, hovered } = useHover()
|
const { ref, hovered } = useHover()
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter()
|
||||||
|
|
||||||
return <Button
|
return (
|
||||||
ref={ref}
|
<Button
|
||||||
color={hovered ? "grey" : colors["blue-button"]}
|
ref={ref}
|
||||||
onMouseEnter={() => {
|
color={hovered ? "gray" : colors["blue-button"]}
|
||||||
stateNav.item = item.children || null
|
onMouseEnter={() => {
|
||||||
stateNav.isSearch = false
|
stateNav.item = item.children || null
|
||||||
}}
|
stateNav.isSearch = false
|
||||||
variant="transparent"
|
}}
|
||||||
onClick={() => {
|
variant="subtle"
|
||||||
router.push(item.href)
|
radius="xl"
|
||||||
stateNav.clear()
|
onClick={() => {
|
||||||
}}
|
router.push(item.href)
|
||||||
>{item.name}</Button>
|
stateNav.clear()
|
||||||
}
|
}}
|
||||||
|
fw={500}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import stateNav from "@/state/state-nav";
|
import stateNav from "@/state/state-nav";
|
||||||
import { Button, Container, Stack } from "@mantine/core";
|
import { Button, Container, Stack, Text } from "@mantine/core";
|
||||||
import _ from "lodash";
|
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
|
import { IconArrowRight } from "@tabler/icons-react";
|
||||||
import { MenuItem } from "../../../../types/menu-item";
|
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 }) {
|
export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={_.uniqueId()}
|
key={Math.random().toString(36).slice(2)}
|
||||||
initial={{ opacity: 0.5 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1 }}
|
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||||
>
|
>
|
||||||
<Container
|
<Container
|
||||||
key={stateNav.item?.[0]?.id}
|
key={stateNav.item?.[0]?.id}
|
||||||
@@ -22,32 +24,50 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
|
|||||||
stateNav.item = null;
|
stateNav.item = null;
|
||||||
stateNav.isSearch = false;
|
stateNav.isSearch = false;
|
||||||
}}
|
}}
|
||||||
w={{
|
w={{ base: "100%", md: "80%" }}
|
||||||
base: "100%",
|
|
||||||
md: "80%",
|
|
||||||
}}
|
|
||||||
fluid
|
fluid
|
||||||
|
py="xl"
|
||||||
>
|
>
|
||||||
<Stack gap={0} align="start" py={"xl"}>
|
{item && item.length > 0 ? (
|
||||||
{item &&
|
<Stack gap="xs" align="stretch">
|
||||||
item.map((item, k) => {
|
{item.map((link, index) => (
|
||||||
return (
|
<Button
|
||||||
<Button
|
key={index}
|
||||||
key={k}
|
variant="subtle"
|
||||||
fz={"lg"}
|
justify="space-between"
|
||||||
color="dark.9"
|
size="lg"
|
||||||
variant="transparent"
|
radius="md"
|
||||||
onClick={() => {
|
color="gray.0"
|
||||||
router.push(item.href)
|
onClick={() => {
|
||||||
stateNav.item = null
|
router.push(link.href);
|
||||||
stateNav.isSearch = false
|
stateNav.item = null;
|
||||||
}}
|
stateNav.isSearch = false;
|
||||||
>
|
}}
|
||||||
{item.name}
|
rightSection={<IconArrowRight size={18} />}
|
||||||
</Button>
|
styles={(theme) => ({
|
||||||
);
|
root: {
|
||||||
})}
|
background: "transparent",
|
||||||
</Stack>
|
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>
|
</Container>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,121 +1,136 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
|
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors'
|
||||||
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, Loader, SimpleGrid, Stack, Text } from '@mantine/core'
|
||||||
import { IconDownload } from '@tabler/icons-react';
|
import { IconDownload } from '@tabler/icons-react'
|
||||||
import Link from 'next/link';
|
import Link from 'next/link'
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils'
|
||||||
|
|
||||||
|
|
||||||
function Apbdes() {
|
function Apbdes() {
|
||||||
const state = useProxy(apbdes);
|
const state = useProxy(apbdes)
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
const textHeading = {
|
const textHeading = {
|
||||||
title: "APBDes",
|
title: 'APBDes',
|
||||||
des: "Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab"
|
des: 'Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola desa yang bersih, terbuka, dan bertanggung jawab.'
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true)
|
||||||
await state.findMany.load();
|
await state.findMany.load()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadData();
|
loadData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const data = (state.findMany.data || []).slice(0, 3);
|
const data = (state.findMany.data || []).slice(0, 3)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
||||||
<Stack p={"sm"} gap={"4rem"} bg={colors.Bg}>
|
<Box w={{ base: '100%', sm: '70%' }}>
|
||||||
<Box
|
<Stack gap="sm">
|
||||||
w={{
|
<Text fz={{ base: '2.4rem', sm: '4rem' }} fw="bold" lh={1.2}>
|
||||||
base: '100%',
|
{textHeading.title}
|
||||||
sm: '60%',
|
</Text>
|
||||||
}}
|
<Text fz={{ base: '1rem', sm: '1.3rem' }} c="dimmed">
|
||||||
>
|
{textHeading.des}
|
||||||
<Stack gap={0}>
|
</Text>
|
||||||
<Text fz={"4.4rem"} fw={"bold"}>
|
</Stack>
|
||||||
{textHeading.title}
|
</Box>
|
||||||
</Text>
|
|
||||||
<Text fz={"1.4rem"}>
|
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="lg">
|
||||||
{textHeading.des}
|
{loading ? (
|
||||||
</Text>
|
<Center mih={200}>
|
||||||
</Stack>
|
<Loader size="lg" color="blue" />
|
||||||
</Box>
|
</Center>
|
||||||
<SimpleGrid
|
) : data.length === 0 ? (
|
||||||
cols={{
|
<Center mih={200}>
|
||||||
base: 1,
|
<Stack align="center" gap="xs">
|
||||||
sm: 3,
|
<Text fz="lg" c="dimmed">
|
||||||
}}
|
Belum ada data APBDes yang tersedia
|
||||||
>
|
</Text>
|
||||||
{loading ? (
|
<Text fz="sm" c="dimmed">
|
||||||
<Center>
|
Data akan ditampilkan di sini setelah diunggah
|
||||||
<Text fz={"2.4rem"}>Memuat Data...</Text>
|
</Text>
|
||||||
</Center>
|
</Stack>
|
||||||
) : (
|
</Center>
|
||||||
data.map((v, k) => {
|
) : (
|
||||||
return (
|
data.map((v, k) => (
|
||||||
<BackgroundImage
|
<BackgroundImage
|
||||||
key={k}
|
key={k}
|
||||||
src={v.image?.link || ''}
|
src={v.image?.link || ''}
|
||||||
h={350}
|
h={360}
|
||||||
radius={16}
|
radius="xl"
|
||||||
pos={"relative"}
|
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
|
{v.name}
|
||||||
style={{
|
</Text>
|
||||||
borderRadius: 16,
|
<Text
|
||||||
zIndex: 0
|
fw="bold"
|
||||||
}}
|
c="white"
|
||||||
pos={"absolute"}
|
fz="3rem"
|
||||||
w={"100%"}
|
ta="center"
|
||||||
h={"100%"}
|
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
|
||||||
bg={colors.trans.dark[2]}
|
>
|
||||||
/>
|
{v.jumlah}
|
||||||
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
|
</Text>
|
||||||
<Box p={"lg"}>
|
<Group justify="center">
|
||||||
<Text
|
<ActionIcon
|
||||||
c={"white"}
|
component={Link}
|
||||||
size={"1.5rem"}
|
href={v.file?.link || ''}
|
||||||
style={{
|
radius="xl"
|
||||||
textAlign: "center",
|
size="lg"
|
||||||
}}>{v.name}</Text>
|
variant="gradient"
|
||||||
</Box>
|
gradient={{ from: '#1C6EA4', to: '#1C6EA4' }}
|
||||||
<Text
|
>
|
||||||
fw={"bold"}
|
<Flex align="center" gap="xs" px="md" py={6}>
|
||||||
c={"white"}
|
<IconDownload size={18} color="white" />
|
||||||
size={"3.5rem"}
|
</Flex>
|
||||||
style={{
|
</ActionIcon>
|
||||||
textAlign: "center",
|
</Group>
|
||||||
}}>{v.jumlah}</Text>
|
</Stack>
|
||||||
<Group justify="center">
|
</BackgroundImage>
|
||||||
<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} />
|
</SimpleGrid>
|
||||||
<Text fz={"sm"} c={"white"}>Download</Text>
|
|
||||||
</Flex>
|
<Group pb={80} justify="center">
|
||||||
</ActionIcon>
|
<Button
|
||||||
</Group>
|
component={Link}
|
||||||
</Stack>
|
href="/darmasaba/apbdes"
|
||||||
</BackgroundImage>
|
radius="xl"
|
||||||
)
|
size="lg"
|
||||||
})
|
variant="gradient"
|
||||||
)}
|
gradient={{ from: "#26667F", to: "#124170" }}
|
||||||
</SimpleGrid>
|
>
|
||||||
<Group pb={80} justify='center'>
|
Lihat Semua Data
|
||||||
<Button component={Link} href="/darmasaba/apbdes" radius={"lg"} bg={colors["blue-button"]} fz={"h4"}>Lihat Semua</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Apbdes;
|
export default Apbdes
|
||||||
|
|||||||
@@ -160,7 +160,12 @@ function Kepuasan() {
|
|||||||
</Center>
|
</Center>
|
||||||
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
<Text 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}>
|
<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>
|
</Center>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={"xl"}>
|
<Box px={"xl"}>
|
||||||
|
|||||||
@@ -1,64 +1,81 @@
|
|||||||
import profileLandingPageState from "@/app/admin/(dashboard)/_state/landing-page/profile";
|
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 { useShallowEffect } from "@mantine/hooks";
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from "framer-motion";
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from "next-view-transitions";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { IconPhotoOff } from "@tabler/icons-react";
|
||||||
|
|
||||||
type ProgramInovasiItem = Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>;
|
type ProgramInovasiItem = Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>;
|
||||||
|
|
||||||
function ModuleItem({ data }: { data: ProgramInovasiItem }) {
|
function ModuleItem({ data }: { data: ProgramInovasiItem }) {
|
||||||
const router = useTransitionRouter();
|
const router = useTransitionRouter();
|
||||||
return (
|
return (
|
||||||
<Paper
|
<motion.div whileHover={{ scale: 1.04 }}>
|
||||||
onClick={() => {
|
<Tooltip label={`Lihat ${data.name}`} withArrow>
|
||||||
router.push(`/${data.name}`);
|
<Paper
|
||||||
}}
|
onClick={() => router.push(`/${data.name}`)}
|
||||||
p={"md"}
|
p="xl"
|
||||||
bg={"white"}
|
radius="2xl"
|
||||||
radius={"32"}
|
bg="white"
|
||||||
pos={"relative"}
|
className="cursor-pointer transition-all shadow-md hover:shadow-xl"
|
||||||
>
|
|
||||||
<Center h={"100%"}>
|
|
||||||
<motion.div
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
>
|
>
|
||||||
{data.image?.link ? (
|
<Center h={180}>
|
||||||
<Image src={data.image.link} alt="icon"
|
{data.image?.link ? (
|
||||||
fit="contain"
|
<Image
|
||||||
sizes="100%"
|
src={data.image.link}
|
||||||
loading="lazy"
|
alt={data.name}
|
||||||
style={{
|
fit="contain"
|
||||||
objectFit: "contain",
|
radius="lg"
|
||||||
objectPosition: "center"
|
loading="lazy"
|
||||||
}}
|
style={{ objectFit: "contain", objectPosition: "center" }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text>
|
<Stack align="center" gap="xs">
|
||||||
-
|
<IconPhotoOff size={40} stroke={1.5} />
|
||||||
</Text>
|
<Text size="sm" c="dimmed">
|
||||||
)}
|
Belum ada gambar
|
||||||
</motion.div>
|
</Text>
|
||||||
</Center>
|
</Stack>
|
||||||
</Paper>
|
)}
|
||||||
|
</Center>
|
||||||
|
<Box mt="md">
|
||||||
|
<Text fw={600} ta="center" size="lg" c="black">
|
||||||
|
{data.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Tooltip>
|
||||||
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModuleView() {
|
function ModuleView() {
|
||||||
const listImageState = useProxy(profileLandingPageState.programInovasi)
|
const listImageState = useProxy(profileLandingPageState.programInovasi);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
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 (
|
return (
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
|
||||||
cols={{
|
|
||||||
base: 2,
|
|
||||||
md: 3,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{listImageState.findMany.data?.map((item) => (
|
{listImageState.findMany.data?.map((item) => (
|
||||||
<ModuleItem key={item.id} data={item} />
|
<ModuleItem key={item.id} data={item} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
import colors from '@/con/colors';
|
import { Box, Card, Image, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { Box, Card, Image, Stack, Text } from '@mantine/core';
|
import { IconUserCircle } from '@tabler/icons-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
interface ProfileViewProps {
|
interface ProfileViewProps {
|
||||||
data: Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null;
|
data: Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProfileView({ data }: ProfileViewProps) {
|
export default function ProfileView({ data }: ProfileViewProps) {
|
||||||
if (!data) {
|
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 (
|
return (
|
||||||
@@ -17,42 +30,38 @@ function ProfileView({ data }: ProfileViewProps) {
|
|||||||
justify="end"
|
justify="end"
|
||||||
align="end"
|
align="end"
|
||||||
pos="relative"
|
pos="relative"
|
||||||
w={{
|
w={{ base: '100%', md: '40%' }}
|
||||||
base: "100%",
|
|
||||||
md: "40%",
|
|
||||||
}}
|
|
||||||
px="xl"
|
px="xl"
|
||||||
>
|
>
|
||||||
{data.image?.link ? (
|
{data.image?.link ? (
|
||||||
<Image
|
<Image
|
||||||
src={data.image.link}
|
src={data.image.link}
|
||||||
alt={data.name || "Profile image"}
|
alt={data.name || 'Foto profil'}
|
||||||
sizes="100%"
|
fit="cover"
|
||||||
fit="contain"
|
radius="lg"
|
||||||
/>
|
/>
|
||||||
): (
|
) : (
|
||||||
<Text>
|
<Stack align="center" gap="xs" w="100%" py="xl">
|
||||||
-
|
<IconUserCircle size={96} stroke={1.5} />
|
||||||
</Text>
|
<Text c="dimmed" fz="sm">
|
||||||
|
Belum ada foto
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box pos="absolute" bottom={0} w="100%" p={{ base: 'xs', md: 'md' }}>
|
||||||
pos="absolute"
|
|
||||||
bottom={0}
|
|
||||||
p={{
|
|
||||||
base: "xs",
|
|
||||||
md: "md",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Card
|
<Card
|
||||||
px="lg"
|
px="lg"
|
||||||
radius="32"
|
radius="2xl"
|
||||||
|
withBorder
|
||||||
className="glass3"
|
className="glass3"
|
||||||
style={{
|
style={{ border: '1px solid rgba(255,255,255,0.15)' }}
|
||||||
border: `1px solid white`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text>{data.position}</Text>
|
<Tooltip label="Jabatan Resmi" withArrow>
|
||||||
<Text c={colors["blue-button"]} fw="bolder" fz="1rem">
|
<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}
|
{data.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -60,5 +69,3 @@ function ProfileView({ data }: ProfileViewProps) {
|
|||||||
</Stack>
|
</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 { Prisma } from "@prisma/client";
|
||||||
import { useTransitionRouter } from "next-view-transitions";
|
import { useTransitionRouter } from "next-view-transitions";
|
||||||
|
import { IconBrandInstagram, IconBrandFacebook, IconBrandTwitter, IconWorld } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
function SosmedView({
|
||||||
|
data,
|
||||||
function SosmedView({data} : {data : Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]}) {
|
}: {
|
||||||
|
data: Prisma.MediaSosialGetPayload<{ include: { image: true } }>[];
|
||||||
|
}) {
|
||||||
const router = useTransitionRouter();
|
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 (
|
return (
|
||||||
<Flex gap={"md"} justify={"center"} align={"center"}>
|
<Flex gap="lg" justify="center" align="center" wrap="wrap">
|
||||||
{data?.map((item, k) => {
|
{data && data.length > 0 ? (
|
||||||
return (
|
data.map((item, k) => (
|
||||||
|
<Tooltip
|
||||||
|
key={k}
|
||||||
|
label={item.name || "Tautan Sosial"}
|
||||||
|
withArrow
|
||||||
|
position="top"
|
||||||
|
transitionProps={{ transition: "pop", duration: 150 }}
|
||||||
|
>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="transparent"
|
variant="light"
|
||||||
key={k}
|
radius="xl"
|
||||||
w={32}
|
size="xl"
|
||||||
h={32}
|
onClick={() => item.iconUrl && router.push(item.iconUrl)}
|
||||||
pos={"relative"}
|
style={{
|
||||||
onClick={() => {
|
transition: "all 0.3s ease",
|
||||||
router.push(item.iconUrl || "");
|
boxShadow: "0 0 12px rgba(28, 110, 164, 0.6)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.image?.link ? (
|
{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>
|
fallbackIcon(item.name)
|
||||||
none
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</ActionIcon>
|
</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>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,79 +11,77 @@ import {
|
|||||||
Image,
|
Image,
|
||||||
Paper,
|
Paper,
|
||||||
Stack,
|
Stack,
|
||||||
Text
|
Text,
|
||||||
|
Center,
|
||||||
|
Tooltip,
|
||||||
|
Badge,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { IconCalendarTime, IconInfoCircle } from "@tabler/icons-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ModuleView from "./ModuleView";
|
import ModuleView from "./ModuleView";
|
||||||
import SosmedView from "./SosmedView";
|
import SosmedView from "./SosmedView";
|
||||||
import ProfileView from "./ProfileView";
|
import ProfileView from "./ProfileView";
|
||||||
|
|
||||||
const getDayOfWeek = () => {
|
const getDayOfWeek = () => {
|
||||||
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
|
const days = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"];
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
return days[today.getDay()];
|
return days[today.getDay()];
|
||||||
}
|
};
|
||||||
|
|
||||||
const getCurrentTime = () => {
|
const getCurrentTime = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const hours = String(now.getHours()).padStart(2, '0');
|
const hours = String(now.getHours()).padStart(2, "0");
|
||||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
||||||
return `${hours}:${minutes}`;
|
return `${hours}:${minutes}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
const isWorkingHours = (currentTime: string): boolean => {
|
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 compareTimes = (time1: string, time2: string) => {
|
||||||
const [hour1, minute1] = time1.split(':').map(Number);
|
const [hour1, minute1] = time1.split(":").map(Number);
|
||||||
const [hour2, minute2] = time2.split(':').map(Number);
|
const [hour2, minute2] = time2.split(":").map(Number);
|
||||||
|
|
||||||
if (hour1 < hour2) return true;
|
if (hour1 < hour2) return true;
|
||||||
if (hour1 > hour2) return false;
|
if (hour1 > hour2) return false;
|
||||||
return minute1 <= minute2;
|
return minute1 <= minute2;
|
||||||
};
|
};
|
||||||
return compareTimes(currentTime, closeTime) && !compareTimes(currentTime, openTime);
|
return compareTimes(currentTime, closeTime) && !compareTimes(currentTime, openTime);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getWorkStatus = (day: string, currentTime: string): { status: string; message: string } => {
|
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)) {
|
if (!workingDays.includes(day)) {
|
||||||
return {
|
return { status: "Tutup", message: "Libur Akhir Pekan" };
|
||||||
status: 'Tutup',
|
|
||||||
message: 'Sabtu - Minggu'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const isOpen = isWorkingHours(currentTime)
|
const isOpen = isWorkingHours(currentTime);
|
||||||
return isOpen ? { status: 'Buka', message: '08:00 - 16:00' } : { status: 'Tutup', message: '08:00 - 16:00' };
|
return isOpen
|
||||||
}
|
? { status: "Buka", message: "08:00 - 16:00" }
|
||||||
|
: { status: "Tutup", message: "08:00 - 16:00" };
|
||||||
|
};
|
||||||
|
|
||||||
function LandingPage() {
|
function LandingPage() {
|
||||||
const [socialMedia, setSocialMedia] = useState<Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]>([]);
|
const [socialMedia, setSocialMedia] = useState<
|
||||||
const [profile, setProfile] = useState<Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null>(null);
|
Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]
|
||||||
|
>([]);
|
||||||
|
const [profile, setProfile] = useState<
|
||||||
|
Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null
|
||||||
|
>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSocialMedia = async () => {
|
const fetchSocialMedia = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/landingpage/mediasosial/findMany');
|
const response = await fetch("/api/landingpage/mediasosial/findMany");
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
// Ensure the data is an array before setting it
|
|
||||||
if (Array.isArray(result.data)) {
|
if (Array.isArray(result.data)) {
|
||||||
setSocialMedia(result.data);
|
setSocialMedia(result.data);
|
||||||
} else if (Array.isArray(result)) {
|
} else if (Array.isArray(result)) {
|
||||||
// In case the API returns the array directly
|
|
||||||
setSocialMedia(result);
|
setSocialMedia(result);
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected API response format:', result);
|
|
||||||
setSocialMedia([]);
|
setSocialMedia([]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error fetching social media:', error);
|
setSocialMedia([]);
|
||||||
setSocialMedia([]); // Ensure we always have an array
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -92,22 +90,22 @@ function LandingPage() {
|
|||||||
const fetchProfile = async () => {
|
const fetchProfile = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/landingpage/pejabatdesa/edit`);
|
const response = await fetch(`/api/landingpage/pejabatdesa/edit`);
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
setProfile(result.data || null); // Handle single object response
|
setProfile(result.data || null);
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error fetching profile:', error);
|
|
||||||
setProfile(null);
|
setProfile(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchSocialMedia();
|
fetchSocialMedia();
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>
|
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>({
|
||||||
({ status: '', message: '' });
|
status: "",
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateWorkStatus = () => {
|
const updateWorkStatus = () => {
|
||||||
@@ -115,212 +113,110 @@ function LandingPage() {
|
|||||||
const time = getCurrentTime();
|
const time = getCurrentTime();
|
||||||
const status = getWorkStatus(day, time);
|
const status = getWorkStatus(day, time);
|
||||||
setWorkStatus(status);
|
setWorkStatus(status);
|
||||||
}
|
};
|
||||||
updateWorkStatus();
|
updateWorkStatus();
|
||||||
const intervalId = setInterval(updateWorkStatus, 60 * 1000);
|
const intervalId = setInterval(updateWorkStatus, 60 * 1000);
|
||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack bg={colors["Bg"]}>
|
<Stack bg={colors.Bg} p="md" gap="xl">
|
||||||
<Flex
|
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
|
||||||
gap={"md"}
|
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
|
||||||
wrap={{
|
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
||||||
base: "wrap",
|
<Stack gap="xl">
|
||||||
md: "nowrap",
|
<Flex gap="md" wrap="wrap">
|
||||||
}}
|
<Grid w="100%">
|
||||||
>
|
<Grid.Col span={{ base: 3, sm: 2 }}>
|
||||||
<Stack
|
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
||||||
gap={"xl"}
|
<Image src="/darmasaba-icon.png" alt="Logo Darmasaba" fit="contain" />
|
||||||
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%"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
<Grid.Col span={{ base: 9, sm: 10 }}>
|
||||||
<Grid.Col span={{
|
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
|
||||||
base: 3,
|
<Image src="/pudak-icon.png" alt="Logo Pudak" fit="contain" />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={{
|
<Grid.Col span={12}>
|
||||||
base: 12,
|
|
||||||
lg: 12,
|
|
||||||
md: 12,
|
|
||||||
}}>
|
|
||||||
<Paper
|
<Paper
|
||||||
pos={"relative"}
|
|
||||||
bg={colors["blue-button"]}
|
bg={colors["blue-button"]}
|
||||||
p={10}
|
p="md"
|
||||||
w={{ base: "100%", sm: "auto", md: "auto" }}
|
radius="lg"
|
||||||
flex={{ base: "1", sm: "1", md: "1" }}
|
shadow="md"
|
||||||
|
style={{ position: "relative", overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid gutter="md">
|
||||||
>
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
<GridCol span={{
|
<Stack gap="xs">
|
||||||
base: 12,
|
<Flex align="center" gap="xs">
|
||||||
lg: 6,
|
<IconCalendarTime size={16} color="white" />
|
||||||
md: 6,
|
<Text c="white" fz="sm">Jam Operasional</Text>
|
||||||
}}>
|
</Flex>
|
||||||
<Box>
|
<Paper p="sm" radius="md" bg="white">
|
||||||
<Text c={colors["white-1"]} fz={"sm"}>
|
<Tooltip label="Status saat ini berdasarkan jam operasional kantor">
|
||||||
Jadwal Kerja
|
<Badge
|
||||||
</Text>
|
color={workStatus.status === "Buka" ? "green" : "red"}
|
||||||
<Paper
|
radius="sm"
|
||||||
w={{ base: "100%", sm: "100%", md: "auto" }}
|
variant="filled"
|
||||||
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"]}
|
|
||||||
>
|
>
|
||||||
|
{workStatus.status}
|
||||||
<Box>
|
</Badge>
|
||||||
<Text fw="bold" fz="sm" c={workStatus.status === 'Buka' ? "black" : "red"}>
|
</Tooltip>
|
||||||
{workStatus.status}
|
<Text fw="bold" fz="lg">{workStatus.message}</Text>
|
||||||
</Text>
|
|
||||||
<Text fw="bold" fz="lg" >
|
|
||||||
{workStatus.message}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
</Flex>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Stack>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
{/* Edit yang ini */}
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
<GridCol span={{ base: 12, lg: 6, md: 6 }}>
|
<Stack gap="xs">
|
||||||
<Box>
|
<Flex align="center" gap="xs">
|
||||||
<Text c={colors["white-1"]} fz={"sm"}>
|
<IconInfoCircle size={16} color="white" />
|
||||||
{new Intl.DateTimeFormat('id-ID', {
|
<Text c="white" fz="sm">Hari Ini</Text>
|
||||||
weekday: 'long',
|
</Flex>
|
||||||
year: 'numeric',
|
<Paper p="sm" radius="md" bg="white">
|
||||||
month: 'long',
|
<Text fz="sm">Status Kantor</Text>
|
||||||
day: 'numeric'
|
<Text fw="bold" fz="lg">
|
||||||
}).format(new Date())}
|
{workStatus.status === "Buka" ? "Sedang Beroperasi" : "Tidak Beroperasi"}
|
||||||
</Text>
|
|
||||||
<Paper bg={colors["white-1"]} p={10}>
|
|
||||||
<Text fz="sm" >
|
|
||||||
Status
|
|
||||||
</Text>
|
|
||||||
<Text fw="bold" fz="lg" >
|
|
||||||
{workStatus.status === 'Buka' ? 'Operasional' : 'Tutup'}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
</Stack>
|
||||||
</Box>
|
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<ModuleView />
|
<ModuleView />
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Skeleton height={32} width="100%" />
|
<Skeleton height={32} width="100%" />
|
||||||
) : socialMedia.length > 0 ? (
|
) : socialMedia.length > 0 ? (
|
||||||
<SosmedView data={socialMedia} />
|
<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"
|
<Text ta="center" c={colors.trans.dark[2]}>
|
||||||
}} >Sampaikan saran dan masukan guna kemajuan dalam pembangunan. Semua lebih mudah melalui fitur interaktif</Text>
|
Bagikan ide, kritik, atau saran Anda untuk mendukung pembangunan desa.
|
||||||
|
Semua lebih mudah dengan fitur interaktif yang kami sediakan.
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Skeleton height={32} width="100%" />
|
<Skeleton height={300} width="100%" radius="lg" />
|
||||||
) : profile ? (
|
) : profile ? (
|
||||||
<ProfileView data={profile} />
|
<ProfileView data={profile} />
|
||||||
) : (
|
) : (
|
||||||
<div>No profile available</div>
|
<Center w="100%">
|
||||||
|
<Text c="dimmed">Informasi profil belum tersedia</Text>
|
||||||
|
</Center>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack >
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,33 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client';
|
'use client';
|
||||||
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
||||||
import colors from "@/con/colors";
|
import { Stack, Box, Container, Button, Text, Loader, Paper } from "@mantine/core";
|
||||||
import { Stack, Box, Container, Button, Text } from "@mantine/core";
|
import { IconAward, IconArrowRight } from "@tabler/icons-react";
|
||||||
import { useTransitionRouter } from 'next-view-transitions'
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
function Penghargaan() {
|
function Penghargaan() {
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter();
|
||||||
const state = useProxy(penghargaanState)
|
const state = useProxy(penghargaanState);
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
await state.findMany.load()
|
await state.findMany.load();
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error)
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
const data = state.findMany.data?.slice(0, 3);
|
||||||
|
|
||||||
const data = state.findMany.data?.slice(0, 3)
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} h={720}>
|
<Stack pos="relative" h={720}>
|
||||||
<video
|
<video
|
||||||
width="320"
|
width="320"
|
||||||
height="240"
|
height="240"
|
||||||
@@ -51,47 +50,68 @@ function Penghargaan() {
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 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}>
|
<Container w={{ base: "100%", md: "80%" }} h={720} p="xl">
|
||||||
<Stack justify="center" align="center">
|
<Stack justify="center" align="center" gap="xl" h="100%">
|
||||||
<Text
|
<Text
|
||||||
style={{
|
fw={900}
|
||||||
textAlign: "center",
|
fz={{ base: "2rem", md: "2.8rem" }}
|
||||||
}}
|
ta="center"
|
||||||
fw={"bold"}
|
variant="gradient"
|
||||||
fz={"2.4rem"}
|
gradient={{ from: "cyan", to: "blue", deg: 60 }}
|
||||||
c={"white"}
|
|
||||||
>
|
>
|
||||||
Penghargaan
|
Penghargaan & Prestasi Desa
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Text
|
<Stack align="center" gap="sm">
|
||||||
style={{
|
<Loader color="blue" size="lg" />
|
||||||
textAlign: "center",
|
<Text c="gray.3" fz="lg">Sedang memuat data penghargaan...</Text>
|
||||||
}}
|
</Stack>
|
||||||
fw={"bold"}
|
) : data && data.length > 0 ? (
|
||||||
fz={"2.4rem"}
|
<Stack gap="md" w="100%" maw={600}>
|
||||||
c={"white"}
|
{data.map((v, k) => (
|
||||||
>
|
<Paper
|
||||||
Memuat Data...
|
key={k}
|
||||||
</Text>
|
withBorder
|
||||||
) : (
|
radius="xl"
|
||||||
data?.map((v, k) => {
|
p="lg"
|
||||||
return (
|
shadow="xl"
|
||||||
<Box key={k}>
|
style={{
|
||||||
<Stack align="center" gap={0}>
|
background: "rgba(255,255,255,0.07)",
|
||||||
<Text fz={"1.4rem"} c={"white"}>
|
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}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</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>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</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 react-hooks/exhaustive-deps */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
"use client";
|
"use client";
|
||||||
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
|
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
@@ -9,118 +8,128 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
|
Loader,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text
|
Text,
|
||||||
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { IconArrowRight, IconInfoCircle } from "@tabler/icons-react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { useTransitionRouter } from "next-view-transitions";
|
import { useTransitionRouter } from "next-view-transitions";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const textHeading = {
|
const textHeading = {
|
||||||
title: "Potensi",
|
title: "Potensi Desa",
|
||||||
des: "Tidak hanya untuk warga desa, fitur ini juga dapat digunakan oleh pemerintah desa untuk merencanakan program pengembangan berbasis potensi lokal",
|
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() {
|
function Potensi() {
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
const state = useProxy(potensiDesaState.potensiDesa)
|
const state = useProxy(potensiDesaState.potensiDesa);
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.findMany.load()
|
await state.findMany.load();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error("Gagal memuat data:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const data = (state.findMany.data || []).slice(0, 4);
|
const data = (state.findMany.data || []).slice(0, 4);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p={"sm"} gap={"4rem"}>
|
<Stack p="sm" gap="4rem">
|
||||||
<Box
|
<Box w={{ base: "100%", sm: "60%" }}>
|
||||||
w={{
|
<Text fz="4.4rem" fw={700} c={colors["blue-button"]}>
|
||||||
base: "100%",
|
{textHeading.title}
|
||||||
sm: "60%",
|
</Text>
|
||||||
}}
|
<Text size="1.4rem" c="black">
|
||||||
>
|
{textHeading.des}
|
||||||
<Text fz={"4.4rem"}>{textHeading.title}</Text>
|
</Text>
|
||||||
<Text size={"1.4rem"}>{textHeading.des}</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
{loading ? (
|
||||||
base: 1,
|
<Stack align="center" justify="center" h={300}>
|
||||||
sm: 2,
|
<Loader size="lg" color={colors["blue-button"]} />
|
||||||
}}
|
<Text c="gray.4">Sedang memuat potensi desa...</Text>
|
||||||
>
|
</Stack>
|
||||||
{_.take(data, 4).map((v, k) => (
|
) : data.length === 0 ? (
|
||||||
<motion.div
|
<Stack align="center" justify="center" h={300} gap="xs">
|
||||||
key={k}
|
<IconInfoCircle size={48} color={colors["blue-button"]} />
|
||||||
whileHover={{ scale: 1.01 }}
|
<Text fw={600} c="gray.3">
|
||||||
whileTap={{ scale: 0.8 }}
|
Belum ada potensi tersedia
|
||||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
</Text>
|
||||||
>
|
<Text size="sm" c="gray.5">
|
||||||
<BackgroundImage
|
Silakan cek kembali nanti untuk pembaruan terbaru.
|
||||||
src={v.image?.link}
|
</Text>
|
||||||
h={320}
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<SimpleGrid cols={{ base: 1, sm: 2 }}>
|
||||||
|
{_.take(data, 4).map((v, k) => (
|
||||||
|
<motion.div
|
||||||
key={k}
|
key={k}
|
||||||
radius={16}
|
whileHover={{ scale: 1.02 }}
|
||||||
pos={"relative"}
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<Box
|
<BackgroundImage src={v.image?.link} h={320} radius={20} pos="relative">
|
||||||
style={{
|
<Box
|
||||||
borderRadius: 16,
|
pos="absolute"
|
||||||
zIndex: 0,
|
w="100%"
|
||||||
}}
|
h="100%"
|
||||||
pos={"absolute"}
|
bg={colors.trans.dark[2]}
|
||||||
w={"100%"}
|
style={{ borderRadius: 20, zIndex: 0 }}
|
||||||
h={"100%"}
|
/>
|
||||||
bg={colors.trans.dark[2]}
|
<Stack
|
||||||
/>
|
justify="end"
|
||||||
<Stack
|
h="100%"
|
||||||
justify="end"
|
p="md"
|
||||||
h={"100%"}
|
align="start"
|
||||||
p={"md"}
|
pos="absolute"
|
||||||
align="start"
|
style={{ zIndex: 1 }}
|
||||||
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"]}
|
|
||||||
>
|
>
|
||||||
{v.deskripsi}
|
<Tooltip label={v.name} position="top-start">
|
||||||
</Text>
|
<Text fw={700} c="white" size="2.2rem" truncate>
|
||||||
</Stack>
|
{v.name}
|
||||||
</BackgroundImage>
|
</Text>
|
||||||
</motion.div>
|
</Tooltip>
|
||||||
))}
|
<Text lineClamp={2} c="gray.2" size="sm">
|
||||||
</SimpleGrid>
|
{v.deskripsi}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</BackgroundImage>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
)}
|
||||||
|
|
||||||
<Stack align="center">
|
<Stack align="center">
|
||||||
<Group>
|
<Group>
|
||||||
<Button onClick={()=> router.push("/darmasaba/desa/potensi")} color={colors["blue-button"]} variant="outline" radius={100} size="md">
|
<Button
|
||||||
Selengkapnya
|
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>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,104 +2,124 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import prestasiState from "@/app/admin/(dashboard)/_state/landing-page/prestasi-desa";
|
import prestasiState from "@/app/admin/(dashboard)/_state/landing-page/prestasi-desa";
|
||||||
import colors from "@/con/colors";
|
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 { useProxy } from "valtio/utils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { IconTrophy } from "@tabler/icons-react";
|
||||||
|
|
||||||
function Prestasi() {
|
function Prestasi() {
|
||||||
const state = useProxy(prestasiState.prestasiDesa);
|
const state = useProxy(prestasiState.prestasiDesa);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
prestasiState.kategoriPrestasi.findMany.load()
|
prestasiState.kategoriPrestasi.findMany.load();
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.findMany.load();
|
await state.findMany.load();
|
||||||
} catch (error) {
|
} finally {
|
||||||
console.error('Error loading data:', error);
|
setLoading(false);
|
||||||
} finally {
|
}
|
||||||
setLoading(false);
|
};
|
||||||
}
|
loadData();
|
||||||
}
|
}, []);
|
||||||
loadData();
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const data = (state.findMany.data || []).slice(0, 3);
|
const data = (state.findMany.data || []).slice(0, 3);
|
||||||
return (
|
|
||||||
<>
|
return (
|
||||||
<Stack p={"sm"}>
|
<Stack p="sm" bg="linear-gradient(180deg, #ffffff 0%, #f8fbff 100%)">
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
<Container w={{ base: "100%", md: "80%" }} p="xl">
|
||||||
<Text ta={"center"} fz={"3.4rem"}>Prestasi Desa</Text>
|
<Stack align="center" gap="sm">
|
||||||
<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>
|
<Group gap="xs">
|
||||||
<Center pt={20}>
|
<IconTrophy size={36} color={colors["blue-button"]} />
|
||||||
<Button radius={"lg"} fz={"h4"} color={colors["blue-button"]} component={Link} href="/darmasaba/prestasi-desa">Selengkapnya</Button>
|
<Text ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
||||||
</Center>
|
Prestasi Desa
|
||||||
</Container>
|
</Text>
|
||||||
<Box py={50}>
|
</Group>
|
||||||
<SimpleGrid
|
<Text fz={{ base: "1rem", md: "1.3rem" }} ta="center" c="dimmed" maw={700}>
|
||||||
cols={{
|
Kami bangga dengan pencapaian desa hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama.
|
||||||
base: 1,
|
</Text>
|
||||||
sm: 3
|
<Button
|
||||||
}}
|
radius="xl"
|
||||||
>
|
size="lg"
|
||||||
{loading ? (
|
variant="gradient"
|
||||||
<Center>
|
gradient={{ from: "#26667F", to: "#124170" }}
|
||||||
<Text fz={"2.4rem"}>Memuat Data...</Text>
|
component={Link}
|
||||||
</Center>
|
href="/darmasaba/prestasi-desa"
|
||||||
) : (
|
>
|
||||||
data.map((v, k) => {
|
Lihat Semua Prestasi
|
||||||
return (
|
</Button>
|
||||||
<BackgroundImage
|
</Stack>
|
||||||
key={k}
|
</Container>
|
||||||
src={v.image?.link || ''}
|
|
||||||
radius={16}
|
<Box py={50}>
|
||||||
pos={"relative"}
|
{loading ? (
|
||||||
>
|
<Center mih={200}>
|
||||||
<Box
|
<Loader color={colors["blue-button"]} size="xl" />
|
||||||
style={{
|
</Center>
|
||||||
borderRadius: 16,
|
) : data.length === 0 ? (
|
||||||
zIndex: 0
|
<Center mih={200}>
|
||||||
}}
|
<Stack align="center" gap="xs">
|
||||||
pos={"absolute"}
|
<IconTrophy size={48} color="gray" />
|
||||||
w={"100%"}
|
<Text fz="1.2rem" fw={500} c="dimmed">
|
||||||
h={"100%"}
|
Belum ada prestasi yang ditampilkan
|
||||||
bg={colors.trans.dark[2]}
|
</Text>
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
</Stack>
|
</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'
|
'use client'
|
||||||
import colors from "@/con/colors";
|
import { useEffect, useState } from "react"
|
||||||
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core";
|
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core"
|
||||||
import { useMediaQuery } from "@mantine/hooks";
|
import { useMediaQuery } from "@mantine/hooks"
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client"
|
||||||
import Link from "next/link";
|
import Link from "next/link"
|
||||||
import { useEffect, useState } from "react";
|
import { IconMoodSad } from "@tabler/icons-react"
|
||||||
|
|
||||||
export default function SDGS() {
|
export default function SDGS() {
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme()
|
||||||
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`)
|
||||||
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[] | null>(null);
|
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[] | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSdgsDesa = async () => {
|
const fetchSdgsDesa = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
|
const response = await fetch("/api/landingpage/sdgsdesa/findMany")
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
const result = await response.json()
|
||||||
}
|
let data = []
|
||||||
const result = await response.json();
|
if (Array.isArray(result.data)) data = result.data
|
||||||
// Ensure the data is an array before setting it
|
else if (Array.isArray(result)) data = result
|
||||||
let data = [];
|
else {
|
||||||
if (Array.isArray(result.data)) {
|
setSdgsDesa([])
|
||||||
data = result.data;
|
return
|
||||||
} else if (Array.isArray(result)) {
|
}
|
||||||
// In case the API returns the array directly
|
const top3Sdgs = [...data].sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah)).slice(0, 3)
|
||||||
data = result;
|
setSdgsDesa(top3Sdgs)
|
||||||
} else {
|
} catch {
|
||||||
console.error('Unexpected API response format:', result);
|
setSdgsDesa([])
|
||||||
setSdgsDesa([]);
|
}
|
||||||
return;
|
}
|
||||||
}
|
fetchSdgsDesa()
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Sort by jumlah in descending order and take top 3
|
return (
|
||||||
const top3Sdgs = [...data]
|
<Stack p="sm">
|
||||||
.sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah))
|
<Container w={{ base: "100%", md: "80%" }} p="xl">
|
||||||
.slice(0, 3);
|
<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);
|
<Box py={50}>
|
||||||
} catch (error) {
|
<Paper
|
||||||
console.error('Error fetching sdgs desa:', error);
|
p={{ base: "md", md: "xl" }}
|
||||||
setSdgsDesa([]);
|
radius="xl"
|
||||||
}
|
withBorder
|
||||||
};
|
shadow="lg"
|
||||||
|
style={{
|
||||||
fetchSdgsDesa();
|
background: "linear-gradient(145deg, #FAF6E9, #FFFDF6)",
|
||||||
}, []);
|
border: "1px solid rgba(255,255,255,0.05)",
|
||||||
return (
|
}}
|
||||||
<Stack p={"sm"}>
|
>
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
{sdgsDesa && sdgsDesa.length > 0 ? (
|
||||||
<Center>
|
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xl" verticalSpacing="xl">
|
||||||
<Text fz={"3.4rem"}>SDGs Desa</Text>
|
{sdgsDesa.map((item) => (
|
||||||
</Center>
|
<Paper
|
||||||
<Text fz={"1.4rem"} ta={"center"}>SDGs Desa adalah upaya menerapkan 17 Tujuan Pembangunan Berkelanjutan di tingkat desa.
|
key={item.id}
|
||||||
Dengan fokus pada pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, dan pelestarian lingkungan, kami berkomitmen untuk menciptakan desa yang lebih baik bagi semua</Text>
|
p="lg"
|
||||||
<Box py={50}>
|
radius="lg"
|
||||||
<Paper p={{ base: 'md', md: 'xl' }} bg={colors.Bg} radius="lg" shadow="sm">
|
shadow="sm"
|
||||||
{sdgsDesa && sdgsDesa.length > 0 ? (
|
style={{
|
||||||
<SimpleGrid
|
background: "linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02))",
|
||||||
cols={{ base: 1, sm: 3 }}
|
border: "1px solid rgba(255,255,255,0.08)",
|
||||||
spacing="xl"
|
transition: "all 0.3s ease",
|
||||||
verticalSpacing="xl"
|
}}
|
||||||
>
|
withBorder
|
||||||
{sdgsDesa.map((item) => (
|
>
|
||||||
<Box
|
<Center mb="lg">
|
||||||
key={item.id}
|
<Box
|
||||||
p="md"
|
p="md"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
background: "rgba(255,255,255,0.06)",
|
||||||
flexDirection: 'column',
|
backdropFilter: "blur(6px)",
|
||||||
alignItems: 'center',
|
width: mobile ? 140 : 160,
|
||||||
justifyContent: 'center',
|
height: mobile ? 140 : 160,
|
||||||
height: '100%',
|
display: "flex",
|
||||||
transition: 'transform 0.2s',
|
alignItems: "center",
|
||||||
'&:hover': {
|
justifyContent: "center",
|
||||||
transform: 'translateY(-5px)'
|
borderRadius: "1rem",
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Image
|
||||||
<Box
|
src={item.image?.link ? item.image.link : "/placeholder-sdgs.png"}
|
||||||
p="md"
|
alt={item.name}
|
||||||
style={{
|
w={mobile ? 90 : 110}
|
||||||
backgroundColor: 'white',
|
h={mobile ? 90 : 110}
|
||||||
width: mobile ? 150 : 180,
|
fit="contain"
|
||||||
height: mobile ? 150 : 180,
|
/>
|
||||||
display: 'flex',
|
</Box>
|
||||||
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>
|
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
<Text ta="center" fz={{ base: "lg", md: "xl" }} fw={700} mb="xs">
|
||||||
</Container>
|
{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