diff --git a/postcss.config.cjs b/postcss.config.cjs index 069b0528..9e9652a2 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,14 +1,15 @@ module.exports = { - plugins: { - 'postcss-preset-mantine': {}, - 'postcss-simple-vars': { - variables: { - 'mantine-breakpoint-xs': '36em', - 'mantine-breakpoint-sm': '48em', - 'mantine-breakpoint-md': '62em', - 'mantine-breakpoint-lg': '75em', - 'mantine-breakpoint-xl': '88em', - }, + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + /* Mobile first */ + 'mantine-breakpoint-xs': '30em', // 480px → mobile kecil–normal + 'mantine-breakpoint-sm': '48em', // 768px → tablet / mobile landscape + 'mantine-breakpoint-md': '64em', // 1024px → laptop & desktop kecil + 'mantine-breakpoint-lg': '80em', // 1280px → desktop standar + 'mantine-breakpoint-xl': '90em', // 1440px+ → desktop besar }, }, - }; \ No newline at end of file + }, +}; diff --git a/src/app/admin/(dashboard)/keamanan/polsek-terdekat/create/page.tsx b/src/app/admin/(dashboard)/keamanan/polsek-terdekat/create/page.tsx index 49dc48b6..20b855f7 100644 --- a/src/app/admin/(dashboard)/keamanan/polsek-terdekat/create/page.tsx +++ b/src/app/admin/(dashboard)/keamanan/polsek-terdekat/create/page.tsx @@ -44,18 +44,56 @@ function CreatePolsekTerdekat() { }; }; + const isValidGoogleMapsEmbed = (url: string): boolean => { + try { + const u = new URL(url); + return ( + u.hostname === 'www.google.com' && + u.pathname === '/maps/embed' && + u.searchParams.has('pb') + ); + } catch { + return false; + } +}; + const handleSubmit = async () => { - try { - setIsSubmitting(true); - await polsekState.create.create(); - resetForm(); - router.push("/admin/keamanan/polsek-terdekat"); - } catch (error) { - console.error(error) - toast.error("Gagal menambah polsek terdekat"); - } finally { - setIsSubmitting(false); + const { embedMapUrl } = polsekState.create.form; + + // ✅ Validasi Google Maps Embed URL (jika diisi) + if (embedMapUrl && !isValidGoogleMapsEmbed(embedMapUrl)) { + toast.error("URL embed peta tidak valid. Harap paste iframe dari Google Maps."); + return; + } + + try { + setIsSubmitting(true); + await polsekState.create.create(); + resetForm(); + router.push("/admin/keamanan/polsek-terdekat"); + } catch (error) { + console.error(error); + toast.error("Gagal menambah polsek terdekat"); + } finally { + setIsSubmitting(false); + } +}; + + const extractEmbedUrl = (input: string): string => { + // Jika sudah berupa URL embed yang valid + if (input.startsWith('https://www.google.com/maps/embed?')) { + return input.trim(); } + + // Coba parse sebagai HTML string (iframe) + const iframeRegex = /]*src=["']([^"']*)["'][^>]*>/i; + const match = input.match(iframeRegex); + if (match && match[1]?.startsWith('https://www.google.com/maps/embed?')) { + return match[1].trim(); + } + + // Jika tidak cocok, kembalikan input asli (atau string kosong) + return input.trim(); }; const fetchLayanan = async () => { @@ -190,9 +228,14 @@ function CreatePolsekTerdekat() { /> (polsekState.create.form.embedMapUrl = val.target.value)} + onChange={(e) => { + const rawValue = e.currentTarget.value; + const cleanUrl = extractEmbedUrl(rawValue); + polsekState.create.form.embedMapUrl = cleanUrl; + }} + description="Contoh: https://www.google.com/maps/embed?pb=..." label={Embed Map URL} - placeholder="Masukkan embed map url" + placeholder="Paste iframe dari Google Maps atau URL embed langsung" /> + - - - - - - Nama Sdgs Desa - Jumlah - Aksi - - - - - - Tidak ada data Sdgs Desa - - - -
-
- -
- ); - } + const isEmpty = data.length === 0; return ( - - - - Daftar Sdgs Desa - + + + + + Daftar Sdgs Desa + + - - + + {/* Desktop Table */} + +
- Nama Sdgs Desa - Jumlah - Aksi + + + Nama Sdgs Desa + + + + + Jumlah + + + + + Aksi + + - {filteredData.map((item) => ( - - - - {item.name} + {isEmpty ? ( + + + + Tidak ada data Sdgs Desa - - - {item.jumlah || '0'} - - - - - - - ))} + + + )) + )}
+ + {/* Mobile Cards */} + + {isEmpty ? ( +
+ + Tidak ada data Sdgs Desa + +
+ ) : ( + + {filteredData.map((item) => ( + + + + {item.name} + + + Jumlah: {item.jumlah || '0'} + + + + + + + ))} + + )} +
-
- { - load(newPage, 10); - window.scrollTo(0, 0); - }} - total={Math.max(1, totalPages)} - withEdges - radius="md" - /> -
+ + {!isEmpty && ( +
+ { + load(newPage, 10); + window.scrollTo(0, 0); + }} + total={Math.max(1, totalPages)} + withEdges + radius="md" + /> +
+ )}
- ) + ); } -export default SdgsDesa; +export default SdgsDesa; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/apbdes/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/apbdes/[id]/edit/page.tsx index a7914073..e6cd2dcc 100644 --- a/src/app/admin/(dashboard)/landing-page/apbdes/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/apbdes/[id]/edit/page.tsx @@ -204,7 +204,7 @@ function EditAPBDes() { }; return ( - + - + + {/* Desktop Table */} + + + + + Daftar APBDes + + + - - - - - APBDes - Tahun - Dokumen - Aksi - - - - {filteredData.length > 0 ? ( - filteredData.map((item) => ( - - - - APBDes {item.tahun} - - - - {item.tahun || '-'} - - - {item.file?.link ? ( - - ) : ( - - Tidak ada dokumen + +
+ + + + APBDes + + + Tahun + + + Dokumen + + + Aksi + + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + APBDes {item.tahun} - )} - - - + + + + {item.tahun || '-'} + + + + {item.file?.link ? ( + + ) : ( + + Tidak ada dokumen + + )} + + - + + + )) + ) : ( + + +
+ + Tidak ada data APBDes yang cocok + +
- )) - ) : ( - - -
- Tidak ada data APBDes yang cocok -
-
-
- )} -
-
-
-
+ )} + + +
+
+
-
+ {/* Mobile Cards */} + + + + + Daftar APBDes + + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + APBDes {item.tahun} + + + + Tahun + + + {item.tahun || '-'} + + + + + + Dokumen + + {item.file?.link ? ( + + ) : ( + + Tidak ada + + )} + + + + + + )) + ) : ( + +
+ + Tidak ada data APBDes yang cocok + +
+
+ )} +
+
+
+ +
{ diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx index 401f85a3..fd7a715f 100644 --- a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx @@ -3,6 +3,7 @@ import colors from "@/con/colors"; import { + Box, ScrollArea, Stack, Tabs, @@ -68,37 +69,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) { keepMounted={false} > {/* ✅ Scroll horizontal wrapper */} - - - {tabs.map((tab, i) => ( - - {tab.label} - - ))} - - + + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + + + + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + {tabs.map((tab, i) => ( + - - - - - - Nama Kategori - Edit - Hapus + // Mobile cards + const renderMobileCards = () => ( + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + + {item.name} + + + + + + + + + )) + ) : ( + + + Tidak ada data kategori yang ditemukan + + + )} + + ); + + // Desktop table + const renderDesktopTable = () => ( + +
+ + + + + Nama Kategori + + + + + Edit + + + + + Hapus + + + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + {item.name} + + + + + + + + - - - {filteredData.length > 0 ? ( - filteredData.map((item) => ( - - - - {item.name} - - - - - - - - - - )) - ) : ( - - -
- Tidak ada data kategori yang ditemukan -
-
-
- )} -
-
-
+ )) + ) : ( + + + + Tidak ada data kategori yang ditemukan + + + + )} + + +
+ ); + + return ( + + + + + Daftar Kategori Kegiatan + + + + + {renderDesktopTable()} + {renderMobileCards()} -
- { - load(newPage, 10); - window.scrollTo({ top: 0, behavior: 'smooth' }); - }} - total={totalPages} - mt="md" - mb="md" - color="blue" - radius="md" - /> -
- {/* Modal Konfirmasi Hapus */} + + {totalPages > 1 && ( +
+ { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + color="blue" + radius="md" + /> +
+ )} + setModalHapus(false)} @@ -158,4 +236,4 @@ function ListKategoriKegiatan({ search }: { search: string }) { ); } -export default KategoriDesaAntiKorupsi +export default KategoriDesaAntiKorupsi; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx index eaf0c853..423a1f99 100644 --- a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx @@ -150,7 +150,7 @@ export default function EditDesaAntiKorupsi() { }; return ( - + + + + + + + Daftar Program Desa Anti Korupsi + + - + + {/* Desktop Table */} + - Nama Program - Kategori - Aksi + Nama Program + Kategori + + Aksi + {filteredData.length > 0 ? ( filteredData.map((item) => ( - - + + {item.name || '-'} - - - + + {item.kategori?.name || '-'} - - +
+ + {/* Mobile Cards */} + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + {item.name || '-'} + + + Kategori: {item.kategori?.name || '-'} + + + + + + + )) + ) : ( + + + Tidak ditemukan data dengan kata kunci pencarian + + + )} + +
@@ -144,7 +196,6 @@ function ListDesaAntiKorupsi({ search }: { search: string }) { }} size="md" radius="md" - mt="md" />
@@ -152,4 +203,4 @@ function ListDesaAntiKorupsi({ search }: { search: string }) { ); } -export default DesaAntiKorupsi; +export default DesaAntiKorupsi; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/_lib/layoutTab.tsx b/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/_lib/layoutTab.tsx index 4b910589..cb633834 100644 --- a/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/_lib/layoutTab.tsx +++ b/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/_lib/layoutTab.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; import { IconChartBar, IconUsers } from '@tabler/icons-react'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; @@ -53,36 +53,41 @@ function LayoutTabsKepuasan({ children }: { children: React.ReactNode }) { radius="lg" keepMounted={false} > + {/* ✅ Scroll horizontal wrapper */} - - - {tabs.map((e, i) => ( - - {e.label} - - ))} - - + + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + + {tabs.map((e, i) => ( <> diff --git a/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/[id]/edit/page.tsx index 6e123982..3cd5d1b0 100644 --- a/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/[id]/edit/page.tsx @@ -149,7 +149,7 @@ function EditResponden() { ); return ( - + + +
+ )) + )} + +
+
@@ -175,4 +228,4 @@ function ListResponden({ search }: ListRespondenProps) { ); } -export default Responden; +export default Responden; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/prestasi-desa/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/landing-page/prestasi-desa/_lib/layoutTabs.tsx index 109e8f80..0988e9c9 100644 --- a/src/app/admin/(dashboard)/landing-page/prestasi-desa/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/landing-page/prestasi-desa/_lib/layoutTabs.tsx @@ -56,6 +56,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) { radius="lg" keepMounted={false} > + {tabs.map((tab, i) => ( @@ -74,6 +79,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) { fontWeight: 600, fontSize: "0.9rem", transition: "all 0.2s ease", + flexShrink: 0, // ✅ jangan mengecil aneh-aneh }} > {tab.label} diff --git a/src/app/admin/(dashboard)/landing-page/prestasi-desa/kategori-prestasi-desa/[id]/page.tsx b/src/app/admin/(dashboard)/landing-page/prestasi-desa/kategori-prestasi-desa/[id]/page.tsx index 0cb9cbbe..b0128513 100644 --- a/src/app/admin/(dashboard)/landing-page/prestasi-desa/kategori-prestasi-desa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/prestasi-desa/kategori-prestasi-desa/[id]/page.tsx @@ -78,7 +78,7 @@ function EditKategoriPrestasi() { }; return ( - + + + List Kategori Prestasi + - - + - Nama Kategori - Edit - Delete + Nama Kategori + Edit + Delete {filteredData.length === 0 ? ( - - + + {search ? 'Tidak ada hasil yang cocok' : 'Belum ada data kategori'} @@ -95,68 +100,130 @@ function ListKategoriPrestasi({ search }: { search: string }) { filteredData.map((item) => ( - - {item.name} - + + {item.name} + - - + + - - + + )) )}
+ + {totalPages > 1 && ( +
+ load(newPage)} + total={totalPages} + withEdges + size="sm" + styles={{ + control: { + '&[data-active]': { + background: `${colors['blue-button']} !important`, + }, + }, + }} + /> +
+ )}
- {totalPages > 1 && ( -
- load(newPage)} - total={totalPages} - withEdges - size="sm" - styles={{ - control: { - '&[data-active]': { - background: `${colors['blue-button']} !important`, - }, - }, - }} - /> -
- )} + {/* MOBILE: Card */} + + + {filteredData.length === 0 ? ( + + + {search ? 'Tidak ada hasil yang cocok' : 'Belum ada data kategori'} + + + ) : ( + filteredData.map((item) => ( + + + {item.name} + + + + + + + )) + )} + + {totalPages > 1 && ( +
+ load(newPage)} + total={totalPages} + withEdges + size="xs" + styles={{ + control: { + '&[data-active]': { + background: `${colors['blue-button']} !important`, + }, + }, + }} + /> +
+ )} +
+
+ + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus kategori prestasi ini?' + /> - {/* Modal Konfirmasi Hapus */} - setModalHapus(false)} - onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus kategori prestasi ini?' - /> -
+
); } -export default KategoriPrestasiDesa +export default KategoriPrestasiDesa \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/prestasi-desa/list-prestasi-desa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/prestasi-desa/list-prestasi-desa/[id]/edit/page.tsx index 085d347a..402ede7e 100644 --- a/src/app/admin/(dashboard)/landing-page/prestasi-desa/list-prestasi-desa/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/prestasi-desa/list-prestasi-desa/[id]/edit/page.tsx @@ -128,7 +128,7 @@ export default function EditPrestasiDesa() { }; return ( - + - - + + {/* Desktop Table */} + +
- Nama Prestasi - Deskripsi - Kategori - Aksi + Nama Prestasi + Deskripsi + Kategori + Aksi {filteredData.length > 0 ? ( filteredData.map((item) => ( - - - {item.name} - + + + {item.name} + - - + + - - - {item.kategori?.name || 'Tidak ada kategori'} - + + + {item.kategori?.name || 'Tidak ada kategori'} + - +
+ + {/* Mobile Cards */} + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + {item.name} + + + + Kategori: {item.kategori?.name || 'Tidak ada kategori'} + + + + + + + )) + ) : ( +
+ + Tidak ada data prestasi + +
+ )} +
+ {totalPages > 1 && ( -
+
)} @@ -132,4 +178,4 @@ function ListPrestasi({ search }: { search: string }) { ) } -export default ListPrestasiDesa; +export default ListPrestasiDesa; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/profil/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/landing-page/profil/_lib/layoutTabs.tsx index 6d889a1f..43419da7 100644 --- a/src/app/admin/(dashboard)/landing-page/profil/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/landing-page/profil/_lib/layoutTabs.tsx @@ -2,6 +2,7 @@ 'use client' import colors from '@/con/colors'; import { + Box, ScrollArea, Stack, Tabs, @@ -74,36 +75,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) { keepMounted={false} > {/* ✅ Scroll horizontal wrapper */} - - + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + + + + - {tabs.map((tab, i) => ( - - {tab.label} - - ))} - - + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + {tabs.map((tab, i) => ( + - + <Title order={2} ml="sm" c="dark" lh={1.2} fz={{ base: 'md', md: 'lg' }}> Tambah Media Sosial @@ -155,7 +153,7 @@ export default function CreateMediaSosial() { {/* Custom icon uploader */} {selectedSosmed === 'custom' && ( - + Upload Custom Icon @@ -185,8 +183,10 @@ export default function CreateMediaSosial() { - Seret gambar atau klik untuk pilih - + + Seret gambar atau klik untuk pilih + + Maksimal 5MB, format .png, .jpg, .jpeg, webp @@ -229,7 +229,11 @@ export default function CreateMediaSosial() { {/* Input name */} + Nama Media Sosial + + } placeholder="Masukkan nama media sosial" value={stateMediaSosial.create.form.name ?? ''} onChange={(e) => (stateMediaSosial.create.form.name = e.target.value)} @@ -238,7 +242,11 @@ export default function CreateMediaSosial() { {/* Input link */} + Link / Kontak + + } placeholder="Masukkan link atau nomor" value={stateMediaSosial.create.form.iconUrl ?? ''} onChange={(e) => (stateMediaSosial.create.form.iconUrl = e.target.value)} @@ -266,4 +274,4 @@ export default function CreateMediaSosial() { ); -} +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/profil/media-sosial/page.tsx b/src/app/admin/(dashboard)/landing-page/profil/media-sosial/page.tsx index 540037c5..ffc1c669 100644 --- a/src/app/admin/(dashboard)/landing-page/profil/media-sosial/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/profil/media-sosial/page.tsx @@ -1,7 +1,25 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Group, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Image, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -28,11 +46,11 @@ function MediaSosial() { } function ListMediaSosial({ search }: { search: string }) { - const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial) + const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial); const router = useRouter(); const getIconSource = (item: any) => { - if (item.image?.link) return item.image.link; + if (item.image?.link) return item.image.link; if (item.icon && sosmedMap[item.icon as keyof typeof sosmedMap]?.src) { return sosmedMap[item.icon as keyof typeof sosmedMap].src; } @@ -48,101 +66,201 @@ function ListMediaSosial({ search }: { search: string }) { } = stateMediaSosial.findMany; useShallowEffect(() => { - load(page, 10, search) - }, [page, search]) + load(page, 10, search); + }, [page, search]); - const filteredData = data || [] + const filteredData = data || []; if (loading || !data) { return ( - + ); } return ( - - - - Daftar Media Sosial - - - - - - Nama Media Sosial / Kontak - Gambar - Link / No. Telepon - Aksi - - - - {filteredData.length > 0 ? ( - filteredData.map((item) => ( - - - {item.name} - - - - {(() => { - const src = getIconSource(item); - - if (src) { - return ( - - ); - } - - return ; - })()} - - - - - - - {item.iconUrl || item.noTelp || '-'} + + {/* Desktop: Table | Mobile: Card-based vertical layout */} + +
+ + + + + Nama Media Sosial / Kontak + + + + + Gambar + + + + + Link / No. Telepon + + + + + Aksi + + + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + {item.name} - - - - + + + + {(() => { + const src = getIconSource(item); + if (src) { + return ( + + ); + } + return ; + })()} + + + + + + {item.iconUrl || item.noTelp || '-'} + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data media sosial yang cocok + +
- )) - ) : ( - - -
- Tidak ada data media sosial yang cocok -
-
-
- )} -
-
+ )} + + +
+ + {/* Mobile layout */} + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + + {item.name} + + + + {(() => { + const src = getIconSource(item); + if (src) { + return ( + {item.name} + ); + } + return ; + })()} + + + + + + {item.iconUrl || item.noTelp || '-'} + + + + + + + + )) + ) : ( +
+ + Tidak ada data media sosial yang cocok + +
+ )} +
+
+ + {dataArray.map((item) => ( @@ -52,7 +51,7 @@ function Page() {
- Logo Desa + Logo Desa
@@ -93,7 +92,7 @@ function Page() { Jabatan - + {item.position} diff --git a/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/edit/page.tsx index 9d41f051..c988b087 100644 --- a/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/edit/page.tsx @@ -130,7 +130,7 @@ function EditProgramInovasi() { }; return ( - + + + + + Gambar {data.image?.link ? ( - Gambar Program - + - +
diff --git a/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/create/page.tsx b/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/create/page.tsx index f13ab8ef..eac0393f 100644 --- a/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/create/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/profil/program-inovasi/create/page.tsx @@ -76,7 +76,7 @@ function CreateProgramInovasi() { }; return ( - + + color="blue" + leftSection={} + variant="light" + radius="md" + onClick={() => router.push('/admin/landing-page/profil/program-inovasi/create')} + > + Tambah Program + - - - - - Nama Program - Deskripsi - Link - Aksi - - - - {filteredData.length === 0 ? ( + + +
+ - -
- Belum ada data program inovasi -
-
+ Nama Program + Deskripsi + Link + Aksi
- ) : ( - filteredData.map((item) => ( - - - {item.name} - - - - - - - - {item.link} - - - - - +
+ + {filteredData.length === 0 ? ( + + +
+ Belum ada data program inovasi +
- )) - )} -
-
+ ) : ( + filteredData.map((item) => ( + + + {item.name} + + + + + + + + {item.link} + + + + + + + + )) + )} + + +
+ + + {filteredData.map((item) => ( + + + {/* Title */} + {item.name} + + {/* Description */} + + {item.description || '-'} + + + {/* Link */} + + + + {item.link} + + + + + {/* Action */} + + + + + + ))} + + + {filteredData.length > 0 && (
_.lowerCase(s)); - -// // const { user } = useSnapshot(authStore); - -// // console.log("Current user in store:", user); - -// // ✅ FIX: Selalu fetch user data setiap kali komponen mount -// useEffect(() => { -// const fetchUser = async () => { -// try { -// const res = await fetch('/api/auth/me'); -// const data = await res.json(); - -// if (data.user) { -// // ✅ Check if user is NOT active → redirect to waiting room -// if (!data.user.isActive) { -// authStore.setUser(null); -// router.replace('/waiting-room'); -// return; -// } - -// // ✅ Fetch menuIds -// const menuRes = await fetch(`/api/admin/user-menu-access?userId=${data.user.id}`); -// const menuData = await menuRes.json(); - -// const menuIds = menuData.success && Array.isArray(menuData.menuIds) -// ? [...menuData.menuIds] -// : null; - -// // ✅ Set user dengan menuIds yang fresh -// authStore.setUser({ -// id: data.user.id, -// name: data.user.name, -// roleId: Number(data.user.roleId), -// menuIds, -// isActive: data.user.isActive -// }); - -// // ✅ TAMBAHKAN INI: Redirect ke dashboard sesuai roleId -// const currentPath = window.location.pathname; -// const expectedPath = getRedirectPath(Number(data.user.roleId)); - -// // Jika user di halaman /admin tapi bukan di path yang sesuai roleId -// if (currentPath === '/admin' || !currentPath.startsWith(expectedPath)) { -// router.replace(expectedPath); -// } - -// } else { -// authStore.setUser(null); -// router.replace('/login'); -// } -// } catch (error) { -// console.error('Gagal memuat data pengguna:', error); -// authStore.setUser(null); -// router.replace('/login'); -// } finally { -// setLoading(false); -// } -// }; - -// fetchUser(); -// }, [router]); - -// // ✅ Fungsi helper untuk get redirect path -// const getRedirectPath = (roleId: number): string => { -// switch (roleId) { -// case 0: // DEVELOPER -// case 1: // SUPERADMIN -// case 2: // ADMIN_DESA -// return '/admin/landing-page/profil/program-inovasi'; -// case 3: // ADMIN_KESEHATAN -// return '/admin/kesehatan/posyandu'; -// case 4: // ADMIN_PENDIDIKAN -// return '/admin/pendidikan/info-sekolah/jenjang-pendidikan'; -// default: -// return '/admin'; -// } -// }; - -// if (loading) { -// return ( -// -// -//
-// -//
-//
-//
-// ); -// } - -// // ✅ Ambil menu berdasarkan roleId dan menuIds -// const currentNav = authStore.user -// ? getNavbar({ roleId: authStore.user.roleId, menuIds: authStore.user.menuIds }) -// : []; - -// const handleLogout = async () => { -// try { -// setIsLoggingOut(true); - -// // ✅ Panggil API logout untuk clear session di server -// const response = await fetch('/api/auth/logout', { method: 'POST' }); -// const result = await response.json(); - -// if (result.success) { -// // Clear user data dari store -// authStore.setUser(null); - -// // Clear localStorage -// localStorage.removeItem('auth_nomor'); -// localStorage.removeItem('auth_kodeId'); - -// // Force reload untuk reset semua state -// window.location.href = '/login'; -// } else { -// console.error('Logout failed:', result.message); -// // Tetap redirect meskipun gagal -// authStore.setUser(null); -// window.location.href = '/login'; -// } -// } catch (error) { -// console.error('Error during logout:', error); -// // Tetap clear store dan redirect jika error -// authStore.setUser(null); -// window.location.href = '/login'; -// } finally { -// setIsLoggingOut(false); -// } -// }; - -// return ( -// -// -// -// -// Logo Darmasaba -// -// Admin Darmasaba -// -// - -// -// {!desktopOpened && ( -// -// -// -// -// -// )} - -// - -// -// { -// router.push("/darmasaba"); -// }} -// color={colors["blue-button"]} -// radius="xl" -// size="lg" -// variant="gradient" -// gradient={{ from: colors["blue-button"], to: "#228be6" }} -// > -// Logo Darmasaba -// -// -// -// -// -// -// -// -// -// - -// -// -// {currentNav.map((v, k) => { -// const isParentActive = segments.includes(_.lowerCase(v.name)); - -// return ( -// -// {v.name} -// -// } -// style={{ -// borderRadius: rem(10), -// marginBottom: rem(4), -// transition: "background 150ms ease", -// }} -// styles={{ -// root: { -// '&:hover': { -// backgroundColor: 'rgba(25, 113, 194, 0.05)', -// }, -// }, -// }} -// variant="light" -// active={isParentActive} -// > -// {v.children.map((child, key) => { -// const isChildActive = segments.includes( -// _.lowerCase(child.name) -// ); - -// return ( -// -// {child.name} -// -// } -// styles={{ -// root: { -// borderRadius: rem(8), -// marginBottom: rem(2), -// transition: 'background 150ms ease', -// padding: '6px 12px', -// '&:hover': { -// backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)', -// }, -// ...(isChildActive && { -// backgroundColor: 'rgba(25, 113, 194, 0.1)', -// }), -// }, -// }} -// active={isChildActive} -// component={Link} -// /> -// ); -// })} -// -// ); -// })} -// - -// -// -// -// -// -// -// -// -// -// - -// -// {children} -// -// -// ); -// } - - -// app/admin/layout.tsx - 'use client' import colors from "@/con/colors"; @@ -429,7 +33,7 @@ import { useEffect, useState } from "react"; import { getNavbar } from "./(dashboard)/user&role/_com/dynamicNavbar"; export default function Layout({ children }: { children: React.ReactNode }) { - const [opened, { toggle }] = useDisclosure(); + const [opened, { toggle, close }] = useDisclosure(); // ✅ Tambahkan 'close' const [loading, setLoading] = useState(true); const [isLoggingOut, setIsLoggingOut] = useState(false); const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true); @@ -441,21 +45,19 @@ export default function Layout({ children }: { children: React.ReactNode }) { const fetchUser = async () => { try { const res = await fetch('/api/auth/me', { - credentials: 'include' // ✅ ADD credentials + credentials: 'include' }); const data = await res.json(); if (data.user) { - // ✅ Check if user is NOT active → redirect to waiting room if (!data.user.isActive) { authStore.setUser(null); router.replace('/waiting-room'); return; } - // ✅ Fetch menuIds const menuRes = await fetch(`/api/admin/user-menu-access?userId=${data.user.id}`, { - credentials: 'include' // ✅ ADD credentials + credentials: 'include' }); const menuData = await menuRes.json(); @@ -463,7 +65,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { ? [...menuData.menuIds] : null; - // ✅ Set user dengan menuIds yang fresh authStore.setUser({ id: data.user.id, name: data.user.name, @@ -472,7 +73,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { isActive: data.user.isActive }); - // ✅ IMPROVED: Redirect ONLY if di root /admin const currentPath = window.location.pathname; if (currentPath === '/admin') { @@ -480,7 +80,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { console.log('🔄 Redirecting from /admin to:', expectedPath); router.replace(expectedPath); } - // ✅ Jangan redirect jika user sudah di path yang valid } else { authStore.setUser(null); @@ -496,17 +95,17 @@ export default function Layout({ children }: { children: React.ReactNode }) { }; fetchUser(); - }, [router]); // ✅ Only depend on router + }, [router]); const getRedirectPath = (roleId: number): string => { switch (roleId) { - case 0: // DEVELOPER - case 1: // SUPERADMIN - case 2: // ADMIN_DESA + case 0: + case 1: + case 2: return '/admin/landing-page/profil/program-inovasi'; - case 3: // ADMIN_KESEHATAN + case 3: return '/admin/kesehatan/posyandu'; - case 4: // ADMIN_PENDIDIKAN + case 4: return '/admin/pendidikan/info-sekolah/jenjang-pendidikan'; default: return '/admin'; @@ -535,7 +134,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { const response = await fetch('/api/auth/logout', { method: 'POST', - credentials: 'include' // ✅ ADD credentials + credentials: 'include' }); const result = await response.json(); @@ -559,6 +158,12 @@ export default function Layout({ children }: { children: React.ReactNode }) { } }; + // ✅ Handler untuk menutup mobile menu saat navigasi + const handleNavClick = (path: string) => { + router.push(path); + close(); // Tutup mobile menu + }; + return ( - {/* ... rest of your JSX (Header, Navbar, Main) sama seperti sebelumnya ... */} - {/* ... Navbar content sama seperti sebelumnya ... */} {currentNav.map((v, k) => { const isParentActive = segments.includes(_.lowerCase(v.name)); return ( - {v.name}} style={{ borderRadius: rem(10), marginBottom: rem(4), transition: "background 150ms ease" }} styles={{ root: { '&:hover': { backgroundColor: 'rgba(25, 113, 194, 0.05)' } } }} variant="light" active={isParentActive}> + {v.name}} + style={{ borderRadius: rem(10), marginBottom: rem(4), transition: "background 150ms ease" }} + styles={{ root: { '&:hover': { backgroundColor: 'rgba(25, 113, 194, 0.05)' } } }} + variant="light" + active={isParentActive} + > {v.children.map((child, key) => { const isChildActive = segments.includes(_.lowerCase(child.name)); return ( - {child.name}} styles={{ root: { borderRadius: rem(8), marginBottom: rem(2), transition: 'background 150ms ease', padding: '6px 12px', '&:hover': { backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)' }, ...(isChildActive && { backgroundColor: 'rgba(25, 113, 194, 0.1)' }) } }} active={isChildActive} component={Link} /> + { + e.preventDefault(); + handleNavClick(child.path); + }} + href={child.path} + c={isChildActive ? colors["blue-button"] : "gray"} + label={{child.name}} + styles={{ + root: { + borderRadius: rem(8), + marginBottom: rem(2), + transition: 'background 150ms ease', + padding: '6px 12px', + '&:hover': { + backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)' + }, + ...(isChildActive && { backgroundColor: 'rgba(25, 113, 194, 0.1)' }) + } + }} + active={isChildActive} + component={Link} + /> ); })} diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/jumlah-penduduk-miskin/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/jumlah-penduduk-miskin/findMany.ts index 4cc14207..7bcfc08c 100644 --- a/src/app/api/[[...slugs]]/_lib/ekonomi/jumlah-penduduk-miskin/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/jumlah-penduduk-miskin/findMany.ts @@ -28,7 +28,7 @@ export default async function grafikJumlahPendudukMiskinFindMany( where, skip, take: limit, - orderBy: { createdAt: "desc" }, + orderBy: { year: "asc" }, }), prisma.grafikJumlahPendudukMiskin.count({ where, diff --git a/src/app/darmasaba/(pages)/ekonomi/demografi-pekerjaan/page.tsx b/src/app/darmasaba/(pages)/ekonomi/demografi-pekerjaan/page.tsx index 6f88b299..e665b354 100644 --- a/src/app/darmasaba/(pages)/ekonomi/demografi-pekerjaan/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/demografi-pekerjaan/page.tsx @@ -1,6 +1,6 @@ 'use client' import colors from '@/con/colors'; -import { Stack, Box, Paper, Text, ColorSwatch, Flex, Skeleton } from '@mantine/core'; +import { Stack, Box, Paper, Text, ColorSwatch, Flex, Skeleton, Title } from '@mantine/core'; import React from 'react'; import BackButton from '../../desa/layanan/_com/BackButto'; import { BarChart } from '@mantine/charts'; @@ -32,23 +32,47 @@ function Page() { - - + + Demografi Pekerjaan + + + Desa Darmasaba memiliki komposisi penduduk yang beragam dalam sektor pekerjaan - Desa Darmasaba memiliki komposisi penduduk yang beragam dalam sektor pekerjaan - - Statistik Demografi Pekerjaan Di Desa Darmasaba + + + Statistik Demografi Pekerjaan Di Desa Darmasaba + ({ id: item.id, Pekerjaan: item.pekerjaan, @@ -62,28 +86,45 @@ function Page() { ]} tickLine="y" xAxisProps={{ - angle: -45, // Rotate labels by -45 degrees - textAnchor: 'end', // Anchor text to the end for better alignment - height: 100, // Increase height for rotated labels - interval: 0, // Show all labels + angle: -45, + textAnchor: 'end', + height: 100, + interval: 0, style: { - fontSize: '12px', // Adjust font size if needed + fontSize: '12px', overflow: 'visible', - whiteSpace: 'nowrap' + whiteSpace: 'nowrap', + lineHeight: 1.4, } }} /> - - Laki-Laki + + + Laki-Laki + - - Perempuan + + + Perempuan + @@ -95,4 +136,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/jumlah-penduduk-miskin/page.tsx b/src/app/darmasaba/(pages)/ekonomi/jumlah-penduduk-miskin/page.tsx index 9f0e9041..9673b549 100644 --- a/src/app/darmasaba/(pages)/ekonomi/jumlah-penduduk-miskin/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/jumlah-penduduk-miskin/page.tsx @@ -2,7 +2,7 @@ import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin'; import colors from '@/con/colors'; import { BarChart } from '@mantine/charts'; -import { Box, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Paper, Skeleton, Stack, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; @@ -17,13 +17,10 @@ function Page() { const state = useProxy(jumlahPendudukMiskin) const [chartData, setChartData] = useState([]) - useShallowEffect(() => { state.findMany.load() }, []) - - useEffect(() => { if (state.findMany.data) { setChartData(state.findMany.data.map((item) => ({ @@ -48,20 +45,30 @@ function Page() { - + Jumlah Penduduk Miskin - </Text> + - Jumlah Data Penduduk Miskin - + + Jumlah Data Penduduk Miskin + + {state.findMany.data?.reduce((sum, item) => sum + (Number(item.totalPoorPopulation) || 0), 0).toLocaleString()} Orang - </Text> + - Jumlah Penduduk Miskin Per Tahun + + Jumlah Penduduk Miskin Per Tahun + @@ -64,114 +64,151 @@ function Page() { ) } - if (!stateGrafikNganggur.findMany.data) { - return ( - - - - ) - } return ( - - + + Jumlah Penduduk Usia Kerja Yang Menganggur - </Text> + - Pengangguran Berdasarkan Usia - {mounted && donutGrafikNganggurData.length > 0 ? ( - - + + Pengangguran Berdasarkan Usia + + {mounted && donutGrafikNganggurData.length > 0 ? ( + + + + - ) : } + ) : ( + + )} - 18-25 + + 18-25 + - 26-35 + + 26-35 + - 36-45 + + 36-45 + - 46+ + + 46+ + - Pengangguran Berdasarkan Pendidikan - {mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (
- - - -
) : } + + Pengangguran Berdasarkan Pendidikan + + {mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? ( +
+ + + +
+ ) : ( + + )} - SD + + SD + - SMP + + SMP + - SMA/SMK + + SMA/SMK + - D3 + + D3 + - S1 + + S1 + @@ -183,4 +220,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/jumlah-pengangguran/page.tsx b/src/app/darmasaba/(pages)/ekonomi/jumlah-pengangguran/page.tsx index ba8b5920..25dd4632 100644 --- a/src/app/darmasaba/(pages)/ekonomi/jumlah-pengangguran/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/jumlah-pengangguran/page.tsx @@ -36,7 +36,6 @@ function Page() { useEffect(() => { setMounted(true); if (state.findMany.data) { - // Set chart data setChartData(state.findMany.data.map((item) => ({ id: item.id, bulan: item.month, @@ -44,7 +43,6 @@ function Page() { takberpendidikan: Number(item.uneducatedUnemployment), }))); - // Calculate yearly totals const currentYearData = state.findMany.data.filter(item => item.year === currentYear); if (currentYearData.length > 0) { const yearlyTotal = { @@ -72,30 +70,37 @@ function Page() { - - + + Jumlah Pengangguran - </Text> + - DATA PENGANGGURAN DESA + + DATA PENGANGGURAN DESA + - + {/* Total Unemployment Card */} - Total Pengangguran - + + Total Pengangguran + + {yearlyData?.total.toLocaleString() || 0} Orang - + Total data tahun {currentYear} @@ -105,11 +110,13 @@ function Page() { - Pengangguran Terdidik - + + Pengangguran Terdidik + + {yearlyData?.educated.toLocaleString() || 0} Orang - + {yearlyData ? <> {((yearlyData.educated / yearlyData.total) * 100).toFixed(1)}% @@ -123,11 +130,13 @@ function Page() { - Pengangguran Tidak Terdidik - + + Pengangguran Tidak Terdidik + + {yearlyData?.uneducated.toLocaleString() || 0} Orang - + {yearlyData ? <> {((yearlyData.uneducated / yearlyData.total) * 100).toFixed(1)}% @@ -142,13 +151,17 @@ function Page() { - Pengangguran Berpendidikan + + Pengangguran Berpendidikan + - Pengangguran Tak Berpendidikan + + Pengangguran Tak Berpendidikan + @@ -156,15 +169,24 @@ function Page() { {!mounted || chartData.length === 0 ? ( - Data Pengangguran Terdidik dan Tidak Terdidik - Belum ada data untuk ditampilkan dalam grafik + + Data Pengangguran Terdidik dan Tidak Terdidik + + + Belum ada data untuk ditampilkan dalam grafik + ) : ( - Data Pengangguran Terdidik dan Tidak Terdidik - + + Data Pengangguran Terdidik dan Tidak Terdidik + + )} - - Detail Data Pengangguran - - - - Bulan - Total - Terdidik - Tidak Terdidik - Perubahan - - - - {state.findMany.data?.map((item, index) => ( - - {item.month} - {item.totalUnemployment} - {item.educatedUnemployment} - {item.uneducatedUnemployment} - {item.percentageChange}% + + Detail Data Pengangguran + + +
+ + + + Bulan + + + Total + + + Terdidik + + + Tidak Terdidik + + + Perubahan + - ))} - -
+ + + {state.findMany.data?.map((item, index) => ( + + + {item.month} + + + {item.totalUnemployment} + + + {item.educatedUnemployment} + + + {item.uneducatedUnemployment} + + + {item.percentageChange}% + + + ))} + + +
@@ -211,4 +256,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/[id]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/[id]/page.tsx index 53d29443..73aa801a 100644 --- a/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/[id]/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/lowongan-kerja-lokal/[id]/page.tsx @@ -2,7 +2,7 @@ import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja'; import colors from '@/con/colors'; -import { Box, Button, Center, Group, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Center, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack, IconBrandWhatsapp, IconBriefcase, IconCurrencyDollar, IconMapPin, IconPhone } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -33,18 +33,25 @@ function DetailLowonganKerjaUser() { ); } + const formatRupiah = (value: number) => + new Intl.NumberFormat("id-ID", { + style: "currency", + currency: "IDR", + minimumFractionDigits: 0, + }).format(value); + return ( - + - {/* Judul */} - + {/* Judul Posisi - H1 */} + {data.posisi} - </Text> - <Text c="dimmed" fz="sm"> + + + {/* Tanggal Posting - Caption */} + Diposting: {new Date(data.createdAt).toLocaleDateString('id-ID', { day: '2-digit', month: 'long', @@ -70,44 +83,72 @@ function DetailLowonganKerjaUser() { - {data.namaPerusahaan} + + {data.namaPerusahaan} + - {data.lokasi} + + {data.lokasi} + - {data.notelp} + + {data.notelp} + - {data.gaji || '-'} + + {formatRupiah(Number(data.gaji)) || '-'} + - {data.tipePekerjaan} + + {data.tipePekerjaan} + + {/* Deskripsi Pekerjaan - H2 */} - + Deskripsi Pekerjaan - </Text> + + {/* Kualifikasi - H2 */} - + Kualifikasi - </Text> + - + Lowongan Kerja Lokal - </Text> + } value={search} onChange={(e) => setSearch(e.currentTarget.value)} + fz={{ base: 'sm', md: 'md' }} + lh={1.5} /> @@ -80,30 +82,42 @@ function Page() { - - + + - {v.posisi} - {v.namaPerusahaan} + + {v.posisi} + + + {v.namaPerusahaan} + - - - {v.lokasi} + + + + {v.lokasi} + - - + + - Full Time - {formatCurrency(v.gaji)} + + Full Time + + + {formatCurrency(v.gaji)} + - + ) @@ -123,4 +137,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx index c013d69f..579c8286 100644 --- a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx @@ -1,7 +1,7 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider } from '@mantine/core'; -import { IconArrowBack, IconMapPin, IconPhone, IconStar } from '@tabler/icons-react'; +import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider, Title } from '@mantine/core'; +import { IconArrowBack, IconBrandWhatsapp, IconMapPin, IconPhone, IconStar } from '@tabler/icons-react'; import { useRouter, useParams } from 'next/navigation'; import React from 'react'; import { useProxy } from 'valtio/utils'; @@ -31,14 +31,16 @@ function DetailProdukPasarUser() { {/* Tombol kembali */} - + - Tidak ada gambar + + Tidak ada gambar + )} {/* Detail Produk */} - + {data.nama || 'Produk Tanpa Nama'} - </Text> + - Rp {data.harga?.toLocaleString('id-ID')} + + Rp {data.harga?.toLocaleString('id-ID')} + {data.rating && ( - {data.rating} + + {data.rating} + )} @@ -95,16 +102,20 @@ function DetailProdukPasarUser() { {/* Info Tambahan */} - Kategori + + Kategori + {data.KategoriToPasar && data.KategoriToPasar.length > 0 ? ( data.KategoriToPasar.map((kategori) => ( - + {kategori.kategori.nama} )) ) : ( - Tidak ada kategori + + Tidak ada kategori + )} @@ -112,14 +123,18 @@ function DetailProdukPasarUser() { {data.alamatUsaha && ( - {data.alamatUsaha} + + {data.alamatUsaha} + )} {data.kontak && ( - {data.kontak} + + {data.kontak} + )} @@ -128,8 +143,10 @@ function DetailProdukPasarUser() { {/* Deskripsi */} - Deskripsi Produk - + + Deskripsi Produk + + Tidak ada deskripsi. @@ -144,8 +161,11 @@ function DetailProdukPasarUser() { component="a" href={`https://wa.me/${data.kontak.replace(/[^0-9]/g, '')}`} target="_blank" + leftSection={} > - Hubungi Penjual via WhatsApp + + Hubungi Penjual via WhatsApp + )} @@ -154,4 +174,4 @@ function DetailProdukPasarUser() { ); } -export default DetailProdukPasarUser; +export default DetailProdukPasarUser; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx index d655b676..601edc81 100644 --- a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx @@ -7,7 +7,7 @@ import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from import { motion } from 'motion/react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; -import { useProxy } from 'valtio/utils'; + import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; function Page() { @@ -30,8 +30,8 @@ function Page() { const filteredData = selectedCategory ? data?.filter(item => - item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory) - ) + item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory) + ) : data; useShallowEffect(() => { @@ -55,7 +55,7 @@ function Page() { - + <Title order={1} c={colors["blue-button"]} fw="bold" lh={1.15}> Pasar Desa @@ -71,7 +71,14 @@ function Page() { - + Pasar Desa Online adalah media promosi untuk membantu warga memasarkan dan memperkenalkan produk mereka. @@ -92,6 +99,9 @@ function Page() { searchable nothingFoundMessage="Tidak ada kategori ditemukan" style={{ width: '100%' }} + fz={{ base: 'sm', md: 'md' }} + lh={{ base: 1.5, md: 1.55 }} + c="black" /> @@ -114,15 +124,29 @@ function Page() { style={{ objectFit: 'cover' }} loading="lazy" /> - + {v.nama} - + Rp {v.harga.toLocaleString('id-ID')} - + - + {v.rating} @@ -130,7 +154,11 @@ function Page() { - + {v.alamatUsaha} diff --git a/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx b/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx index 03d578ba..df12bb8d 100644 --- a/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/program-kemiskinan/page.tsx @@ -69,48 +69,47 @@ function Page() { } return ( - + - + Program Kemiskinan setSearch(e.target.value)} leftSection={} - w={{ base: "50%", md: "100%" }} + w="100%" /> Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat - + {state.findMany.data.map(v => { return ( - + {v.nama} @@ -139,7 +137,7 @@ function Page() { ) })} -
+
{ @@ -147,16 +145,15 @@ function Page() { window.scrollTo({ top: 0, behavior: 'smooth' }) }} total={totalPages} - my={"md"} + my="md" />
- + Statistik Kemiskinan Masyarakat @@ -166,7 +163,7 @@ function Page() { <Box w="100%" style={{ overflowX: 'auto' }}> <Center> <RechartsLineChart - width={Math.min(800, window.innerWidth - 100)} + width={Math.min(800, typeof window !== 'undefined' ? window.innerWidth - 100 : 800)} height={400} data={statistikData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }} @@ -175,10 +172,12 @@ function Page() { <XAxis dataKey="tahun" label={{ value: 'Tahun', position: 'insideBottomRight', offset: -5 }} + tick={{ fontSize: 12 }} /> <YAxis label={{ value: 'Jumlah', angle: -90, position: 'insideLeft' }} domain={[0, 'auto']} + tick={{ fontSize: 12 }} /> <Tooltip formatter={(value) => [`${value} orang`, 'Jumlah']} @@ -199,9 +198,9 @@ function Page() { ) : ( <Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}> <Text - fz={{ base: '12px', md: '14px' }} + fz={{ base: 'xs', md: 'sm' }} c="dimmed" - lh={{ base: '1.4', md: '1.5' }} + lh={{ base: 1.4, md: 1.4 }} > {state.findMany.loading ? 'Memuat data statistik...' diff --git a/src/app/darmasaba/(pages)/ekonomi/sektor-unggulan-desa/page.tsx b/src/app/darmasaba/(pages)/ekonomi/sektor-unggulan-desa/page.tsx index ff9953e0..6ae9be12 100644 --- a/src/app/darmasaba/(pages)/ekonomi/sektor-unggulan-desa/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/sektor-unggulan-desa/page.tsx @@ -1,6 +1,6 @@ 'use client' import colors from '@/con/colors'; -import { Stack, Box, Text, Paper, Skeleton } from '@mantine/core'; +import { Stack, Box, Text, Paper, Skeleton, Center, Title } from '@mantine/core'; import React from 'react'; import BackButton from '../../desa/layanan/_com/BackButto'; import { BarChart } from '@mantine/charts'; @@ -28,16 +28,15 @@ function Page() { ) } - // Add this check before the return statement if (data.length === 0) { return ( <Stack pos="relative" bg={colors.Bg} py="xl" gap={22}> <Box px={{ base: 'md', md: 100 }}> <BackButton /> - <Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold"> + <Title order={1} c={colors['blue-button']} fw="bold"> Sektor Unggulan Desa Darmasaba - </Text> - <Text c="dimmed" mt="md"> + + Data sektor unggulan belum tersedia @@ -53,53 +52,72 @@ function Page() { Ton: item.value, })); - const chartWidth = Math.max(600, chartData.length * 150); // contoh: 150px per bar + const chartWidth = Math.max(600, chartData.length * 150); return ( - + - - + + Sektor Unggulan Desa Darmasaba + + + Desa Darmasaba dikenal sebagai desa dengan potensi unggulan di sektor pertanian dan peternakan - Desa Darmasaba dikenal sebagai desa dengan potensi unggulan di sektor pertanian dan peternakan - - + + {data.map((v, k) => { return ( - - {v.name} - + + + {v.name} + + - ) + ); })} - Statistik Sektor Unggulan Darmasaba + + Statistik Sektor Unggulan Darmasaba + - +
+ +
@@ -109,4 +127,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/[id]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/[id]/page.tsx new file mode 100644 index 00000000..3a8400e5 --- /dev/null +++ b/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/[id]/page.tsx @@ -0,0 +1,174 @@ +'use client'; +import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi'; +import colors from '@/con/colors'; +import { + Box, + Divider, + Group, + Image, + Paper, + Skeleton, + Stack, + Text, + Title, +} from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; + +function DetailPegawaiBumdes() { + const statePegawai = useProxy(stateStrukturBumDes.pegawai); + const params = useParams(); + const router = useRouter(); + + useShallowEffect(() => { + stateStrukturBumDes.posisiOrganisasi.findMany.load(); + statePegawai.findUnique.load(params?.id as string); + }, []); + + if (!statePegawai.findUnique.data) { + return ( + + + + ); + } + + const data = statePegawai.findUnique.data; + + return ( + + {/* Back button */} + + router.back()} + style={{ + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: 8, + }} + > + + + Kembali + + + + + + + {/* Foto Profil */} + {data.namaLengkap + + {/* Nama & Jabatan */} + + + {data.namaLengkap || '-'} {data.gelarAkademik || ''} + + + + {data.posisi?.nama || 'Posisi tidak tersedia'} + + + + + + + {/* Informasi Detail */} + + + + + + + + + + ); +} + +/* Komponen Baris Informasi */ +function InfoRow({ + label, + value, + valueColor, + multiline = false, +}: { + label: string; + value?: string | null; + valueColor?: string; + multiline?: boolean; +}) { + return ( + + + {label} + + + + {value || '-'} + + + ); +} + +export default DetailPegawaiBumdes; diff --git a/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/page.tsx b/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/page.tsx index 5471fb5d..8432fb34 100644 --- a/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/page.tsx @@ -1,7 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' - import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi' import colors from '@/con/colors' import { @@ -32,12 +31,13 @@ import { IconZoomOut, } from '@tabler/icons-react' import { debounce } from 'lodash' +import { useTransitionRouter } from 'next-view-transitions' import { OrganizationChart } from 'primereact/organizationchart' import { useEffect, useRef, useState } from 'react' import { useProxy } from 'valtio/utils' import BackButton from '../../desa/layanan/_com/BackButto' +import '../../ppid/struktur-ppid/struktur.css' import { useMediaQuery } from '@mantine/hooks' -import { useTransitionRouter } from 'next-view-transitions' export default function Page() { return ( @@ -49,14 +49,16 @@ export default function Page() { paddingBottom: 48, }} > - + + Struktur Organisasi & SK Pengurus BumDes @@ -75,14 +77,18 @@ export default function Page() { } function StrukturOrganisasiBumDes() { - const router = useTransitionRouter() const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai) + const router = useTransitionRouter() const chartContainerRef = useRef(null) const [scale, setScale] = useState(1) const [isFullscreen, setFullscreen] = useState(false) const [searchQuery, setSearchQuery] = useState('') + + // debounce pencarian const debouncedSearch = useRef( - debounce((value: string) => setSearchQuery(value), 1000) + debounce((value: string) => { + setSearchQuery(value) + }, 1000) ).current useEffect(() => { @@ -90,8 +96,7 @@ function StrukturOrganisasiBumDes() { }, []) const isLoading = - !stateOrganisasi.findMany.data && - stateOrganisasi.findMany.loading !== false + !stateOrganisasi.findMany.data && stateOrganisasi.findMany.loading !== false if (isLoading) { return ( @@ -149,7 +154,7 @@ function StrukturOrganisasiBumDes() { ) } - // 📊 susun struktur organisasi + // 🧩 buat struktur organisasi const posisiMap = new Map() const aktifPegawai = data.filter((p: any) => p.isActive) @@ -183,7 +188,6 @@ function StrukturOrganisasiBumDes() { name: pegawai?.namaLengkap || 'Belum Ditugaskan', title: node.nama || 'Tanpa Jabatan', image: pegawai?.image?.link || '/img/default.png', - description: node.deskripsi || '', }, children: node.children?.map(toOrgChartFormat) || [], } @@ -208,7 +212,7 @@ function StrukturOrganisasiBumDes() { chartData = filterNodes(chartData) } - // 🔍 fullscreen dan zoom control + // 🎬 fullscreen & zoom control const toggleFullscreen = () => { if (!document.fullscreenElement) { chartContainerRef.current?.requestFullscreen() @@ -225,7 +229,7 @@ function StrukturOrganisasiBumDes() { return ( - {/* 🧭 Kontrol atas */} + {/* 🔍 Controls */} + - {/* 🧩 Chart Container */} -
- + + + } + className="p-organizationchart p-organizationchart-horizontal" + /> + + +
+
+ ) +} + +function NodeCard({ node, router }: any) { + const imageSrc = node?.data?.image || '/img/default.png' + const name = node?.data?.name || 'Tanpa Nama' + const title = node?.data?.title || 'Tanpa Jabatan' + const hasId = Boolean(node?.data?.id) + const isMobile = useMediaQuery("(max-width: 768px)"); + + return ( + + {(styles) => ( + { + if (hasId) { + e.currentTarget.style.transform = 'translateY(-4px)' + e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)' + } + }} + onMouseLeave={(e) => { + if (hasId) { + e.currentTarget.style.transform = 'translateY(0)' + e.currentTarget.style.boxShadow = '' + } + }} + > + + {/* Photo */} + + {name} + + + {/* Name */} + + {name} + + + {/* Title/Position */} + + {title} + + + {/* Detail Button */} + {hasId && ( +
-
- ) - } - - function NodeCard({ node, router }: any) { - const imageSrc = node?.data?.image || '/img/default.png' - const name = node?.data?.name || 'Tanpa Nama' - const title = node?.data?.title || 'Tanpa Jabatan' - const hasId = Boolean(node?.data?.id) - const isMobile = useMediaQuery("(max-width: 768px)"); - - return ( - - {(styles) => ( - { - if (hasId) { - e.currentTarget.style.transform = 'translateY(-4px)' - e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)' - } - }} - onMouseLeave={(e) => { - if (hasId) { - e.currentTarget.style.transform = 'translateY(0)' - e.currentTarget.style.boxShadow = '' - } - }} - > - - {/* Photo */} - - {name} - - - {/* Name */} - - {name} - - - {/* Title/Position */} - - {title} - - - {/* Detail Button */} - {hasId && ( - - )} - - + Lihat Detail + )} - - ) - } \ No newline at end of file +
+ + )} + + ) +} diff --git a/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx b/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx index b4aca08d..4fd1380f 100644 --- a/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx +++ b/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx @@ -54,7 +54,7 @@ function Page() { value={search} onChange={(e) => setSearch(e.target.value)} leftSection={} - w={{ base: "50%", md: "100%" }} + w={"100%"} /> diff --git a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx index 5d33fd81..8f313af7 100644 --- a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core' +import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core' import { useShallowEffect } from '@mantine/hooks' import { useParams } from 'next/navigation' import { useProxy } from 'valtio/utils' @@ -31,59 +31,62 @@ function DetailKeamananLingkunganUser() { return ( - - + + - {/* Wrapper Detail */} - - - {/* Judul */} - - {data?.name || 'Tanpa Judul'} - + {/* Wrapper Detail */} + + + {/* Judul */} + + {data?.name || 'Tanpa Judul'} + - {/* Gambar */} -
- {data?.name -
+ {/* Gambar */} +
+ {data?.name +
- {/* Deskripsi */} - - - Deskripsi - - - -
-
-
+ {/* Deskripsi */} + + + Deskripsi + + + + + +
+
+
) } -export default DetailKeamananLingkunganUser +export default DetailKeamananLingkunganUser \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx index 7761bb69..d54568ec 100644 --- a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx @@ -1,7 +1,7 @@ 'use client' import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan'; import colors from '@/con/colors'; -import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; +import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { IconSearch } from '@tabler/icons-react'; import { useState } from 'react'; @@ -14,7 +14,7 @@ function Page() { const state = useProxy(keamananLingkunganState) const router = useRouter() const [search, setSearch] = useState('') - const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay + const [debouncedSearch] = useDebouncedValue(search, 1000); const { data, page, @@ -43,9 +43,9 @@ function Page() { - + Keamanan Lingkungan (Pecalang / Patwal) - </Text> + setSearch(e.target.value)} leftSection={} - w={{ base: "50%", md: "100%" }} + w={"100%"} /> - + Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga. + cols={{ base: 1, sm: 2, md: 3 }} + spacing="xl" + mt="lg" + > {data.map((v, k) => ( (e.currentTarget.style.transform = 'scale(1)')} /> - + {v.name} - </Text> + - + Kontak Darurat - </Text> - <Text fz="md" > + + Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung. @@ -66,17 +66,21 @@ function Page() { - + Nomor Darurat Utama - 112 + + 112 +
- Tidak ada kontak darurat yang ditemukan + + Tidak ada kontak darurat yang ditemukan +
); @@ -89,10 +93,10 @@ function Page() {
- + Kontak Darurat - </Text> - <Text fz={{ base: "h4", md: "h3" }} > + + Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung. @@ -113,10 +117,12 @@ function Page() { - + Nomor Darurat Utama - 112 + + 112 + @@ -124,19 +130,13 @@ function Page() {
- {/* Layanan Darurat */} {data.map((item) => ( - + {item.icon && ( @@ -147,12 +147,11 @@ function Page() { /> )} - + {item.nama} - </Text> + - {/* Kontak Items */} {item.kontakItems?.map((kontak) => ( )} - + {kontak.kontakItem?.nama} - + {kontak.kontakItem?.nomorTelepon} @@ -204,4 +203,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx b/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx index b859cf1e..574009f3 100644 --- a/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/pencegahan-kriminalitas/page.tsx @@ -2,7 +2,7 @@ 'use client' import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas'; import colors from '@/con/colors'; -import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowRight } from '@tabler/icons-react'; import { useTransitionRouter } from 'next-view-transitions'; @@ -26,7 +26,7 @@ function Page() { if (!findFirst.data && !findFirst.loading) { kriminalitasState.findFirst.load(); } - }, [findFirst.data, findFirst.loading]); + }, []); useShallowEffect(() => { const LIMIT = 3; @@ -45,10 +45,10 @@ function Page() { return ( - + Pencegahan Kriminalitas - </Text> - <Text fz='md'> + + Keamanan Komunitas & Pencegahan Kriminal @@ -58,11 +58,11 @@ function Page() { spacing="xl" > - + Program Keamanan Berjalan - </Text> + - + Tidak ada data pencegahan kriminalitas yang cocok @@ -75,10 +75,10 @@ function Page() { return ( - + Pencegahan Kriminalitas - </Text> - <Text fz='md'> + + Keamanan Komunitas & Pencegahan Kriminal @@ -88,13 +88,13 @@ function Page() { spacing="xl" > - + Program Keamanan Berjalan - </Text> + {data.length > 0 ? ( @@ -120,14 +120,16 @@ function Page() { } > - + {item.judul} - </Text> + )) ) : ( - Tidak ada data pencegahan kriminalitas yang cocok + + Tidak ada data pencegahan kriminalitas yang cocok + )} - - - - ) - })} + + + + {data.map((v, k) => ( + + + + {v.nama} + + + Alamat: {v.alamat} + + + Jarak: {v.jarakKeDesa} + + + Telepon: {v.nomorTelepon} + + + Jam Operasional: {v.jamOperasional} + + + + + + + + + + ))} +
load(newPage)} // ini penting! + onChange={(newPage) => load(newPage)} total={totalPages} mt="md" mb="md" @@ -99,4 +126,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx b/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx index 3e5c1d5b..de2af2fe 100644 --- a/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/component/edukasiCard.tsx @@ -1,7 +1,7 @@ -// Create a new component: components/EdukasiCard.tsx +// components/EdukasiCard.tsx 'use client'; -import { Box, Paper, Stack, Text } from '@mantine/core'; +import { Box, Paper, Stack, Text, Title } from '@mantine/core'; import { ReactNode } from 'react'; interface EdukasiCardProps { @@ -18,7 +18,7 @@ export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: Edu radius="md" shadow="sm" withBorder - style={{ + style={{ height: '100%', transition: 'transform 0.2s, box-shadow 0.2s', '&:hover': { @@ -31,32 +31,35 @@ export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: Edu {icon} - + </Stack> - <Text - size="sm" - pl={20} - style={{ - wordBreak: 'break-word', - lineHeight: 1.6, - color: 'var(--mantine-color-gray-7)' - }} - dangerouslySetInnerHTML={{ __html: description }} - /> + <Box pl={20}> + <Text + fz={{ base: 'sm', md: 'md' }} + lh={1.5} + c="gray.7" + ta="justify" + style={{ + wordBreak: 'break-word' + }} + dangerouslySetInnerHTML={{ __html: description }} + /> + </Box> </Box> </Stack> </Paper> diff --git a/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/page.tsx b/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/page.tsx index 14a4a925..9074daaa 100644 --- a/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/edukasi-lingkungan/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Box, Container, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Container, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconLeaf, IconPlant2, IconRecycle } from '@tabler/icons-react'; import { useProxy } from 'valtio/utils'; @@ -51,19 +51,21 @@ export default function EdukasiLingkunganPage() { </Box> <Container size="lg" ta="center"> - <Text - component="h1" - fz={{ base: 'h2', md: '2.5rem' }} + <Title + order={1} c={colors['blue-button']} fw={700} mb="md" + lh={1.15} > Edukasi Lingkungan - </Text> + Program edukasi ini membimbing masyarakat untuk peduli dan bertanggung jawab terhadap alam, meningkatkan kesehatan, kenyamanan, dan keberlanjutan hidup bersama. diff --git a/src/app/darmasaba/(pages)/lingkungan/gotong-royong/[kategori]/[id]/page.tsx b/src/app/darmasaba/(pages)/lingkungan/gotong-royong/[kategori]/[id]/page.tsx index fc930270..6c032d55 100644 --- a/src/app/darmasaba/(pages)/lingkungan/gotong-royong/[kategori]/[id]/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/gotong-royong/[kategori]/[id]/page.tsx @@ -41,23 +41,23 @@ function DetailKegiatanDesaUser() { shadow="sm" maw={900} mx="auto" - > + > - {data.image?.link && ( - {data.judul - )} {/* Judul */} - + <Title order={1} ta={"center"} c={colors['blue-button']}> {data.judul || 'Kegiatan Desa'} + {data.image?.link && ( + {data.judul + )} {/* Meta Info */} diff --git a/src/app/darmasaba/(pages)/lingkungan/gotong-royong/layout.tsx b/src/app/darmasaba/(pages)/lingkungan/gotong-royong/layout.tsx index ff7ea037..933d5b47 100644 --- a/src/app/darmasaba/(pages)/lingkungan/gotong-royong/layout.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/gotong-royong/layout.tsx @@ -1,6 +1,10 @@ // app/desa/berita/BeritaLayoutClient.tsx 'use client' +import colors from '@/con/colors'; +import { Box } from '@mantine/core'; import dynamic from 'next/dynamic'; +import { usePathname } from 'next/navigation'; +import BackButton from '../../desa/layanan/_com/BackButto'; const LayoutTabsGotongRoyong = dynamic( () => import('./_lib/layoutTabs'), @@ -8,5 +12,21 @@ const LayoutTabsGotongRoyong = dynamic( ); export default function GotongRoyongLayoutClient({ children }: { children: React.ReactNode }) { + const pathname = usePathname() + const segments = pathname.split('/').filter(Boolean) + const isDetailPage = segments.length === 5; + + if (isDetailPage) { + // Tampilkan tanpa tab menu + return ( + + + + + {children} + + ); + } + return {children}; } \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/lingkungan/gotong-royong/semua/page.tsx b/src/app/darmasaba/(pages)/lingkungan/gotong-royong/semua/page.tsx index 785e5b92..beb8f4e4 100644 --- a/src/app/darmasaba/(pages)/lingkungan/gotong-royong/semua/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/gotong-royong/semua/page.tsx @@ -11,7 +11,6 @@ import { Center, Container, Divider, - Flex, Grid, GridCol, Group, @@ -23,7 +22,7 @@ import { Stack, Text, Title, - Transition, + Transition } from '@mantine/core'; import { IconArrowRight, IconCalendar } from '@tabler/icons-react'; import { motion } from 'framer-motion'; @@ -46,7 +45,7 @@ export default function Page() { // Load featured data once on component mount useEffect(() => { let mounted = true; - + const loadFeatured = async () => { try { if (!featured.data && !loadingFeatured) { @@ -68,7 +67,7 @@ export default function Page() { useEffect(() => { let mounted = true; - + const loadData = async () => { try { const limit = 3; @@ -92,7 +91,7 @@ export default function Page() { if (search) url.set('search', search); if (newPage > 1) url.set('page', newPage.toString()); else url.delete('page'); - + // Use push instead of replace to keep browser history router.push(`?${url.toString()}`, { scroll: false }); }; @@ -139,7 +138,6 @@ export default function Page() { height={400} fit="cover" radius="md" - style={{ borderBottomRightRadius: 0, borderTopRightRadius: 0 }} loading="lazy" /> @@ -222,6 +220,7 @@ export default function Page() { alt={item.judul} fit="cover" loading="lazy" + radius={"md"} /> @@ -241,14 +240,17 @@ export default function Page() { dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} /> - - - {new Date(item.createdAt).toLocaleDateString('id-ID', { - day: 'numeric', - month: 'short', - year: 'numeric', - })} - + + + + + {new Date(item.createdAt).toLocaleDateString('id-ID', { + day: 'numeric', + month: 'short', + year: 'numeric', + })} + + - + ))} 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 76a39cf6..c8b87968 100644 --- a/src/app/darmasaba/(pages)/lingkungan/konservasi-adat-bali/page.tsx +++ b/src/app/darmasaba/(pages)/lingkungan/konservasi-adat-bali/page.tsx @@ -1,11 +1,12 @@ 'use client' import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali'; import colors from '@/con/colors'; -import { Box, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Center, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; + function Page() { const filosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita.findById) const nilai = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat.findById) @@ -30,11 +31,24 @@ function Page() { - - + + Konservasi Adat Bali - </Text> - <Text px={20} ta="center" fz="lg" c="black"> + + Pelestarian lingkungan di Bali yang berpijak pada kearifan lokal, menjaga harmoni antara alam, budaya, dan manusia. @@ -54,53 +68,31 @@ function Page() { >
- + {filosofi.data?.judul} - </Text> - </Center> - <div - style={{ - wordBreak: "break-word", - whiteSpace: "normal", - flexGrow: 1 - }} - dangerouslySetInnerHTML={{ __html: filosofi.data?.deskripsi || '' }} - /> - </Stack> - </Paper> - </Box> - {/* Nilai */} - <Box style={{ display: 'flex', height: '100%' }}> - <Paper - p="lg" - style={{ - borderRadius: 16, - width: '100%', - display: 'flex', - flexDirection: 'column', - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)' - }} - > - <Stack gap="md" px={20} style={{ height: '100%' }}> - <Center> - <Text fz="xl" fw="bold" c="black"> - {nilai.data?.judul} - </Text> +
- - {/* Bentuk */} - + + {/* Nilai */} +
- - {bentuk.data?.judul} - + + {nilai.data?.judul} +
+ + + + {/* Bentuk */} + + + +
+ + {bentuk.data?.judul} + +
+
- + ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/pelajari-lebih-lanjut/page.tsx b/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/pelajari-lebih-lanjut/page.tsx index 89db8ad0..c408b5b1 100644 --- a/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/pelajari-lebih-lanjut/page.tsx +++ b/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/pelajari-lebih-lanjut/page.tsx @@ -5,6 +5,7 @@ import { Button, Container, Divider, + Flex, Group, Modal, Paper, @@ -23,11 +24,11 @@ import { useProxy } from 'valtio/utils'; import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa'; import colors from '@/con/colors'; - export default function BeasiswaPage() { const router = useRouter(); - const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar) + const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar); const [opened, { open, close }] = useDisclosure(false); + const resetForm = () => { beasiswaDesa.create.form = { namaLengkap: "", @@ -61,6 +62,7 @@ export default function BeasiswaPage() { leftSection={} onClick={() => router.back()} mb="lg" + style={{ fontSize: '1rem', fontWeight: 500 }} > Kembali @@ -69,11 +71,18 @@ export default function BeasiswaPage() { {/* Hero Section */} - + - Program Beasiswa Pendidikan Desa Darmasaba - - + + Program Beasiswa Pendidikan Desa Darmasaba + + + Program ini bertujuan untuk mendukung pendidikan generasi muda di Desa Darmasaba agar dapat melanjutkan studi ke jenjang lebih tinggi dengan dukungan finansial dan pendampingan. @@ -84,20 +93,22 @@ export default function BeasiswaPage() { - Tentang Program + + Tentang Program + - + Program Beasiswa Desa Darmasaba adalah inisiatif pemerintah desa untuk meningkatkan akses pendidikan bagi siswa berprestasi dan kurang mampu. Melalui program ini, desa memberikan bantuan biaya sekolah, bimbingan akademik, serta pelatihan soft skill bagi peserta terpilih. - {/* Tambahkan info tahun berjalan di sini */} + {/* Periode Beasiswa */} - + Periode Beasiswa Tahun 2025 - + Pendaftaran beasiswa dibuka mulai 1 Januari 2025 dan ditutup pada 31 Mei 2025. Pengumuman hasil seleksi akan diumumkan pada pertengahan Juni 2025 melalui website resmi Desa Darmasaba. @@ -108,27 +119,35 @@ export default function BeasiswaPage() { - Syarat Pendaftaran + + Syarat Pendaftaran + - Domisili Desa Darmasaba - + + Domisili Desa Darmasaba + + Peserta harus merupakan warga desa yang berdomisili minimal 2 tahun. - Nilai Akademik - + + Nilai Akademik + + Rata-rata nilai raport minimal 80 atau setara. - Surat Rekomendasi - + + Surat Rekomendasi + + Diperlukan surat rekomendasi dari sekolah atau guru wali kelas. @@ -139,75 +158,102 @@ export default function BeasiswaPage() { - Proses Seleksi + + Proses Seleksi + - - + + Pendaftaran Online + + } + > + Calon peserta mengisi formulir pendaftaran dan mengunggah dokumen pendukung. - + Estimasi waktu: 1 Februari – 31 Mei 2025 - - + + Seleksi Administrasi + + } + > + Panitia memverifikasi kelengkapan dan validitas berkas. - + Estimasi waktu: 5–7 hari kerja setelah penutupan pendaftaran - - + + Wawancara dan Penilaian + + } + > + Peserta yang lolos administrasi akan diundang untuk wawancara langsung dengan tim seleksi. - + Estimasi waktu: 7–10 hari kerja setelah pengumuman seleksi administrasi - - + + Pengumuman Penerima + + } + > + Daftar penerima beasiswa diumumkan melalui website resmi Desa Darmasaba. - + Estimasi waktu: 5 hari kerja setelah tahap wawancara selesai - + Total estimasi keseluruhan proses: sekitar 3–4 minggu setelah penutupan pendaftaran - {/* Testimoni */} - Cerita Sukses Penerima Beasiswa + + Cerita Sukses Penerima Beasiswa + - + “Program ini sangat membantu saya melanjutkan kuliah di Universitas Udayana. Terima kasih Desa Darmasaba!” - + – Ni Kadek Ayu S., Penerima Beasiswa 2024 - + “Selain bantuan dana, kami juga mendapatkan pelatihan komputer dan bahasa Inggris.” - + – I Made Gede A., Penerima Beasiswa 2023 @@ -218,16 +264,25 @@ export default function BeasiswaPage() { - Siap Bergabung dengan Program Ini? + + Siap Bergabung dengan Program Ini? + - + Segera daftar dan wujudkan mimpimu bersama Desa Darmasaba. - + {/* Modal Formulir */} + Formulir Beasiswa } @@ -245,64 +300,105 @@ export default function BeasiswaPage() { { beasiswaDesa.create.form.namaLengkap = val.target.value }} /> + labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }} + onChange={(val) => { beasiswaDesa.create.form.namaLengkap = val.target.value }} + /> { beasiswaDesa.create.form.nis = val.target.value }} /> + labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }} + onChange={(val) => { beasiswaDesa.create.form.nis = val.target.value }} + /> { beasiswaDesa.create.form.kelas = val.target.value }} /> + labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }} + onChange={(val) => { beasiswaDesa.create.form.kelas = val.target.value }} + />