From 3a726a33343bdcef7c2e94a8183abcca10061d75 Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 26 Aug 2025 17:49:33 +0800 Subject: [PATCH] Fix Menu Lingkungan Darmasaba User --- .../_com/LeafletMultiMarkerMap.tsx | 62 +++ .../_state/lingkungan/data-lingkungan-desa.ts | 8 +- .../_state/lingkungan/pengelolaan-sampah.ts | 8 +- .../_state/lingkungan/program-penghijauan.ts | 8 +- .../lingkungan/data-lingkungan-desa/page.tsx | 14 +- .../lingkungan/program-penghijauan/page.tsx | 4 +- .../data-lingkungan-desa/findMany.ts | 16 +- .../lingkungan/pengelolaan-sampah/findMany.ts | 15 +- .../program-penghijauan/findMany.ts | 14 +- .../(pages)/desa/layanan/[id]/page.tsx | 45 +- .../keamanan/pencegahan-kriminalitas/page.tsx | 154 ++++--- .../lingkungan/data-lingkungan-desa/page.tsx | 183 +++++--- .../lingkungan/konservasi-adat-bali/page.tsx | 116 +++-- .../pengelolaan-sampah-bank-sampah/page.tsx | 221 +++++----- .../lingkungan/program-penghijauan/page.tsx | 172 +++++--- .../(pages)/ppid/profile-ppid/page.tsx | 147 ++++--- .../(pages)/ppid/struktur-ppid/page.tsx | 416 +++++++++++++++--- src/app/darmasaba/(tambahan)/apbdes/page.tsx | 167 ++++--- .../desa-anti-korupsi/detail/page.tsx | 177 ++++---- .../(tambahan)/penghargaan/[id]/page.tsx | 97 ++-- .../darmasaba/(tambahan)/penghargaan/page.tsx | 202 +++++---- src/app/darmasaba/_com/Footer.tsx | 297 +++++-------- src/app/darmasaba/_com/NavBarSearch.tsx | 40 +- src/app/darmasaba/_com/Navbar.tsx | 124 +++--- src/app/darmasaba/_com/NavbarMainMenu.tsx | 145 +++--- src/app/darmasaba/_com/NavbarSubMenu.tsx | 84 ++-- .../darmasaba/_com/main-page/apbdes/index.tsx | 217 ++++----- .../_com/main-page/kepuasan/index.tsx | 7 +- .../main-page/landing-page/ModuleView.tsx | 103 +++-- .../main-page/landing-page/ProfileView.tsx | 69 +-- .../main-page/landing-page/SosmedView.tsx | 84 +++- .../_com/main-page/landing-page/index.tsx | 314 +++++-------- .../_com/main-page/penghargaan/index.tsx | 116 +++-- .../_com/main-page/potensi/index.tsx | 169 +++---- .../_com/main-page/prestasi/index.tsx | 202 +++++---- .../darmasaba/_com/main-page/sdgs/index.tsx | 269 ++++++----- 36 files changed, 2509 insertions(+), 1977 deletions(-) create mode 100644 src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx diff --git a/src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx b/src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx new file mode 100644 index 00000000..ab1d1b03 --- /dev/null +++ b/src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx @@ -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 ( +
+ + + {markers.map((marker, index) => ( + + {marker.popup} + + ))} + +
+ ); +} diff --git a/src/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa.ts b/src/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa.ts index 65857112..f7092245 100644 --- a/src/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa.ts +++ b/src/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa.ts @@ -56,13 +56,17 @@ const dataLingkunganDesaState = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { + search: "", + load: async (page = 1, limit = 10, search = "") => { // Change to arrow function dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property dataLingkunganDesaState.findMany.page = page; + dataLingkunganDesaState.findMany.search = search; try { + const query: any = { page, limit }; + if (search) query.search = search; const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({ - query: { page, limit }, + query, }); if (res.status === 200 && res.data?.success) { diff --git a/src/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah.ts b/src/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah.ts index 78d9d5c6..f030359b 100644 --- a/src/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah.ts +++ b/src/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah.ts @@ -52,15 +52,19 @@ const pengelolaanSampah = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { + search: "", + load: async (page = 1, limit = 10, search = "") => { // Change to arrow function pengelolaanSampah.findMany.loading = true; // Use the full path to access the property pengelolaanSampah.findMany.page = page; + pengelolaanSampah.findMany.search = search; try { + const query: any = { page, limit }; + if (search) query.search = search; const res = await ApiFetch.api.lingkungan.pengelolaansampah[ "find-many" ].get({ - query: { page, limit }, + query, }); if (res.status === 200 && res.data?.success) { diff --git a/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.ts b/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.ts index df0cd94e..84d3083e 100644 --- a/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.ts +++ b/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.ts @@ -56,13 +56,17 @@ const programPenghijauanState = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { + search: "", + load: async (page = 1, limit = 10, search = "") => { // Change to arrow function programPenghijauanState.findMany.loading = true; // Use the full path to access the property programPenghijauanState.findMany.page = page; + programPenghijauanState.findMany.search = search; try { + const query: any = { page, limit }; + if (search) query.search = search; const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({ - query: { page, limit }, + query, }); if (res.status === 200 && res.data?.success) { diff --git a/src/app/admin/(dashboard)/lingkungan/data-lingkungan-desa/page.tsx b/src/app/admin/(dashboard)/lingkungan/data-lingkungan-desa/page.tsx index e8ce6a50..cda0e3bb 100644 --- a/src/app/admin/(dashboard)/lingkungan/data-lingkungan-desa/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/data-lingkungan-desa/page.tsx @@ -42,18 +42,10 @@ function ListDataLingkunganDesa({ search }: { search: string }) { const router = useRouter(); useEffect(() => { - load(page, 10) - }, [page]) + load(page, 10, search) + }, [page, search]) - const filteredData = (data || []).filter(item => { - 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 filteredData = data || [] const iconMap: Record> = { ekowisata: IconLeaf, diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx index 5b49f244..dc1b4f7e 100644 --- a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx @@ -126,7 +126,9 @@ function ListProgramPenghijauan({ search }: { search: string }) { {index + 1} {item.name} - + + + {iconMap[item.icon] && ( diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/data-lingkungan-desa/findMany.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/data-lingkungan-desa/findMany.ts index 187d09c5..0050dc9e 100644 --- a/src/app/api/[[...slugs]]/_lib/lingkungan/data-lingkungan-desa/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/data-lingkungan-desa/findMany.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; @@ -6,17 +7,28 @@ export default async function dataLingkunganDesaFindMany(context: Context) { const page = Number(context.query.page) || 1; const limit = Number(context.query.limit) || 10; 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 { const [data, total] = await Promise.all([ prisma.dataLingkunganDesa.findMany({ - where: { isActive: true }, + where, skip, take: limit, orderBy: { createdAt: 'desc' }, }), prisma.dataLingkunganDesa.count({ - where: { isActive: true } + where, }) ]); diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/pengelolaan-sampah/findMany.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/pengelolaan-sampah/findMany.ts index 2e575465..366d0f4d 100644 --- a/src/app/api/[[...slugs]]/_lib/lingkungan/pengelolaan-sampah/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/pengelolaan-sampah/findMany.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; @@ -5,18 +6,28 @@ import { Context } from "elysia"; export default async function pengelolaanSampahFindMany(context: Context) { const page = Number(context.query.page) || 1; const limit = Number(context.query.limit) || 10; + const search = (context.query.search as string) || ''; const skip = (page - 1) * limit; + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + ]; + } + try { const [data, total] = await Promise.all([ prisma.pengelolaanSampah.findMany({ - where: { isActive: true }, + where, skip, take: limit, orderBy: { createdAt: 'desc' }, }), prisma.pengelolaanSampah.count({ - where: { isActive: true } + where, }) ]); diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts index 0aa06bb3..2ae70bba 100644 --- a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; @@ -6,17 +7,26 @@ export default async function programPenghijauanFindMany(context: Context) { const page = Number(context.query.page) || 1; const limit = Number(context.query.limit) || 10; 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 { const [data, total] = await Promise.all([ prisma.programPenghijauan.findMany({ - where: { isActive: true }, + where, skip, take: limit, orderBy: { createdAt: 'desc' }, }), prisma.programPenghijauan.count({ - where: { isActive: true } + where, }) ]); diff --git a/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx b/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx index 2c543394..7dc134fe 100644 --- a/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx +++ b/src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx @@ -1,9 +1,9 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' -import { useParams } from 'next/navigation'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core'; +import { useParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import BackButton from '../_com/BackButto'; @@ -34,46 +34,44 @@ function Page() { useEffect(() => { const loadData = async () => { if (!id) return; - try { setLoading(true); await state.suratKeterangan.findUnique.load(id); const result = state.suratKeterangan.findUnique.data as unknown as LayananData; setData(result); } catch (error) { - console.error('Error loading data:', error); + console.error('Terjadi kesalahan saat memuat data:', error); } finally { setLoading(false); } }; - loadData(); }, [id]); if (loading) { return ( -
- +
+
); } if (!data) { return ( -
- Data tidak ditemukan +
+ Maaf, data layanan tidak ditemukan
); } return ( - + - + @@ -81,19 +79,32 @@ function Page() { - + {data.image2?.link && (
- {data.name} + {data.name}
)} - - diff --git a/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx b/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx index 73d02b8c..cc376b5b 100644 --- a/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx @@ -5,112 +5,130 @@ import { IconArrowRight } from '@tabler/icons-react'; function Page() { return ( - + - {/* Content 1 */} - - - Info Keamanan dan Pencegahan Kriminalitas + + + Community Safety & Crime Prevention - - Kontak Darurat + + Emergency Contacts - - {/* Content 2 */} - - - - Tips menjaga keamanan lingkungan + + + + How to Keep Your Neighborhood Safe - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. + + Practical safety habits everyone can apply daily to reduce risks. - - - - Mengenali tanda-tanda kegiatan kriminal + + + Recognizing Criminal Activities - - the printing and typesetting industry. the printing and typesetting industry. + + Key warning signs and behavior patterns you should stay aware of. - - {/* Content 3 */} - - - Program Keamanan Rutin + + + Ongoing Security Programs - - - - - Ronda Malam - + + {['Night Patrol', 'Neighborhood Watch', 'Emergency Preparedness'].map((program, i) => ( + + + + {program} + + - - - - - Ronda Malam - - - - - - - - Ronda Malam - - - - + ))} - + - {/* Content 4 */} - - - + 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" + /> - - Patroli Malam Darmasaba + + Darmasaba Night Patrol - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. + + A closer look at how the community works together to maintain safety. - diff --git a/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx b/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx index b56c3b7e..b40ea743 100644 --- a/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/data-lingkungan-desa/page.tsx @@ -1,85 +1,126 @@ +'use client' +import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa'; import colors from '@/con/colors'; -import { Box, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; -import { IconChristmasTree, IconDroplet, IconHome, IconLeaf, IconTrash } from '@tabler/icons-react'; +import { Badge, Box, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Tooltip } from '@mantine/core'; +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'; -const data = [ - { - id: 1, - icon: , - title: 'Luas Lahan Hijau', - jumlah: '± 25 hektar', - deskripsi: 'tersebar di area persawahan, kebun, dan taman desa.' - }, - { - id: 2, - icon: , - title: 'Jumlah Rumah Tangga', - jumlah: '± 1.500 rumah tangga', - deskripsi: 'yang mayoritas sudah memiliki fasilitas pengelolaan sampah mandiri.' - }, - { - id: 3, - icon: , - title: 'Jumlah Sungai dan Saluran Air', - jumlah: '± 3 Sungai Besar', - deskripsi: 'dan beberapa saluran irigasi tradisional (subak) yang masih aktif digunakan.' - }, - { - id: 4, - icon: , - title: 'Program Penghijauan', - jumlah: '± 1000 Pohon', - deskripsi: 'Dilaksanakan secara berkala melalui kegiatan menanam pohon di area umum dan perbukitan.' - }, - { - id: 5, - icon: , - title: 'Pengelolaan Sampah', - jumlah: '± 5 Bank Sampah', - deskripsi: 'Didukung oleh Bank Sampah dan sistem pemilahan sampah rumah tangga.' - }, -] 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 = { + 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 ( + + + + ) + } + return ( - + - - - Data Lingkungan Desa + + + + Data Lingkungan Desa + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya. Fokus utama meliputi penghijauan, pengelolaan sampah, dan perlindungan kawasan hijau. - Desa Darmasaba memiliki lingkungan yang terus dijaga dan dikembangkan demi kesejahteraan warganya. Upaya pelestarian lingkungan difokuskan pada penghijauan, pengelolaan sampah, serta perlindungan kawasan hijau. - - - {data.map((v, k) => { - return ( - - - - {v.title} - - - {v.icon} - - {v.jumlah} - - - - {v.deskripsi} - - - - - ) - })} + + + {data.map((item) => ( + + + +
+ {iconMap[item.icon] ? ( + React.createElement(iconMap[item.icon], { + size: 55, + color: colors['blue-button'], + style: { + transition: 'all 0.3s', + filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))' + } + }) + ) : null} +
+
+ + ± {item.jumlah} + + + + {item.name} + +
+
+ ))}
+
+ load(newPage)} + total={totalPages} + color="blue" + radius="xl" + /> +
); diff --git a/src/app/darmasaba/(pages)/lingkungan/konservasi-adat-bali/page.tsx b/src/app/darmasaba/(pages)/lingkungan/konservasi-adat-bali/page.tsx index 44869d53..c53e1f3d 100644 --- a/src/app/darmasaba/(pages)/lingkungan/konservasi-adat-bali/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/konservasi-adat-bali/page.tsx @@ -1,93 +1,79 @@ 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'; const data = [ { id: 1, title: 'Filosofi Tri Hita Karana', - listDeskripsi: - - Parahyangan: Hubungan manusia dengan Tuhan - - - Pawongan: Hubungan antar manusia - - - Palemahan: Hubungan manusia dengan alam - - + listDeskripsi: ( + + Parahyangan: Hubungan manusia dengan Tuhan yang dijaga penuh kesadaran spiritual + Pawongan: Harmoni dan kerja sama antar manusia dalam masyarakat + Palemahan: Pelestarian lingkungan dan hubungan manusia dengan alam + + ), }, { id: 2, title: 'Bentuk Konservasi Berdasarkan Adat', - listDeskripsi: - - Pelestarian Hutan Adat seperti Alas Pala Sangeh atau Wana Kerthi - - - Subak: Sistem pengelolaan irigasi tradisional yang menjunjung kebersamaan dan keberlanjutan - - - Hari Raya Tumpek Uduh: Perayaan khusus untuk menghormati pohon dan tumbuhan - - - Perarem dan Awig-Awig: Aturan adat desa yang mengatur larangan menebang pohon sembarangan, membuang limbah ke sungai, dll. - - - Ritual penyucian alam seperti Melasti, Piodalan Segara, dan lainnya - - + listDeskripsi: ( + + Pelestarian Hutan Adat seperti Alas Pala Sangeh dan Wana Kerthi + Subak: Sistem irigasi tradisional yang menekankan kebersamaan dan keberlanjutan + Hari Raya Tumpek Uduh: Perayaan untuk menghormati pohon dan tumbuhan + Perarem & Awig-Awig: Aturan adat untuk menjaga lingkungan dari kerusakan + Ritual penyucian alam seperti Melasti dan Piodalan Segara + + ), }, { id: 3, title: 'Nilai Konservasi Adat', - listDeskripsi: - - Menjaga keseimbangan ekosistem - - - Melestarikan spiritualitas lokal dan kesucian alam - - - Menumbuhkan kesadaran kolektif untuk hidup selaras dengan lingkungan - - - Menjaga keberlangsungan sumber daya alam untuk generasi mendatang - - + listDeskripsi: ( + + Menjaga keseimbangan ekosistem dan lingkungan hidup + Melestarikan spiritualitas lokal dan kesucian alam + Meningkatkan kesadaran kolektif untuk hidup selaras dengan alam + Menjamin keberlanjutan sumber daya alam untuk generasi mendatang + + ), }, -] +]; + function Page() { return ( - + - - + + Konservasi Adat Bali - - 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. - - - {data.map((v, k) => { - return ( - - - {v.title} - {v.listDeskripsi} - - - ) - })} + + + {data.map((item) => ( + + +
+ + {item.title} + +
+ {item.listDeskripsi} +
+
+ ))}
diff --git a/src/app/darmasaba/(pages)/lingkungan/pengelolaan-sampah-bank-sampah/page.tsx b/src/app/darmasaba/(pages)/lingkungan/pengelolaan-sampah-bank-sampah/page.tsx index 17435994..ce38423d 100644 --- a/src/app/darmasaba/(pages)/lingkungan/pengelolaan-sampah-bank-sampah/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/pengelolaan-sampah-bank-sampah/page.tsx @@ -1,63 +1,60 @@ +'use client' +import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah'; import colors from '@/con/colors'; -import { Box, Flex, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core'; -import { IconClipboardTextFilled, IconMapPin, IconRecycle, IconScale, IconSearch, IconTrashFilled, IconTruckFilled } from '@tabler/icons-react'; +import { Box, Flex, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; +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 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: , - deskripsi: '1. Pilah sampah sesuai jenisnya' - }, - { - id: 2, - icon: , - deskripsi: '2. Bawa sampah ke Bank Sampah' - }, - { - id: 3, - icon: , - deskripsi: '3. Timbang sampah di Bank Sampah' - }, - { - id: 4, - icon: , - deskripsi: '4. Catat hasil timbangan di buku tabungan' - }, - { - id: 5, - icon: , - deskripsi: '5. Sampah didaur ulang oleh petugas Bank Sampah' - }, -] -const bankSampah = [ - { - id: 1, - icon: , - deskripsi: 'Bank Sampah Sarana Gathi', - alamat: 'Jl. Ahmad Yani Utara No.453, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115' - }, - { - id: 2, - icon: , - 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: , - deskripsi: 'Bank Sampah Jempiring Sari', - alamat: 'Jl. Gn. Lebah I No.9, Tegal Harum, Kec. Denpasar Bar., Kota Denpasar, Bali 80119' - }, - { - id: 4, - icon: , - deskripsi: 'Bank Sampah Sarana Gathi', - alamat: 'Jl. Ahmad Yani Utara No.453, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115' - }, -] 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 = { + ekowisata: IconLeaf, + kompetisi: IconTrophy, + wisata: IconTent, + ekonomi: IconChartLine, + sampah: IconRecycle, + truck: IconTruckFilled, + scale: IconScale, + clipboard: IconClipboardTextFilled, + trash: IconTrashFilled, + }; + + if (state.findMany.loading || !data) { + return ( + + + + ) + } + return ( @@ -84,10 +81,10 @@ function Page() { - - {v.icon} + + {k + 1} {iconMap[v.icon] ? React.createElement(iconMap[v.icon]) : null} - {v.deskripsi} + {v.name} @@ -96,7 +93,7 @@ function Page() {
- + Keterangan Bank Sampah Terdekat @@ -107,66 +104,64 @@ function Page() { leftSection={} placeholder='Cari Bank Sampah Terdekat' /> - + + + {/* Left side - List of bank locations */} - - {bankSampah.map((v, k) => { - return ( - - - - - {v.icon} - - - - {v.deskripsi} - - - {v.alamat} - - - - - - ) - })} - + + Daftar Bank Sampah + + {data2?.map((v, k) => ( + + {v.namaTempatMaps} + {v.alamat} + {v.lat && v.lng ? ( + + 📌 Buka di Google Maps + + ) : ( + Koordinat belum tersedia + )} + + ))} + + - - + + {/* Right side - Single map showing all locations */} + + + Peta Lokasi + {data2?.some(v => v.lat && v.lng) ? ( + + v.lat && v.lng) + .map(v => ({ + position: [v.lat, v.lng], + popup: v.namaTempatMaps + }))} + /> + + ) : ( + Tidak ada koordinat yang tersedia + )} + -
+
); } export default Page; + diff --git a/src/app/darmasaba/(pages)/lingkungan/program-penghijauan/page.tsx b/src/app/darmasaba/(pages)/lingkungan/program-penghijauan/page.tsx index ce52f6fc..489eb06d 100644 --- a/src/app/darmasaba/(pages)/lingkungan/program-penghijauan/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/program-penghijauan/page.tsx @@ -1,71 +1,131 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; -import { IconChristmasTreeFilled, IconHomeEco, IconShieldFilled, IconTrendingUp } from '@tabler/icons-react'; +import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; +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 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: , - }, - { - id: 2, - deskripsi: 'Sumber Oksigen', - icon: , - }, - { - id: 3, - deskripsi: 'Ekonomi Berkelanjutan', - icon: , - }, - { - id: 4, - deskripsi: 'Mencegah Bencana', - icon: , - }, -] 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 = { + 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 ( + + + + ); + } + return ( - + - - - Program Penghijauan Desa + + + + + Program Penghijauan Desa + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + + Mari berpartisipasi menanam dan merawat pohon untuk menciptakan lingkungan hijau, sehat, dan seimbang bagi seluruh warga desa. - Program Penghijauan Desa bertujuan untuk meningkatkan kesadaran masyarakat akan pentingnya lingkungan hijau melalui penanaman pohon dan perawatan tanaman. - - Manfaat Program Penghijauan - - - {data.map((v, k) => { - return ( - - - -
- {v.icon} -
- - {v.deskripsi} - - - - -
-
-
- ) - })} + + Manfaat Program + + + {data.map((v) => ( + { + const el = e.currentTarget; + el.style.transform = 'translateY(-8px)'; + el.style.boxShadow = '0 15px 30px rgba(28,110,164,0.5)'; + }} + onMouseLeave={(e) => { + const el = e.currentTarget; + el.style.transform = 'translateY(0)'; + el.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)'; + }} + > + +
+ {iconMap[v.icon] && React.createElement(iconMap[v.icon], { size: 50, stroke: 1.5, color: colors['blue-button'] })} +
+ + + {v.name} + + + + {v.judul} + + +
+
+ ))}
+
+ load(newPage)} + total={totalPages} + color="blue" + radius="xl" + /> +
); diff --git a/src/app/darmasaba/(pages)/ppid/profile-ppid/page.tsx b/src/app/darmasaba/(pages)/ppid/profile-ppid/page.tsx index 536b4bf2..6de605d3 100644 --- a/src/app/darmasaba/(pages)/ppid/profile-ppid/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/profile-ppid/page.tsx @@ -3,119 +3,132 @@ import stateProfilePPID from '@/app/admin/(dashboard)/_state/ppid/profile_ppid/p import colors from '@/con/colors'; import { Box, Center, Divider, Flex, Image, List, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; +import { IconBuildingCommunity, IconTargetArrow, IconTimeline, IconUser } from '@tabler/icons-react'; import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; function Page() { const allList = useProxy(stateProfilePPID) useShallowEffect(() => { - allList.profile.load("edit") // Assuming "1" is your default ID, adjust as needed + allList.profile.load("edit") }, []) - if (!allList.profile.data) return - - - - - - - - - {Array.from({ length: 10 }).map((v, k) => - - )} - - - + if (!allList.profile.data) return ( + + + + + + + + + + {Array.from({ length: 8 }).map((_, i) => ( + + ))} + + + + ) const dataArray = Array.isArray(allList.profile.data) ? allList.profile.data - : [allList.profile.data]; + : [allList.profile.data] return ( - + - - Profil Singkat PPID Desa Darmasaba + + PPID Desa Darmasaba Profile {dataArray.map((item) => ( - + - - - PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA + + Village logo + + Public Information Officer + - - {/* biodata perbekel */} - - - - - -
- -
- - > - + + + + + +
+ Leader photo +
+ + {item.name}
+ - - Biodata - - - - Riwayat Karir - - + + + + + Biography + + + + + + + Career History + + + +
- - Pengalaman Organisasi - + + + + + Organizational Experience + + - + - - Program Kerja Unggulan - + + + + + Flagship Programs + + - +
- ))}
- ); + ) } -export default Page; +export default Page diff --git a/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx b/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx index 86d877eb..5e8c40eb 100644 --- a/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/struktur-ppid/page.tsx @@ -1,121 +1,395 @@ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-explicit-any */ -'use client' -import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID'; -import colors from '@/con/colors'; -import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; -import { OrganizationChart } from 'primereact/organizationchart'; -import { useEffect } from 'react'; -import { useProxy } from 'valtio/utils'; -import BackButton from '../../desa/layanan/_com/BackButto'; +// /* eslint-disable react-hooks/exhaustive-deps */ +// /* eslint-disable @typescript-eslint/no-explicit-any */ +// 'use client' +// import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID'; +// import colors from '@/con/colors'; +// import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; +// import { OrganizationChart } from 'primereact/organizationchart'; +// import { useEffect } from 'react'; +// import { useProxy } from 'valtio/utils'; +// import BackButton from '../../desa/layanan/_com/BackButto'; -function Page() { - return ( - - - - - Struktur PPID - +// function Page() { +// return ( +// +// +// +// +// Struktur PPID +// - - ); +// +// ); +// } + +// function StrukturOrganisasiPPID() { +// const stateOrganisasi = useProxy(stateStrukturPPID.pegawai) + +// useEffect(() => { +// stateOrganisasi.findMany.load() +// }, []) + +// if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) { +// return ( +// +// +// +// ); +// } + +// // Step 1: Group pegawai berdasarkan posisiId +// const posisiMap = new Map(); + +// 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 ( +// +// +// +// +// +// ); +// } + + +// 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 ( +// +// +// {name} +// {name} +// {status} +// +// +// ); +// } + +// 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 ( + + + + + + + + Struktur Organisasi PPID + + + Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor + untuk melihat detail atau klik node untuk fokus tampilan. + + + + + + + + ) } function StrukturOrganisasiPPID() { - const stateOrganisasi = useProxy(stateStrukturPPID.pegawai) + const stateOrganisasi: any = useProxy(stateStrukturPPID.pegawai) 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 ( - - - - ); +
+ + + Memuat struktur organisasi… + + Mengambil data pegawai dan posisi. Mohon tunggu sebentar. + + +
+ ) } - // Step 1: Group pegawai berdasarkan posisiId - const posisiMap = new Map(); + if ( + !stateOrganisasi.findMany.data || + stateOrganisasi.findMany.data.length === 0 + ) { + return ( +
+ + +
+ +
+ + Data pegawai belum tersedia + + + Belum ada data pegawai yang tercatat untuk PPID. Silakan coba + muat ulang atau periksa sumber data. + + + + + +
+
+
+ ) + } + const posisiMap = new Map() for (const pegawai of stateOrganisasi.findMany.data) { - const posisiId = pegawai.posisi.id; + const posisiId = pegawai.posisi.id if (!posisiMap.has(posisiId)) { posisiMap.set(posisiId, { ...pegawai.posisi, pegawaiList: [], - children: [] - }); + children: [], + }) } - posisiMap.get(posisiId)!.pegawaiList.push(pegawai); + posisiMap.get(posisiId)!.pegawaiList.push(pegawai) } - - // Step 2: Buat struktur pohon berdasarkan parentId - const root: any[] = []; - + const root: any[] = [] posisiMap.forEach((posisi) => { if (posisi.parentId) { - const parent = posisiMap.get(posisi.parentId); + const parent = posisiMap.get(posisi.parentId) if (parent) { - parent.children.push(posisi); + parent.children.push(posisi) + } else { + root.push(posisi) } } else { - root.push(posisi); + 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' + name: node.pegawaiList?.[0]?.namaLengkap || 'Belum ditugaskan', + title: node.nama || 'Tanpa jabatan', + 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 ( - - - + + + - ); + ) } - 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'; + const imageSrc = node?.data?.image || '/img/default.png' + const name = node?.data?.name || 'Tanpa Nama' + const title = node?.data?.title || 'Tanpa Jabatan' + const description = node?.data?.description || '' return ( - - - {name} - {name} - {status} - - - ); + + {(styles) => ( + + {name} + {name} + + {title} + + + {description || 'Belum ada deskripsi.'} + + + + + + )} + + ) } -export default Page; + + diff --git a/src/app/darmasaba/(tambahan)/apbdes/page.tsx b/src/app/darmasaba/(tambahan)/apbdes/page.tsx index 5712a563..b3d419d4 100644 --- a/src/app/darmasaba/(tambahan)/apbdes/page.tsx +++ b/src/app/darmasaba/(tambahan)/apbdes/page.tsx @@ -1,112 +1,97 @@ /* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' -import colors from '@/con/colors'; -import { ActionIcon, BackgroundImage, Box, Center, Container, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core'; -import { IconDownload } from '@tabler/icons-react'; -import BackButton from '../../(pages)/desa/layanan/_com/BackButto'; -import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'; -import { useProxy } from 'valtio/utils'; -import { useEffect, useState } from 'react'; -import { Link } from 'next-view-transitions'; +import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes' +import colors from '@/con/colors' +import { ActionIcon, BackgroundImage, Box, Center, Container, Group, Loader, SimpleGrid, Stack, Text, Title } from '@mantine/core' +import { IconDownload } from '@tabler/icons-react' +import { Link } from 'next-view-transitions' +import { useEffect, useState } from 'react' +import { useProxy } from 'valtio/utils' +import BackButton from '../../(pages)/desa/layanan/_com/BackButto' function Page() { - const state = useProxy(apbdes); - const [loading, setLoading] = useState(false); + const state = useProxy(apbdes) + const [loading, setLoading] = useState(false) useEffect(() => { const loadData = async () => { try { - setLoading(true); - await state.findMany.load(); + setLoading(true) + await state.findMany.load() } catch (error) { - console.error('Error loading data:', error); + console.error(error) } finally { - setLoading(false); + setLoading(false) } } - loadData(); + loadData() }, []) - const data = state.findMany.data || []; + const data = state.findMany.data || [] + return ( - - - - - - APBDes - - - Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab. Adapun APBDes sebagai berikut: + + + + + + + + Anggaran Pendapatan & Belanja Desa (APBDes) + + + Laporan transparansi APBDes Desa Darmasaba sebagai bentuk keterbukaan dan akuntabilitas pengelolaan anggaran desa. - - {loading ? ( -
- Memuat Data... -
- ) : ( - data.map((v, k) => { - return ( - - - - - {v.name} - - {v.jumlah} - - - - - Download - - - - - - ) - }) - )} -
+ {loading ? ( +
+ + + Sedang memuat data APBDes... + +
+ ) : data.length === 0 ? ( +
+ + Belum ada data APBDes tersedia + Data akan ditampilkan jika sudah diunggah oleh admin desa + +
+ ) : ( + + {data.map((v: any, k: number) => ( + + + + + + {v.name} + + + + {v.jumlah} + + + + + + + + + ))} + + )}
- ); + ) } -export default Page; +export default Page diff --git a/src/app/darmasaba/(tambahan)/desa-anti-korupsi/detail/page.tsx b/src/app/darmasaba/(tambahan)/desa-anti-korupsi/detail/page.tsx index 7e232e32..ca475190 100644 --- a/src/app/darmasaba/(tambahan)/desa-anti-korupsi/detail/page.tsx +++ b/src/app/darmasaba/(tambahan)/desa-anti-korupsi/detail/page.tsx @@ -1,137 +1,136 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client'; +import React, { useEffect, useState } from 'react'; import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi'; import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto'; 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 { + Box, + Button, + Container, + Flex, + Paper, + SimpleGrid, + Stack, + Text, + ActionIcon, + Loader, + Tooltip, +} from '@mantine/core'; +import { IconFile, IconInbox } from '@tabler/icons-react'; function Lokal() { const [selectedKategori, setSelectedKategori] = useState('PENGUATAN TATA LAKSANA'); const [loading, setLoading] = useState(true); const state = useProxy(korupsiState); - - // Load data on component mount + useEffect(() => { const loadData = async () => { try { setLoading(true); - await state.desaAntikorupsi.findMany.load(1, 100); // Load first 100 items - } catch (error) { - console.error('Error loading data:', error); + await state.desaAntikorupsi.findMany.load(1, 100); } finally { setLoading(false); } }; - loadData(); }, []); - - // Get data from state + const data = state.desaAntikorupsi.findMany.data || []; - - // Debug: Log the complete data structure - console.log('Complete data:', JSON.parse(JSON.stringify(data))); - - // Get unique categories - 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))); + const categories = [...new Set(data.filter(i => i.kategori?.name).map(i => i.kategori.name))]; + const filteredData = + selectedKategori === 'PENGUATAN TATA LAKSANA' + ? data + : data.filter(i => i.kategori?.name === selectedKategori); + return ( - - + + - - - + + + + Desa Anti Korupsi - - 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 : + + Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola + terbuka dengan melibatkan warga untuk mengawasi anggaran sehingga tepat sasaran sesuai + kebutuhan. - - {/* Category Filter Buttons */} - - {categories.map((kategori) => ( + + + + {categories.map(kategori => ( ))} - - {/* Loading State */} + {loading ? ( - Memuat data... + + + + ) : filteredData.length === 0 ? ( + + + + Belum ada data untuk kategori ini + + ) : ( - - {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) => { + + {filteredData.map(item => { + const handleDownload = (e: React.MouseEvent) => { e.stopPropagation(); if (!item?.file?.link) return; - - try { - const fileUrl = item.file.link.startsWith('http') - ? item.file.link - : `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${item.file.link}`; - - window.open(fileUrl, '_blank'); - } catch (error) { - console.error('Error opening file:', error); - } + const url = item.file.link.startsWith('http') + ? item.file.link + : `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${ + item.file.link + }`; + window.open(url, '_blank'); }; - + return ( - + -
- - {item.name} - -
- + + {item.name} + {item?.file && ( - - - + + + + + )}
@@ -141,9 +140,7 @@ function Lokal() { )}
- ) + ); } export default Lokal; - - diff --git a/src/app/darmasaba/(tambahan)/penghargaan/[id]/page.tsx b/src/app/darmasaba/(tambahan)/penghargaan/[id]/page.tsx index ec29d9af..866c972d 100644 --- a/src/app/darmasaba/(tambahan)/penghargaan/[id]/page.tsx +++ b/src/app/darmasaba/(tambahan)/penghargaan/[id]/page.tsx @@ -2,13 +2,12 @@ 'use client' import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto'; 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 React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; import { useProxy } from 'valtio/utils'; import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan'; -import { useState } from 'react'; function Page() { const params = useParams<{ id: string }>(); @@ -23,77 +22,99 @@ function Page() { setLoading(true); await state.findUnique.load(id); } catch (error) { - console.error('Error loading data:', error); + console.error('Gagal memuat data:', error); } finally { setLoading(false); } - } - loadData() - }, [id]) + }; + loadData(); + }, [id]); if (loading) { return ( -
- +
+ + + + +
); } if (!state.findUnique.data) { return ( -
- Data tidak ditemukan +
+ + + Data penghargaan tidak tersedia + + + Silakan kembali dan pilih penghargaan lainnya + +
); } - return ( - + - - - + + + {state.findUnique.data?.name} - + Gambar penghargaan - - {new Date(state.findUnique.data?.createdAt).toLocaleDateString()} - - - - + + + Diterbitkan: {new Date(state.findUnique.data?.createdAt).toLocaleDateString('id-ID')} + + + + + - - + + + + - - + + + + - - + + + + - - + + - + diff --git a/src/app/darmasaba/(tambahan)/penghargaan/page.tsx b/src/app/darmasaba/(tambahan)/penghargaan/page.tsx index fd315729..948cadbb 100644 --- a/src/app/darmasaba/(tambahan)/penghargaan/page.tsx +++ b/src/app/darmasaba/(tambahan)/penghargaan/page.tsx @@ -3,109 +3,131 @@ import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan"; import colors from "@/con/colors"; 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 Autoplay from "embla-carousel-autoplay"; +import { IconAward, IconArrowRight } from "@tabler/icons-react"; import { useTransitionRouter } from "next-view-transitions"; import { useEffect, useRef } from "react"; import { useProxy } from "valtio/utils"; import BackButton from "../../(pages)/desa/layanan/_com/BackButto"; export default function Page() { - return ( - - - - - - - - Penghargaan - - - 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: - - - - + return ( + + + + + + + + + + Penghargaan Desa + + + + Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan. + + - ) + + + ); } + function Slider() { - const height = 720; - const width = 1200; - const theme = useMantineTheme(); - const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`); - const autoplay = useRef(Autoplay({ delay: 2000 })); - const state = useProxy(penghargaanState); - const roter = useTransitionRouter() + const height = 500; + const width = 1200; + const theme = useMantineTheme(); + const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`); + const autoplay = useRef(Autoplay({ delay: 3000 })); + const state = useProxy(penghargaanState); + const router = useTransitionRouter(); - useEffect(() => { - const loadData = async () => { - try { - await state.findMany.load(); - } catch (error) { - console.error('Error loading data:', error); - } - } - loadData(); - }, []) + useEffect(() => { + state.findMany.load(); + }, []); - const data = state.findMany.data || []; - - const slides = data.map((item) => ( - - - - - - - {item.name} - - - - - - - - - )); + const data = state.findMany.data || []; + const loading = state.findMany.loading; + if (loading) { return ( - - {slides} - + + + + + ); -} \ No newline at end of file + } + + if (!loading && data.length === 0) { + return ( + + + + Belum ada penghargaan yang ditambahkan + + + ); + } + + const slides = data.map((item) => ( + + + + + + {item.name} + + + + + + + + )); + + return ( + + {slides} + + ); +} diff --git a/src/app/darmasaba/_com/Footer.tsx b/src/app/darmasaba/_com/Footer.tsx index de6f1360..e25655af 100644 --- a/src/app/darmasaba/_com/Footer.tsx +++ b/src/app/darmasaba/_com/Footer.tsx @@ -1,207 +1,118 @@ 'use client' -import colors from '@/con/colors'; -import { ActionIcon, Anchor, Box, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title, useMantineTheme } from '@mantine/core'; -import { useMediaQuery } from '@mantine/hooks'; +import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core'; import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react'; function Footer() { - const theme = useMantineTheme(); - const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`); return ( - <> - - -
- - - - - - Komitmen Dalam Pelayanan - - - 1. Transparansi: - - Kami berkomitmen untuk mengelola dana desa secara terbuka, sehingga masyarakat dapat - mengetahui penggunaan anggaran secara jelas dan bertanggung jawab. - - - - - 2. Profesionalisme: - - - Setiap layanan desa akan dilakukan dengan profesional, cepat, dan tanpa diskriminasi, - demi memastikan kepuasan masyarakat. - - - - - 3. Partisipatif: - - Kami percaya bahwa partisipasi aktif masyarakat adalah kunci keberhasilan pembangunan desa. - Oleh karena itu, kami akan terus melibatkan warga dalam setiap proses pengambilan keputusan. - - - - - 4. Inovasi: - - Kami berkomitmen untuk terus berinovasi dalam memberikan solusi bagi permasalahan desa, - termasuk melalui pemanfaatan teknologi untuk mempermudah akses layanan. - - - - - 5. Berkeadilan: - - Setiap kebijakan dan program desa akan dirancang untuk memberikan manfaat yang merata - bagi seluruh lapisan masyarakat, tanpa memandang status sosial atau ekonomi. - - - - - 6. Pemberdayaan: - - Kami berkomitmen untuk memberdayakan masyarakat melalui pelatihan, pendampingan, - dan dukungan terhadap usaha-usaha lokal agar desa semakin mandiri. - - - - - 7. Ramah Lingkungan: - - Seluruh kegiatan pembangunan dan pelayanan desa akan memperhatikan keberlanjutan lingkungan, - demi menjaga keseimbangan alam dan kenyamanan hidup warga. - - - - - - - - - Tujuan Akhir - - Dengan visi, misi dan komitmen ini, kami bertekad untuk menjadikan desa sebagai tempat tinggal - yang nyaman, aman dan sejahtera bagi seluruh warganya. - - - 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. - - - - - {"Desa Kuat, Masyarakat Sejahtera!"} - - - Logo Desa - - - - - - - -
- - - - - Tentang Darmasaba - Desa Darmasaba adalah desa - budaya yang kaya akan tradisi dan - nilai-nilai luhur masyarakat Bali. + + +
+ + + + - - - - - - - - - - - - - - + Komitmen Layanan Kami + + {[ + { title: "Transparansi", text: "Pengelolaan dana desa dilakukan secara terbuka agar masyarakat dapat memahami dan memantau penggunaan anggaran." }, + { title: "Profesionalisme", text: "Layanan desa diberikan secara cepat, adil, dan profesional demi kepuasan masyarakat." }, + { title: "Partisipasi", text: "Masyarakat dilibatkan aktif dalam pengambilan keputusan demi pembangunan desa yang berhasil." }, + { title: "Inovasi", text: "Kami terus berinovasi, termasuk melalui teknologi, agar layanan semakin mudah diakses." }, + { title: "Keadilan", text: "Kebijakan dan program disusun untuk memberi manfaat yang merata bagi seluruh warga." }, + { title: "Pemberdayaan", text: "Masyarakat didukung melalui pelatihan, pendampingan, dan pengembangan usaha lokal." }, + { title: "Ramah Lingkungan", text: "Seluruh kegiatan pembangunan memperhatikan keberlanjutan demi menjaga alam dan kesehatan warga." } + ].map((item, i) => ( + + {i + 1}. {item.title}: + {item.text} + + ))} + + + + + + Visi Kami + + Dengan visi ini, kami berkomitmen menjadikan desa sebagai tempat yang aman, sejahtera, dan nyaman bagi seluruh warga. + + + 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. + + + + + "Desa Kuat, Warga Sejahtera!" + + Logo Desa + + - - - - Layanan - - Administrasi Kependudukan - - - Pelayanan Sosial - - - Pengaduan Masyarakat - - - Informasi Publik - - - - - - Tautan Penting - - Portal Badung - - - E-Government - - - Transparansi - - - Unduhan - - - - - - Newsletter - Dapatkan informasi terbaru - tentang kegiatan dan program - desa + + + +
+ + + + + + Tentang Darmasaba + + Darmasaba adalah desa budaya yang kaya akan tradisi dan nilai-nilai warisan Bali. + + + + + + + + + + + + + Layanan Desa + Administrasi Kependudukan + Layanan Sosial + Pengaduan Masyarakat + Informasi Publik + + + + + + Tautan Penting + Portal Badung + E-Government + Transparansi + Unduhan + + + + + + Berlangganan Info + Dapatkan kabar terbaru tentang program dan kegiatan desa langsung ke email Anda. + } + w="70%" + placeholder="Masukkan email Anda" + rightSection={} /> - - - - - - - © 2024 Desa Darmasaba. Hak Cipta Dilindungi. - + + +
+
+
-
- + + + © 2025 Desa Darmasaba. Hak cipta dilindungi. + +
); } diff --git a/src/app/darmasaba/_com/NavBarSearch.tsx b/src/app/darmasaba/_com/NavBarSearch.tsx index 78744307..4585fa6e 100644 --- a/src/app/darmasaba/_com/NavBarSearch.tsx +++ b/src/app/darmasaba/_com/NavBarSearch.tsx @@ -1,23 +1,27 @@ import stateNav from "@/state/state-nav"; -import { Container, Stack, TextInput } from "@mantine/core"; +import { Container, Stack, TextInput, Tooltip } from "@mantine/core"; +import { IconSearch } from "@tabler/icons-react"; export function NavbarSearch() { - return - - - + + + } + /> + + -} \ No newline at end of file + ); +} diff --git a/src/app/darmasaba/_com/Navbar.tsx b/src/app/darmasaba/_com/Navbar.tsx index d75eb141..c4a1042e 100644 --- a/src/app/darmasaba/_com/Navbar.tsx +++ b/src/app/darmasaba/_com/Navbar.tsx @@ -2,78 +2,96 @@ import colors from "@/con/colors"; import navbarListMenu from "@/con/navbar-list-menu"; import stateNav from "@/state/state-nav"; -import { ActionIcon, Box, Burger, Group, Image, Stack, Text } from "@mantine/core"; +import { ActionIcon, Box, Burger, Group, Image, Paper, ScrollArea, Stack, Text, Tooltip } from "@mantine/core"; import { IconSquareArrowRight } from "@tabler/icons-react"; -import { motion } from 'framer-motion'; -import { useRouter } from 'next/navigation'; +import { motion } from "framer-motion"; +import { useRouter } from "next/navigation"; import { useSnapshot } from "valtio"; import { MenuItem } from "../../../../types/menu-item"; import { NavbarMainMenu } from "./NavbarMainMenu"; export function Navbar() { const { item, isSearch, mobileOpen } = useSnapshot(stateNav); - const router = useRouter() + const router = useRouter(); + return ( - - - - - { - router.push("/darmasaba") - stateNav.mobileOpen = false - }} - size={80} radius={"xl"} - > - Logo Desa - - stateNav.mobileOpen = !stateNav.mobileOpen} color={colors["blue-button"]} opened={mobileOpen} /> - - {mobileOpen && - - } - - + + + { + router.push("/darmasaba"); + stateNav.mobileOpen = false; + }} + > + + Village Logo + + + + (stateNav.mobileOpen = !stateNav.mobileOpen)} + size="sm" + /> + + + {mobileOpen && ( + + + + )} + + {(item || isSearch) && } ); } function NavbarMobile({ listNavbar }: { listNavbar: MenuItem[] }) { - const router = useRouter() - return - {listNavbar.map((item, k) => { - return - { - router.push(item.href) - stateNav.mobileOpen = false - }}> - {item.name} - - - {item.children && } + const router = useRouter(); + return ( + + + {listNavbar.map((item, k) => ( + + { + router.push(item.href); + stateNav.mobileOpen = false; + }} + style={{ cursor: "pointer" }} + > + + {item.name} + + + + {item.children && } + + ))} - })} - + + ); } diff --git a/src/app/darmasaba/_com/NavbarMainMenu.tsx b/src/app/darmasaba/_com/NavbarMainMenu.tsx index d06479b3..9de4baa6 100644 --- a/src/app/darmasaba/_com/NavbarMainMenu.tsx +++ b/src/app/darmasaba/_com/NavbarMainMenu.tsx @@ -2,7 +2,7 @@ import colors from "@/con/colors" import stateNav from "@/state/state-nav" -import { ActionIcon, Button, Container, Flex, Image, Stack } from "@mantine/core" +import { ActionIcon, Button, Container, Flex, Image, Stack, Tooltip } from "@mantine/core" import { useHover } from "@mantine/hooks" import { IconSearch, IconUser } from "@tabler/icons-react" import { useTransitionRouter } from 'next-view-transitions' @@ -12,68 +12,91 @@ import { NavbarSearch } from "./NavBarSearch" import { NavbarSubMenu } from "./NavbarSubMenu" import { useRouter } from "next/navigation" -export function NavbarMainMenu({ listNavbar }: { - listNavbar: MenuItem[] -}) { - const { item, isSearch } = useSnapshot(stateNav) - const router = useTransitionRouter() - const next = useRouter() - return - - - { - router.push("/darmasaba") - stateNav.clear() +export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) { + const { item, isSearch } = useSnapshot(stateNav) + const router = useTransitionRouter() + const next = useRouter() - }} > - icon - - {listNavbar.map((item, k) => { - return - })} - { - stateNav.item = null - stateNav.isSearch = !stateNav.isSearch - }} - > - {/* TODO: add icon search */} - - - { - next.push("/admin/landing-page/profile/program-inovasi") - }} color={colors["blue-button"]} radius={'xl'}> - - - - - {item && } - {isSearch && } + return ( + + + + + { + router.push("/darmasaba") + stateNav.clear() + }} + > + Darmasaba Logo + + + {listNavbar.map((item, k) => ( + + ))} + + { + stateNav.item = null + stateNav.isSearch = !stateNav.isSearch + }} + radius="xl" + > + + + + + { + next.push("/admin/landing-page/profile/program-inovasi") + }} + color={colors["blue-button"]} + radius="xl" + variant="light" + > + + + + + + {item && } + {isSearch && } - + ) } -function MenuItemCom({ item, }: { item: MenuItem }) { - const { ref, hovered } = useHover() - const router = useTransitionRouter() +function MenuItemCom({ item }: { item: MenuItem }) { + const { ref, hovered } = useHover() + const router = useTransitionRouter() - return -} \ No newline at end of file + return ( + + ) +} diff --git a/src/app/darmasaba/_com/NavbarSubMenu.tsx b/src/app/darmasaba/_com/NavbarSubMenu.tsx index 37dcc180..be7235ba 100644 --- a/src/app/darmasaba/_com/NavbarSubMenu.tsx +++ b/src/app/darmasaba/_com/NavbarSubMenu.tsx @@ -1,20 +1,22 @@ "use client"; import stateNav from "@/state/state-nav"; -import { Button, Container, Stack } from "@mantine/core"; -import _ from "lodash"; +import { Button, Container, Stack, Text } from "@mantine/core"; import { motion } from "motion/react"; +import { IconArrowRight } from "@tabler/icons-react"; import { MenuItem } from "../../../../types/menu-item"; -import { useTransitionRouter } from 'next-view-transitions' +import { useTransitionRouter } from "next-view-transitions"; +import colors from "@/con/colors"; export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) { - const router = useTransitionRouter() + const router = useTransitionRouter(); + return ( - - {item && - item.map((item, k) => { - return ( - - ); - })} - + {item && item.length > 0 ? ( + + {item.map((link, index) => ( + + ))} + + ) : ( + + + No submenu available + + + )} ); diff --git a/src/app/darmasaba/_com/main-page/apbdes/index.tsx b/src/app/darmasaba/_com/main-page/apbdes/index.tsx index f5087435..85e9d8ba 100644 --- a/src/app/darmasaba/_com/main-page/apbdes/index.tsx +++ b/src/app/darmasaba/_com/main-page/apbdes/index.tsx @@ -1,121 +1,136 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' -import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'; -import colors from '@/con/colors'; -import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core'; -import { IconDownload } from '@tabler/icons-react'; -import Link from 'next/link'; -import { useEffect, useState } from 'react'; -import { useProxy } from 'valtio/utils'; - +import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes' +import colors from '@/con/colors' +import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, Loader, SimpleGrid, Stack, Text } from '@mantine/core' +import { IconDownload } from '@tabler/icons-react' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import { useProxy } from 'valtio/utils' function Apbdes() { - const state = useProxy(apbdes); - const [loading, setLoading] = useState(false); + const state = useProxy(apbdes) + const [loading, setLoading] = useState(false) + const textHeading = { - title: "APBDes", - des: "Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab" + title: 'APBDes', + des: 'Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola desa yang bersih, terbuka, dan bertanggung jawab.' } useEffect(() => { const loadData = async () => { try { - setLoading(true); - await state.findMany.load(); + setLoading(true) + await state.findMany.load() } catch (error) { - console.error('Error loading data:', error); + console.error('Error loading data:', error) } finally { - setLoading(false); + setLoading(false) } } - loadData(); + loadData() }, []) - const data = (state.findMany.data || []).slice(0, 3); + const data = (state.findMany.data || []).slice(0, 3) + return ( - <> - - - - - {textHeading.title} - - - {textHeading.des} - - - - - {loading ? ( -
- Memuat Data... -
- ) : ( - data.map((v, k) => { - return ( - + + + + {textHeading.title} + + + {textHeading.des} + + + + + + {loading ? ( +
+ +
+ ) : data.length === 0 ? ( +
+ + + Belum ada data APBDes yang tersedia + + + Data akan ditampilkan di sini setelah diunggah + + +
+ ) : ( + data.map((v, k) => ( + + + + - - - - {v.name} - - {v.jumlah} - - - - - Download - - - - - - ) - }) - )} -
- - - -
- - ); + {v.name} + + + {v.jumlah} + + + + + + + + +
+ + )) + )} + + + + + +
+ ) } -export default Apbdes; +export default Apbdes diff --git a/src/app/darmasaba/_com/main-page/kepuasan/index.tsx b/src/app/darmasaba/_com/main-page/kepuasan/index.tsx index 119b5438..d9687ba7 100644 --- a/src/app/darmasaba/_com/main-page/kepuasan/index.tsx +++ b/src/app/darmasaba/_com/main-page/kepuasan/index.tsx @@ -160,7 +160,12 @@ function Kepuasan() {
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!
- +
diff --git a/src/app/darmasaba/_com/main-page/landing-page/ModuleView.tsx b/src/app/darmasaba/_com/main-page/landing-page/ModuleView.tsx index 267d2aee..b8ac4c36 100644 --- a/src/app/darmasaba/_com/main-page/landing-page/ModuleView.tsx +++ b/src/app/darmasaba/_com/main-page/landing-page/ModuleView.tsx @@ -1,64 +1,81 @@ import profileLandingPageState from "@/app/admin/(dashboard)/_state/landing-page/profile"; -import { Center, Image, Paper, SimpleGrid, Text } from "@mantine/core"; +import { Box, Center, Image, Paper, SimpleGrid, Stack, Text, Tooltip } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; -import { motion } from 'framer-motion'; -import { useTransitionRouter } from 'next-view-transitions'; +import { motion } from "framer-motion"; +import { useTransitionRouter } from "next-view-transitions"; import { useProxy } from "valtio/utils"; import { Prisma } from "@prisma/client"; - +import { IconPhotoOff } from "@tabler/icons-react"; type ProgramInovasiItem = Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>; function ModuleItem({ data }: { data: ProgramInovasiItem }) { const router = useTransitionRouter(); return ( - { - router.push(`/${data.name}`); - }} - p={"md"} - bg={"white"} - radius={"32"} - pos={"relative"} - > -
- + + router.push(`/${data.name}`)} + p="xl" + radius="2xl" + bg="white" + className="cursor-pointer transition-all shadow-md hover:shadow-xl" > - {data.image?.link ? ( - icon - ) : ( - - - - - )} - -
-
+
+ {data.image?.link ? ( + {data.name} + ) : ( + + + + Belum ada gambar + + + )} +
+ + + {data.name} + + + + + ); } function ModuleView() { - const listImageState = useProxy(profileLandingPageState.programInovasi) + const listImageState = useProxy(profileLandingPageState.programInovasi); useShallowEffect(() => { - listImageState.findMany.load() - }, []) + listImageState.findMany.load(); + }, []); + + if (!listImageState.findMany.loading && !listImageState.findMany.data?.length) { + return ( +
+ + + + Belum ada program inovasi + + + Tambahkan program inovasi untuk ditampilkan di sini + + +
+ ); + } + return ( - + {listImageState.findMany.data?.map((item) => ( ))} diff --git a/src/app/darmasaba/_com/main-page/landing-page/ProfileView.tsx b/src/app/darmasaba/_com/main-page/landing-page/ProfileView.tsx index 9a723e08..18839fb6 100644 --- a/src/app/darmasaba/_com/main-page/landing-page/ProfileView.tsx +++ b/src/app/darmasaba/_com/main-page/landing-page/ProfileView.tsx @@ -1,15 +1,28 @@ -import colors from '@/con/colors'; -import { Box, Card, Image, Stack, Text } from '@mantine/core'; +import { Box, Card, Image, Stack, Text, Tooltip } from '@mantine/core'; +import { IconUserCircle } from '@tabler/icons-react'; import React from 'react'; import { Prisma } from '@prisma/client'; +import colors from '@/con/colors'; interface ProfileViewProps { data: Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null; } -function ProfileView({ data }: ProfileViewProps) { +export default function ProfileView({ data }: ProfileViewProps) { if (!data) { - return
No profile data available
; + return ( + + + + + Profil belum tersedia + + + Data pejabat desa akan muncul di sini + + + + ); } return ( @@ -17,42 +30,38 @@ function ProfileView({ data }: ProfileViewProps) { justify="end" align="end" pos="relative" - w={{ - base: "100%", - md: "40%", - }} + w={{ base: '100%', md: '40%' }} px="xl" > {data.image?.link ? ( {data.name - ): ( - - - - + ) : ( + + + + Belum ada foto + + )} - + - {data.position} - + + + {data.position || 'Tidak ada jabatan'} + + + {data.name} @@ -60,5 +69,3 @@ function ProfileView({ data }: ProfileViewProps) { ); } - -export default ProfileView; \ No newline at end of file diff --git a/src/app/darmasaba/_com/main-page/landing-page/SosmedView.tsx b/src/app/darmasaba/_com/main-page/landing-page/SosmedView.tsx index 30ff5e9c..66d770fc 100644 --- a/src/app/darmasaba/_com/main-page/landing-page/SosmedView.tsx +++ b/src/app/darmasaba/_com/main-page/landing-page/SosmedView.tsx @@ -1,35 +1,79 @@ -import { ActionIcon, Flex, Image, Text } from "@mantine/core"; +import { ActionIcon, Card, Flex, Image, Text, Tooltip } from "@mantine/core"; import { Prisma } from "@prisma/client"; import { useTransitionRouter } from "next-view-transitions"; +import { IconBrandInstagram, IconBrandFacebook, IconBrandTwitter, IconWorld } from "@tabler/icons-react"; - - -function SosmedView({data} : {data : Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]}) { +function SosmedView({ + data, +}: { + data: Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]; +}) { const router = useTransitionRouter(); + + const fallbackIcon = (platform?: string) => { + switch (platform?.toLowerCase()) { + case "instagram": + return ; + case "facebook": + return ; + case "twitter": + return ; + default: + return ; + } + }; + return ( - - {data?.map((item, k) => { - return ( + + {data && data.length > 0 ? ( + data.map((item, k) => ( + { - router.push(item.iconUrl || ""); + variant="light" + radius="xl" + size="xl" + onClick={() => item.iconUrl && router.push(item.iconUrl)} + style={{ + transition: "all 0.3s ease", + boxShadow: "0 0 12px rgba(28, 110, 164, 0.6)", }} > {item.image?.link ? ( - icon + {item.name ) : ( - - none - + fallbackIcon(item.name) )} - ); - })} + + )) + ) : ( + + + Belum ada media sosial yang terhubung + + + )} ); } diff --git a/src/app/darmasaba/_com/main-page/landing-page/index.tsx b/src/app/darmasaba/_com/main-page/landing-page/index.tsx index 64160485..3f9133dc 100644 --- a/src/app/darmasaba/_com/main-page/landing-page/index.tsx +++ b/src/app/darmasaba/_com/main-page/landing-page/index.tsx @@ -11,79 +11,77 @@ import { Image, Paper, Stack, - Text + Text, + Center, + Tooltip, + Badge, } from "@mantine/core"; +import { IconCalendarTime, IconInfoCircle } from "@tabler/icons-react"; import { useEffect, useState } from "react"; import ModuleView from "./ModuleView"; import SosmedView from "./SosmedView"; import ProfileView from "./ProfileView"; const getDayOfWeek = () => { - const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu']; + const days = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"]; const today = new Date(); return days[today.getDay()]; -} +}; const getCurrentTime = () => { const now = new Date(); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, "0"); + const minutes = String(now.getMinutes()).padStart(2, "0"); return `${hours}:${minutes}`; -} +}; const isWorkingHours = (currentTime: string): boolean => { - const [openTime, closeTime] = ['08:00', '16:00']; - + const [openTime, closeTime] = ["08:00", "16:00"]; const compareTimes = (time1: string, time2: string) => { - const [hour1, minute1] = time1.split(':').map(Number); - const [hour2, minute2] = time2.split(':').map(Number); - + const [hour1, minute1] = time1.split(":").map(Number); + const [hour2, minute2] = time2.split(":").map(Number); if (hour1 < hour2) return true; if (hour1 > hour2) return false; return minute1 <= minute2; }; return compareTimes(currentTime, closeTime) && !compareTimes(currentTime, openTime); -} +}; + const getWorkStatus = (day: string, currentTime: string): { status: string; message: string } => { - const workingDays = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat']; - + const workingDays = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat"]; if (!workingDays.includes(day)) { - return { - status: 'Tutup', - message: 'Sabtu - Minggu' - } + return { status: "Tutup", message: "Libur Akhir Pekan" }; } - const isOpen = isWorkingHours(currentTime) - return isOpen ? { status: 'Buka', message: '08:00 - 16:00' } : { status: 'Tutup', message: '08:00 - 16:00' }; -} - + const isOpen = isWorkingHours(currentTime); + return isOpen + ? { status: "Buka", message: "08:00 - 16:00" } + : { status: "Tutup", message: "08:00 - 16:00" }; +}; function LandingPage() { - const [socialMedia, setSocialMedia] = useState[]>([]); - const [profile, setProfile] = useState | null>(null); + const [socialMedia, setSocialMedia] = useState< + Prisma.MediaSosialGetPayload<{ include: { image: true } }>[] + >([]); + const [profile, setProfile] = useState< + Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null + >(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchSocialMedia = async () => { try { - const response = await fetch('/api/landingpage/mediasosial/findMany'); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + const response = await fetch("/api/landingpage/mediasosial/findMany"); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const result = await response.json(); - // Ensure the data is an array before setting it if (Array.isArray(result.data)) { setSocialMedia(result.data); } else if (Array.isArray(result)) { - // In case the API returns the array directly setSocialMedia(result); } else { - console.error('Unexpected API response format:', result); setSocialMedia([]); } - } catch (error) { - console.error('Error fetching social media:', error); - setSocialMedia([]); // Ensure we always have an array + } catch { + setSocialMedia([]); } finally { setIsLoading(false); } @@ -92,22 +90,22 @@ function LandingPage() { const fetchProfile = async () => { try { const response = await fetch(`/api/landingpage/pejabatdesa/edit`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const result = await response.json(); - setProfile(result.data || null); // Handle single object response - } catch (error) { - console.error('Error fetching profile:', error); + setProfile(result.data || null); + } catch { setProfile(null); } }; + fetchSocialMedia(); fetchProfile(); }, []); - const [workStatus, setWorkStatus] = useState<{ status: string; message: string }> - ({ status: '', message: '' }); + const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>({ + status: "", + message: "", + }); useEffect(() => { const updateWorkStatus = () => { @@ -115,212 +113,110 @@ function LandingPage() { const time = getCurrentTime(); const status = getWorkStatus(day, time); setWorkStatus(status); - } + }; updateWorkStatus(); const intervalId = setInterval(updateWorkStatus, 60 * 1000); return () => clearInterval(intervalId); }, []); + return ( - - - - - - - - - - icon + + + + + + + + + + Logo Darmasaba - - - - icon + + + Logo Pudak - + - - - - - Jadwal Kerja - - - - + + + + + Jam Operasional + + + + - - - - {workStatus.status} - - - {workStatus.message} - - - - + {workStatus.status} + + + {workStatus.message} - + - {/* Edit yang ini */} - - - - {new Intl.DateTimeFormat('id-ID', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric' - }).format(new Date())} - - - - Status - - - {workStatus.status === 'Buka' ? 'Operasional' : 'Tutup'} + + + + + Hari Ini + + + Status Kantor + + {workStatus.status === "Buka" ? "Sedang Beroperasi" : "Tidak Beroperasi"} - - + - - + + {isLoading ? ( ) : socialMedia.length > 0 ? ( ) : ( -
No social media links available
+
+ Belum ada tautan media sosial yang tersedia +
)} - Sampaikan saran dan masukan guna kemajuan dalam pembangunan. Semua lebih mudah melalui fitur interaktif + + + Bagikan ide, kritik, atau saran Anda untuk mendukung pembangunan desa. + Semua lebih mudah dengan fitur interaktif yang kami sediakan. +
-
+ {isLoading ? ( - + ) : profile ? ( ) : ( -
No profile available
+
+ Informasi profil belum tersedia +
)}
-
+ ); } diff --git a/src/app/darmasaba/_com/main-page/penghargaan/index.tsx b/src/app/darmasaba/_com/main-page/penghargaan/index.tsx index 554472c2..995984b3 100644 --- a/src/app/darmasaba/_com/main-page/penghargaan/index.tsx +++ b/src/app/darmasaba/_com/main-page/penghargaan/index.tsx @@ -1,34 +1,33 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client'; import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan"; -import colors from "@/con/colors"; -import { Stack, Box, Container, Button, Text } from "@mantine/core"; -import { useTransitionRouter } from 'next-view-transitions' +import { Stack, Box, Container, Button, Text, Loader, Paper } from "@mantine/core"; +import { IconAward, IconArrowRight } from "@tabler/icons-react"; +import { useTransitionRouter } from 'next-view-transitions'; import { useEffect, useState } from "react"; import { useProxy } from "valtio/utils"; function Penghargaan() { - const router = useTransitionRouter() - const state = useProxy(penghargaanState) - const [loading, setLoading] = useState(false) + const router = useTransitionRouter(); + const state = useProxy(penghargaanState); + const [loading, setLoading] = useState(false); useEffect(() => { const loadData = async () => { try { - setLoading(true) - await state.findMany.load() - } catch (error) { - console.error('Error loading data:', error) + setLoading(true); + await state.findMany.load(); } finally { - setLoading(false) + setLoading(false); } - } - loadData() - }, []) + }; + loadData(); + }, []); + + const data = state.findMany.data?.slice(0, 3); - const data = state.findMany.data?.slice(0, 3) return ( - +
+ )} + - + ); diff --git a/src/app/darmasaba/_com/main-page/prestasi/index.tsx b/src/app/darmasaba/_com/main-page/prestasi/index.tsx index 1b7fed31..d9222063 100644 --- a/src/app/darmasaba/_com/main-page/prestasi/index.tsx +++ b/src/app/darmasaba/_com/main-page/prestasi/index.tsx @@ -2,104 +2,124 @@ 'use client' import prestasiState from "@/app/admin/(dashboard)/_state/landing-page/prestasi-desa"; import colors from "@/con/colors"; -import { BackgroundImage, Box, Button, Center, Container, Group, SimpleGrid, Stack, Text } from "@mantine/core"; +import { BackgroundImage, Box, Button, Center, Container, Group, Loader, SimpleGrid, Stack, Text } from "@mantine/core"; import { useProxy } from "valtio/utils"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; +import { IconTrophy } from "@tabler/icons-react"; function Prestasi() { - const state = useProxy(prestasiState.prestasiDesa); - const [loading, setLoading] = useState(false); - const router = useRouter() + const state = useProxy(prestasiState.prestasiDesa); + const [loading, setLoading] = useState(false); + const router = useRouter(); - useEffect(() => { - prestasiState.kategoriPrestasi.findMany.load() - const loadData = async () => { - try { - setLoading(true); - await state.findMany.load(); - } catch (error) { - console.error('Error loading data:', error); - } finally { - setLoading(false); - } - } - loadData(); - }, []) + useEffect(() => { + prestasiState.kategoriPrestasi.findMany.load(); + const loadData = async () => { + try { + setLoading(true); + await state.findMany.load(); + } finally { + setLoading(false); + } + }; + loadData(); + }, []); - const data = (state.findMany.data || []).slice(0, 3); - return ( - <> - - - Prestasi Desa - 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. -
- -
-
- - - {loading ? ( -
- Memuat Data... -
- ) : ( - data.map((v, k) => { - return ( - - - - - - {v.kategori.name} - - - - - - - - - ) - }) - )} -
-
+ const data = (state.findMany.data || []).slice(0, 3); + + return ( + + + + + + + Prestasi Desa + + + + Kami bangga dengan pencapaian desa hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama. + + + + + + + {loading ? ( +
+ +
+ ) : data.length === 0 ? ( +
+ + + + Belum ada prestasi yang ditampilkan + - - ) +
+ ) : ( + + {data.map((v, k) => ( + + + + + + {v.kategori.name} + + + + + + + + + ))} + + )} +
+
+ ); } -export default Prestasi; \ No newline at end of file + +export default Prestasi; diff --git a/src/app/darmasaba/_com/main-page/sdgs/index.tsx b/src/app/darmasaba/_com/main-page/sdgs/index.tsx index af2b564b..7588ae96 100644 --- a/src/app/darmasaba/_com/main-page/sdgs/index.tsx +++ b/src/app/darmasaba/_com/main-page/sdgs/index.tsx @@ -1,143 +1,142 @@ 'use client' -import colors from "@/con/colors"; -import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core"; -import { useMediaQuery } from "@mantine/hooks"; -import { Prisma } from "@prisma/client"; -import Link from "next/link"; -import { useEffect, useState } from "react"; +import { useEffect, useState } from "react" +import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core" +import { useMediaQuery } from "@mantine/hooks" +import { Prisma } from "@prisma/client" +import Link from "next/link" +import { IconMoodSad } from "@tabler/icons-react" export default function SDGS() { - const theme = useMantineTheme(); - const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`); - const [sdgsDesa, setSdgsDesa] = useState[] | null>(null); + const theme = useMantineTheme() + const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`) + const [sdgsDesa, setSdgsDesa] = useState[] | null>(null) - useEffect(() => { - const fetchSdgsDesa = async () => { - try { - const response = await fetch('/api/landingpage/sdgsdesa/findMany'); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const result = await response.json(); - // Ensure the data is an array before setting it - let data = []; - if (Array.isArray(result.data)) { - data = result.data; - } else if (Array.isArray(result)) { - // In case the API returns the array directly - data = result; - } else { - console.error('Unexpected API response format:', result); - setSdgsDesa([]); - return; - } + useEffect(() => { + const fetchSdgsDesa = async () => { + try { + const response = await fetch("/api/landingpage/sdgsdesa/findMany") + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) + const result = await response.json() + let data = [] + if (Array.isArray(result.data)) data = result.data + else if (Array.isArray(result)) data = result + else { + setSdgsDesa([]) + return + } + const top3Sdgs = [...data].sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah)).slice(0, 3) + setSdgsDesa(top3Sdgs) + } catch { + setSdgsDesa([]) + } + } + fetchSdgsDesa() + }, []) - // Sort by jumlah in descending order and take top 3 - const top3Sdgs = [...data] - .sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah)) - .slice(0, 3); + return ( + + +
+ + SDGs Desa + +
+ + 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. + - setSdgsDesa(top3Sdgs); - } catch (error) { - console.error('Error fetching sdgs desa:', error); - setSdgsDesa([]); - } - }; - - fetchSdgsDesa(); - }, []); - return ( - - -
- SDGs Desa -
- SDGs Desa adalah upaya menerapkan 17 Tujuan Pembangunan Berkelanjutan di tingkat desa. - Dengan fokus pada pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, dan pelestarian lingkungan, kami berkomitmen untuk menciptakan desa yang lebih baik bagi semua - - - {sdgsDesa && sdgsDesa.length > 0 ? ( - - {sdgsDesa.map((item) => ( - - - {item.name} - - - {item.name} - - - {item.jumlah} - - - ))} - - ) : ( - Tidak ada data SDGs Desa - )} - -
- + + + {sdgsDesa && sdgsDesa.length > 0 ? ( + + {sdgsDesa.map((item) => ( + +
+ + {item.name} +
-
- + + {item.name} + + + {item.jumlah} + + + ))} + + ) : ( +
+ + + Belum ada data SDGs Desa + +
+ )} + - - ); -} \ No newline at end of file +
+ +
+ + + + ) +}