From 2adf60f9ebc640522bf194b62c47394fee487d41 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 3 Sep 2025 15:30:02 +0800 Subject: [PATCH] Fix UI Admin menu desa --- .../admin/(dashboard)/_state/desa/berita.ts | 42 +- .../(dashboard)/_state/desa/layananDesa.ts | 57 +-- .../(dashboard)/_state/desa/penghargaan.ts | 13 +- .../(dashboard)/_state/desa/pengumuman.ts | 36 +- .../admin/(dashboard)/_state/desa/potensi.ts | 35 +- .../desa/_com/layoutTabLayanan.tsx | 91 ++++- .../desa/berita/_com/layoutTabs.tsx | 101 +++-- .../desa/berita/kategori-berita/[id]/page.tsx | 134 ++++--- .../berita/kategori-berita/create/page.tsx | 85 ++-- .../desa/berita/kategori-berita/page.tsx | 211 ++++++---- .../berita/list-berita/[id]/edit/page.tsx | 232 ++++++----- .../desa/berita/list-berita/[id]/page.tsx | 182 +++++---- .../desa/berita/list-berita/create/page.tsx | 226 ++++++----- .../desa/berita/list-berita/page.tsx | 173 +++++---- .../desa/gallery/foto/[id]/edit/page.tsx | 161 -------- .../desa/gallery/foto/[id]/page.tsx | 112 ------ .../desa/gallery/foto/create/page.tsx | 147 ------- .../(dashboard)/desa/gallery/foto/page.tsx | 184 +++++---- .../desa/gallery/lib/layoutTabs.tsx | 80 +++- .../desa/gallery/video/[id]/edit/page.tsx | 106 +++-- .../desa/gallery/video/[id]/page.tsx | 171 ++++---- .../desa/gallery/video/create/page.tsx | 152 +++++--- .../(dashboard)/desa/gallery/video/page.tsx | 160 +++++--- .../edit/page.tsx | 108 +++--- .../pelayanan_penduduk_non_permanent/page.tsx | 112 ++++-- .../edit/page.tsx | 126 +++--- .../pelayanan_perizinan_berusaha/page.tsx | 231 +++++++---- .../[id]/edit/page.tsx | 339 ++++++++-------- .../pelayanan_surat_keterangan/[id]/page.tsx | 209 ++++++---- .../create/page.tsx | 221 ++++++----- .../pelayanan_surat_keterangan/page.tsx | 208 +++++----- .../[id]/edit/page.tsx | 153 +++++--- .../[id]/page.tsx | 185 ++++++--- .../create/page.tsx | 111 ++++-- .../pelayanan_telunjuk_sakti_desa/page.tsx | 365 +++++++++++++----- .../desa/penghargaan/[id]/edit/page.tsx | 180 +++++---- .../desa/penghargaan/[id]/page.tsx | 187 ++++++--- .../desa/penghargaan/create/page.tsx | 225 ++++++----- .../(dashboard)/desa/penghargaan/page.tsx | 200 +++++----- .../desa/pengumuman/_com/layoutTabs.tsx | 75 +++- .../kategori-pengumuman/[id]/page.tsx | 145 ++++--- .../kategori-pengumuman/create/page.tsx | 83 ++-- .../pengumuman/kategori-pengumuman/page.tsx | 141 ++++--- .../list-pengumuman/[id]/edit/page.tsx | 139 ++++--- .../pengumuman/list-pengumuman/[id]/page.tsx | 197 ++++++---- .../list-pengumuman/create/page.tsx | 133 ++++--- .../desa/pengumuman/list-pengumuman/page.tsx | 157 +++++--- .../desa/potensi/_lib/layoutTabs.tsx | 76 +++- .../potensi/kategori-potensi/[id]/page.tsx | 129 ++++--- .../potensi/kategori-potensi/create/page.tsx | 85 ++-- .../desa/potensi/kategori-potensi/page.tsx | 131 ++++--- .../potensi/list-potensi/[id]/edit/page.tsx | 245 ++++++------ .../desa/potensi/list-potensi/[id]/page.tsx | 207 +++++----- .../desa/potensi/list-potensi/create/page.tsx | 183 +++++---- .../desa/potensi/list-potensi/page.tsx | 180 ++++----- .../desa/profile/_lib/layoutTabsDetail.tsx | 79 +++- .../profile-desa/[id]/lambang_desa/page.tsx | 130 ++++--- .../profile-desa/[id]/maskot_desa/page.tsx | 321 ++++++++------- .../profile-desa/[id]/sejarah_desa/page.tsx | 131 ++++--- .../profile-desa/[id]/visi_misi_desa/page.tsx | 237 +++++++----- .../desa/profile/profile-desa/page.tsx | 294 ++++++++------ .../[id]/edit/page.tsx | 165 ++++---- .../[id]/page.tsx | 155 +++++--- .../create/page.tsx | 211 +++++----- .../page.tsx | 127 +++--- .../profile/profile-perbekel/[id]/page.tsx | 233 ++++++----- .../desa/profile/profile-perbekel/page.tsx | 162 ++++---- .../responden/page.tsx | 1 - .../page.tsx | 2 - .../ikm-desa-darmasaba/responden/page.tsx | 4 +- .../desa/berita/kategori-berita/findMany.ts | 56 ++- .../pelayanan_surat_keterangan/find-many.ts | 38 +- .../find-many.ts | 86 +++-- .../_lib/desa/penghargaan/find-many.ts | 40 +- .../kategori-pengumuman/findMany.ts | 62 ++- .../_lib/desa/potensi/find-many.ts | 21 +- .../desa/potensi/kategori-potensi/findMany.ts | 56 ++- 77 files changed, 6566 insertions(+), 4402 deletions(-) delete mode 100644 src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx delete mode 100644 src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx delete mode 100644 src/app/admin/(dashboard)/desa/gallery/foto/create/page.tsx diff --git a/src/app/admin/(dashboard)/_state/desa/berita.ts b/src/app/admin/(dashboard)/_state/desa/berita.ts index a2b7df23..2105e58f 100644 --- a/src/app/admin/(dashboard)/_state/desa/berita.ts +++ b/src/app/admin/(dashboard)/_state/desa/berita.ts @@ -74,18 +74,18 @@ const berita = proxy({ totalPages: 1, loading: false, search: "", - load: async (page = 1, limit = 10, search = "", kategori = "") => { + load: async (page = 1, limit = 10, search = "", kategori = "") => { berita.findMany.loading = true; // ✅ Akses langsung via nama path berita.findMany.page = page; berita.findMany.search = search; - + try { const query: any = { page, limit }; if (search) query.search = search; if (kategori) query.kategori = kategori; - + const res = await ApiFetch.api.desa.berita["find-many"].get({ query }); - + if (res.status === 200 && res.data?.success) { berita.findMany.data = res.data.data ?? []; berita.findMany.totalPages = res.data.totalPages ?? 1; @@ -368,11 +368,37 @@ const kategoriBerita = proxy({ isActive: true; }; }>[], + page: 1, + totalPages: 1, loading: false, - async load() { - const res = await ApiFetch.api.desa.kategoriberita["findMany"].get(); - if (res.status === 200) { - kategoriBerita.findMany.data = res.data?.data ?? []; + search: "", + load: async (page = 1, limit = 10, search = "") => { + kategoriBerita.findMany.loading = true; // ✅ Akses langsung via nama path + kategoriBerita.findMany.page = page; + kategoriBerita.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.desa.kategoriberita[ + "findMany" + ].get({ query }); + + if (res.status === 200 && res.data?.success) { + kategoriBerita.findMany.data = res.data.data ?? []; + kategoriBerita.findMany.totalPages = + res.data.totalPages ?? 1; + } else { + kategoriBerita.findMany.data = []; + kategoriBerita.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch kategori berita paginated:", err); + kategoriBerita.findMany.data = []; + kategoriBerita.findMany.totalPages = 1; + } finally { + kategoriBerita.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts index f174c8ee..3cc787a3 100644 --- a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts +++ b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts @@ -30,7 +30,6 @@ const templateTelunjukSaktiDesaForm = z.object({ deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), }); - const templatePelayananPerizinanBerusaha = z.object({ name: z.string().min(3, "Nama minimal 3 karakter"), deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), @@ -72,7 +71,6 @@ const pelayananPendudukNonPermanenForm = { deskripsi: "", }; - const suratKeterangan = proxy({ create: { form: { ...suratKeteranganForm }, @@ -113,16 +111,21 @@ const suratKeterangan = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { // Change to arrow function - suratKeterangan.findMany.loading = true; // Use the full path to access the property + search: "", + load: async (page = 1, limit = 10, search = "") => { + // Change to arrow function + suratKeterangan.findMany.loading = true; // Use the full path to access the property suratKeterangan.findMany.page = page; + suratKeterangan.findMany.search = search; try { + const query: any = { page, limit }; + if (search) query.search = search; const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[ "find-many" ].get({ - query: { page, limit }, + query, }); - + if (res.status === 200 && res.data?.success) { suratKeterangan.findMany.data = res.data.data || []; suratKeterangan.findMany.total = res.data.total || 0; @@ -341,28 +344,34 @@ const pelayananTelunjukSaktiDesa = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { // Change to arrow function - pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property + search: "", + load: async (page = 1, limit = 10, search = "") => { + // Change to arrow function + pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property pelayananTelunjukSaktiDesa.findMany.page = page; + pelayananTelunjukSaktiDesa.findMany.search = search; try { + const query: any = { page, limit }; + if (search) query.search = search; const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[ "find-many" ].get({ - query: { page, limit }, + query, }); - + if (res.status === 200 && res.data?.success) { pelayananTelunjukSaktiDesa.findMany.data = res.data.data || []; pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0; - pelayananTelunjukSaktiDesa.findMany.totalPages = res.data.totalPages || 1; + pelayananTelunjukSaktiDesa.findMany.totalPages = + res.data.totalPages || 1; } else { - console.error("Failed to load telunjuk sakti desa:", res.data?.message); + console.error("Failed to load surat keterangan:", res.data?.message); pelayananTelunjukSaktiDesa.findMany.data = []; - pelayananTelunjukSaktiDesa.findMany.total = 0; - pelayananTelunjukSaktiDesa.findMany.totalPages = 1; + suratKeterangan.findMany.total = 0; + suratKeterangan.findMany.totalPages = 1; } } catch (error) { - console.error("Error loading telunjuk sakti desa:", error); + console.error("Error loading surat keterangan:", error); pelayananTelunjukSaktiDesa.findMany.data = []; pelayananTelunjukSaktiDesa.findMany.total = 0; pelayananTelunjukSaktiDesa.findMany.totalPages = 1; @@ -410,7 +419,9 @@ const pelayananTelunjukSaktiDesa = proxy({ ); const result = await response.json(); if (response.ok) { - toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus"); + toast.success( + result.message || "Telunjuk Sakti Desa berhasil dihapus" + ); await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list } else { toast.error(result.message || "Gagal menghapus telunjuk sakti desa"); @@ -501,7 +512,9 @@ const pelayananTelunjukSaktiDesa = proxy({ } const result = await response.json(); if (result.success) { - toast.success(result.message || "Telunjuk Sakti Desa berhasil diupdate"); + toast.success( + result.message || "Telunjuk Sakti Desa berhasil diupdate" + ); await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list return true; } else { @@ -522,7 +535,7 @@ const pelayananTelunjukSaktiDesa = proxy({ } }, }, -}) +}); const pelayananPerizinanBerusaha = proxy({ findById: { @@ -596,9 +609,7 @@ const pelayananPerizinanBerusaha = proxy({ } catch (error) { console.error("Error fetching pelayanan perizinan berusaha:", error); toast.error( - error instanceof Error - ? error.message - : "Gagal memuat data" + error instanceof Error ? error.message : "Gagal memuat data" ); return null; } @@ -713,9 +724,7 @@ const pelayananPendudukNonPermanen = proxy({ } catch (error) { console.error("Error fetching pelayanan penduduk non permanen:", error); toast.error( - error instanceof Error - ? error.message - : "Gagal memuat data" + error instanceof Error ? error.message : "Gagal memuat data" ); return null; } diff --git a/src/app/admin/(dashboard)/_state/desa/penghargaan.ts b/src/app/admin/(dashboard)/_state/desa/penghargaan.ts index 917af2b3..68be0ba7 100644 --- a/src/app/admin/(dashboard)/_state/desa/penghargaan.ts +++ b/src/app/admin/(dashboard)/_state/desa/penghargaan.ts @@ -56,16 +56,21 @@ const penghargaanState = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { // Change to arrow function - penghargaanState.findMany.loading = true; // Use the full path to access the property + search: "", + load: async (page = 1, limit = 10, search = "") => { + // Change to arrow function + penghargaanState.findMany.loading = true; // Use the full path to access the property penghargaanState.findMany.page = page; + penghargaanState.findMany.search = search; try { + const query: any = { page, limit }; + if (search) query.search = search; const res = await ApiFetch.api.desa.penghargaan[ "find-many" ].get({ - query: { page, limit }, + query, }); - + if (res.status === 200 && res.data?.success) { penghargaanState.findMany.data = res.data.data || []; penghargaanState.findMany.total = res.data.total || 0; diff --git a/src/app/admin/(dashboard)/_state/desa/pengumuman.ts b/src/app/admin/(dashboard)/_state/desa/pengumuman.ts index 0fcffb7e..09320003 100644 --- a/src/app/admin/(dashboard)/_state/desa/pengumuman.ts +++ b/src/app/admin/(dashboard)/_state/desa/pengumuman.ts @@ -55,11 +55,39 @@ const category = proxy({ pengumumans: number; }; })[], + page: 1, + totalPages: 1, + total: 0, loading: false, - async load() { - const res = await ApiFetch.api.desa.kategoripengumuman["findMany"].get(); - if (res.status === 200) { - category.findMany.data = res.data?.data ?? []; + search: "", + load: async (page = 1, limit = 10, search = "") => { // Change to arrow function + category.findMany.loading = true; // Use the full path to access the property + category.findMany.page = page; + category.findMany.search = search; + try { + const res = await ApiFetch.api.desa.kategoripengumuman[ + "findMany" + ].get({ + query: { page, limit }, + }); + + if (res.status === 200 && res.data?.success) { + category.findMany.data = res.data.data || []; + category.findMany.total = res.data.total || 0; + category.findMany.totalPages = res.data.totalPages || 1; + } else { + console.error("Failed to load potensi desa:", res.data?.message); + category.findMany.data = []; + category.findMany.total = 0; + category.findMany.totalPages = 1; + } + } catch (error) { + console.error("Error loading potensi desa:", error); + category.findMany.data = []; + category.findMany.total = 0; + category.findMany.totalPages = 1; + } finally { + category.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/desa/potensi.ts b/src/app/admin/(dashboard)/_state/desa/potensi.ts index 3e60fdf8..0c158b38 100644 --- a/src/app/admin/(dashboard)/_state/desa/potensi.ts +++ b/src/app/admin/(dashboard)/_state/desa/potensi.ts @@ -56,9 +56,11 @@ const potensiDesa = proxy({ totalPages: 1, total: 0, loading: false, - load: async (page = 1, limit = 10) => { // Change to arrow function + search: "", + load: async (page = 1, limit = 10, search = "") => { // Change to arrow function potensiDesa.findMany.loading = true; // Use the full path to access the property potensiDesa.findMany.page = page; + potensiDesa.findMany.search = search; try { const res = await ApiFetch.api.desa.potensi[ "find-many" @@ -298,11 +300,34 @@ const kategoriPotensi = proxy({ isActive: true; }; }>[], + page: 1, + totalPages: 1, loading: false, - async load() { - const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get(); - if (res.status === 200) { - kategoriPotensi.findMany.data = res.data?.data ?? []; + search: "", + load: async (page = 1, limit = 10, search = "") => { + kategoriPotensi.findMany.loading = true; // ✅ Akses langsung via nama path + kategoriPotensi.findMany.page = page; + kategoriPotensi.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get({ query }); + + if (res.status === 200 && res.data?.success) { + kategoriPotensi.findMany.data = res.data.data ?? []; + kategoriPotensi.findMany.totalPages = res.data.totalPages ?? 1; + } else { + kategoriPotensi.findMany.data = []; + kategoriPotensi.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch kategori potensi paginated:", err); + kategoriPotensi.findMany.data = []; + kategoriPotensi.findMany.totalPages = 1; + } finally { + kategoriPotensi.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx index 47eea7f9..0ece72c8 100644 --- a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx +++ b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx @@ -1,9 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react'; function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { const router = useRouter() @@ -12,26 +13,35 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { { label: "Pelayanan Surat Keterangan", value: "pelayanansuratketerangan", - href: "/admin/desa/layanan/pelayanan_surat_keterangan" + href: "/admin/desa/layanan/pelayanan_surat_keterangan", + icon: , + tooltip: "Layanan terkait surat keterangan resmi desa" }, { label: "Pelayanan Perizinan Berusaha", value: "pelayananperizinanusaha", - href: "/admin/desa/layanan/pelayanan_perizinan_berusaha" + href: "/admin/desa/layanan/pelayanan_perizinan_berusaha", + icon: , + tooltip: "Layanan untuk izin usaha masyarakat" }, { label: "Pelayanan Telunjuk Sakti Desa", value: "pelayanantelunjuksaktidesa", - href: "/admin/desa/layanan/pelayanan_telunjuk_sakti_desa" + href: "/admin/desa/layanan/pelayanan_telunjuk_sakti_desa", + icon: , + tooltip: "Layanan inovasi khusus desa" }, { label: "Pelayanan Penduduk Non-Permanent", - value: "pelayanantelunjuknonpermanent", - href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent" + value: "pelayanannonpermanent", + href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent", + icon: , + tooltip: "Pendataan penduduk non-permanent" } ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { const tab = tabs.find(t => t.value === value) @@ -49,24 +59,65 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { }, [pathname]) return ( - - Layanan - - - {tabs.map((e, i) => ( - {e.label} + + Layanan + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {/* Konten dummy, bisa diganti sesuai routing */} + <>{children} ))} - {children} ); } -export default LayoutTabsLayanan; \ No newline at end of file +export default LayoutTabsLayanan; diff --git a/src/app/admin/(dashboard)/desa/berita/_com/layoutTabs.tsx b/src/app/admin/(dashboard)/desa/berita/_com/layoutTabs.tsx index 605fedc8..d65d755d 100644 --- a/src/app/admin/(dashboard)/desa/berita/_com/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/desa/berita/_com/layoutTabs.tsx @@ -1,63 +1,110 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { IconNews, IconCategory } from '@tabler/icons-react'; function LayoutTabsBerita({ children }: { children: React.ReactNode }) { - const router = useRouter() - const pathname = usePathname() + const router = useRouter(); + const pathname = usePathname(); + const tabs = [ { label: "List Berita", value: "list_berita", - href: "/admin/desa/berita/list-berita" + href: "/admin/desa/berita/list-berita", + icon: , + tooltip: "Lihat dan kelola semua berita desa" }, { label: "Kategori Berita", value: "kategori_berita", - href: "/admin/desa/berita/kategori-berita" + href: "/admin/desa/berita/kategori-berita", + icon: , + tooltip: "Kelola kategori berita desa" }, - ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname); + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { - const tab = tabs.find(t => t.value === value) + const tab = tabs.find(t => t.value === value); if (tab) { - router.push(tab.href) + router.push(tab.href); } - setActiveTab(value) - } + setActiveTab(value); + }; useEffect(() => { - const match = tabs.find(tab => tab.href === pathname) + const match = tabs.find(tab => tab.href === pathname); if (match) { - setActiveTab(match.value) + setActiveTab(match.value); } - }, [pathname]) + }, [pathname]); return ( - - Gallery - - - {tabs.map((e, i) => ( - {e.label} + + Berita Desa + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {/* Konten dummy, bisa diganti sesuai routing */} + <>{children} ))} - {children} ); } -export default LayoutTabsBerita; \ No newline at end of file +export default LayoutTabsBerita; diff --git a/src/app/admin/(dashboard)/desa/berita/kategori-berita/[id]/page.tsx b/src/app/admin/(dashboard)/desa/berita/kategori-berita/[id]/page.tsx index 13361e24..e42a0071 100644 --- a/src/app/admin/(dashboard)/desa/berita/kategori-berita/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/kategori-berita/[id]/page.tsx @@ -2,7 +2,16 @@ 'use client' import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita'; import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -10,67 +19,102 @@ import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function EditKategoriBerita() { - const editState = useProxy(stateDashboardBerita.kategoriBerita) + const editState = useProxy(stateDashboardBerita.kategoriBerita); const router = useRouter(); const params = useParams(); + const [formData, setFormData] = useState({ - name: editState.update.form.name || '', - }); + name: editState.update.form.name || '', + }); useEffect(() => { - const loadKategori = async () => { - const id = params?.id as string; - if (!id) return; - - try { - const data = await editState.update.load(id); // akses langsung, bukan dari proxy - if (data) { - setFormData({ - name: data.name || '', - }); - } - } catch (error) { - console.error("Error loading kategori Berita:", error); - toast.error("Gagal memuat data kategori Berita"); - } - }; - - loadKategori(); - }, [params?.id]); + const loadKategori = async () => { + const id = params?.id as string; + if (!id) return; - const handleSubmit = async () => { try { - editState.update.form = { - ...editState.update.form, - name: formData.name, - }; - await editState.update.update(); - toast.success('Kategori Berita berhasil diperbarui!'); - router.push('/admin/desa/berita/kategori-berita'); + const data = await editState.update.load(id); + if (data) { + setFormData({ + name: data.name || '', + }); + } } catch (error) { - console.error('Error updating kategori Berita:', error); - toast.error('Terjadi kesalahan saat memperbarui kategori Berita'); + console.error('Error loading kategori Berita:', error); + toast.error('Gagal memuat data kategori Berita'); } }; + loadKategori(); + }, [params?.id]); + + const handleSubmit = async () => { + try { + editState.update.form = { + ...editState.update.form, + name: formData.name, + }; + + await editState.update.update(); + toast.success('Kategori Berita berhasil diperbarui!'); + router.push('/admin/desa/berita/kategori-berita'); + } catch (error) { + console.error('Error updating kategori Berita:', error); + toast.error('Terjadi kesalahan saat memperbarui kategori Berita'); + } + }; + return ( - - - - - - - Edit Kategori Berita + + {/* Back Button + Title */} + + + + + + Edit Kategori Berita + + + + {/* Form Wrapper */} + + setFormData({ ...formData, name: e.target.value })} - label={Nama Kategori Berita} - placeholder="masukkan nama kategori Berita" + required /> - + + + diff --git a/src/app/admin/(dashboard)/desa/berita/kategori-berita/create/page.tsx b/src/app/admin/(dashboard)/desa/berita/kategori-berita/create/page.tsx index 526891f1..06fa984b 100644 --- a/src/app/admin/(dashboard)/desa/berita/kategori-berita/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/kategori-berita/create/page.tsx @@ -1,50 +1,87 @@ -'use client' +'use client'; import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; - - function CreateKategoriBerita() { - const createState = useProxy(stateDashboardBerita.kategoriBerita) + const createState = useProxy(stateDashboardBerita.kategoriBerita); const router = useRouter(); const resetForm = () => { createState.create.form = { - name: "", + name: '', }; }; const handleSubmit = async () => { await createState.create.create(); resetForm(); - router.push("/admin/desa/berita/kategori-berita") + router.push('/admin/desa/berita/kategori-berita'); }; return ( - - - - + + {/* Header dengan back button */} + + + + + + Tambah Kategori Berita + + - - - Create Kategori Berita + {/* Form utama */} + + Nama Kategori Berita} - placeholder='Masukkan nama kategori Berita' - value={createState.create.form.name} - onChange={(val) => { - createState.create.form.name = val.target.value; - }} + label={Nama Kategori Berita} + placeholder="Masukkan nama kategori berita" + value={createState.create.form.name || ''} + onChange={(e) => (createState.create.form.name = e.target.value)} + required /> - - + + + diff --git a/src/app/admin/(dashboard)/desa/berita/kategori-berita/page.tsx b/src/app/admin/(dashboard)/desa/berita/kategori-berita/page.tsx index e4a3005a..94dc8326 100644 --- a/src/app/admin/(dashboard)/desa/berita/kategori-berita/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/kategori-berita/page.tsx @@ -1,25 +1,40 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; +import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import stateDashboardBerita from '../../../_state/desa/berita'; - - function KategoriBerita() { const [search, setSearch] = useState(''); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -30,99 +45,155 @@ function KategoriBerita() { } function ListKategoriBerita({ search }: { search: string }) { - const listDataState = useProxy(stateDashboardBerita.kategoriBerita) + const listDataState = useProxy(stateDashboardBerita.kategoriBerita); const router = useRouter(); - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + + const { + data, + loading, + load, + page, + totalPages, + } = listDataState.findMany; useEffect(() => { - listDataState.findMany.load() - }, []) + load(page, 10, search); + }, [page, search]); const handleDelete = () => { if (selectedId) { - listDataState.delete.delete(selectedId) - setModalHapus(false) - setSelectedId(null) - - listDataState.findMany.load() + listDataState.delete.delete(selectedId); + setModalHapus(false); + setSelectedId(null); + load(page, 10, search); } - } + }; - const filteredData = (listDataState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || []; - if (!listDataState.findMany.data) { + if (loading || !data) { return ( - + - ) + ); } + return ( - - - - - - - - No - Nama - Edit - Hapus - - - - {filteredData.map((item, index) => ( + + + Daftar Kategori Berita + + + + + + +
+ + + No + Nama + Edit + Hapus + + + + {filteredData.length > 0 ? ( + filteredData.map((item, index) => ( - - {index + 1} - - - {item.name} - - + {index + 1} - + + {item.name} + + + + + + + + + + + - ))} - -
-
-
+ )) + ) : ( + + +
+ + Tidak ada data kategori berita yang cocok + +
+
+
+ )} + + +
+
+ { + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+ {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleDelete} - text='Apakah anda yakin ingin menghapus kategori Berita ini?' + text="Apakah anda yakin ingin menghapus kategori berita ini?" />
- ) + ); } export default KategoriBerita; diff --git a/src/app/admin/(dashboard)/desa/berita/list-berita/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/berita/list-berita/[id]/edit/page.tsx index 00a92075..52f49669 100644 --- a/src/app/admin/(dashboard)/desa/berita/list-berita/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/list-berita/[id]/edit/page.tsx @@ -15,7 +15,8 @@ import { Stack, Text, TextInput, - Title + Title, + Tooltip, } from "@mantine/core"; import { Dropzone } from "@mantine/dropzone"; import { IconArrowBack, IconPhoto, IconUpload, IconX } from "@tabler/icons-react"; @@ -24,7 +25,6 @@ import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useProxy } from "valtio/utils"; - function EditBerita() { const beritaState = useProxy(stateDashboardBerita); const router = useRouter(); @@ -33,29 +33,29 @@ function EditBerita() { const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); const [formData, setFormData] = useState({ - judul: beritaState.berita.edit.form.judul || '', - deskripsi: beritaState.berita.edit.form.deskripsi || '', - kategoriBeritaId: beritaState.berita.edit.form.kategoriBeritaId || '', - content: beritaState.berita.edit.form.content || '', - imageId: beritaState.berita.edit.form.imageId || '' + judul: beritaState.berita.edit.form.judul || "", + deskripsi: beritaState.berita.edit.form.deskripsi || "", + kategoriBeritaId: beritaState.berita.edit.form.kategoriBeritaId || "", + content: beritaState.berita.edit.form.content || "", + imageId: beritaState.berita.edit.form.imageId || "", }); // Load berita by id saat pertama kali useEffect(() => { - beritaState.kategoriBerita.findMany.load() + beritaState.kategoriBerita.findMany.load(); const loadBerita = async () => { const id = params?.id as string; if (!id) return; try { - const data = await stateDashboardBerita.berita.edit.load(id); // akses langsung, bukan dari proxy + const data = await stateDashboardBerita.berita.edit.load(id); if (data) { setFormData({ - judul: data.judul || '', - deskripsi: data.deskripsi || '', - kategoriBeritaId: data.kategoriBeritaId || '', - content: data.content || '', - imageId: data.imageId || '', + judul: data.judul || "", + deskripsi: data.deskripsi || "", + kategoriBeritaId: data.kategoriBeritaId || "", + content: data.content || "", + imageId: data.imageId || "", }); if (data?.image?.link) { @@ -69,31 +69,26 @@ function EditBerita() { }; loadBerita(); - }, [params?.id]); // ✅ hapus beritaState dari dependency + }, [params?.id]); const handleSubmit = async () => { - try { - // Update global state with form data beritaState.berita.edit.form = { ...beritaState.berita.edit.form, - judul: formData.judul, - deskripsi: formData.deskripsi, - content: formData.content, - kategoriBeritaId: formData.kategoriBeritaId || '', - imageId: formData.imageId // Keep existing imageId if not changed + ...formData, }; - // Jika ada file baru, upload if (file) { - const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name }); + const res = await ApiFetch.api.fileStorage.create.post({ + file, + name: file.name, + }); const uploaded = res.data?.data; if (!uploaded?.id) { return toast.error("Gagal upload gambar"); } - // Update imageId in global state beritaState.berita.edit.form.imageId = uploaded.id; } @@ -107,87 +102,111 @@ function EditBerita() { }; return ( - - - - - - - Edit Berita + + + + + + + Edit Berita + + + + + setFormData({ ...formData, judul: e.target.value })} - label={Judul} - placeholder="masukkan judul" + onChange={(e) => + setFormData({ ...formData, judul: e.target.value }) + } + required /> setFormData({ ...formData, deskripsi: e.target.value })} - label={Deskripsi} - placeholder="masukkan deskripsi" + onChange={(e) => + setFormData({ ...formData, deskripsi: e.target.value }) + } + required /> - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + + Gambar Berita + + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error("File tidak valid, gunakan format gambar")} + maxSize={5 * 1024 ** 2} + accept={{ "image/*": [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file + + + Maksimal 5MB, format gambar wajib + + + + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
+ {previewImage && ( + + Preview Gambar + + )}
+ - Konten + + Konten + { @@ -199,13 +218,15 @@ function EditBerita() { Kategori} + label="Kategori" placeholder="Pilih kategori" data={beritaState.kategoriBerita.findMany.data.map((item) => ({ label: item.name, @@ -93,85 +115,83 @@ export default function CreateBerita() { value={beritaState.berita.create.form.kategoriBeritaId || null} onChange={(val: string | null) => { if (val) { - const selected = beritaState.kategoriBerita.findMany.data?.find((item) => item.id === val); + const selected = beritaState.kategoriBerita.findMany.data?.find( + (item) => item.id === val + ); if (selected) { beritaState.berita.create.form.kategoriBeritaId = selected.id; } } else { - beritaState.berita.create.form.kategoriBeritaId = ""; + beritaState.berita.create.form.kategoriBeritaId = ''; } }} searchable clearable nothingFoundMessage="Tidak ditemukan" + required /> + { - beritaState.berita.create.form.deskripsi = val.target.value; - }} - label={Deskripsi} - placeholder="masukkan deskripsi" + onChange={(e) => (beritaState.berita.create.form.deskripsi = e.target.value)} /> - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + + Gambar Berita + + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file (maks 5MB) + + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
+ {previewImage && ( + + Preview Gambar + + )}
+ - Konten + + Konten + { @@ -179,7 +199,21 @@ export default function CreateBerita() { }} /> - + + + +
diff --git a/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx b/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx index 3d4729e8..01125dd4 100644 --- a/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx @@ -1,6 +1,25 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Image, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -9,15 +28,13 @@ import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; import stateDashboardBerita from '../../../_state/desa/berita'; - - function Berita() { const [search, setSearch] = useState(""); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -28,103 +45,125 @@ function Berita() { } function ListBerita({ search }: { search: string }) { - const beritaState = useProxy(stateDashboardBerita) - const router = useRouter() - const { - data, - page, - totalPages, - loading, - load, - } = beritaState.berita.findMany; + const beritaState = useProxy(stateDashboardBerita); + const router = useRouter(); + const { data, page, totalPages, loading, load } = beritaState.berita.findMany; - // Fetch data when page or search changes useShallowEffect(() => { load(page, 10, search); }, [page, search]); if (loading || !data) { - return ; + return ( + + + + ); } const filteredData = data || []; return ( - - - - - - List Berita - - - - - - - - + + Daftar Berita + + + + + + +
+ + + Judul + Kategori + Gambar + Aksi + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( - - - - {item.judul} - + + + {item.judul} + + + + + {item.kategoriBerita?.name || '-'} + + + + + {item.image?.link ? ( + + ) : ( + + )} - {item.kategoriBerita?.name} - - - - + - ))} - -
-
-
+ )) + ) : ( + + +
+ + Tidak ada data berita yang cocok + +
+
+
+ )} + + +
+
load(newPage)} // ini penting! + onChange={(newPage) => { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
); } + export default Berita; diff --git a/src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx deleted file mode 100644 index 4e635c6c..00000000 --- a/src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx +++ /dev/null @@ -1,161 +0,0 @@ -'use client' -/* eslint-disable react-hooks/exhaustive-deps */ -import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; -import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; -import colors from '@/con/colors'; -import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { Dropzone } from '@mantine/dropzone'; -import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; -import { useParams, useRouter } from 'next/navigation'; -import { useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; -import { useProxy } from 'valtio/utils'; - - -function EditFoto() { - const fotoState = useProxy(stateGallery.foto) - const router = useRouter(); - const params = useParams(); - const [previewImage, setPreviewImage] = useState(null); - const [file, setFile] = useState(null); - const [formData, setFormData] = useState({ - name: fotoState.update.form.name || '', - deskripsi: fotoState.update.form.deskripsi || '', - imagesId: fotoState.update.form.imagesId || '' - }); - - useEffect(() => { - const loadFoto = async () => { - const id = params?.id as string; - if (!id) return; - try { - const data = await fotoState.update.load(id); - if (data) { - setFormData({ - name: data.name || '', - deskripsi: data.deskripsi || '', - imagesId: data.imageGalleryFoto?.id || '' - }); - if (data?.imageGalleryFoto?.link) { - setPreviewImage(data.imageGalleryFoto.link); - } - } - } catch (error) { - console.error('Error loading foto:', error); - toast.error('Gagal memuat data foto'); - } - }; - loadFoto(); - }, [params?.id]); - - const handleSubmit = async () => { - try { - fotoState.update.form = { - ...fotoState.update.form, - name: formData.name, - deskripsi: formData.deskripsi, - imagesId: formData.imagesId - }; - if (file) { - const res = await ApiFetch.api.fileStorage.create.post({ - file, - name: file.name, - }); - const uploaded = res.data?.data; - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } - fotoState.update.form.imagesId = uploaded.id; - } - await fotoState.update.update(); - toast.success('Foto berhasil diperbarui!'); - router.push('/admin/desa/gallery/foto'); - } catch (error) { - console.error('Error updating foto:', error); - toast.error('Terjadi kesalahan saat memperbarui foto'); - } - }; - - return ( - - - - - - - - Edit Foto - Judul Foto} - placeholder='Masukkan judul foto' - value={formData.name} - onChange={(e) => - (formData.name = e.target.value) - } - /> - - Upload Foto - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - - -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {previewImage ? ( - - ) : ( -
- -
- )} -
- - Deskripsi Foto - { - fotoState.update.form.deskripsi = val; - }} - /> - - - - -
-
-
- ); -} - -export default EditFoto; diff --git a/src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx b/src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx deleted file mode 100644 index 682c33c0..00000000 --- a/src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx +++ /dev/null @@ -1,112 +0,0 @@ -'use client' -import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; -import React from 'react'; -import { useProxy } from 'valtio/utils'; -import { useState } from 'react'; -import { useParams, useRouter } from 'next/navigation'; -import { useShallowEffect } from '@mantine/hooks'; -import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; -import colors from '@/con/colors'; -import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; - -function DetailFoto() { - const fotoState = useProxy(stateGallery.foto) - const [modalHapus, setModalHapus] = useState(false); - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() - - useShallowEffect(() => { - fotoState.findUnique.load(params?.id as string) - }, []) - - const handleHapus = () => { - if (selectedId) { - fotoState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/desa/gallery/foto") - } - } - - if (!fotoState.findUnique.data) { - return ( - - - - ) - } - - return ( - - - - - - - Detail Foto - {fotoState.findUnique.data ? ( - - - - Judul - {fotoState.findUnique.data?.name} - - - Tanggal Foto - {new Date(fotoState.findUnique.data?.createdAt).toDateString()} - - - Deskripsi - - - - Gambar - gambar - - - - - - - - ) : null} - - - - {/* Modal Konfirmasi Hapus */} - setModalHapus(false)} - onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus berita ini?' - /> - - ); -} - -export default DetailFoto; diff --git a/src/app/admin/(dashboard)/desa/gallery/foto/create/page.tsx b/src/app/admin/(dashboard)/desa/gallery/foto/create/page.tsx deleted file mode 100644 index bcb4ea02..00000000 --- a/src/app/admin/(dashboard)/desa/gallery/foto/create/page.tsx +++ /dev/null @@ -1,147 +0,0 @@ -'use client' -import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; -import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; -import colors from '@/con/colors'; -import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { Dropzone } from '@mantine/dropzone'; -import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import { useState } from 'react'; -import { toast } from 'react-toastify'; -import { useProxy } from 'valtio/utils'; - - - -function CreateFoto() { - const fotoState = useProxy(stateGallery.foto) - const router = useRouter(); - const [previewImage, setPreviewImage] = useState(null); - const [file, setFile] = useState(null); - - const resetForm = () => { - fotoState.create.form = { - name: "", - deskripsi: "", - imagesId: "", - }; - - setPreviewImage(null) - setFile(null) - }; - - const handleSubmit = async () => { - if (!file) { - return toast.warn("Pilih file gambar terlebih dahulu"); - } - - const res = await ApiFetch.api.fileStorage.create.post({ - file, - name: file.name, - }); - - const uploaded = res.data?.data; - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } - - fotoState.create.form.imagesId = uploaded.id; - await fotoState.create.create(); - resetForm(); - router.push("/admin/desa/gallery/foto") - }; - - return ( - - - - - - - - Create Foto - Judul Foto} - placeholder='Masukkan judul foto' - value={fotoState.create.form.name} - onChange={(val) => { - fotoState.create.form.name = val.target.value; - }} - /> - - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - - -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
-
- - Deskripsi Foto - { - fotoState.create.form.deskripsi = val; - }} - /> - - - - -
-
-
- ); -} - -export default CreateFoto; diff --git a/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx b/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx index 7fe4e25a..e850cec6 100644 --- a/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/foto/page.tsx @@ -1,9 +1,9 @@ "use client"; -import colors from "@/con/colors"; import stateFileStorage from "@/state/state-list-image"; import { ActionIcon, Box, + Card, Flex, Group, Image, @@ -13,7 +13,8 @@ import { Stack, Text, TextInput, - Title + Title, + Tooltip, } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; import { IconSearch, IconTrash, IconX } from "@tabler/icons-react"; @@ -29,95 +30,128 @@ export default function ListImage() { }, []); let timeOut: NodeJS.Timer; + return ( - - - List Foto + + + + Galeri Foto + } + radius="xl" + size="md" + placeholder="Cari foto berdasarkan nama..." + leftSection={} rightSection={ { - stateFileStorage.load(); - }} + variant="light" + color="gray" + radius="xl" + onClick={() => stateFileStorage.load()} > - + } - placeholder="Pencarian" onChange={(e) => { if (timeOut) clearTimeout(timeOut); timeOut = setTimeout(() => { stateFileStorage.load({ search: e.target.value }); - }, 200); + }, 300); }} /> - - - {list && - list.map((v, k) => { - return ( - - - { - // copy to clipboard - navigator.clipboard.writeText(v.url); - toast("Berhasil disalin"); - }} - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.8 }} - > - {v.name} - - - - {v.name} - - - - + {list && list.length > 0 ? ( + + {list.map((v, k) => ( + + + { + navigator.clipboard.writeText(v.url); + toast("Tautan foto berhasil disalin"); + }} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + style={{ cursor: "pointer" }} + > + {v.name} + + + + + {v.name} + + + + + + { - stateFileStorage.del({ name: v.name }).finally(() => { - toast("Berhasil dihapus"); - }); - }} - /> - - - - ); - })} - + radius="md" + onClick={() => { + stateFileStorage + .del({ name: v.name }) + .finally(() => toast("Foto berhasil dihapus")); + }} + > + + + + + + + ))} + + ) : ( + + Kosong + + Belum ada foto yang tersedia + + + )}
- {total && ( - { - stateFileStorage.page = e; - stateFileStorage.load(); - }} - /> + + {total && total > 1 && ( + + { + stateFileStorage.page = page; + stateFileStorage.load(); + }} + /> + )}
); diff --git a/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx b/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx index 7a148d73..8db41797 100644 --- a/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/lib/layoutTabs.tsx @@ -1,9 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { IconPhoto, IconVideo } from '@tabler/icons-react'; function LayoutTabsGallery({ children }: { children: React.ReactNode }) { const router = useRouter() @@ -12,16 +13,21 @@ function LayoutTabsGallery({ children }: { children: React.ReactNode }) { { label: "Foto", value: "foto", - href: "/admin/desa/gallery/foto" + href: "/admin/desa/gallery/foto", + icon: , + tooltip: "Kelola foto-foto galeri desa" }, { label: "Video", value: "video", - href: "/admin/desa/gallery/video" + href: "/admin/desa/gallery/video", + icon: , + tooltip: "Kelola video galeri desa" }, ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { const tab = tabs.find(t => t.value === value) @@ -39,24 +45,64 @@ function LayoutTabsGallery({ children }: { children: React.ReactNode }) { }, [pathname]) return ( - - Gallery - - - {tabs.map((e, i) => ( - {e.label} + + Gallery + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + <>{children} ))} - {children} ); } -export default LayoutTabsGallery; \ No newline at end of file +export default LayoutTabsGallery; diff --git a/src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx index 25550868..df68af39 100644 --- a/src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx @@ -3,7 +3,16 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -12,9 +21,9 @@ import { useProxy } from 'valtio/utils'; import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils'; function EditVideo() { - const router = useRouter(); - const videoState = useProxy(stateGallery.video) - const params = useParams() + const router = useRouter(); + const videoState = useProxy(stateGallery.video); + const params = useParams(); const [formData, setFormData] = useState({ name: '', @@ -30,7 +39,7 @@ function EditVideo() { const data = await videoState.update.load(id); if (data) { setFormData({ - name: data.name || '', + name: data.name || '', deskripsi: data.deskripsi || '', linkVideo: data.linkVideo || '', }); @@ -66,27 +75,36 @@ function EditVideo() { console.error('Error updating video:', error); toast.error('Terjadi kesalahan saat memperbarui video'); } - } + }; return ( - - - - - - - - Edit Video + + + + + + + Edit Video + + + + Judul Video} - placeholder='Masukkan judul video' + label="Judul Video" + placeholder="Masukkan judul video" value={formData.name} - onChange={(val) => { - setFormData({ ...formData, name: val.target.value }); - }} + onChange={(e) => setFormData({ ...formData, name: e.target.value })} + required /> @@ -94,36 +112,46 @@ function EditVideo() { label="Link Video YouTube" placeholder="https://www.youtube.com/watch?v=abc123" value={formData.linkVideo} - onChange={(e) => { - setFormData({ ...formData, linkVideo: e.currentTarget.value }); - }} + onChange={(e) => setFormData({ ...formData, linkVideo: e.currentTarget.value })} required /> - {embedLink && ( - + + + )} - Deskripsi Video + + Deskripsi Video + { - setFormData({ ...formData, deskripsi: val }); - }} + onChange={(val) => setFormData({ ...formData, deskripsi: val })} /> - - + + diff --git a/src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx b/src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx index b8783c63..52bef7cd 100644 --- a/src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx @@ -2,107 +2,145 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; import colors from '@/con/colors'; -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; - function DetailVideo() { - const videoState = useProxy(stateGallery.video) + const videoState = useProxy(stateGallery.video); const [modalHapus, setModalHapus] = useState(false); - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - videoState.findUnique.load(params?.id as string) - }, []) + videoState.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - videoState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/desa/gallery/video") + videoState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push("/admin/desa/gallery/video"); } - } + }; if (!videoState.findUnique.data) { return ( - + - ) + ); } + const data = videoState.findUnique.data; + return ( - - - - - - - Detail Video - {videoState.findUnique.data ? ( - - - - Judul - {videoState.findUnique.data?.name} - - - Video - + {/* Tombol Kembali */} + + + {/* Detail Video */} + + + + Detail Video + + + + + + Judul + {data?.name || '-'} + + + + Video + {data?.linkVideo ? ( + + ) : ( + Tidak ada video + )} + - + + Tanggal Video + + {data?.createdAt ? new Date(data.createdAt).toDateString() : '-'} + + - - Tanggal Video - {new Date(videoState.findUnique.data?.createdAt).toDateString()} - - - Deskripsi - - - + + Deskripsi + {data?.deskripsi ? ( + + ) : ( + Tidak ada deskripsi + )} + + + {/* Tombol Aksi */} + + + + + - - - - ) : null} + + + + @@ -111,17 +149,16 @@ function DetailVideo() { opened={modalHapus} onClose={() => setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus berita ini?' + text="Apakah Anda yakin ingin menghapus video ini?" /> - ); + function convertToEmbedUrl(youtubeUrl: string): string { try { const url = new URL(youtubeUrl); const videoId = url.searchParams.get("v"); if (!videoId) return youtubeUrl; - return `https://www.youtube.com/embed/${videoId}`; } catch (err) { console.error("Error converting YouTube URL to embed:", err); diff --git a/src/app/admin/(dashboard)/desa/gallery/video/create/page.tsx b/src/app/admin/(dashboard)/desa/gallery/video/create/page.tsx index bf48f34d..9194a15e 100644 --- a/src/app/admin/(dashboard)/desa/gallery/video/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/video/create/page.tsx @@ -1,8 +1,18 @@ -'use client' +'use client'; import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -10,77 +20,104 @@ import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; import { convertYoutubeUrlToEmbed } from '../../lib/youtube-utils'; - - function CreateVideo() { - const videoState = useProxy(stateGallery.video) + const videoState = useProxy(stateGallery.video); const router = useRouter(); - const [link, setLink] = useState(""); + const [link, setLink] = useState(''); const embedLink = convertYoutubeUrlToEmbed(link); const resetForm = () => { videoState.create.form = { - name: "", - deskripsi: "", - linkVideo: "", + name: '', + deskripsi: '', + linkVideo: '', }; + setLink(''); }; + const handleSubmit = async () => { if (!embedLink) { - toast.error("Link YouTube tidak valid. Pastikan formatnya benar."); + toast.error('Link YouTube tidak valid. Pastikan formatnya benar.'); return; } - - videoState.create.form.linkVideo = embedLink; // pastikan diset di sini juga (jaga-jaga) + + videoState.create.form.linkVideo = embedLink; await videoState.create.create(); resetForm(); - router.push("/admin/desa/gallery/video"); + router.push('/admin/desa/gallery/video'); }; - return ( - - - - + + {/* Header Back Button + Title */} + + + + + + Tambah Video + + - - - Create Video + {/* Card Form */} + + + {/* Judul */} Judul Video} - placeholder='Masukkan judul video' + label="Judul Video" + placeholder="Masukkan judul video" value={videoState.create.form.name} - onChange={(val) => { - videoState.create.form.name = val.target.value; + onChange={(e) => { + videoState.create.form.name = e.currentTarget.value; }} + required /> - - - { - setLink(e.currentTarget.value); - }} - required - /> - {embedLink && ( - - )} - - + {/* Link YouTube */} + setLink(e.currentTarget.value)} + required + /> + + {/* Preview Video */} + {embedLink && ( + + + + )} + + {/* Deskripsi */} - Deskripsi Video + + Deskripsi Video + { @@ -88,8 +125,21 @@ function CreateVideo() { }} /> - - + + {/* Button Submit */} + + diff --git a/src/app/admin/(dashboard)/desa/gallery/video/page.tsx b/src/app/admin/(dashboard)/desa/gallery/video/page.tsx index 3b33aad8..232204ff 100644 --- a/src/app/admin/(dashboard)/desa/gallery/video/page.tsx +++ b/src/app/admin/(dashboard)/desa/gallery/video/page.tsx @@ -1,13 +1,30 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import stateGallery from '../../../_state/desa/gallery'; function Video() { @@ -15,8 +32,8 @@ function Video() { return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -29,6 +46,7 @@ function Video() { function ListVideo({ search }: { search: string }) { const videoState = useProxy(stateGallery.video) const router = useRouter(); + const { data, page, @@ -41,72 +59,104 @@ function ListVideo({ search }: { search: string }) { load(page, 10, search) }, [page, search]) - const filteredData = (data || []) + const filteredData = data || [] if (loading || !data) { return ( - - - + + + ) } return ( - - - - - - Judul Video - Tanggal Video - Deskripsi Video - Detail - - - - {filteredData.map((item) => ( - - - - {item.name} - - - - - - {new Date(item.createdAt).toLocaleDateString('id-ID', { - day: 'numeric', - month: 'long', - year: 'numeric', - })} - - - - - - - - - - + + + Daftar Video + + + + + +
+ + + Judul Video + Tanggal + Deskripsi + Aksi - ))} - -
+ + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + {item.name} + + + + + + {new Date(item.createdAt).toLocaleDateString('id-ID', { + day: 'numeric', + month: 'long', + year: 'numeric', + })} + + + + + + + + + + + + + )) + ) : ( + + +
+ Tidak ada video yang cocok +
+
+
+ )} +
+ +
load(newPage)} // ini penting! + onChange={(newPage) => { + load(newPage, 10) + window.scrollTo({ top: 0, behavior: 'smooth' }) + }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx index 80841411..eed1a9a3 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx @@ -3,7 +3,7 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -49,52 +49,74 @@ function EditPelayananPendudukNonPermanent() { } return ( - - - - - - - - Edit Pelayanan Penduduk Non Permanent - Judul - { - setFormData({ - ...formData, - name: val.target.value, - }) - }} - /> - Deskripsi + + + + + + + Edit Pelayanan Penduduk Non Permanent + + + + + + Edit Pelayanan Penduduk Non Permanent + + {/* Nama Field */} + + setFormData({ ...formData, name: e.target.value }) + } + required + /> + + {/* Posisi Field */} + + + Deskripsi + { - setFormData({ - ...formData, - deskripsi: val, - }) + onChange={(htmlContent) => { + setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); }} /> - - - - - - - + + + {/* Submit Button */} + + + + + +
+
); diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx index 3d9065f3..0d89b55b 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx @@ -1,51 +1,103 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Divider, + Grid, + GridCol, + Paper, + Skeleton, + Stack, + Text, + Title, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconEdit } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; import stateLayananDesa from '../../../_state/desa/layananDesa'; -function SuratKeterangan() { - const router = useRouter() - const pelayananPendudukNonPermanen = useProxy(stateLayananDesa.pelayananPendudukNonPermanen) +function PelayananPendudukNonPermanent() { + const router = useRouter(); + const pelayananPendudukNonPermanen = useProxy( + stateLayananDesa.pelayananPendudukNonPermanen + ); useShallowEffect(() => { - pelayananPendudukNonPermanen.findById.load('1') - }, []) + pelayananPendudukNonPermanen.findById.load('1'); + }, []); if (!pelayananPendudukNonPermanen.findById.data) { return ( - - + + - ) + ); } + + const data = pelayananPendudukNonPermanen.findById.data; + return ( - - - - - - - - Preview Pelayanan Perizinan Berusaha - - - - - - + + + {/* Header */} + + + + Preview Pelayanan Penduduk Non Permanen + + + + + + + + + + {/* Content */} + + +
+ + {data.name} + +
+ + + + + +
- {pelayananPendudukNonPermanen.findById.data.name} -
-
-
+
+ ); } -export default SuratKeterangan; +export default PelayananPendudukNonPermanent; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/edit/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/edit/page.tsx index 777b65ef..05488b2d 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/edit/page.tsx @@ -3,7 +3,7 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -14,6 +14,7 @@ function EditPelayananPerizinanBerusaha() { const router = useRouter(); const params = useParams() const statePerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha) + const [formData, setFormData] = useState({ name: statePerizinanBerusaha.findById.data?.name || '', deskripsi: statePerizinanBerusaha.findById.data?.deskripsi || '', @@ -50,64 +51,81 @@ function EditPelayananPerizinanBerusaha() { } router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha') } + return ( - - - - - - - - Edit Pelayanan Perizinan Berusaha - Judul - { - setFormData({ - ...formData, - name: val.target.value, - }) - }} - /> - Link - { - setFormData({ - ...formData, - link: val.target.value, - }) - }} - /> - Deskripsi + + {/* Header Section */} + + + + + + Edit Pelayanan Perizinan Berusaha + + + + {/* Form Section */} + + + Edit Pelayanan Perizinan Berusaha + + {/* Nama Field */} + setFormData({ ...formData, name: e.target.value })} + required + /> + + {/* Link Field */} + setFormData({ ...formData, link: e.target.value })} + /> + + {/* Deskripsi Field */} + + Deskripsi { - setFormData({ - ...formData, - deskripsi: val, - }) - }} + onChange={(val) => setFormData({ ...formData, deskripsi: val })} /> - - - - - - - + + + {/* Action Buttons */} + + + + + +
+ ); diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx index 21ad4f68..2c6a41fe 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx @@ -1,6 +1,23 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Grid, GridCol, Group, Paper, Skeleton, Stack, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Divider, + Grid, + GridCol, + Group, + Paper, + Skeleton, + Stack, + Stepper, + StepperCompleted, + StepperStep, + Text, + Title, + Tooltip, +} from '@mantine/core'; import { IconEdit } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -9,88 +26,158 @@ import { useProxy } from 'valtio/utils'; import { useShallowEffect } from '@mantine/hooks'; function PerizinanBerusaha() { - const router = useRouter() - const pelayananPerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha) + const router = useRouter(); + const pelayananPerizinanBerusaha = useProxy( + stateLayananDesa.pelayananPerizinanBerusaha + ); const [active, setActive] = useState(1); - const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current)); - const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current)); + const nextStep = () => + setActive((current) => (current < 6 ? current + 1 : current)); + const prevStep = () => + setActive((current) => (current > 0 ? current - 1 : current)); + useShallowEffect(() => { - pelayananPerizinanBerusaha.findById.load('1') - }, []) + pelayananPerizinanBerusaha.findById.load('1'); + }, []); - if(!pelayananPerizinanBerusaha.findById.data) { + if (!pelayananPerizinanBerusaha.findById.data) { return ( - - + + - ) + ); } + + const data = pelayananPerizinanBerusaha.findById.data; + return ( - - - - - - - - Preview Pelayanan Perizinan Berusaha - - - - - - - - {pelayananPerizinanBerusaha.findById.data.name} - - Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti: - - + + {/* Header */} + + + + Preview Pelayanan Perizinan Berusaha + + + + + + + + - - - - - Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah - seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs - resmi OSS {pelayananPerizinanBerusaha.findById.data.link} atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha. + {/* Content */} + + +
+ + {data.name} + +
+ + + + + + + + Proses pendaftaran NIB melalui OSS mencakup beberapa langkah + umum: + + + + + + Pendaftaran akun pada portal OSS + + + Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya + + + Memilih KBLI dengan jenis usaha yang akan didaftarkan + + + Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku + + + Proses verifikasi dan persetujuan oleh instansi terkait + + + Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda + + + Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS + + + + + + + + + + + Penting untuk diingat bahwa prosedur dan persyaratan dapat + berubah seiring waktu. Untuk informasi yang lebih akurat dan + terkini, silakan kunjungi situs resmi OSS{' '} + + {data.link} + {' '} + atau hubungi instansi terkait di pemerintah Indonesia yang + bertanggung jawab atas urusan perizinan usaha. + +
-
-
+
+ ); } diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx index 17aef9a8..0a95e7d1 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx @@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -13,9 +24,10 @@ import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function EditSuratKeterangan() { - const router = useRouter() - const params = useParams() - const stateSurat = useProxy(stateLayananDesa.suratKeterangan) + const router = useRouter(); + const params = useParams(); + const stateSurat = useProxy(stateLayananDesa.suratKeterangan); + const [previewImage, setPreviewImage] = useState(null); const [previewImage2, setPreviewImage2] = useState(null); const [file, setFile] = useState(null); @@ -25,39 +37,32 @@ function EditSuratKeterangan() { deskripsi: stateSurat.edit.form.deskripsi, imageId: stateSurat.edit.form.imageId, image2Id: stateSurat.edit.form.image2Id, - }) + }); useEffect(() => { const loadSurat = async () => { const id = params?.id as string; if (!id) return; + try { const data = await stateSurat.edit.load(id); if (data) { setFormData({ - name: data.name || "", - deskripsi: data.deskripsi || "", - imageId: data.imageId || "", - image2Id: data.image2Id || "", + name: data.name || '', + deskripsi: data.deskripsi || '', + imageId: data.imageId || '', + image2Id: data.image2Id || '', }); - if (data.image?.link) { - setPreviewImage(data.image.link); - } else { - setPreviewImage(null); - } - - if (data.image2?.link) { - setPreviewImage2(data.image2.link); - } else { - setPreviewImage2(null); - } + setPreviewImage(data.image?.link || null); + setPreviewImage2(data.image2?.link || null); } } catch (error) { - console.error("Error loading surat:", error); - toast.error("Gagal memuat data surat"); + console.error('Error loading surat:', error); + toast.error('Gagal memuat data surat'); } }; + loadSurat(); }, [params?.id]); @@ -65,171 +70,199 @@ function EditSuratKeterangan() { try { stateSurat.edit.form = { ...stateSurat.edit.form, - name: formData.name, - deskripsi: formData.deskripsi, - imageId: formData.imageId, - image2Id: formData.image2Id, - } + ...formData, + }; + if (file) { const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name }); const uploaded = res.data?.data; - - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } - + if (!uploaded?.id) return toast.error('Gagal upload gambar'); stateSurat.edit.form.imageId = uploaded.id; } if (file2) { const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name }); const uploaded = res.data?.data; - - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } - + if (!uploaded?.id) return toast.error('Gagal upload gambar'); stateSurat.edit.form.image2Id = uploaded.id; } - await stateSurat.edit.update() - toast.success("Surat berhasil diperbarui!") - router.push("/admin/desa/layanan/pelayanan_surat_keterangan") + await stateSurat.edit.update(); + toast.success('Surat berhasil diperbarui!'); + router.push('/admin/desa/layanan/pelayanan_surat_keterangan'); } catch (error) { - console.error("Error updating surat:", error); - toast.error("Terjadi kesalahan saat memperbarui surat"); + console.error('Error updating surat:', error); + toast.error('Terjadi kesalahan saat memperbarui surat'); } - } - + }; return ( - - - - - - - Edit Surat Keterangan + + {/* Back Button */} + + + + + + Edit Surat Keterangan + + + + + { - setFormData({ ...formData, name: val.target.value }); - }} - label={Nama Surat Keterangan} - placeholder="masukkan nama surat keterangan" + onChange={(e) => setFormData({ ...formData, name: e.target.value })} + required /> + - Konten + + Konten + { - setFormData({ ...formData, deskripsi: htmlContent }); - }} + onChange={(htmlContent) => setFormData({ ...formData, deskripsi: htmlContent })} /> - - Gambar - - { - const file = files[0]; // Hanya ambil file pertama - if (file) { - setFile(file); - setPreviewImage(URL.createObjectURL(file)); // Buat preview - } - }} - maxSize={5 * 1024 ** 2} // 5MB - accept={{ - 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] - }} - > - - - - - - - - - - -
- - Drag images here or click to select files - - - Attach as many files as you like, each file should not exceed 5mb - -
-
-
- {previewImage && ( + {/* Upload Gambar 1 */} + + + Gambar 1 + + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file + + + Maksimal 5MB, format gambar wajib + + + + + + {previewImage && ( + Preview - )} - + + )}
- - Gambar - - { - const file = files[0]; // Hanya ambil file pertama - if (file) { - setFile2(file); - setPreviewImage2(URL.createObjectURL(file)); // Buat preview - } - }} - maxSize={5 * 1024 ** 2} // 5MB - accept={{ - 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] - }} - > - - - - - - - - - - -
- - Drag images here or click to select files - - - Attach as many files as you like, each file should not exceed 5mb - -
-
-
- {previewImage2 && ( + {/* Upload Gambar 2 */} + + + Gambar 2 + + { + const selectedFile = files[0]; + if (selectedFile) { + setFile2(selectedFile); + setPreviewImage2(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file + + + Maksimal 5MB, format gambar wajib + + + + + + {previewImage2 && ( + Preview - )} - + + )}
- + + + +
diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx index 15ec3005..0caa6304 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx @@ -2,100 +2,177 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; -import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Skeleton, + Stack, + Text, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; function DetailSuratKeterangan() { - const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan) - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() + const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - suratKeteranganState.findUnique.load(params?.id as string) - }, []) + suratKeteranganState.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - suratKeteranganState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/desa/layanan/pelayanan_surat_keterangan") + suratKeteranganState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push('/admin/desa/layanan/pelayanan_surat_keterangan'); } - } + }; if (!suratKeteranganState.findUnique.data) { return ( - {Array.from({ length: 10 }).map((_, k) => ( - - ))} + - ) + ); } + const data = suratKeteranganState.findUnique.data; + return ( - - - - - - - Detail Surat Keterangan - {suratKeteranganState.findUnique.data ? ( - - - - Nama - {suratKeteranganState.findUnique.data?.name} - - - Deskripsi - - - - Gambar - gambar - - - Gambar - gambar - - + + {/* Tombol Kembali */} + + + + + + Detail Surat Keterangan + + + + + + + Nama + + + {data?.name || '-'} + + + + + + Deskripsi + + + + + + + Gambar + + {data?.image?.link ? ( + gambar + ) : ( + + Tidak ada gambar + + )} + + + + + Gambar 2 + + {data?.image2?.link ? ( + gambar + ) : ( + + Tidak ada gambar + + )} + + + + + + + - - - - ) : null} + + + +
@@ -104,7 +181,7 @@ function DetailSuratKeterangan() { opened={modalHapus} onClose={() => setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus berita ini?' + text="Apakah Anda yakin ingin menghapus surat keterangan ini?" />
); diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx index 546ce53f..55c95ffb 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx @@ -1,9 +1,21 @@ -'use client' +'use client'; + import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -12,25 +24,25 @@ import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function CreateSuratKeterangan() { - const stateSurat = useProxy(stateLayananDesa.suratKeterangan) + const stateSurat = useProxy(stateLayananDesa.suratKeterangan); const [previewImage2, setPreviewImage2] = useState<{ preview: string; file: File } | null>(null); const [previewImage, setPreviewImage] = useState<{ preview: string; file: File } | null>(null); - const router = useRouter() + const router = useRouter(); const resetForm = () => { stateSurat.create.form = { - name: "", - deskripsi: "", - imageId: "", - image2Id: "" - } - setPreviewImage(null) - setPreviewImage2(null) - } + name: '', + deskripsi: '', + imageId: '', + image2Id: '', + }; + setPreviewImage(null); + setPreviewImage2(null); + }; const handleSubmit = async () => { if (!previewImage) { - return toast.warn("Pilih file gambar utama terlebih dahulu"); + return toast.warn('Pilih file gambar utama terlebih dahulu'); } try { @@ -42,11 +54,10 @@ function CreateSuratKeterangan() { const uploadedImage1 = res1.data?.data; if (!uploadedImage1?.id) { - return toast.error("Gagal upload gambar utama"); + return toast.error('Gagal upload gambar utama'); } let uploadedImage2 = null; - // Upload gambar kedua jika ada if (previewImage2) { const res2 = await ApiFetch.api.fileStorage.create.post({ file: previewImage2.file, @@ -55,44 +66,58 @@ function CreateSuratKeterangan() { uploadedImage2 = res2.data?.data; } - // Set form data stateSurat.create.form.imageId = uploadedImage1.id; if (uploadedImage2?.id) { stateSurat.create.form.image2Id = uploadedImage2.id; } - // Create the record await stateSurat.create.create(); - - // Reset form dan redirect resetForm(); - toast.success("Data surat keterangan berhasil ditambahkan"); - router.push("/admin/desa/layanan/pelayanan_surat_keterangan"); + toast.success('Data surat keterangan berhasil ditambahkan'); + router.push('/admin/desa/layanan/pelayanan_surat_keterangan'); } catch (error) { - console.error("Error creating surat keterangan:", error); - toast.error("Terjadi kesalahan saat menambahkan surat keterangan"); + console.error('Error creating surat keterangan:', error); + toast.error('Terjadi kesalahan saat menambahkan surat keterangan'); } }; + return ( - - - - - - - Create Surat Keterangan + + {/* Header */} + + + + + + Tambah Surat Keterangan + + + + + + {/* Nama Surat */} { - stateSurat.create.form.name = val.target.value; - }} - label={Nama Surat Keterangan} - placeholder="masukkan nama surat keterangan" + onChange={(val) => (stateSurat.create.form.name = val.target.value)} + label={Nama Surat Keterangan} + placeholder="Masukkan nama surat keterangan" + required /> + + {/* Konten */} - Konten + + Konten + { @@ -100,106 +125,124 @@ function CreateSuratKeterangan() { }} /> + + {/* Gambar Utama */} - Gambar Utama + + Gambar Utama + { const file = files[0]; if (file) { setPreviewImage({ file, - preview: URL.createObjectURL(file) + preview: URL.createObjectURL(file), }); } }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} maxSize={5 * 1024 ** 2} - accept={{ - 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] - }} + accept={{ 'image/*': [] }} + radius="md" + p="xl" > - + - + - + - + -
- Seret gambar ke sini atau klik untuk memilih - - Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP) - -
+ + Seret gambar atau klik untuk memilih file (maks 5MB) +
+ {previewImage && ( - Preview Gambar Utama + + Preview Gambar Utama + )}
- - Gambar Tambahan (Opsional) + {/* Gambar Tambahan */} + + + Gambar Tambahan (Opsional) + { const file = files[0]; if (file) { setPreviewImage2({ file, - preview: URL.createObjectURL(file) + preview: URL.createObjectURL(file), }); } }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} maxSize={5 * 1024 ** 2} - accept={{ - 'image/*': ['.jpeg', '.jpg', '.png', '.webp'] - }} + accept={{ 'image/*': [] }} + radius="md" + p="xl" > - + - + - + - + -
- Seret gambar ke sini atau klik untuk memilih - - Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP) - -
+ + Seret gambar atau klik untuk memilih file (maks 5MB) +
+ {previewImage2 ? ( - Preview Gambar Tambahan + + Preview Gambar Tambahan + ) : ( - + Kosongkan jika tidak ada gambar tambahan )}
- + + {/* Tombol Simpan */} + + +
diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx index 787b141e..5276c315 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx @@ -1,13 +1,30 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; +import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import stateLayananDesa from '../../../_state/desa/layananDesa'; function SuratKeterangan() { @@ -16,7 +33,7 @@ function SuratKeterangan() { } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -27,8 +44,8 @@ function SuratKeterangan() { } function ListSuratKeterangan({ search }: { search: string }) { - const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan) - const router = useRouter() + const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan); + const router = useRouter(); const { data, @@ -39,102 +56,111 @@ function ListSuratKeterangan({ search }: { search: string }) { } = suratKeteranganState.findMany; useEffect(() => { - load(page, 10) - }, []) + load(page, 10, search); + }, [page, search]); + + const filteredData = useMemo(() => { + if (!data) return []; + const keyword = search.toLowerCase(); + return data.filter(item => + item.name?.toLowerCase().includes(keyword) || + item.deskripsi?.toLowerCase().includes(keyword) + ); + }, [data, search]); + + // Loading state + if (loading || !data) { + return ( + + + + ); + } - const filteredData = useMemo(() => { - if (!data) return []; - return data.filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name?.toLowerCase().includes(keyword) || - item.deskripsi?.toLowerCase().includes(keyword) - ); - }) - }, [data, search]); - - // Handle loading state - if (loading || !data) { - return ( - - - - ); - } - - if (data.length === 0) { - return ( - - - - - - - - Nama - Deskripsi - Detail - - -
-
-
-
- ); - } return ( - - - - - - Nama - Deskripsi - Detail - - - - {filteredData.map((item) => ( - - - - {item.name} - - - - - - - - - - - - - - ))} - -
+ + + List Surat Keterangan + + + + + + + + + Nama + Deskripsi + Aksi + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + + {item.name} + + + + + + + + + + + + + )) + ) : ( + + +
+ Tidak ada data surat keterangan yang cocok +
+
+
+ )} +
+
+
-
+
{ - load(newPage, 10); - window.scrollTo(0, 0); + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/edit/page.tsx index 8e84e4a3..a80f7bb1 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/edit/page.tsx @@ -2,22 +2,34 @@ /* eslint-disable react-hooks/exhaustive-deps */ import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; +import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; function EditPelayananTelunjukSakti() { - const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa) - const router = useRouter() - const params = useParams() + const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa); + const router = useRouter(); + const params = useParams(); + const [formData, setFormData] = useState({ name: stateTelunjukDesa.edit.form.name, deskripsi: stateTelunjukDesa.edit.form.deskripsi, link: stateTelunjukDesa.edit.form.link, - }) + }); useEffect(() => { const loadPelayananTelunjukSakti = async () => { @@ -27,14 +39,14 @@ function EditPelayananTelunjukSakti() { const data = await stateTelunjukDesa.edit.load(id); if (data) { setFormData({ - name: data.name, - deskripsi: data.deskripsi, - link: data.link, + name: data.name || '', + deskripsi: data.deskripsi || '', + link: data.link || '', }); } } catch (error) { - console.error("Error loading pelayanan telunjuk sakti:", error); - toast.error("Gagal memuat data pelayanan telunjuk sakti"); + console.error('Error loading pelayanan telunjuk sakti:', error); + toast.error('Gagal memuat data pelayanan telunjuk sakti'); } }; loadPelayananTelunjukSakti(); @@ -44,57 +56,86 @@ function EditPelayananTelunjukSakti() { try { stateTelunjukDesa.edit.form = { ...stateTelunjukDesa.edit.form, - name: formData.name, - deskripsi: formData.deskripsi, - link: formData.link, - } - await stateTelunjukDesa.edit.update() - toast.success("Pelayanan telunjuk sakti berhasil diperbarui!") - router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa") + ...formData, + }; + await stateTelunjukDesa.edit.update(); + toast.success('Pelayanan telunjuk sakti berhasil diperbarui!'); + router.push('/admin/desa/layanan/pelayanan_telunjuk_sakti_desa'); } catch (error) { - console.error("Error updating pelayanan telunjuk sakti:", error); - toast.error("Terjadi kesalahan saat memperbarui pelayanan telunjuk sakti"); + console.error('Error updating pelayanan telunjuk sakti:', error); + toast.error('Terjadi kesalahan saat memperbarui pelayanan telunjuk sakti'); } - } + }; return ( - - - + + {/* Back Button + Title */} + + + + + + Edit Pelayanan Telunjuk Sakti Desa + + + + + + {/* Nama */} + setFormData({ ...formData, name: e.target.value })} + required + /> + + {/* Deskripsi pakai editor */} + + + Deskripsi + + setFormData({ ...formData, deskripsi: htmlContent })} + /> - - - Edit Surat Keterangan - { - setFormData({ ...formData, name: val.target.value }); - }} - label={Nama Surat Keterangan} - placeholder="masukkan nama surat keterangan" - /> - { - setFormData({ ...formData, deskripsi: val.target.value }); - }} - label={Tautan Link} - placeholder="masukkan tautan link" - /> - { - setFormData({ ...formData, link: val.target.value }); - }} - label={Link} - placeholder="masukkan link" - /> - - - - + + {/* Link */} + setFormData({ ...formData, link: e.target.value })} + /> + + {/* Tombol Simpan */} + + + + + + ); } diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/page.tsx index 7cee7349..37ca8a44 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/[id]/page.tsx @@ -2,109 +2,166 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Skeleton, + Stack, + Text, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; function DetailPelayananTelunjukSakti() { - const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa) - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() + const telunjukSaktiState = useProxy( + stateLayananDesa.pelayananTelunjukSaktiDesa + ); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - telunjukSaktiState.findUnique.load(params?.id as string) - }, []) + telunjukSaktiState.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - telunjukSaktiState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa") + telunjukSaktiState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push('/admin/desa/layanan/pelayanan_telunjuk_sakti_desa'); } - } + }; if (!telunjukSaktiState.findUnique.data) { return ( - {Array.from({ length: 10 }).map((_, k) => ( - - ))} + - ) + ); } + const data = telunjukSaktiState.findUnique.data; + return ( - - - - - - - Detail Pelayanan Telunjuk Sakti Desa - {telunjukSaktiState.findUnique.data ? ( - - - - Nama - {telunjukSaktiState.findUnique.data?.name} - - - Link + + {/* Tombol Kembali */} + + + + + + Detail Pelayanan Telunjuk Sakti Desa + + + + + + + Nama + + + {data?.name || '-'} + + + + + + Link + + {data?.link ? ( - {telunjukSaktiState.findUnique.data?.link} + {data.link} - - - Deskripsi - {telunjukSaktiState.findUnique.data?.deskripsi} - - + ) : ( + + Tidak ada link + + )} + + + + + Deskripsi + + + + + + + + + - - - - ) : null} + + + + @@ -113,7 +170,7 @@ function DetailPelayananTelunjukSakti() { opened={modalHapus} onClose={() => setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus berita ini?' + text="Apakah Anda yakin ingin menghapus layanan ini?" /> ); diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/create/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/create/page.tsx index 83d28412..5cf7028f 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/create/page.tsx @@ -1,64 +1,117 @@ -'use client' +'use client'; + import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; +import { toast } from 'react-toastify'; function CreatePelayananTelunjukDesa() { - const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa) - const router = useRouter() + const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa); + const router = useRouter(); const resetForm = () => { stateTelunjukDesa.create.form = { - name: "", - deskripsi: "", - link: "", - } - } + name: '', + deskripsi: '', + link: '', + }; + }; const handleSubmit = async () => { - await stateTelunjukDesa.create.create() - resetForm() - router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa") + try { + await stateTelunjukDesa.create.create(); + resetForm(); + toast.success('Data pelayanan telunjuk sakti berhasil ditambahkan'); + router.push('/admin/desa/layanan/pelayanan_telunjuk_sakti_desa'); + } catch (error) { + console.error('Error create pelayanan telunjuk sakti:', error); + toast.error('Terjadi kesalahan saat menambahkan data'); + } + }; - } return ( - - - - - - - Create Pelayanan Telunjuk Sakti Desa + + {/* Header */} + + + + + + Tambah Pelayanan Telunjuk Sakti Desa + + + + {/* Form */} + + + {/* Nama */} { stateTelunjukDesa.create.form.name = val.target.value; }} - label={Nama Pelayanan Telunjuk Sakti Desa} - placeholder="masukkan nama pelayanan telunjuk sakti desa" + label={Nama Pelayanan} + placeholder="Masukkan nama pelayanan telunjuk sakti desa" + required /> + + {/* Deskripsi */} { stateTelunjukDesa.create.form.deskripsi = val.target.value; }} - label={Tautan Link} - placeholder="masukkan tautan link" + label={Deskripsi} + placeholder="Masukkan deskripsi pelayanan" /> + + {/* Link */} { stateTelunjukDesa.create.form.link = val.target.value; }} - label={Link} - placeholder="masukkan link" + label={Link} + placeholder="Masukkan link pelayanan" /> - + + {/* Tombol Simpan */} + + + diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx index bee8a092..be6ea158 100644 --- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx @@ -1,13 +1,187 @@ +// /* eslint-disable react-hooks/exhaustive-deps */ +// 'use client' +// import colors from '@/con/colors'; +// import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +// import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +// import { useRouter } from 'next/navigation'; +// import { useEffect, useMemo, useState } from 'react'; +// import { useProxy } from 'valtio/utils'; +// import HeaderSearch from '../../../_com/header'; +// import JudulList from '../../../_com/judulList'; +// import stateLayananDesa from '../../../_state/desa/layananDesa'; + +// function PelayananTelunjukSakti() { +// const [search, setSearch] = useState(""); +// return ( +// +// } +// value={search} +// onChange={(e) => setSearch(e.currentTarget.value)} +// /> +// +// +// ); +// } + +// function ListPelayananTelunjukSakti({ search }: { search: string }) { +// const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa) +// const router = useRouter() + +// const { +// data, +// page, +// totalPages, +// loading, +// load, +// } = telunjukSaktiState.findMany; + +// useEffect(() => { +// load(page, 10) +// }, []) + +// const filteredData = useMemo(() => { +// if (!data) return []; +// return data.filter(item => { +// const keyword = search.toLowerCase(); +// return ( +// item.name?.toLowerCase().includes(keyword) || +// item.link?.toLowerCase().includes(keyword) || +// item.deskripsi?.toLowerCase().includes(keyword) +// ); +// }) +// .sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki); +// }, [data, search]); + +// if (loading || !data) { +// return ( +// +// +// +// ); +// } + +// if (data.length === 0) { +// return ( +// +// +// +// +// +// +// Nama +// Link +// Detail +// +// +// +// +// +// +// Tidak ada data +// +// +// +// +//
+//
+//
+// ); +// } + +// return ( +// +// +// +// +// +// +// Nama +// Link +// Detail +// +// +// +// {filteredData.map((item) => ( +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ))} +// +//
+//
+//
+// { +// load(newPage, 10); +// window.scrollTo(0, 0); +// }} +// total={totalPages} +// mt="md" +// mb="md" +// /> +//
+//
+// ); +// } + +// export default PelayananTelunjukSakti; + /* eslint-disable react-hooks/exhaustive-deps */ 'use client' + import colors from '@/con/colors'; -import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip, +} from '@mantine/core'; +import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import stateLayananDesa from '../../../_state/desa/layananDesa'; function PelayananTelunjukSakti() { @@ -15,8 +189,8 @@ function PelayananTelunjukSakti() { return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -27,125 +201,113 @@ function PelayananTelunjukSakti() { } function ListPelayananTelunjukSakti({ search }: { search: string }) { - const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa) - const router = useRouter() + const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa); + const router = useRouter(); - const { - data, - page, - totalPages, - loading, - load, - } = telunjukSaktiState.findMany; + const { data, page, totalPages, loading, load } = telunjukSaktiState.findMany; useEffect(() => { - load(page, 10) - }, []) + load(page, 10, search); + }, [page, search]); - const filteredData = useMemo(() => { - if (!data) return []; - return data.filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name?.toLowerCase().includes(keyword) || - item.link?.toLowerCase().includes(keyword) || - item.deskripsi?.toLowerCase().includes(keyword) - ); - }) - .sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki); - }, [data, search]); + const filteredData = data || []; if (loading || !data) { return ( - + ); } - if (data.length === 0) { - return ( - - - - - - - Nama - Link - Detail - - - - - - - Tidak ada data - - - - -
-
-
- ); - } - return ( - - - - - - Nama - Link - Detail - - - - {filteredData.map((item) => ( - - - - - - - - - - - - - - - - - - + + + Daftar Pelayanan Telunjuk Sakti + + + + + +
+ + + Nama + Link + Detail - ))} - -
+ + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + + {item.name} + + + + + + + + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data layanan yang cocok + +
+
+
+ )} +
+ +
{ - load(newPage, 10); - window.scrollTo(0, 0); + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
@@ -153,3 +315,4 @@ function ListPelayananTelunjukSakti({ search }: { search: string }) { } export default PelayananTelunjukSakti; + diff --git a/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx index f189d63c..f9a4c105 100644 --- a/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx @@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -84,87 +95,104 @@ function EditPenghargaan() { } return ( - - - - - - - Edit Penghargaan + + {/* Tombol Back + Title */} + + + + + + Edit Penghargaan + + + + {/* Card Form */} + + + {/* Input Judul */} setFormData({ ...formData, name: e.target.value })} - label={Judul} - placeholder="masukkan judul" + required /> + {/* Input Juara */} setFormData({ ...formData, juara: e.target.value })} - label={Juara} - placeholder="masukkan juara" + required /> + + {/* Upload Gambar */} - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + + Gambar Penghargaan + + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file + + + Maksimal 5MB, format gambar wajib + + + + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
+ {previewImage && ( + + Preview Gambar + + )}
+ {/* Deskripsi */} - Deskripsi + + Deskripsi + { @@ -174,7 +202,21 @@ function EditPenghargaan() { /> - + {/* Tombol Simpan */} + + +
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx index 2204d825..07a07bb1 100644 --- a/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx @@ -4,105 +4,166 @@ import penghargaanState from '../../../_state/desa/penghargaan'; import { useProxy } from 'valtio/utils'; import { useParams, useRouter } from 'next/navigation'; import { useShallowEffect } from '@mantine/hooks'; -import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Skeleton, + Stack, + Text, + Tooltip, +} from '@mantine/core'; import colors from '@/con/colors'; -import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; function DetailPenghargaan() { - const statePenghargaan = useProxy(penghargaanState) + const statePenghargaan = useProxy(penghargaanState); const [modalHapus, setModalHapus] = useState(false); const [selectedId, setSelectedId] = useState(null); - const router = useRouter() - const params = useParams() + const router = useRouter(); + const params = useParams(); useShallowEffect(() => { - statePenghargaan.findUnique.load(params?.id as string) - }, []) + statePenghargaan.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - statePenghargaan.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/desa/penghargaan") + statePenghargaan.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push('/admin/desa/penghargaan'); } - } + }; if (!statePenghargaan.findUnique.data) { return ( - + - ) + ); } + const data = statePenghargaan.findUnique.data; + return ( - - - - - - - Detail Penghargaan - {statePenghargaan.findUnique.data ? ( - - - - Gambar - gambar - - - Judul - {statePenghargaan.findUnique.data?.name} - - - Juara - {statePenghargaan.findUnique.data?.juara} - - - Deskripsi - - - + + + + + + + Detail Penghargaan + + + + + + + Gambar + + {data.image?.link ? ( + {data.name + ) : ( + + Tidak ada gambar + + )} + + + + + Judul + + + {data.name || '-'} + + + + + + Juara + + + {data.juara || '-'} + + + + + + Deskripsi + + + + + + + + + - - - - ) : null} + + + +
- {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus penghargaan ini?' + text="Apakah Anda yakin ingin menghapus penghargaan ini?" />
); diff --git a/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx index 9083e826..37c36247 100644 --- a/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx @@ -1,7 +1,18 @@ -'use client' +'use client'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -11,74 +22,88 @@ import { useProxy } from 'valtio/utils'; import CreateEditor from '../../../_com/createEditor'; import penghargaanState from '../../../_state/desa/penghargaan'; - function CreatePenghargaan() { - const statePenghargaan = useProxy(penghargaanState) + const statePenghargaan = useProxy(penghargaanState); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); - const router = useRouter() + const router = useRouter(); const resetForm = () => { statePenghargaan.create.form = { - name: "", - juara: "", - deskripsi: "", - imageId: "", - } - setPreviewImage(null) - setFile(null) - } + name: '', + juara: '', + deskripsi: '', + imageId: '', + }; + setPreviewImage(null); + setFile(null); + }; const handleSubmit = async () => { if (!file) { - return toast.error("Silahkan pilih file gambar terlebih dahulu") + return toast.warn('Silakan pilih file gambar terlebih dahulu'); } const res = await ApiFetch.api.fileStorage.create.post({ - file: file, - name: file.name - }) + file, + name: file.name, + }); + + const uploaded = res.data?.data; - const uploaded = res.data?.data if (!uploaded?.id) { - return toast.error("Gagal upload gambar") + return toast.error('Gagal mengunggah gambar, silakan coba lagi'); } - statePenghargaan.create.form.imageId = uploaded.id + statePenghargaan.create.form.imageId = uploaded.id; - await statePenghargaan.create.create() - resetForm() - router.push("/admin/desa/penghargaan") + await statePenghargaan.create.create(); + resetForm(); + router.push('/admin/desa/penghargaan'); + }; - } return ( - - - - - - - Create Penghargaan + + {/* Header */} + + + + + + Tambah Penghargaan + + + + {/* Form */} + + { - statePenghargaan.create.form.name = val.target.value; - }} - label={Nama Penghargaan} - placeholder="masukkan nama penghargaan" + onChange={(val) => (statePenghargaan.create.form.name = val.target.value)} + label={Nama Penghargaan} + placeholder="Masukkan nama penghargaan" + required /> + { - statePenghargaan.create.form.juara = val.target.value; - }} - label={Juara} - placeholder="masukkan juara" + onChange={(val) => (statePenghargaan.create.form.juara = val.target.value)} + label={Juara} + placeholder="Masukkan juara" + required /> + - Deskripsi + Deskripsi { @@ -86,63 +111,67 @@ function CreatePenghargaan() { }} /> + + {/* Dropzone Upload */} - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + Gambar + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file (maks 5MB) + + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
+ {previewImage && ( + + Preview Gambar + + )}
- + + {/* Button Submit */} + + +
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/page.tsx index 0ec41675..1e2887e0 100644 --- a/src/app/admin/(dashboard)/desa/penghargaan/page.tsx +++ b/src/app/admin/(dashboard)/desa/penghargaan/page.tsx @@ -2,21 +2,38 @@ 'use client' import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan'; import colors from '@/con/colors'; -import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; +import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../_com/header'; -import JudulList from '../../_com/judulList'; function Penghargaan() { const [search, setSearch] = useState(""); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -27,125 +44,114 @@ function Penghargaan() { } function ListPenghargaan({ search }: { search: string }) { - const state = useProxy(penghargaanState) - const router = useRouter() + const state = useProxy(penghargaanState); + const router = useRouter(); - const { - data, - page, - totalPages, - loading, - load, - } = state.findMany; + const { data, page, totalPages, loading, load } = state.findMany; useEffect(() => { - load(page, 10) - }, []) + load(page, 10, search); + }, [page, search]); - const filteredData = useMemo(() => { - if (!data) return []; - return data.filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name?.toLowerCase().includes(keyword) || - item.deskripsi?.toLowerCase().includes(keyword) - ); - }) - }, [data, search]); + const filteredData = data || [] - // Handle loading state + // Loading state if (loading || !data) { return ( - + ); } - if (data.length === 0) { - return ( - - - - + return ( + + + + List Penghargaan + + + + + +
- Nama - Deskripsi - Image - Detail + Nama + Deskripsi + Aksi - - - Tidak ada data - - + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + + + {item.name} + + + + + + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data penghargaan yang cocok + +
+
+
+ )}
-
-
- ) - } - - return ( - - - - - - - Nama - Deskripsi - Image - Detail - - - - {filteredData.map((item) => ( - - - - {item.name} - - - - - - - - - - - - - - - - - ))} - -
+
{ - load(newPage, 10); - window.scrollTo(0, 0); + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
diff --git a/src/app/admin/(dashboard)/desa/pengumuman/_com/layoutTabs.tsx b/src/app/admin/(dashboard)/desa/pengumuman/_com/layoutTabs.tsx index d65f89a5..6569c5ea 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/_com/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/_com/layoutTabs.tsx @@ -1,9 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { IconListDetails, IconCategory } from '@tabler/icons-react'; function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { const router = useRouter() @@ -12,16 +13,21 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { { label: "List Pengumuman", value: "listpengumuman", - href: "/admin/desa/pengumuman/list-pengumuman" + href: "/admin/desa/pengumuman/list-pengumuman", + icon: , + tooltip: "Lihat semua daftar pengumuman" }, { label: "Kategori Pengumuman", value: "kategoripengumuman", - href: "/admin/desa/pengumuman/kategori-pengumuman" + href: "/admin/desa/pengumuman/kategori-pengumuman", + icon: , + tooltip: "Kelola kategori pengumuman" }, ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { const tab = tabs.find(t => t.value === value) @@ -39,24 +45,59 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { }, [pathname]) return ( - - Pengumuman - - - {tabs.map((e, i) => ( - {e.label} + + Pengumuman + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {/* Konten dummy, bisa diganti sesuai routing */} + <>{children} ))} - {children} ); } -export default LayoutTabsLayanan; \ No newline at end of file +export default LayoutTabsLayanan; diff --git a/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/[id]/page.tsx b/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/[id]/page.tsx index 8c4c06bf..fe07bfd4 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/[id]/page.tsx @@ -1,8 +1,18 @@ /* eslint-disable react-hooks/exhaustive-deps */ -'use client' +'use client'; import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman'; import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip, + Text, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -10,67 +20,108 @@ import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function EditKategoriPengumuman() { - const editState = useProxy(stateDesaPengumuman.category) + const editState = useProxy(stateDesaPengumuman.category); const router = useRouter(); const params = useParams(); + const [formData, setFormData] = useState({ - name: editState.update.form.name || '', - }); + name: editState.update.form.name || '', + }); useEffect(() => { - const loadKategori = async () => { - const id = params?.id as string; - if (!id) return; - - try { - const data = await editState.update.load(id); // akses langsung, bukan dari proxy - if (data) { - setFormData({ - name: data.name || '', - }); - } - } catch (error) { - console.error("Error loading kategori Pengumuman:", error); - toast.error("Gagal memuat data kategori Pengumuman"); - } - }; - - loadKategori(); - }, [params?.id]); + const loadKategori = async () => { + const id = params?.id as string; + if (!id) return; - const handleSubmit = async () => { try { - editState.update.form = { - ...editState.update.form, - name: formData.name, - }; - await editState.update.update(); - toast.success('Kategori Pengumuman berhasil diperbarui!'); - router.push('/admin/desa/pengumuman/kategori-pengumuman'); + const data = await editState.update.load(id); + if (data) { + setFormData({ + name: data.name || '', + }); + } } catch (error) { - console.error('Error updating kategori Pengumuman:', error); - toast.error('Terjadi kesalahan saat memperbarui kategori Pengumuman'); + console.error('Error loading kategori Pengumuman:', error); + toast.error('Gagal memuat data kategori Pengumuman'); } }; + loadKategori(); + }, [params?.id]); + + const handleSubmit = async () => { + try { + editState.update.form = { + ...editState.update.form, + name: formData.name, + }; + + await editState.update.update(); + toast.success('Kategori Pengumuman berhasil diperbarui!'); + router.push('/admin/desa/pengumuman/kategori-pengumuman'); + } catch (error) { + console.error('Error updating kategori Pengumuman:', error); + toast.error('Terjadi kesalahan saat memperbarui kategori Pengumuman'); + } + }; + return ( - - - - - - - Edit Kategori Pengumuman + + {/* Header */} + + + + + + Edit Kategori Pengumuman + + + + {/* Form */} + + + Nama Kategori Pengumuman + + } + placeholder="Masukkan nama kategori Pengumuman" value={formData.name} - onChange={(e) => setFormData({ ...formData, name: e.target.value })} - label={Nama Kategori Pengumuman} - placeholder="masukkan nama kategori Pengumuman" + onChange={(e) => + setFormData({ ...formData, name: e.target.value }) + } + required /> - + + + diff --git a/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/create/page.tsx b/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/create/page.tsx index d238e152..1284d18f 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/create/page.tsx @@ -1,50 +1,87 @@ 'use client' import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; - - function CreateKategoriPengumuman() { - const createState = useProxy(stateDesaPengumuman.category) + const createState = useProxy(stateDesaPengumuman.category); const router = useRouter(); const resetForm = () => { createState.create.form = { - name: "", + name: '', }; }; const handleSubmit = async () => { await createState.create.create(); resetForm(); - router.push("/admin/desa/pengumuman/kategori-pengumuman") + router.push('/admin/desa/pengumuman/kategori-pengumuman'); }; return ( - - - - + + {/* Header dengan back button */} + + + + + + Tambah Kategori Pengumuman + + - - - Create Kategori Pengumuman + {/* Form utama */} + + Nama Kategori Pengumuman} - placeholder='Masukkan nama kategori Pengumuman' - value={createState.create.form.name} - onChange={(val) => { - createState.create.form.name = val.target.value; - }} + label={Nama Kategori Pengumuman} + placeholder="Masukkan nama kategori pengumuman" + value={createState.create.form.name || ''} + onChange={(e) => (createState.create.form.name = e.target.value)} + required /> - - + + + diff --git a/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/page.tsx b/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/page.tsx index 7ed4b149..541c0cae 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/page.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/kategori-pengumuman/page.tsx @@ -1,25 +1,26 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +import { + Box, Button, Center, Paper, Skeleton, Stack, + Table, TableTbody, TableTd, TableTh, TableThead, TableTr, + Text, Title, Tooltip, Pagination +} from '@mantine/core'; +import { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import stateDesaPengumuman from '../../../_state/desa/pengumuman'; - - function KategoriPengumuman() { const [search, setSearch] = useState(''); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -34,87 +35,121 @@ function ListKategoriPengumuman({ search }: { search: string }) { const router = useRouter(); const [modalHapus, setModalHapus] = useState(false) const [selectedId, setSelectedId] = useState(null) + const { data, page, totalPages, loading, load } = listDataState.findMany; useEffect(() => { - listDataState.findMany.load() - }, []) + load(1, 10, search) + }, [search]) const handleDelete = () => { if (selectedId) { listDataState.delete.delete(selectedId) setModalHapus(false) setSelectedId(null) - - listDataState.findMany.load() + load(page, 10, search) } } - const filteredData = (listDataState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!listDataState.findMany.data) { + if (loading || !data) { return ( - + ) } + return ( - + - + + List Kategori Pengumuman + + + + + - +
- No - Nama - Edit - Hapus + No + Nama + Edit + Hapus - {filteredData.map((item, index) => ( - - - - {index + 1} - - - {item.name} - - - - - + {filteredData.length > 0 ? ( + filteredData.map((item, index) => ( + + + {(page - 1) * 10 + index + 1} + + + {item.name} + + + + + + + + + + + + + )) + ) : ( + + +
+ Tidak ada data kategori pengumuman yang cocok +
- ))} + )}
- {/* Modal Konfirmasi Hapus */} +
+ load(newPage, 10, search)} + total={totalPages} + color="blue" + radius="md" + /> +
+ setModalHapus(false)} diff --git a/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/[id]/edit/page.tsx index 68890371..745672ee 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/[id]/edit/page.tsx @@ -7,12 +7,14 @@ import colors from "@/con/colors"; import { Box, Button, + Group, Paper, Select, Stack, Text, TextInput, - Title + Title, + Tooltip, } from "@mantine/core"; import { IconArrowBack } from "@tabler/icons-react"; import { useParams, useRouter } from "next/navigation"; @@ -20,34 +22,34 @@ import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useProxy } from "valtio/utils"; - function EditPengumuman() { const editState = useProxy(stateDesaPengumuman); const router = useRouter(); const params = useParams(); const [formData, setFormData] = useState({ - judul: editState.pengumuman.edit.form.judul || '', - deskripsi: editState.pengumuman.edit.form.deskripsi || '', - categoryPengumumanId: editState.pengumuman.edit.form.categoryPengumumanId || '', - content: editState.pengumuman.edit.form.content || '' + judul: editState.pengumuman.edit.form.judul || "", + deskripsi: editState.pengumuman.edit.form.deskripsi || "", + categoryPengumumanId: + editState.pengumuman.edit.form.categoryPengumumanId || "", + content: editState.pengumuman.edit.form.content || "", }); // Load pengumuman by id saat pertama kali useEffect(() => { - editState.category.findMany.load() + editState.category.findMany.load(); const loadpengumuman = async () => { const id = params?.id as string; if (!id) return; try { - const data = await stateDesaPengumuman.pengumuman.edit.load(id); // akses langsung, bukan dari proxy + const data = await stateDesaPengumuman.pengumuman.edit.load(id); if (data) { setFormData({ - judul: data.judul || '', - deskripsi: data.deskripsi || '', - categoryPengumumanId: data.categoryPengumumanId || '', - content: data.content || '', + judul: data.judul || "", + deskripsi: data.deskripsi || "", + categoryPengumumanId: data.categoryPengumumanId || "", + content: data.content || "", }); } } catch (error) { @@ -57,21 +59,18 @@ function EditPengumuman() { }; loadpengumuman(); - }, [params?.id]); // ✅ hapus editState dari dependency + }, [params?.id]); const handleSubmit = async () => { - try { - // edit global state with form data + // update global state editState.pengumuman.edit.form = { ...editState.pengumuman.edit.form, - judul: formData.judul, - deskripsi: formData.deskripsi, - content: formData.content, - categoryPengumumanId: formData.categoryPengumumanId || '' + ...formData, }; + await editState.pengumuman.edit.update(); - toast.success("pengumuman berhasil diperbarui!"); + toast.success("Pengumuman berhasil diperbarui!"); router.push("/admin/desa/pengumuman/list-pengumuman"); } catch (error) { console.error("Error updating pengumuman:", error); @@ -80,57 +79,97 @@ function EditPengumuman() { }; return ( - - - - - - - Edit pengumuman + + + + + + + Edit Pengumuman + + + + + setFormData({ ...formData, judul: e.target.value })} - label={Judul} - placeholder="masukkan judul" + required /> setFormData({ ...formData, deskripsi: e.target.value })} - label={Deskripsi} - placeholder="masukkan deskripsi" + onChange={(e) => + setFormData({ ...formData, deskripsi: e.target.value }) + } + required /> - - Konten - { - setFormData((prev) => ({ ...prev, content: htmlContent })); - editState.pengumuman.edit.form.content = htmlContent; - }} - /> - Kategori} - placeholder='Pilih kategori' + label={Kategori} + placeholder="Pilih kategori" + value={pengumumanState.pengumuman.create.form.categoryPengumumanId || ""} + onChange={(val) => { + pengumumanState.pengumuman.create.form.categoryPengumumanId = val ?? ""; + }} data={pengumumanState.category.findMany.data?.map((item) => ({ label: item.name, value: item.id, }))} - onChange={(val) => { - const selected = pengumumanState.category.findMany.data?.find((item) => item.id === val); - if (selected) { - pengumumanState.pengumuman.create.form.categoryPengumumanId = selected.id; - } - }} searchable nothingFoundMessage="Tidak ditemukan" /> + + {/* Deskripsi Singkat */} Deskripsi Singkat} - placeholder='Masukkan deskripsi singkat' - onChange={(val) => { - pengumumanState.pengumuman.create.form.deskripsi = val.target.value - }} + value={pengumumanState.pengumuman.create.form.deskripsi} + onChange={(val) => (pengumumanState.pengumuman.create.form.deskripsi = val.target.value)} + label={Deskripsi Singkat} + placeholder="Masukkan deskripsi singkat" + required /> + + {/* Konten Editor */} - Deskripsi + + Konten Lengkap + { @@ -82,8 +113,20 @@ function CreatePengumuman() { /> - - + {/* Tombol Submit */} + + diff --git a/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/page.tsx b/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/page.tsx index aaf8020b..1286d018 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/page.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/list-pengumuman/page.tsx @@ -1,6 +1,25 @@ 'use client' + import colors from '@/con/colors'; -import { Box, Button, Center, Grid, GridCol, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -9,14 +28,13 @@ import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; import stateDesaPengumuman from '../../../_state/desa/pengumuman'; - function Pengumuman() { const [search, setSearch] = useState(""); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -27,86 +45,107 @@ function Pengumuman() { } function ListPengumuman({ search }: { search: string }) { - const pengumumanState = useProxy(stateDesaPengumuman) - const router = useRouter() - const { - data, - page, - totalPages, - loading, - load, - } = pengumumanState.pengumuman.findMany; + const pengumumanState = useProxy(stateDesaPengumuman); + const router = useRouter(); + + const { data, page, totalPages, loading, load } = pengumumanState.pengumuman.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 ( - - - - - List Pengumuman - - - - - - - - - - Judul - Kategori - Detail - - - - - {filteredData.map((item) => ( + + + Daftar Pengumuman + + + + + +
+ + + Judul + Kategori + Detail + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( - - - {item.judul} - - - {item.CategoryPengumuman?.name} - - + + + {item.judul} + + + + + {item.CategoryPengumuman?.name || '-'} + + + + + + + )) + ) : ( + + +
+ Tidak ada pengumuman yang cocok +
- ))} -
-
-
-
+ )} + + +
load(newPage)} + onChange={(newPage) => { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
- ) + ); } export default Pengumuman; diff --git a/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx index 1b3d5978..fc0aae1b 100644 --- a/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx @@ -1,7 +1,8 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; +import { IconCategory, IconListCheck } from '@tabler/icons-react'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; @@ -12,17 +13,21 @@ function LayoutTabsPotensi({ children }: { children: React.ReactNode }) { { label: "List Potensi", value: "list_potensi", - href: "/admin/desa/potensi/list-potensi" + href: "/admin/desa/potensi/list-potensi", + icon: , + tooltip: "Lihat semua potensi desa" }, { label: "Kategori Potensi", value: "kategori_potensi", - href: "/admin/desa/potensi/kategori-potensi" + href: "/admin/desa/potensi/kategori-potensi", + icon: , + tooltip: "Kelola kategori potensi" }, - ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { const tab = tabs.find(t => t.value === value) @@ -40,24 +45,59 @@ function LayoutTabsPotensi({ children }: { children: React.ReactNode }) { }, [pathname]) return ( - - Potensi - - - {tabs.map((e, i) => ( - {e.label} + + Potensi + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {/* Konten dummy, bisa diganti sesuai routing */} + <>{children} ))} - {children} ); } -export default LayoutTabsPotensi; \ No newline at end of file +export default LayoutTabsPotensi; diff --git a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/[id]/page.tsx b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/[id]/page.tsx index 612a577a..73092b8f 100644 --- a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/[id]/page.tsx @@ -1,8 +1,17 @@ /* eslint-disable react-hooks/exhaustive-deps */ -'use client' +'use client'; import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi'; import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -10,67 +19,95 @@ import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function EditKategoriPotensi() { - const editState = useProxy(potensiDesaState.kategoriPotensi) + const editState = useProxy(potensiDesaState.kategoriPotensi); const router = useRouter(); const params = useParams(); + const [formData, setFormData] = useState({ - nama: editState.update.form.nama || '', - }); + nama: editState.update.form.nama || '', + }); useEffect(() => { - const loadKategori = async () => { - const id = params?.id as string; - if (!id) return; - - try { - const data = await editState.update.load(id); // akses langsung, bukan dari proxy - if (data) { - setFormData({ - nama: data.nama || '', - }); - } - } catch (error) { - console.error("Error loading kategori potensi:", error); - toast.error("Gagal memuat data kategori potensi"); - } - }; - - loadKategori(); - }, [params?.id]); + const loadKategori = async () => { + const id = params?.id as string; + if (!id) return; - const handleSubmit = async () => { try { - editState.update.form = { - ...editState.update.form, - nama: formData.nama, - }; - await editState.update.update(); - toast.success('Kategori Potensi berhasil diperbarui!'); - router.push('/admin/desa/potensi/kategori-potensi'); + const data = await editState.update.load(id); + if (data) { + setFormData({ + nama: data.nama || '', + }); + } } catch (error) { - console.error('Error updating kategori potensi:', error); - toast.error('Terjadi kesalahan saat memperbarui kategori potensi'); + console.error('Error loading kategori potensi:', error); + toast.error('Gagal memuat data kategori potensi'); } }; + loadKategori(); + }, [params?.id]); + + const handleSubmit = async () => { + try { + editState.update.form = { + ...editState.update.form, + nama: formData.nama, + }; + + await editState.update.update(); + toast.success('Kategori Potensi berhasil diperbarui!'); + router.push('/admin/desa/potensi/kategori-potensi'); + } catch (error) { + console.error('Error updating kategori potensi:', error); + toast.error('Terjadi kesalahan saat memperbarui kategori potensi'); + } + }; + return ( - - - - - - - Edit Kategori Potensi + + + + + + + Edit Kategori Potensi + + + + + setFormData({ ...formData, nama: e.target.value })} - label={Nama Kategori Potensi} - placeholder="masukkan nama kategori potensi" + required /> - + + + diff --git a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/create/page.tsx b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/create/page.tsx index 9c65118a..61a46a0a 100644 --- a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/create/page.tsx @@ -1,50 +1,87 @@ -'use client' +'use client'; import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; - - function CreateKategoriPotensi() { - const createState = useProxy(potensiDesaState.kategoriPotensi) + const createState = useProxy(potensiDesaState.kategoriPotensi); const router = useRouter(); const resetForm = () => { createState.create.form = { - nama: "", + nama: '', }; }; const handleSubmit = async () => { await createState.create.create(); resetForm(); - router.push("/admin/desa/potensi/kategori-potensi") + router.push('/admin/desa/potensi/kategori-potensi'); }; return ( - - - - + + {/* Header dengan back button */} + + + + + + Tambah Kategori Potensi + + - - - Create Kategori Potensi + {/* Form utama */} + + Nama Kategori Potensi} - placeholder='Masukkan nama kategori Potensi' - value={createState.create.form.nama} - onChange={(val) => { - createState.create.form.nama = val.target.value; - }} + label={Nama Kategori Potensi} + placeholder="Masukkan nama kategori potensi" + value={createState.create.form.nama || ''} + onChange={(e) => (createState.create.form.nama = e.target.value)} + required /> - - + + + diff --git a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx index db465690..c7f2d203 100644 --- a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx @@ -1,17 +1,14 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +import { Box, Button, Center, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip, Pagination } from '@mantine/core'; +import { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; -import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import potensiDesaState from '../../../_state/desa/potensi'; - - +import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; function KategoriPotensi() { const [search, setSearch] = useState(''); @@ -19,7 +16,7 @@ function KategoriPotensi() { } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -34,87 +31,113 @@ function ListKategoriPotensi({ search }: { search: string }) { const router = useRouter(); const [modalHapus, setModalHapus] = useState(false) const [selectedId, setSelectedId] = useState(null) + const { data, page, totalPages, loading, load } = listDataState.findMany; useEffect(() => { - listDataState.findMany.load() - }, []) + load(1, 10, search) + }, [search]) const handleDelete = () => { if (selectedId) { listDataState.delete.delete(selectedId) setModalHapus(false) setSelectedId(null) - - listDataState.findMany.load() + load(page, 10, search) } } - const filteredData = (listDataState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.nama.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!listDataState.findMany.data) { + if (loading || !data) { return ( - + ) } + return ( - + - + + List Kategori Potensi + + + + + - +
- No - Nama - Edit - Hapus + No + Nama + Edit + Hapus - {filteredData.map((item, index) => ( - - - - {index + 1} - - - {item.nama} - - - - - + {filteredData.length > 0 ? ( + filteredData.map((item, index) => ( + + + {(page - 1) * 10 + index + 1} + + + {item.nama} + + + + + + + + + )) + ) : ( + + +
+ Tidak ada data kategori potensi yang cocok +
- ))} + )}
- {/* Modal Konfirmasi Hapus */} +
+ load(newPage, 10, search)} + total={totalPages} + color="blue" + radius="md" + /> +
+ setModalHapus(false)} diff --git a/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx index d8c138da..6f35d9d0 100644 --- a/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx @@ -5,7 +5,19 @@ import EditEditor from "@/app/admin/(dashboard)/_com/editEditor"; import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi"; import colors from "@/con/colors"; import ApiFetch from "@/lib/api-fetch"; -import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from "@mantine/core"; +import { + Box, + Button, + Group, + Image, + Paper, + Select, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from "@mantine/core"; import { Dropzone } from "@mantine/dropzone"; import { IconArrowBack, IconPhoto, IconUpload, IconX } from "@tabler/icons-react"; import { useParams, useRouter } from "next/navigation"; @@ -13,38 +25,36 @@ import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useProxy } from "valtio/utils"; - - function EditPotensi() { - const potensiState = useProxy(potensiDesaState.potensiDesa) - const router = useRouter() - const params = useParams() + const potensiState = useProxy(potensiDesaState.potensiDesa); + const router = useRouter(); + const params = useParams(); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); const [formData, setFormData] = useState({ - name: '', - deskripsi: '', - kategoriId: '', - content: '', - imageId: '' + name: "", + deskripsi: "", + kategoriId: "", + content: "", + imageId: "", }); useEffect(() => { - potensiDesaState.kategoriPotensi.findMany.load() + potensiDesaState.kategoriPotensi.findMany.load(); const loadPotensi = async () => { const id = params?.id as string; if (!id) return; try { - const data = await potensiState.edit.load(id); // ambil data dari API + const data = await potensiState.edit.load(id); if (data) { setFormData({ - name: data.name || '', - deskripsi: data.deskripsi || '', - kategoriId: data.kategoriId || '', - content: data.content || '', - imageId: data.imageId || '', + name: data.name || "", + deskripsi: data.deskripsi || "", + kategoriId: data.kategoriId || "", + content: data.content || "", + imageId: data.imageId || "", }); if (data?.image?.link) { @@ -62,13 +72,9 @@ function EditPotensi() { const handleSubmit = async () => { try { - // Sinkronkan semua data dari formData ke state global potensiState.edit.form = { ...potensiState.edit.form, - name: formData.name, - deskripsi: formData.deskripsi, - kategoriId: formData.kategoriId, - content: formData.content, + ...formData, }; if (file) { @@ -92,44 +98,52 @@ function EditPotensi() { }; return ( - - - - - - - Edit Potensi + + + + + + + Edit Potensi Desa + + + + + { - const val = e.target.value; - setFormData((prev) => ({ ...prev, name: val })); - potensiState.edit.form.name = val; - }} - label={Judul} - placeholder="masukkan judul" + onChange={(e) => setFormData({ ...formData, name: e.target.value })} + required /> + { - const val = e.target.value; - setFormData((prev) => ({ ...prev, deskripsi: val })); - potensiState.edit.form.deskripsi = val; - }} - label={Deskripsi} - placeholder="masukkan deskripsi" + onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })} + required /> + Kategori} - placeholder='Pilih kategori' + label={Kategori} + placeholder="Pilih kategori" value={potensiState.create.form.kategoriId || ""} onChange={(val) => { potensiState.create.form.kategoriId = val ?? ""; @@ -97,65 +123,58 @@ function CreatePotensi() { }))} /> + {/* Upload Gambar */} - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + + Gambar Potensi + + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file (maks 5MB) + + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
+ {previewImage && ( + + Preview Gambar + + )}
+ {/* Konten Editor */} - Konten + + Konten Lengkap + { @@ -164,9 +183,21 @@ function CreatePotensi() { /> - + {/* Tombol Simpan */} + + +
diff --git a/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx b/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx index b335c40e..987f0417 100644 --- a/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx @@ -1,25 +1,40 @@ /* eslint-disable react-hooks/exhaustive-deps */ - 'use client' + import colors from '@/con/colors'; -import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Tooltip +} from '@mantine/core'; +import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import potensiDesaState from '../../../_state/desa/potensi'; - - function Potensi() { const [search, setSearch] = useState(""); return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -30,8 +45,8 @@ function Potensi() { } function ListPotensi({ search }: { search: string }) { - const potensiState = useProxy(potensiDesaState) - const router = useRouter() + const potensiState = useProxy(potensiDesaState); + const router = useRouter(); const { data, @@ -42,117 +57,108 @@ function ListPotensi({ search }: { search: string }) { } = potensiState.potensiDesa.findMany; useEffect(() => { - potensiState.kategoriPotensi.findMany.load() - load(page, 10) - }, []) + potensiState.kategoriPotensi.findMany.load(); + load(page, 10, search); + }, [page, search]); - const filteredData = (potensiState.potensiDesa.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.kategori?.nama.toLowerCase().includes(keyword) || - item.deskripsi.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - // Handle loading state if (loading || !data) { return ( - + ); } - if (data.length === 0) { - return ( - - - - - - - - - Judul - Kategori - Deskripsi - Detail - - - - - Tidak Ada Data - - -
-
-
-
-
- ) - } - return ( - - - - - - - - Judul - Kategori - Deskripsi - Detail - - - - {filteredData.map((item) => ( + + + Daftar Potensi Desa + + + + + +
+ + + Judul + Kategori + Deskripsi + Detail + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( - - {item.name} - - {item.kategori?.nama} + + {item.name} + + + + {item.kategori?.nama || '-'} + - + - - ))} - -
-
-
+ )) + ) : ( + + +
+ Tidak ada data potensi yang cocok +
+
+
+ )} + + +
{ load(newPage, 10); - window.scrollTo(0, 0); + window.scrollTo({ top: 0, behavior: 'smooth' }); }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
- ) + ); } export default Potensi; diff --git a/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx b/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx index a3da9bae..c6cb6506 100644 --- a/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx +++ b/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx @@ -1,9 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { IconUser, IconUsers, IconCalendar } from '@tabler/icons-react'; function LayoutTabsDetail({ children }: { children: React.ReactNode }) { const router = useRouter() @@ -12,21 +13,28 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) { { label: "Profile Desa", value: "profiledesa", - href: "/admin/desa/profile/profile-desa" + href: "/admin/desa/profile/profile-desa", + icon: , + tooltip: "Lihat dan kelola profil desa" }, { label: "Profile Perbekel", value: "profileperbekel", - href: "/admin/desa/profile/profile-perbekel" + href: "/admin/desa/profile/profile-perbekel", + icon: , + tooltip: "Kelola data Perbekel" }, { label: "Profile Perbekel Dari Masa Ke Masa", value: "profile-perbekel-dari-masa-ke-masa", - href: "/admin/desa/profile/profile-perbekel-dari-masa-ke-masa" + href: "/admin/desa/profile/profile-perbekel-dari-masa-ke-masa", + icon: , + tooltip: "Riwayat Perbekel dari masa ke masa" } ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { const tab = tabs.find(t => t.value === value) @@ -44,24 +52,59 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) { }, [pathname]) return ( - - Profile Desa - - - {tabs.map((e, i) => ( - {e.label} + + Profile Desa + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {/* Konten dummy, bisa diganti sesuai routing */} + <>{children} ))} - {children} ); } -export default LayoutTabsDetail; \ No newline at end of file +export default LayoutTabsDetail; diff --git a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/lambang_desa/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/lambang_desa/page.tsx index c0d2fb78..c0ee0456 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/lambang_desa/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/lambang_desa/page.tsx @@ -3,8 +3,8 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack } from '@tabler/icons-react'; +import { Alert, Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; +import { IconAlertCircle, IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -15,7 +15,7 @@ function Page() { const router = useRouter() const params = useParams() const [isSubmitting, setIsSubmitting] = useState(false); - + // Load data useEffect(() => { const loadData = async () => { const id = params?.id as string; @@ -25,9 +25,12 @@ function Page() { return; } - const data = await lambangState.findUnique.load(id); - if (data) { + try { + const data = await lambangState.findUnique.load(id); lambangState.update.initialize(data); + } catch (error) { + console.error("Error loading lambang:", error); + toast.error("Gagal memuat data lambang desa"); } }; @@ -35,19 +38,21 @@ function Page() { return () => { lambangState.update.reset(); - lambangState.findUnique.reset(); // opsional: reset juga data lama + lambangState.findUnique.reset(); }; }, [params?.id, router]); - const handleSubmit = async () => { if (isSubmitting || !lambangState.update.form.judul.trim()) { toast.error("Judul wajib diisi"); return; } - setIsSubmitting(true) + + setIsSubmitting(true); + try { - const success = await lambangState.update.submit() + const success = await lambangState.update.submit(); + if (success) { toast.success("Data berhasil disimpan"); router.push("/admin/desa/profile/profile-desa"); @@ -58,17 +63,12 @@ function Page() { } finally { setIsSubmitting(false); } - } + }; - const handleBack = () => { - router.back() - } + const handleBack = () => router.back(); - if ( - lambangState.findUnique.loading || - !lambangState.findUnique.data || - lambangState.update.loading - ) { + // Loading state + if (lambangState.findUnique.loading || lambangState.update.loading) { return (
@@ -77,45 +77,73 @@ function Page() { ); } - return ( - - - + + // Error state + if (lambangState.findUnique.error) { + return ( + + + } color="red"> + Error + {lambangState.findUnique.error} + + + + ); + } + + return ( + + + + + + + Edit Lambang Desa - - + + + + Edit Lambang Desa + + {/* Judul */} + Judul} + placeholder="Judul lambang" + value={lambangState.update.form.judul} + onChange={(e) => lambangState.update.form.judul = e.currentTarget.value} + error={!lambangState.update.form.judul && "Judul wajib diisi"} + /> + + {/* Deskripsi */} - - - Edit Lambang Desa - Judul} - placeholder="Judul" - value={lambangState.update.form.judul} - onChange={(e) => lambangState.update.form.judul = e.currentTarget.value} - error={!lambangState.update.form.judul && "Judul wajib diisi"} - /> - - Deskripsi - lambangState.update.form.deskripsi = val} - /> - - - - - - + Deskripsi + lambangState.update.form.deskripsi = val} + /> + + {/* Buttons */} + + + + + diff --git a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/maskot_desa/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/maskot_desa/page.tsx index b252024a..fa0b0ddb 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/maskot_desa/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/maskot_desa/page.tsx @@ -5,38 +5,40 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title, Tooltip, Center, Alert } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; -import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconPhoto, IconUpload, IconX, IconAlertCircle } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function Page() { - const maskotState = useProxy(stateProfileDesa.maskotDesa) - const router = useRouter() - const params = useParams() - - const [images, setImages] = useState< - Array<{ file: File; preview: string; label: string }> - >([]); + const maskotState = useProxy(stateProfileDesa.maskotDesa); + const router = useRouter(); + const params = useParams(); + const [images, setImages] = useState>([]); const [formData, setFormData] = useState({ - judul: maskotState.update.form.judul || '', - deskripsi: maskotState.update.form.deskripsi || '', - images: [] as Array<{ label: string; imageId: string }> - }) + judul: '', + deskripsi: '', + images: [] as Array<{ label: string; imageId: string }>, + }); + const [isSubmitting, setIsSubmitting] = useState(false); + // Load data useEffect(() => { const loadData = async () => { const id = params?.id as string; - if (!id) return; + if (!id) { + toast.error("ID tidak valid"); + router.push("/admin/desa/profile/profile-desa"); + return; + } try { const data = await maskotState.findUnique.load(id); if (data) { - // 🔥 INI YANG KURANG! maskotState.update.initialize(data); setFormData({ @@ -57,28 +59,39 @@ function Page() { } } } catch (error) { - console.error("Error loading berita:", error); - toast.error("Gagal memuat data berita"); + console.error("Error loading maskot:", error); + toast.error("Gagal memuat data maskot"); } }; loadData(); - }, [params?.id]); + return () => { + maskotState.update.reset(); + maskotState.findUnique.reset(); + }; + }, [params?.id, router]); - const handleBack = () => { - router.back() - } + const handleBack = () => router.back(); const handleSubmit = async () => { + if (isSubmitting || !formData.judul.trim()) { + toast.error("Judul wajib diisi"); + return; + } + + setIsSubmitting(true); + try { const uploadedImages = []; // Upload semua gambar baru for (const img of images) { - if (!img.file || !(img.file instanceof File)) { - toast.error("File tidak valid untuk di-upload"); - continue; // atau return kalau kamu mau hentikan semua + if (!img.file) { + // Kalau gambar lama, skip upload + if (!img.preview) continue; + uploadedImages.push({ imageId: '', label: img.label }); + continue; } const res = await ApiFetch.api.fileStorage.create.post({ @@ -92,10 +105,7 @@ function Page() { return; } - uploadedImages.push({ - imageId: uploaded.id, - label: img.label || 'main', - }); + uploadedImages.push({ imageId: uploaded.id, label: img.label || 'main' }); } // Update ke global state @@ -109,130 +119,159 @@ function Page() { toast.success("Maskot berhasil diperbarui!"); router.push("/admin/desa/profile/profile-desa"); } - } catch (error) { console.error("Error update maskot:", error); toast.error("Gagal update maskot"); + } finally { + setIsSubmitting(false); } }; - return ( - - - + // Loading state + if (maskotState.findUnique.loading || maskotState.update.loading) { + return ( + +
+ Memuat data... +
+
+ ); + } + + // Error state + if (maskotState.findUnique.error) { + return ( + + -
- - - - - - Edit Maskot Desa - Judul} - placeholder="Masukkan judul" - value={formData.judul} - onChange={(val) => setFormData({ ...formData, judul: val.currentTarget.value })} - /> - - Deskripsi - setFormData({ ...formData, deskripsi: val })} - /> - - - Gambar - - { - const newImages = files.map((file) => ({ - file, - preview: URL.createObjectURL(file), - label: '', - })); - setImages((prev) => [...prev, ...newImages]); - }} - > - - - - - - - - - - + } color="red"> + Error + {maskotState.findUnique.error} + + + + ); + } -
- - Drag images here or click to select files - - - Attach as many files as you like, each file should not exceed 5mb - -
-
- -
- - - {images.map((img, index) => ( - - - - - - - {`Preview - { - const updated = [...images]; - updated[index].label = e.currentTarget.value; - setImages(updated); - }} - /> - - - - ))} - - - - - - + return ( + + + + + + + Edit Maskot Desa + + + + + Edit Maskot Desa + + {/* Judul */} + Judul} + placeholder="Masukkan judul maskot" + value={formData.judul} + onChange={(e) => setFormData({ ...formData, judul: e.currentTarget.value })} + error={!formData.judul && "Judul wajib diisi"} + /> + + {/* Deskripsi */} + + Deskripsi + setFormData({ ...formData, deskripsi: val })} + /> + + {/* Upload Gambar */} + + Gambar + { + const newImages = files.map((file) => ({ + file, + preview: URL.createObjectURL(file), + label: '', + })); + setImages((prev) => [...prev, ...newImages]); + }} + > + + + + +
+ Drag images here or click to select files + Attach as many files as you like, each file max 5mb +
+
+
+
+ + {/* Preview Gambar */} + + {images.map((img, index) => ( + + + + + + + {`Preview + { + const updated = [...images]; + updated[index].label = e.currentTarget.value; + setImages(updated); + }} + /> + + + + ))} + + + {/* Buttons */} + + + +
diff --git a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/sejarah_desa/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/sejarah_desa/page.tsx index d1592de1..89bd1d64 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/sejarah_desa/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/sejarah_desa/page.tsx @@ -3,8 +3,8 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack } from '@tabler/icons-react'; +import { Alert, Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; +import { IconAlertCircle, IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -16,6 +16,7 @@ function Page() { const params = useParams() const [isSubmitting, setIsSubmitting] = useState(false); + // Load data useEffect(() => { const loadData = async () => { const id = params?.id as string; @@ -25,9 +26,14 @@ function Page() { return; } - const data = await sejarahState.findUnique.load(id); - if (data) { - sejarahState.update.initialize(data); + try { + const data = await sejarahState.findUnique.load(id); + if (data) { + sejarahState.update.initialize(data); + } + } catch (error) { + console.error("Error loading sejarah:", error); + toast.error("Gagal memuat data sejarah desa"); } }; @@ -35,19 +41,19 @@ function Page() { return () => { sejarahState.update.reset(); - sejarahState.findUnique.reset(); // opsional: reset juga data lama + sejarahState.findUnique.reset(); }; }, [params?.id, router]); - const handleSubmit = async () => { if (isSubmitting || !sejarahState.update.form.judul.trim()) { toast.error("Judul wajib diisi"); return; } - setIsSubmitting(true) + + setIsSubmitting(true); try { - const success = await sejarahState.update.submit() + const success = await sejarahState.update.submit(); if (success) { toast.success("Data berhasil disimpan"); router.push("/admin/desa/profile/profile-desa"); @@ -58,17 +64,12 @@ function Page() { } finally { setIsSubmitting(false); } - } + }; - const handleBack = () => { - router.back() - } + const handleBack = () => router.back(); - if ( - sejarahState.findUnique.loading || - !sejarahState.findUnique.data || - sejarahState.update.loading - ) { + // Loading state + if (sejarahState.findUnique.loading || sejarahState.update.loading) { return (
@@ -77,45 +78,73 @@ function Page() { ); } - return ( - - - + + // Error state + if (sejarahState.findUnique.error) { + return ( + + + } color="red"> + Error + {sejarahState.findUnique.error} + + + + ); + } + + return ( + + + + + + + Edit Sejarah Desa - - + + + + Edit Sejarah Desa + + {/* Judul */} + Judul} + placeholder="Judul sejarah" + value={sejarahState.update.form.judul} + onChange={(e) => sejarahState.update.form.judul = e.currentTarget.value} + error={!sejarahState.update.form.judul && "Judul wajib diisi"} + /> + + {/* Deskripsi */} - - - Edit Sejarah Desa - Judul} - placeholder="Judul" - value={sejarahState.update.form.judul} - onChange={(e) => sejarahState.update.form.judul = e.currentTarget.value} - error={!sejarahState.update.form.judul && "Judul wajib diisi"} - /> - - Deskripsi - sejarahState.update.form.deskripsi = val} - /> - - - - - - + Deskripsi + sejarahState.update.form.deskripsi = val} + /> + + {/* Buttons */} + + + + + diff --git a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/visi_misi_desa/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/visi_misi_desa/page.tsx index dbbacc8d..0a06bc88 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/visi_misi_desa/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-desa/[id]/visi_misi_desa/page.tsx @@ -3,122 +3,153 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Button, Center, Group, Paper, Stack, Text, Title } from '@mantine/core'; -import { IconArrowBack } from '@tabler/icons-react'; +import { Alert, Box, Button, Center, Group, Paper, Stack, Text, Title, Tooltip } from '@mantine/core'; +import { IconAlertCircle, IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; function Page() { - const visiMisiState = useProxy(stateProfileDesa.visiMisiDesa) - const router = useRouter() - const params = useParams() - const [isSubmitting, setIsSubmitting] = useState(false); + const visiMisiState = useProxy(stateProfileDesa.visiMisiDesa) + const router = useRouter() + const params = useParams() + const [isSubmitting, setIsSubmitting] = useState(false); - useEffect(() => { - const loadData = async () => { - const id = params?.id as string; - if (!id) { - toast.error("ID tidak valid"); - router.push("/admin/desa/profile/profile-desa"); - return; - } + // Load data + useEffect(() => { + const loadData = async () => { + const id = params?.id as string; + if (!id) { + toast.error("ID tidak valid"); + router.push("/admin/desa/profile/profile-desa"); + return; + } - const data = await visiMisiState.findUnique.load(id); - if (data) { - visiMisiState.update.initialize(data); - } + try { + const data = await visiMisiState.findUnique.load(id); + visiMisiState.update.initialize(data); + } catch (error) { + console.error("Error loading visi misi:", error); + toast.error("Gagal memuat data visi misi desa"); + } + }; + + loadData(); + + return () => { + visiMisiState.update.reset(); + visiMisiState.findUnique.reset(); + }; + }, [params?.id, router]); + + const handleSubmit = async () => { + if (isSubmitting || !visiMisiState.update.form.visi.trim()) { + toast.error("Visi wajib diisi"); + return; + } + + setIsSubmitting(true); + + try { + const success = await visiMisiState.update.submit(); + + if (success) { + toast.success("Data berhasil disimpan"); + router.push("/admin/desa/profile/profile-desa"); + } + } catch (error) { + console.error("Error update visi misi desa:", error); + toast.error("Terjadi kesalahan saat update visi misi desa"); + } finally { + setIsSubmitting(false); + } }; - loadData(); + const handleBack = () => router.back(); - return () => { - visiMisiState.update.reset(); - visiMisiState.findUnique.reset(); // opsional: reset juga data lama - }; - }, [params?.id, router]); - - - const handleSubmit = async () => { - if (isSubmitting || !visiMisiState.update.form.visi.trim()) { - toast.error("Visi wajib diisi"); - return; - } - setIsSubmitting(true) - try { - const success = await visiMisiState.update.submit() - if (success) { - toast.success("Data berhasil disimpan"); - router.push("/admin/desa/profile/profile-desa"); - } - } catch (error) { - console.error("Error update sejarah desa:", error); - toast.error("Terjadi kesalahan saat update sejarah desa"); - } finally { - setIsSubmitting(false); - } - } - - const handleBack = () => { - router.back() - } - - if ( - visiMisiState.findUnique.loading || - !visiMisiState.findUnique.data || - visiMisiState.update.loading - ) { - return ( - -
- Memuat data... -
-
- ); - } - return ( - - - - - - - + // Loading state + if (visiMisiState.findUnique.loading || visiMisiState.update.loading) { + return ( - - - Edit Visi Misi Desa - Visi - visiMisiState.update.form.visi = val} - /> - - Misi - visiMisiState.update.form.misi = val} - /> - - - - - - +
+ Memuat data... +
-
-
-
-
- ); + ); + } + + // Error state + if (visiMisiState.findUnique.error) { + return ( + + + + } color="red"> + Error + {visiMisiState.findUnique.error} + + + + ); + } + + return ( + + + + + + + Edit Visi Misi Desa + + + + + Edit Visi Misi Desa + + {/* Visi */} + + Visi + visiMisiState.update.form.visi = val} + /> + + + {/* Misi */} + + Misi + visiMisiState.update.form.misi = val} + /> + + + {/* Buttons */} + + + + + + + + + + ); } export default Page; diff --git a/src/app/admin/(dashboard)/desa/profile/profile-desa/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-desa/page.tsx index 2e105a0f..572c6303 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-desa/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-desa/page.tsx @@ -1,7 +1,7 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Card, Center, Grid, GridCol, Group, Image, Paper, Stack, Text, Title } from '@mantine/core'; +import { Box, Button, Card, Center, Divider, Grid, GridCol, Image, Paper, SimpleGrid, Stack, Text, Title, Tooltip } from '@mantine/core'; import { useSnapshot } from 'valtio'; import stateProfileDesa from '../../../_state/desa/profile'; import { useEffect } from 'react'; @@ -12,7 +12,6 @@ function Page() { const router = useRouter(); const snap = useSnapshot(stateProfileDesa); - // Panggil load data sekali saat komponen mount useEffect(() => { stateProfileDesa.sejarahDesa.findUnique.load("edit"); stateProfileDesa.visiMisiDesa.findUnique.load("edit"); @@ -26,142 +25,219 @@ function Page() { const maskot = snap.maskotDesa.findUnique.data; return ( - - - Preview Profile Desa + + + Preview Profile Desa + {/* Sejarah Desa */} {sejarah && ( - - - - - - Preview Sejarah Desa - - - - - - + + + + Preview Sejarah Desa + + + + + + + + + + +
- + Logo Desa
- {sejarah.judul} -
- - - + + + {sejarah.judul} + + +
+ +
-
-
+
+ )} {/* Visi Misi Desa */} {visiMisi && ( - - - - - - Preview Visi Misi Desa - - - - - - + + + + Preview Visi Misi Desa + + + + + + + + + + +
- + Logo Desa
- Visi Misi Desa -
- - Visi Desa - - Misi Desa - - + + + Visi Misi Desa + + +
+ + Visi Desa + + Misi Desa + - -
+ + )} {/* Lambang Desa */} {lambang && ( - - - - - - Preview Lambang Desa - - - - - - + + + + Preview Lambang Desa + + + + + + + + + + +
- + Logo Desa
- Lambang Desa -
- - - + + + Lambang Desa + + +
+ + - -
+ + )} {/* Maskot Desa */} {maskot && ( - - - - - - Preview Maskot Desa - - - - - - + + + + Preview Maskot Desa + + + + + + + + + + +
- + Maskot Desa
- Maskot Desa -
- - - - {maskot.images.map((img, index) => ( - - {img.label} + + + Maskot Desa + + +
+ + + + + {maskot.images.map((img, idx) => ( + +
+ {img.label} +
{img.label}
))} - - +
+
- -
+ + )} diff --git a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx index a8f5022a..7dee124a 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx @@ -3,17 +3,27 @@ import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; -import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; - function EditPerbekelDariMasaKeMasa() { - const state = useProxy(stateProfileDesa.mantanPerbekel) + const state = useProxy(stateProfileDesa.mantanPerbekel); const router = useRouter(); const params = useParams(); const [previewImage, setPreviewImage] = useState(null); @@ -38,9 +48,7 @@ function EditPerbekelDariMasaKeMasa() { periode: data.periode || '', imageId: data.imageId || '' }); - if (data?.imageGalleryFoto?.link) { - setPreviewImage(data.imageGalleryFoto.link); - } + if (data?.imageGalleryFoto?.link) setPreviewImage(data.imageGalleryFoto.link); } } catch (error) { console.error('Error loading foto:', error); @@ -52,24 +60,18 @@ function EditPerbekelDariMasaKeMasa() { const handleSubmit = async () => { try { - state.update.form = { - ...state.update.form, - nama: formData.nama, - daerah: formData.daerah, - periode: formData.periode, - imageId: formData.imageId - }; + state.update.form = { ...state.update.form, ...formData }; + if (file) { const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name, }); const uploaded = res.data?.data; - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } + if (!uploaded?.id) return toast.error("Gagal upload gambar"); state.update.form.imageId = uploaded.id; } + await state.update.update(); toast.success('Perbekel dari masa ke masa berhasil diperbarui!'); router.push('/admin/desa/profile/profile-perbekel-dari-masa-ke-masa'); @@ -80,86 +82,119 @@ function EditPerbekelDariMasaKeMasa() { }; return ( - - - - + + + + + + + Edit Perbekel Dari Masa Ke Masa + + - - - Edit Perbekel Dari Masa Ke Masa + + Nama} - placeholder='Masukkan nama' + label="Nama" + placeholder="Masukkan nama" value={formData.nama} - onChange={(e) => - (formData.nama = e.target.value) - } + onChange={(e) => setFormData({ ...formData, nama: e.target.value })} + required /> + - Upload Foto + + Foto Perbekel + { - const selectedFile = files[0]; // Ambil file pertama + const selectedFile = files[0]; if (selectedFile) { setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview + setPreviewImage(URL.createObjectURL(selectedFile)); } }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} accept={{ 'image/*': [] }} + radius="md" + p="xl" > - + - + - + - + - -
- - Drag gambar ke sini atau klik untuk pilih file + + + Seret gambar atau klik untuk memilih file - - Maksimal 5MB dan harus format gambar + + Maksimal 5MB, format gambar wajib -
+
- {previewImage ? ( - - ) : ( -
- -
+ {previewImage && ( + + Preview Gambar + )}
+ Daerah} - placeholder='Masukkan daerah' + label="Daerah" + placeholder="Masukkan daerah" value={formData.daerah} - onChange={(e) => - (formData.daerah = e.target.value) - } + onChange={(e) => setFormData({ ...formData, daerah: e.target.value })} + required /> + Periode} - placeholder='Masukkan periode' + label="Periode" + placeholder="Masukkan periode" value={formData.periode} - onChange={(e) => - (formData.periode = e.target.value) - } + onChange={(e) => setFormData({ ...formData, periode: e.target.value })} + required /> - - + + + diff --git a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/page.tsx index 288a5874..2a2d200b 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/page.tsx @@ -2,7 +2,7 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -10,99 +10,130 @@ import { useState } from 'react'; import { useProxy } from 'valtio/utils'; function DetailPerbekelDariMasa() { - const state = useProxy(stateProfileDesa.mantanPerbekel) + const state = useProxy(stateProfileDesa.mantanPerbekel); const [modalHapus, setModalHapus] = useState(false); - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - state.findUnique.load(params?.id as string) - }, []) + state.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - state.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa") + state.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa"); } - } + }; if (!state.findUnique.data) { return ( - + - ) + ); } + const data = state.findUnique.data; + return ( - - - - - - - Detail Perbekel Dari Masa Ke Masa - {state.findUnique.data ? ( - - - - Nama Perbekel - {state.findUnique.data?.nama} - - - Daerah - {state.findUnique.data?.daerah} - - - Periode - {state.findUnique.data?.periode} - - - Gambar - gambar - - + + + + + + + Detail Perbekel Dari Masa Ke Masa + + + + + + Gambar + {data.image?.link ? ( + {data.nama + ) : ( + Tidak ada gambar + )} + + + + Nama Perbekel + {data.nama || '-'} + + + + Daerah + {data.daerah || '-'} + + + + Periode + {data.periode || '-'} + + + + + + + - - - - ) : null} + + + + - {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus perbekel dari masa ke masa ini?' + text="Apakah Anda yakin ingin menghapus perbekel dari masa ke masa ini?" />
); diff --git a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/create/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/create/page.tsx index db0c1e45..c261db14 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/create/page.tsx @@ -1,8 +1,8 @@ -'use client' +'use client'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -10,29 +10,26 @@ import { useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; - - -function CreateVideo() { - const state = useProxy(stateProfileDesa.mantanPerbekel) +function CreatePerbekelDariMasaKeMasa() { + const state = useProxy(stateProfileDesa.mantanPerbekel); const router = useRouter(); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); const resetForm = () => { state.create.form = { - nama: "", - daerah: "", - periode: "", - imageId: "", + nama: '', + daerah: '', + periode: '', + imageId: '', }; - setPreviewImage(null) - setFile(null) + setPreviewImage(null); + setFile(null); }; - const handleSubmit = async () => { if (!file) { - return toast.warn("Pilih file gambar terlebih dahulu"); + return toast.warn('Pilih file gambar terlebih dahulu'); } const res = await ApiFetch.api.fileStorage.create.post({ @@ -41,110 +38,118 @@ function CreateVideo() { }); const uploaded = res.data?.data; - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } + if (!uploaded?.id) return toast.error('Gagal upload gambar'); + state.create.form.imageId = uploaded.id; await state.create.create(); resetForm(); - router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa"); + router.push('/admin/desa/profile/profile-perbekel-dari-masa-ke-masa'); }; - return ( - - - - + + {/* Back button + Title */} + + + + + + Create Perbekel Dari Masa Ke Masa + + - - - Create Perbekel Dari Masa Ke Masa + + Nama Perbekel} - placeholder='Masukkan nama perbekel' + label={Nama Perbekel} + placeholder="Masukkan nama perbekel" value={state.create.form.nama} - onChange={(val) => { - state.create.form.nama = val.target.value; - }} - /> - { - state.create.form.daerah = e.currentTarget.value; - }} + onChange={(e) => (state.create.form.nama = e.target.value)} required /> Periode} - placeholder='Masukkan periode' - value={state.create.form.periode} - onChange={(e) => - (state.create.form.periode = e.target.value) - } + label={Daerah} + placeholder="Masukkan daerah" + value={state.create.form.daerah} + onChange={(e) => (state.create.form.daerah = e.target.value)} + required /> + Periode} + placeholder="Masukkan periode" + value={state.create.form.periode} + onChange={(e) => (state.create.form.periode = e.target.value)} + required + /> + + {/* Dropzone */} - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + Gambar Perbekel + { + const selectedFile = files[0]; + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); + } + }} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} + maxSize={5 * 1024 ** 2} + accept={{ 'image/*': [] }} + radius="md" + p="xl" + > + + + + + + + + + + + + + Seret gambar atau klik untuk memilih file (maks 5MB) + + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
-
- - {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
+ {previewImage && ( + + Preview Gambar + + )}
- - + + {/* Submit */} + +
@@ -152,4 +157,4 @@ function CreateVideo() { ); } -export default CreateVideo; +export default CreatePerbekelDariMasaKeMasa; diff --git a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/page.tsx index d676cac4..07c93826 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/page.tsx @@ -1,13 +1,12 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import stateProfileDesa from '../../../_state/desa/profile'; function PerbekelDariMasaKeMasa() { @@ -16,7 +15,7 @@ function PerbekelDariMasaKeMasa() { } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -29,74 +28,96 @@ function PerbekelDariMasaKeMasa() { function ListPerbekelDariMasaKeMasa({ search }: { search: string }) { const state = useProxy(stateProfileDesa.mantanPerbekel) const router = useRouter(); - const { - data, - page, - totalPages, - loading, - load, - } = state.findMany; + const { data, page, totalPages, loading, load } = state.findMany; useShallowEffect(() => { load(page, 10, search) - }, [page, search]) + }, [page, search]); - const filteredData = (data || []) + const filteredData = data || []; if (loading || !data) { return ( - - - - ) + + + + ); } return ( - - - - - - Nama Perbekel - Periode - Detail - - - - {filteredData.map((item) => ( - - - - {item.nama} - - - - - - {item.periode} - - - - - + + + List Perbekel Dari Masa Ke Masa + + + + + + +
+ + + Nama Perbekel + Periode + Aksi - ))} - -
+ + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + {item.nama} + + + {item.periode} + + + + + + )) + ) : ( + + +
+ Tidak ada data perbekel yang cocok +
+
+
+ )} +
+ +
+
load(newPage)} // ini penting! + onChange={(newPage) => { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} total={totalPages} mt="md" mb="md" + color="blue" + radius="md" />
diff --git a/src/app/admin/(dashboard)/desa/profile/profile-perbekel/[id]/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-perbekel/[id]/page.tsx index ed9f0e62..ed786d9e 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel/[id]/page.tsx @@ -4,9 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Center, Group, Image, Paper, Stack, Text, Title } from '@mantine/core'; +import { Box, Button, Center, Group, Image, Paper, Stack, Text, Title, Tooltip } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; -import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconPhoto, IconUpload, IconX, IconImageInPicture } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -30,22 +30,29 @@ function ProfilePerbekel() { } const data = await perbekelState.findUnique.load(id); - if (data) { - perbekelState.edit.initialize(data); - } - if (data?.image?.link) { - setPreviewImage(data.image.link); - } + if (data) perbekelState.edit.initialize(data); + if (data?.image?.link) setPreviewImage(data.image.link); }; loadData(); return () => { perbekelState.edit.reset(); - perbekelState.findUnique.reset(); // opsional: reset juga data lama + perbekelState.findUnique.reset(); }; }, [params?.id, router]); + const handleFileChange = (newFile: File | null) => { + if (!newFile) { + setFile(null); + setPreviewImage(null); + return; + } + setFile(newFile); + const reader = new FileReader(); + reader.onload = (event) => setPreviewImage(event.target?.result as string); + reader.readAsDataURL(newFile); + } const handleSubmit = async () => { if (isSubmitting || !perbekelState.edit.form.biodata.trim()) { @@ -62,7 +69,6 @@ function ProfilePerbekel() { toast.error("Gagal upload gambar"); return; } - perbekelState.edit.form.imageId = uploaded.id; } const success = await perbekelState.edit.submit() @@ -78,15 +84,9 @@ function ProfilePerbekel() { } } - const handleBack = () => { - router.back() - } + const handleBack = () => router.back(); - if ( - perbekelState.findUnique.loading || - !perbekelState.findUnique.data || - perbekelState.edit.loading - ) { + if (perbekelState.findUnique.loading || perbekelState.edit.loading) { return (
@@ -98,117 +98,112 @@ function ProfilePerbekel() { return ( - - - + + {/* Header */} + + + + + + Edit Profil Perbekel + - - + + + + {/* Biodata */} - - - Edit Profil Perbekel - Biodata - perbekelState.edit.form.biodata = val} - /> - - Gambar - - { - const selectedFile = files[0]; // Ambil file pertama - if (selectedFile) { - setFile(selectedFile); - setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview - } - }} - onReject={() => toast.error('File tidak valid.')} - maxSize={5 * 1024 ** 2} // Maks 5MB - accept={{ 'image/*': [] }} - > - - - - - - - - - - + Biodata + perbekelState.edit.form.biodata = val} + /> + -
- - Drag gambar ke sini atau klik untuk pilih file - - - Maksimal 5MB dan harus format gambar - -
-
- + {/* Gambar */} + + Gambar + handleFileChange(files[0])} + onReject={() => toast.error('File tidak valid.')} + maxSize={5 * 1024 ** 2} // 5MB + accept={{ 'image/*': [] }} + > + + + + +
+ Drag gambar ke sini atau klik untuk pilih file + Maksimal 5MB dan harus format gambar +
+
+
- {/* Tampilkan preview kalau ada */} - {previewImage && ( - - Preview - - )} - -
-
- - - Pengalaman - perbekelState.edit.form.pengalaman = val} - /> - - - Pengalaman Organisasi - perbekelState.edit.form.pengalamanOrganisasi = val} - /> - - - Program Unggulan - perbekelState.edit.form.programUnggulan = val} - /> - - - - - + {/* Preview */} + + {previewImage ? ( + Preview + ) : ( +
+ + + Tidak ada gambar + +
+ )}
+ + {/* Pengalaman */} + + Pengalaman + perbekelState.edit.form.pengalaman = val} + /> + + + {/* Pengalaman Organisasi */} + + Pengalaman Organisasi + perbekelState.edit.form.pengalamanOrganisasi = val} + /> + + + {/* Program Unggulan */} + + Program Unggulan + perbekelState.edit.form.programUnggulan = val} + /> + + + {/* Submit */} + + + + + - ); + ) } export default ProfilePerbekel; diff --git a/src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx b/src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx index e13b914e..50838a9e 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx @@ -1,7 +1,7 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Stack, Text, Title } from '@mantine/core'; +import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core'; import { IconEdit } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; @@ -12,98 +12,102 @@ function Page() { const router = useRouter(); const snap = useSnapshot(stateProfileDesa); - // Panggil load data sekali saat komponen mount + // Load data saat mount useEffect(() => { stateProfileDesa.profilPerbekel.findUnique.load("edit"); }, []); const perbekel = snap.profilPerbekel.findUnique.data; + if (!perbekel) { + return ( + + + + ); + } + return ( - - - + + + {/* Header + tombol edit */} + - Preview Profile PPID + Preview Profil PPID - + + + - {perbekel && ( - - - - - -
- -
-
- - PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA - -
-
- - {/* biodata perbekel */} - - - - -
- Foto Profil PPID { - e.currentTarget.src = "/perbekel.png"; - }} - /> -
- - - I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P. - - -
-
-
- - - Biodata - - - - Pengalaman - - - - - Pengalaman Organisasi - - - - - - Program Kerja Unggulan - - - - -
-
+ {/* Card Profil */} + + + + +
+ Logo Desa +
+
+ + + Profil Pimpinan Badan Publik Desa Darmasaba + + +
- )} + + + +
+ Foto Profil Perbekel { e.currentTarget.src = "/perbekel.png"; }} + /> +
+ + + I.B. Surya Prabhawa Manuaba, S.H., M.H. + + +
+ + {/* Biodata & Info */} + + Biodata + + + Pengalaman + + + Pengalaman Organisasi + + + Program Kerja Unggulan + + +
); diff --git a/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/page.tsx b/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/page.tsx index 6cfb3f2a..ba9c6864 100644 --- a/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/indeks-kepuasan-masyarakat/responden/page.tsx @@ -91,7 +91,6 @@ function ListResponden({ search }: ListRespondenProps) { diff --git a/src/app/admin/(dashboard)/ppid/daftar-informasi-publik-desa-darmasaba/page.tsx b/src/app/admin/(dashboard)/ppid/daftar-informasi-publik-desa-darmasaba/page.tsx index 41b4f398..d7652a95 100644 --- a/src/app/admin/(dashboard)/ppid/daftar-informasi-publik-desa-darmasaba/page.tsx +++ b/src/app/admin/(dashboard)/ppid/daftar-informasi-publik-desa-darmasaba/page.tsx @@ -73,8 +73,6 @@ function ListDaftarInformasi({ search }: { search: string }) {
Data Responden -
+
No @@ -82,7 +82,7 @@ function ListResponden({ search }: ListRespondenProps) { Data Responden -
+
No diff --git a/src/app/api/[[...slugs]]/_lib/desa/berita/kategori-berita/findMany.ts b/src/app/api/[[...slugs]]/_lib/desa/berita/kategori-berita/findMany.ts index a150835b..3c8d9c09 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/berita/kategori-berita/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/berita/kategori-berita/findMany.ts @@ -1,11 +1,53 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// /api/berita/findManyPaginated.ts import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function kategoriBeritaFindMany() { - const data = await prisma.kategoriBerita.findMany(); +async function kategoriBeritaFindMany(context: Context) { + // Ambil parameter dari query + 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; - return { - success: true, - message: "Success get all kategori berita", - data, - }; + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + ]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.kategoriBerita.findMany({ + where, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.kategoriBerita.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil kategori berita dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }; + } catch (e) { + console.error("Error di findMany paginated:", e); + return { + success: false, + message: "Gagal mengambil data kategori berita", + }; + } } + +export default kategoriBeritaFindMany; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts index ed52ae9f..3a6d5e4f 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; @@ -5,43 +6,44 @@ export default async function pelayananSuratKeteranganFindMany(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' } }, + ]; + } + try { + // Ambil data dan total count secara paralel const [data, total] = await Promise.all([ prisma.pelayananSuratKeterangan.findMany({ - where: { isActive: true }, - include: { - image: true, - image2: true, - }, + where, skip, take: limit, orderBy: { createdAt: 'desc' }, }), - prisma.pelayananSuratKeterangan.count({ - where: { isActive: true } - }) + prisma.pelayananSuratKeterangan.count({ where }), ]); - const totalPages = Math.ceil(total / limit); - return { success: true, - message: "Success fetch pelayanan surat keterangan with pagination", + message: "Berhasil ambil pelayanan surat keterangan dengan pagination", data, page, - totalPages, + limit, total, + totalPages: Math.ceil(total / limit), }; } catch (e) { - console.error("Find many paginated error:", e); + console.error("Error di findMany paginated:", e); return { success: false, - message: "Failed fetch pelayanan surat keterangan with pagination", - data: [], - page: 1, - totalPages: 1, - total: 0, + message: "Gagal mengambil data pelayanan surat keterangan", }; } } \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/find-many.ts index 29d84dd9..d60649ac 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/find-many.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/find-many.ts @@ -1,43 +1,49 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; -export default async function pelayananTelunjukSaktiDesaFindMany(context: Context) { - const page = Number(context.query.page) || 1; - const limit = Number(context.query.limit) || 10; - const skip = (page - 1) * limit; - - try { - const [data, total] = await Promise.all([ - prisma.pelayananTelunjukSaktiDesa.findMany({ - where: { isActive: true }, - skip, - take: limit, - orderBy: { createdAt: 'desc' }, - }), - prisma.pelayananTelunjukSaktiDesa.count({ - where: { isActive: true } - }) - ]); - - const totalPages = Math.ceil(total / limit); - - return { - success: true, - message: "Success fetch pelayanan telunjuk sakti desa with pagination", - data, - page, - totalPages, - total, - }; - } catch (e) { - console.error("Find many paginated error:", e); - return { - success: false, - message: "Failed fetch pelayanan telunjuk sakti desa with pagination", - data: [], - page: 1, - totalPages: 1, - total: 0, - }; - } -} \ No newline at end of file +export default async function pelayananTelunjukSaktiDesaFindMany( + 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; + + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [{ name: { contains: search, mode: "insensitive" } }]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.pelayananTelunjukSaktiDesa.findMany({ + where, + skip, + take: limit, + orderBy: { createdAt: "desc" }, + }), + prisma.pelayananTelunjukSaktiDesa.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil pelayanan telunjuk sakti desa dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }; + } catch (e) { + console.error("Error di findMany paginated:", e); + return { + success: false, + message: "Gagal mengambil data pelayanan telunjuk sakti desa", + }; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts index 980c86e7..d3b362e2 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts @@ -1,46 +1,50 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; export default async function penghargaanFindMany(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; + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: "insensitive" } }, + { deskripsi: { contains: search, mode: "insensitive" } }, + ]; + } + try { + // Ambil data dan total count secara paralel const [data, total] = await Promise.all([ prisma.penghargaan.findMany({ - where: { isActive: true }, - include: { - image: true, - }, + where, skip, take: limit, - orderBy: { createdAt: 'desc' }, + orderBy: { createdAt: "desc" }, }), - prisma.penghargaan.count({ - where: { isActive: true } - }) + prisma.penghargaan.count({ where }), ]); - const totalPages = Math.ceil(total / limit); - return { success: true, - message: "Success fetch penghargaan with pagination", + message: "Berhasil ambil penghargaan dengan pagination", data, page, - totalPages, + limit, total, + totalPages: Math.ceil(total / limit), }; } catch (e) { - console.error("Find many paginated error:", e); + console.error("Error di findMany paginated:", e); return { success: false, - message: "Failed fetch penghargaan with pagination", - data: [], - page: 1, - totalPages: 1, - total: 0, + message: "Gagal mengambil data penghargaan", }; } } \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/pengumuman/kategori-pengumuman/findMany.ts b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/kategori-pengumuman/findMany.ts index c54ad743..7fcee604 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/pengumuman/kategori-pengumuman/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/kategori-pengumuman/findMany.ts @@ -1,16 +1,54 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -async function kategoriPengumumanFindMany() { - const data = await prisma.categoryPengumuman.findMany({ - include: { - _count: { - select: { - pengumumans: true - } - } - } - }); - return { data }; +async function kategoriPengumumanFindMany(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; + + // Buat where clause + 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.categoryPengumuman.findMany({ + where, + skip, + take: limit, + orderBy: { createdAt: "desc" }, + include: { + _count: { + select: { + pengumumans: true, + }, + }, + }, + }), + prisma.categoryPengumuman.count({ where }), + ]); + return { + success: true, + message: "Berhasil ambil kategori potensi dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }; + } catch (error) { + console.error("Error di findMany paginated:", error); + return { + success: false, + message: "Gagal mengambil data kategori potensi", + }; + } } -export default kategoriPengumumanFindMany +export default kategoriPengumumanFindMany; diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/find-many.ts index 1f449980..a8350551 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/find-many.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/find-many.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; @@ -5,11 +6,23 @@ export default async function potensiDesaFindMany(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' } }, + { kategori: { nama: { contains: search, mode: 'insensitive' } } }, + { deskripsi: { contains: search, mode: 'insensitive' } }, + ]; + } try { const [data, total] = await Promise.all([ prisma.potensiDesa.findMany({ - where: { isActive: true }, + where, skip, take: limit, include: { @@ -22,15 +35,13 @@ export default async function potensiDesaFindMany(context: Context) { where: { isActive: true } }) ]); - - const totalPages = Math.ceil(total / limit); - + return { success: true, message: "Success fetch potensi desa with pagination", data, page, - totalPages, + totalPages: Math.ceil(total / limit), total, }; } catch (e) { diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findMany.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findMany.ts index 6e3e8588..ac2e94a8 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findMany.ts @@ -1,11 +1,53 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// /api/berita/findManyPaginated.ts import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function kategoriPotensiFindMany() { - const data = await prisma.kategoriPotensi.findMany(); +async function kategoriPotensiFindMany(context: Context) { + // Ambil parameter dari query + 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; - return { - success: true, - message: "Success get all kategori potensi", - data, - }; + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { nama: { contains: search, mode: 'insensitive' } }, + ]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.kategoriPotensi.findMany({ + where, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.kategoriPotensi.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil kategori potensi dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }; + } catch (e) { + console.error("Error di findMany paginated:", e); + return { + success: false, + message: "Gagal mengambil data kategori potensi", + }; + } } + +export default kategoriPotensiFindMany; \ No newline at end of file