From b8a45bc451e7f254022e77282431b73103b0cc24 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 13 Aug 2025 14:53:48 +0800 Subject: [PATCH] Sinkronisasi UI & API Admin - User Submenu Profile, Menu Desa --- prisma/schema.prisma | 72 +++-- .../admin/(dashboard)/_state/desa/profile.ts | 284 ++++++++++++++++-- .../desa/profile/_lib/layoutTabsDetail.tsx | 5 + .../[id]/edit/page.tsx | 170 +++++++++++ .../[id]/page.tsx | 111 +++++++ .../create/page.tsx | 155 ++++++++++ .../page.tsx | 106 +++++++ .../desa/profile/profile-perbekel/page.tsx | 2 +- src/app/api/[[...slugs]]/_lib/desa/index.ts | 3 + .../profile/profile-mantan-perbekel/create.ts | 29 ++ .../profile/profile-mantan-perbekel/del.ts | 53 ++++ .../profile-mantan-perbekel/findMany.ts | 58 ++++ .../profile-mantan-perbekel/findUnique.ts | 49 +++ .../profile/profile-mantan-perbekel/index.ts | 44 +++ .../profile/profile-mantan-perbekel/updt.ts | 82 +++++ .../darmasaba/(pages)/desa/profile/page.tsx | 1 - .../(pages)/desa/profile/ui/lambangDesa.tsx | 63 ++-- .../(pages)/desa/profile/ui/lembagaDesa.tsx | 39 --- .../(pages)/desa/profile/ui/maskotDesa.tsx | 117 +++----- .../desa/profile/ui/profilPerbekel.tsx | 146 ++++----- .../(pages)/desa/profile/ui/sejarahDesa.tsx | 35 ++- .../(pages)/desa/profile/ui/semuaPerbekel.tsx | 92 ++---- .../(pages)/desa/profile/ui/visimisiDesa.tsx | 41 +-- 23 files changed, 1348 insertions(+), 409 deletions(-) create mode 100644 src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/create/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/page.tsx create mode 100644 src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/create.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/del.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findMany.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findUnique.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/index.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/updt.ts delete mode 100644 src/app/darmasaba/(pages)/desa/profile/ui/lembagaDesa.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 410178b8..7a181220 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -99,6 +99,7 @@ model FileStorage { PrestasiDesa PrestasiDesa[] DataPerpustakaan DataPerpustakaan[] PegawaiPPID PegawaiPPID[] + PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[] } //========================================= MENU LANDING PAGE ========================================= // @@ -220,48 +221,48 @@ model KategoriPrestasiDesa { //========================================= INDEKS KEPUASAAN MASYARAKAT ========================================= // model Responden { - id String @id @default(cuid()) - name String @unique - tanggal DateTime // misal: 2025-05-01 - jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id]) + id String @id @default(cuid()) + name String @unique + tanggal DateTime // misal: 2025-05-01 + jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id]) jenisKelaminId String - rating PilihanRatingResponden @relation(fields: [ratingId], references: [id]) - ratingId String - kelompokUmur UmurResponden @relation(fields: [kelompokUmurId], references: [id]) + rating PilihanRatingResponden @relation(fields: [ratingId], references: [id]) + ratingId String + kelompokUmur UmurResponden @relation(fields: [kelompokUmurId], references: [id]) kelompokUmurId String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } model JenisKelaminResponden { - id String @id @default(cuid()) - name String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) Responden Responden[] } model PilihanRatingResponden { - id String @id @default(cuid()) - name String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) Responden Responden[] } model UmurResponden { - id String @id @default(cuid()) - name String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) Responden Responden[] } @@ -552,6 +553,19 @@ model ProfilPerbekel { isActive Boolean @default(true) } +model PerbekelDariMasaKeMasa { + id String @id @default(cuid()) + nama String @db.Text + periode String @db.Text + image FileStorage? @relation(fields: [imageId], references: [id]) + imageId String? + daerah String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + // ========================================= BERITA ========================================= // model Berita { id String @id @default(cuid()) diff --git a/src/app/admin/(dashboard)/_state/desa/profile.ts b/src/app/admin/(dashboard)/_state/desa/profile.ts index 07142d72..b8bb16d8 100644 --- a/src/app/admin/(dashboard)/_state/desa/profile.ts +++ b/src/app/admin/(dashboard)/_state/desa/profile.ts @@ -1,7 +1,9 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { toast } from "react-toastify"; import { proxy } from "valtio"; import { z } from "zod"; import { Prisma } from "@prisma/client"; +import ApiFetch from "@/lib/api-fetch"; // ========================================= SEJARAH DESA ========================================= // const sejarahDesaForm = z.object({ @@ -106,16 +108,13 @@ const sejarahDesa = proxy({ this.error = null; try { - const response = await fetch( - `/api/desa/profile/sejarah/${this.id}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(this.form), - } - ); + const response = await fetch(`/api/desa/profile/sejarah/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); @@ -409,16 +408,13 @@ const lambangDesa = proxy({ this.error = null; try { - const response = await fetch( - `/api/desa/profile/lambang/${this.id}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(this.form), - } - ); + const response = await fetch(`/api/desa/profile/lambang/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); @@ -588,14 +584,11 @@ const maskotDesa = proxy({ this.error = null; try { - const response = await fetch( - `/api/desa/profile/maskot/${this.id}`, - { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(this.form), - } - ); + const response = await fetch(`/api/desa/profile/maskot/${this.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.form), + }); const result = await response.json(); @@ -818,12 +811,247 @@ const profilPerbekel = proxy({ }, }); +//========================================= MANTAN PERBEKEL ========================================= // +const mantanPerbekelForm = z.object({ + nama: z.string().min(3, "Nama minimal 3 karakter"), + daerah: z.string().min(3, "Daerah minimal 3 karakter"), + periode: z.string().min(3, "Periode minimal 3 karakter"), + imageId: z.string().min(1, "Gambar wajib dipilih"), +}); + +const mantanPerbekelDefaultForm = { + nama: "", + daerah: "", + periode: "", + imageId: "", +}; + +const mantanPerbekel = proxy({ + create: { + form: { ...mantanPerbekelDefaultForm }, + loading: false, + async create() { + const cek = mantanPerbekelForm.safeParse(mantanPerbekel.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + mantanPerbekel.create.loading = true; + const res = await ApiFetch.api.desa.mantanperbekel["create"].post( + mantanPerbekel.create.form + ); + if (res.status === 200) { + mantanPerbekel.findMany.load(); + return toast.success("Foto berhasil disimpan!"); + } + return toast.error("Gagal menyimpan foto"); + } catch (error) { + console.log((error as Error).message); + } finally { + mantanPerbekel.create.loading = false; + } + }, + resetForm() { + mantanPerbekel.create.form = { ...mantanPerbekelDefaultForm }; + }, + }, + findMany: { + data: null as + | Prisma.PerbekelDariMasaKeMasaGetPayload<{ + include: { + image: true; + }; + }>[] + | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + mantanPerbekel.findMany.loading = true; // ✅ Akses langsung via nama path + mantanPerbekel.findMany.page = page; + mantanPerbekel.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.desa.mantanperbekel["findMany"].get({ + query, + }); + + if (res.status === 200 && res.data?.success) { + mantanPerbekel.findMany.data = res.data.data ?? []; + mantanPerbekel.findMany.totalPages = res.data.totalPages ?? 1; + } else { + mantanPerbekel.findMany.data = []; + mantanPerbekel.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch mantan perbekel paginated:", err); + mantanPerbekel.findMany.data = []; + mantanPerbekel.findMany.totalPages = 1; + } finally { + mantanPerbekel.findMany.loading = false; + } + }, + }, + findUnique: { + data: null as Prisma.PerbekelDariMasaKeMasaGetPayload<{ + include: { + image: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/desa/mantanperbekel/${id}`); + if (res.ok) { + const data = await res.json(); + mantanPerbekel.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch mantan perbekel:", res.statusText); + mantanPerbekel.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching mantan perbekel:", error); + mantanPerbekel.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + mantanPerbekel.delete.loading = true; + const response = await fetch(`/api/desa/mantanperbekel/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "Mantan perbekel berhasil dihapus"); + await mantanPerbekel.findMany.load(); // refresh list + } else { + toast.error(result.message || "Gagal menghapus mantan perbekel"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus mantan perbekel"); + } finally { + mantanPerbekel.delete.loading = false; + } + }, + }, + update: { + id: "", + form: { ...mantanPerbekelDefaultForm }, + loading: false, + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + try { + const response = await fetch(`/api/desa/mantanperbekel/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + if (result?.success) { + const data = result.data; + this.id = data.id; + this.form = { + nama: data.nama, + daerah: data.daerah, + periode: data.periode, + imageId: data.imageId || "", + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading foto:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = mantanPerbekelForm.safeParse(mantanPerbekel.update.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + try { + mantanPerbekel.update.loading = true; + const response = await fetch(`/api/desa/mantanperbekel/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nama: this.form.nama, + daerah: this.form.daerah, + periode: this.form.periode, + imageId: this.form.imageId, + }), + }); + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + const result = await response.json(); + if (result.success) { + toast.success(result.message || "Mantan perbekel berhasil diupdate"); + await mantanPerbekel.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal mengupdate mantan perbekel"); + } + } catch (error) { + console.error("Error updating mantan perbekel:", error); + toast.error( + error instanceof Error + ? error.message + : "Gagal mengupdate mantan perbekel" + ); + return false; + } finally { + mantanPerbekel.update.loading = false; + } + }, + reset() { + mantanPerbekel.update.id = ""; + mantanPerbekel.update.form = { ...mantanPerbekelDefaultForm }; + }, + }, +}); + const stateProfileDesa = proxy({ lambangDesa, maskotDesa, profilPerbekel, visiMisiDesa, sejarahDesa, + mantanPerbekel, }); export default stateProfileDesa; diff --git a/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx b/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx index 1cd94190..a3da9bae 100644 --- a/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx +++ b/src/app/admin/(dashboard)/desa/profile/_lib/layoutTabsDetail.tsx @@ -18,6 +18,11 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) { label: "Profile Perbekel", value: "profileperbekel", href: "/admin/desa/profile/profile-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" } ]; const curentTab = tabs.find(tab => tab.href === pathname) 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 new file mode 100644 index 00000000..a8f5022a --- /dev/null +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/edit/page.tsx @@ -0,0 +1,170 @@ +'use client' +/* eslint-disable react-hooks/exhaustive-deps */ +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 { 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 EditPerbekelDariMasaKeMasa() { + const state = useProxy(stateProfileDesa.mantanPerbekel) + const router = useRouter(); + const params = useParams(); + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + const [formData, setFormData] = useState({ + nama: state.update.form.nama || '', + daerah: state.update.form.daerah || '', + periode: state.update.form.periode || '', + imageId: state.update.form.imageId || '' + }); + + useEffect(() => { + const loadFoto = async () => { + const id = params?.id as string; + if (!id) return; + try { + const data = await state.update.load(id); + if (data) { + setFormData({ + nama: data.nama || '', + daerah: data.daerah || '', + periode: data.periode || '', + imageId: data.imageId || '' + }); + 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 { + state.update.form = { + ...state.update.form, + nama: formData.nama, + daerah: formData.daerah, + periode: formData.periode, + imageId: formData.imageId + }; + 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"); + } + 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'); + } catch (error) { + console.error('Error updating perbekel dari masa ke masa:', error); + toast.error('Terjadi kesalahan saat memperbarui perbekel dari masa ke masa'); + } + }; + + return ( + + + + + + + + Edit Perbekel Dari Masa Ke Masa + Nama} + placeholder='Masukkan nama' + value={formData.nama} + onChange={(e) => + (formData.nama = 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 ? ( + + ) : ( +
+ +
+ )} +
+ Daerah} + placeholder='Masukkan daerah' + value={formData.daerah} + onChange={(e) => + (formData.daerah = e.target.value) + } + /> + Periode} + placeholder='Masukkan periode' + value={formData.periode} + onChange={(e) => + (formData.periode = e.target.value) + } + /> + + + +
+
+
+ ); +} + +export default EditPerbekelDariMasaKeMasa; 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 new file mode 100644 index 00000000..288a5874 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/[id]/page.tsx @@ -0,0 +1,111 @@ +'use client' +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 { useShallowEffect } from '@mantine/hooks'; +import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; + +function DetailPerbekelDariMasa() { + const state = useProxy(stateProfileDesa.mantanPerbekel) + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null) + const params = useParams() + const router = useRouter() + + useShallowEffect(() => { + 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") + } + } + + if (!state.findUnique.data) { + return ( + + + + ) + } + + 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 + + + + + + + + ) : null} + + + + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus perbekel dari masa ke masa ini?' + /> + + ); +} + +export default DetailPerbekelDariMasa; 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 new file mode 100644 index 00000000..db0c1e45 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/create/page.tsx @@ -0,0 +1,155 @@ +'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 { 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 CreateVideo() { + 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: "", + }; + 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"); + } + state.create.form.imageId = uploaded.id; + await state.create.create(); + resetForm(); + router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa"); + }; + + + return ( + + + + + + + + Create Perbekel Dari Masa Ke Masa + 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; + }} + required + /> + Periode} + placeholder='Masukkan periode' + value={state.create.form.periode} + onChange={(e) => + (state.create.form.periode = 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/*': [] }} + > + + + + + + + + + + + +
+ + Drag gambar ke sini atau klik untuk pilih file + + + Maksimal 5MB dan harus format gambar + +
+
+
+ + {/* Tampilkan preview kalau ada */} + {previewImage && ( + + Preview + + )} + +
+
+ + + +
+
+
+ ); +} + +export default CreateVideo; 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 new file mode 100644 index 00000000..d676cac4 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel-dari-masa-ke-masa/page.tsx @@ -0,0 +1,106 @@ +'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 { useShallowEffect } from '@mantine/hooks'; +import { IconDeviceImac, 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() { + const [search, setSearch] = useState(""); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListPerbekelDariMasaKeMasa({ search }: { search: string }) { + const state = useProxy(stateProfileDesa.mantanPerbekel) + const router = useRouter(); + const { + data, + page, + totalPages, + loading, + load, + } = state.findMany; + + useShallowEffect(() => { + load(page, 10, search) + }, [page, search]) + + const filteredData = (data || []) + + if (loading || !data) { + return ( + + + + ) + } + + return ( + + + + + + + Nama Perbekel + Periode + Detail + + + + {filteredData.map((item) => ( + + + + {item.nama} + + + + + + {item.periode} + + + + + + + ))} + +
+
+
+ load(newPage)} // ini penting! + total={totalPages} + mt="md" + mb="md" + /> +
+
+ ); +} + +export default PerbekelDariMasaKeMasa; 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 97b4f13a..e13b914e 100644 --- a/src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx +++ b/src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx @@ -33,7 +33,7 @@ function Page() { {perbekel && ( - + diff --git a/src/app/api/[[...slugs]]/_lib/desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/index.ts index 6c4ce431..d89cfd0a 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/index.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/index.ts @@ -10,6 +10,7 @@ import Penghargaan from "./penghargaan"; import KategoriPotensi from "./potensi/kategori-potensi"; import KategoriBerita from "./berita/kategori-berita"; import KategoriPengumuman from "./pengumuman/kategori-pengumuman"; +import MantanPerbekel from "./profile/profile-mantan-perbekel"; const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] }) @@ -19,10 +20,12 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] }) .use(PotensiDesa) .use(GalleryFoto) .use(GalleryVideo) + .use(MantanPerbekel) .use(LayananDesa) .use(Penghargaan) .use(KategoriPotensi) .use(KategoriBerita) .use(KategoriPengumuman) + export default Desa; diff --git a/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/create.ts b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/create.ts new file mode 100644 index 00000000..350729cc --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/create.ts @@ -0,0 +1,29 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreate = { + nama: string; + periode: string; + imageId: string; + daerah: string; +} + +export default async function profileMantanPerbekelCreate(context: Context) { + const body = context.body as FormCreate; + + await prisma.perbekelDariMasaKeMasa.create({ + data: { + nama: body.nama, + periode: body.periode, + imageId: body.imageId, + daerah: body.daerah, + }, + }); + return { + success: true, + message: "Success create profile mantan perbekel", + data: { + ...body, + }, + }; +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/del.ts b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/del.ts new file mode 100644 index 00000000..78f1dbd3 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/del.ts @@ -0,0 +1,53 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; +import fs from "fs/promises"; +import path from "path"; + +const profileMantanPerbekelDelete = async (context: Context) => { + const id = context.params?.id as string; + + if (!id) { + return { + status: 400, + body: "ID tidak diberikan", + }; + } + + const foto = await prisma.perbekelDariMasaKeMasa.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!foto) { + return { + status: 404, + body: "Foto tidak ditemukan", + }; + } + + // Hapus file gambar dari filesystem jika ada + if (foto.image) { + try { + const filePath = path.join(foto.image.path, foto.image.name); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: foto.image.id }, + }); + } catch (err) { + console.error("Gagal hapus gambar lama:", err); + } + } + + await prisma.perbekelDariMasaKeMasa.delete({ + where: { id }, + }); + + return { + status: 200, + body: "Foto berhasil dihapus", + }; +} + +export default profileMantanPerbekelDelete diff --git a/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findMany.ts b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findMany.ts new file mode 100644 index 00000000..7d4dcf47 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findMany.ts @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// /api/berita/findManyPaginated.ts +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +async function profileMantanPerbekelFindMany(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; + + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { nama: { contains: search, mode: 'insensitive' } }, + { periode: { contains: search, mode: 'insensitive' } }, + { daerah: { contains: search, mode: 'insensitive' } }, + ]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.perbekelDariMasaKeMasa.findMany({ + where, + include: { + image: true, + }, + skip, + take: limit, + orderBy: { periode: 'asc' }, + }), + prisma.perbekelDariMasaKeMasa.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil foto 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 foto", + }; + } +} + +export default profileMantanPerbekelFindMany; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findUnique.ts new file mode 100644 index 00000000..df64152c --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findUnique.ts @@ -0,0 +1,49 @@ +import prisma from "@/lib/prisma"; + +export default async function profileMantanPerbekelFindUnique(request: Request) { + const url = new URL(request.url); + const pathSegments = url.pathname.split('/'); + const id = pathSegments[pathSegments.length - 1]; + + if (!id) { + return Response.json({ + success: false, + message: "ID tidak boleh kosong", + }, { status: 400 }); + } + + try { + if (typeof id !== 'string') { + return Response.json({ + success: false, + message: "ID tidak valid", + }, { status: 400 }); + } + + const data = await prisma.perbekelDariMasaKeMasa.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!data) { + return Response.json({ + success: false, + message: "Profile mantan perbekel tidak ditemukan", + }, { status: 404 }); + } + + return Response.json({ + success: true, + message: "Success fetch profile mantan perbekel by ID", + data, + }, { status: 200 }); + } catch (e) { + console.error("Find by ID error:", e); + return Response.json({ + success: false, + message: "Gagal mengambil profile mantan perbekel: " + (e instanceof Error ? e.message : 'Unknown error'), + }, { status: 500 }); + } +} diff --git a/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/index.ts b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/index.ts new file mode 100644 index 00000000..a4be7110 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/index.ts @@ -0,0 +1,44 @@ +import Elysia, { t } from "elysia"; +import profileMantanPerbekelCreate from "./create"; +import profileMantanPerbekelDelete from "./del"; +import profileMantanPerbekelFindMany from "./findMany"; +import profileMantanPerbekelUpdate from "./updt"; +import profileMantanPerbekelFindUnique from "./findUnique"; + +const MantanPerbekel = new Elysia({ + prefix: "mantanperbekel", + tags: ["Desa/Profile/MantanPerbekel"], +}) + .get("/findMany", profileMantanPerbekelFindMany) + .get("/:id", async (context) => { + const response = await profileMantanPerbekelFindUnique( + new Request(context.request) + ); + return response; + }) + .post("/create", profileMantanPerbekelCreate, { + body: t.Object({ + nama: t.String(), + daerah: t.String(), + periode: t.String(), + imageId: t.String(), + }), + }) + .delete("/del/:id", profileMantanPerbekelDelete) + .put( + "/:id", + async (context) => { + const response = await profileMantanPerbekelUpdate(context); + return response; + }, + { + body: t.Object({ + nama: t.String(), + daerah: t.String(), + periode: t.String(), + imageId: t.String(), + }), + } + ); + +export default MantanPerbekel; diff --git a/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/updt.ts new file mode 100644 index 00000000..8b99f07d --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/updt.ts @@ -0,0 +1,82 @@ +import prisma from "@/lib/prisma"; +import { Prisma } from "@prisma/client"; +import { Context } from "elysia"; +import fs from "fs/promises"; +import path from "path"; + +type FormUpdate = Prisma.PerbekelDariMasaKeMasaGetPayload<{ + select: { + id: true; + nama: true; + periode: true; + imageId: true; + daerah: true; + }; +}>; + +export default async function profileMantanPerbekelUpdate(context: Context) { + try { + const id = context.params?.id; + const body = (await context.body) as Omit; + + const { nama, periode, imageId, daerah } = body; + + if (!id) { + return { + success: false, + message: "ID tidak diberikan", + }; + } + + const existing = await prisma.perbekelDariMasaKeMasa.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!existing) { + return { + success: false, + message: "Profile mantan perbekel tidak ditemukan", + }; + } + + if (existing.imageId && existing.imageId !== imageId) { + const oldImage = existing.image; + if (oldImage) { + try { + const filePath = path.join(oldImage.path, oldImage.name); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: oldImage.id }, + }); + } catch (err) { + console.error("Gagal hapus gambar lama:", err); + } + } + } + + const updated = await prisma.perbekelDariMasaKeMasa.update({ + where: { id }, + data: { + nama, + periode, + imageId, + daerah, + }, + }); + + return { + success: true, + message: "Success update profile mantan perbekel", + data: updated, + }; + } catch (error) { + console.error("Error updating profile mantan perbekel:", error); + return { + success: false, + message: "Terjadi kesalahan saat mengupdate profile mantan perbekel", + }; + } +} diff --git a/src/app/darmasaba/(pages)/desa/profile/page.tsx b/src/app/darmasaba/(pages)/desa/profile/page.tsx index 3ae4fdbd..de4f921f 100644 --- a/src/app/darmasaba/(pages)/desa/profile/page.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/page.tsx @@ -31,7 +31,6 @@ function Page() { - {/* */} diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/lambangDesa.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/lambangDesa.tsx index 676fa13f..195ea95b 100644 --- a/src/app/darmasaba/(pages)/desa/profile/ui/lambangDesa.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/ui/lambangDesa.tsx @@ -1,8 +1,27 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' import colors from '@/con/colors'; -import { Box, Stack, Paper, Image, Text, Center } from '@mantine/core'; -import React from 'react'; +import { Box, Stack, Paper, Image, Text, Center, Skeleton } from '@mantine/core'; +import React, { useEffect } from 'react'; +import { useProxy } from 'valtio/utils'; +import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; function LambangDesa() { + const state = useProxy(stateProfileDesa.lambangDesa) + + useEffect(() => { + state.findUnique.load("edit") + }, []) + + const { data, loading } = state.findUnique + + if (loading || !data) { + return ( + + + + ) + } return ( @@ -13,46 +32,8 @@ function LambangDesa() { Lambang Desa - - Simbul atau lambang bukanlah suatu gambar bentuk tanpa mengandung makna, namun segala yang terdapat di dalamnya mengandung makna filosofi yang mendalam dan kepribadian, setidaknya mengandung suatu pengharapan atau tujuan yang ingin dicapai oleh yang empunya lambang tersebut. - - - Lambang Desa Darmasaba berbentuk persegi lima sama sisi dengan dasar warna biru yang di dalamnya terdapat bangunan Padmasana dengan rong tiga, di dasarnya terdapat bunga Padma berwarna merah muda yang dikelilingi oleh padi dan kapas serta rantai sebagai pengikatnya, terdapat pula pita berwarna putih bertuliskan “Dharma Temaja”. - - - - Bentuk Dasar Persegi Lima - - - Melambangkan Pancasila yang menjadi dasar Negara Republik Indonesia yang harus dihayati dan diamalkan oleh masyarakat Indonesia termasuk masyarakat Desa Darmaaba merupakan bagian dari Bangsa Indonesia. - - - - - Dasar Persegi Lima Yang Berwarna Biru - - - Mengandung makna wilayah Desa Darmasaba yang luas, tanahnya yg subur dan masyarakatnya memiliki berbagai macam mata pencaharian. - - - - - Bangunan Pelinggih Padmasana Rong Tiga - - - Merupakan perlambang wilayah Desa Darmasaba terdiri dari tiga Desa Adat antara lain Desa Adat Aban, Desa Adat Tegal dan Desa Adat Darmasaba yang dengan rasa persatuan dan kesatuan dilandasi semangat segalak segilik seguluk sebayantaka membangun Desa Darmasaba. - - - - - Bunga Padma Merah Muda - - - Memiliki arti kemuliaan. - - + - ); diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/lembagaDesa.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/lembagaDesa.tsx deleted file mode 100644 index 18e27ab8..00000000 --- a/src/app/darmasaba/(pages)/desa/profile/ui/lembagaDesa.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import colors from '@/con/colors'; -import { Box, Center, Image, Paper, Stack, Text } from '@mantine/core'; - -function LembagaDesa() { - return ( - - - - Lembaga Desa - Badan Permusyawaratan Desa (BPD) - - -
- -
-
- - Lembaga pemberdayaan Masyarakat (LPM) - - -
- -
-
- - Perangkat Desa - Struktur Organisasi Tata Kerja Pemerintahan Desa Darmasaba - - -
- -
-
-
-
- ); -} - -export default LembagaDesa; diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/maskotDesa.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/maskotDesa.tsx index 2316e6f7..11ad7378 100644 --- a/src/app/darmasaba/(pages)/desa/profile/ui/maskotDesa.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/ui/maskotDesa.tsx @@ -1,7 +1,27 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Center, Image, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; +import { Box, Card, Center, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { useEffect } from 'react'; +import { useProxy } from 'valtio/utils'; function MaskotDesa() { + const state = useProxy(stateProfileDesa.maskotDesa) + + useEffect(() => { + state.findUnique.load("edit") + }, []) + + const { data, loading } = state.findUnique + + if (loading || !data) { + return ( + + + + ) + } return ( @@ -11,83 +31,24 @@ function MaskotDesa() { Maskot Desa - - - Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing. - - - Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang. - - {/* Pohon dan Bunga Pudak */} - - -
- - - - Pohon Pudak - - -
-
- - - - Bunga Pudak - - -
-
-
- - - Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman. - - - Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). - “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas. - - - {/* Tari dan Pose Klimaks Sekar Pudak */} - - - -
- - - Tari Sekar Pudak - -
-
- -
- - - Pose Klimaks Tari - Sekar Pudak - -
-
-
-
- - - Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri). - - - Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit. - - + + + + {data.images.map((img, index) => ( + + {img.label} + {img.label} + + ))} +
diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/profilPerbekel.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/profilPerbekel.tsx index 11133ade..493b3d95 100644 --- a/src/app/darmasaba/(pages)/desa/profile/ui/profilPerbekel.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/ui/profilPerbekel.tsx @@ -1,7 +1,27 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Image, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; +import { Box, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; +import { useEffect } from 'react'; +import { useProxy } from 'valtio/utils'; function ProfilPerbekel() { + const state = useProxy(stateProfileDesa.profilPerbekel) + + useEffect(() => { + state.findUnique.load("edit") + }, []) + + const { data, loading } = state.findUnique + + if (loading || !data) { + return ( + + + + ) + } return ( @@ -19,7 +39,16 @@ function ProfilPerbekel() { - + { + e.currentTarget.src = "/perbekel.png"; + }} + /> - - - - Biodata - - - I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan. - - - Pengalaman - - - - 2021 - 2027: Perbekel Desa Darmasaba - 2015 - Sekarang: Founder & Managing Director Mantra - Legal Consultants & Advocates - 2020 - Sekarang: Founder Ugawa Record Music Studio - 2010 - 2016: Dosen Fakultas Hukum Universitas - Mahasaraswati Denpasar - - - - Pengalaman Organisasi - - - - 1996 - 1997: Ketua OSIS SMP Negeri 1 Abiansemal - 1999 - 2000: Ketua OSIS SMA Negeri 1 Mengwi - 2008 - 2009: Ketua BEM Universitas Mahasaraswati Denpasar - 2008 - 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba - 2020 - Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar - 2021 - Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung - 2023 - 2028: Komite Tetap Advokasi - Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung - - - - - - + + + Biodata + + + + Pengalaman + + + + - - Program Kerja Unggulan - - - - - Pemberdayaan Ekonomi dan UMKM - - - - Pelatihan dan pendampingan UMKM lokal - Program bantuan modal usaha bagi pelaku usaha kecil - Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal - - + + Pengalaman Organisasi + + + + Program Kerja Unggulan + + - - - Peningkatan Infrastruktur Desa - - - - Pembangunan dan perbaikan jalan desa - Penyediaan fasilitas umum dan ruang terbuka hijau - Optimalisasi layanan publik berbasis digital - - - - - - Pendidikan dan Pengembangan SDM - - - - Beasiswa pendidikan bagi siswa berprestasi dari keluarga kurang mampu - Program kursus dan pelatihan kerja bagi pemuda desa - Peningkatan kualitas pendidikan melalui kerja sama dengan perguruan tinggi - - - - - - Pelestarian Budaya dan Pariwisata - - - - Revitalisasi pura dan tempat bersejarah di Darmasaba - Pengembangan desa wisata berbasis budaya dan seni lokal - Festival budaya dan seni sebagai daya tarik wisata - - - - + - + ); } diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/sejarahDesa.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/sejarahDesa.tsx index 192d1813..25adfb3f 100644 --- a/src/app/darmasaba/(pages)/desa/profile/ui/sejarahDesa.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/ui/sejarahDesa.tsx @@ -1,7 +1,28 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Center, Image, Paper, Stack, Text } from '@mantine/core'; +import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { useEffect } from 'react'; +import { useProxy } from 'valtio/utils'; function SejarahDesa() { + const state = useProxy(stateProfileDesa.sejarahDesa) + + useEffect(() => { + state.findUnique.load("edit") + }, []) + + const { data, loading } = state.findUnique + + if (loading || !data) { + return ( + + + + ) + } + return ( <> @@ -12,16 +33,8 @@ function SejarahDesa() { Sejarah Desa - - - Asal –usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. - - - Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. - - - Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu. - + + diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/semuaPerbekel.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/semuaPerbekel.tsx index fd777d16..7ecb8292 100644 --- a/src/app/darmasaba/(pages)/desa/profile/ui/semuaPerbekel.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/ui/semuaPerbekel.tsx @@ -1,73 +1,25 @@ 'use client' import colors from '@/con/colors'; -import { Box, Center, Image, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; - -const data = [ - { - id: 1, - nama: "SI GEDE KANIA", - wilayah: "PERBEKEL TEGAL", - periode: "Tahun 1943 - 1946", - foto: "/api/img/perbekel-1.png" - }, - { - id: 2, - nama: "SI GEDE GANDEM", - wilayah: "PERBEKEL TEGAL", - periode: "Tahun 1946 - 1950", - foto: "/api/img/perbekel-2.png" - }, - { - id: 3, - nama: "I WAYAN SAMA", - wilayah: "PERBEKEL TEGAL", - periode: "Tahun 1950 - 1953", - foto: "/api/img/perbekel-3.png" - }, - { - id: 4, - nama: "I WAYAN NAMBREG", - wilayah: "PERBEKEL DARMASABA", - periode: "Tahun 1950 - 1960", - foto: "/api/img/perbekel-4.png" - }, - { - id: 5, - nama: "IDA BAGUS PUTU OKA", - wilayah: "PERBEKEL TEGAL/DARMASABA", - periode: "Tahun 1953 - 1974", - foto: "/api/img/perbekel-5.png" - }, - { - id: 6, - nama: "I NYOMAN PATRA", - wilayah: "PERBEKEL DARMASABA", - periode: "Tahun 1974 - 1991", - foto: "/api/img/perbekel-6.png" - }, - { - id: 7, - nama: "I MADE RUDJA", - wilayah: "PERBEKEL DARMASABA", - periode: "Tahun 1991 - 2007", - foto: "/api/img/perbekel-7.png" - }, - { - id: 8, - nama: "I WAYAN KALER, SH.MH.", - wilayah: "PERBEKEL DARMASABA", - periode: "Tahun 2007 - 2013", - foto: "/api/img/perbekel-8.png" - }, - { - id: 9, - nama: "I MADE TARAM, SH.", - wilayah: "PERBEKEL DARMASABA", - periode: "Tahun 2013 - 2019", - foto: "/api/img/perbekel-9.png" - }, -] +import { Box, Center, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; +import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; +import { useProxy } from 'valtio/utils'; +import { useShallowEffect } from '@mantine/hooks'; function SemuaPerbekel() { +const state = useProxy(stateProfileDesa.mantanPerbekel) + +useShallowEffect(() => { + state.findMany.load() +}, []) + +const {data, loading} = state.findMany + +if (loading || !data) { + return ( + + + + ) +} return ( @@ -84,16 +36,16 @@ function SemuaPerbekel() { {data.map((v, k) => { return ( - +
- +
{v.nama} - {v.wilayah} + {v.daerah} {v.periode} diff --git a/src/app/darmasaba/(pages)/desa/profile/ui/visimisiDesa.tsx b/src/app/darmasaba/(pages)/desa/profile/ui/visimisiDesa.tsx index 1162fb50..99ded8c8 100644 --- a/src/app/darmasaba/(pages)/desa/profile/ui/visimisiDesa.tsx +++ b/src/app/darmasaba/(pages)/desa/profile/ui/visimisiDesa.tsx @@ -1,8 +1,27 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'; import colors from '@/con/colors'; -import { Box, Stack, Paper, Image, Text, ListItem, List } from '@mantine/core'; -import React from 'react'; +import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { useEffect } from 'react'; +import { useProxy } from 'valtio/utils'; function VisimisiDesa() { + const state = useProxy(stateProfileDesa.visiMisiDesa) + + useEffect(() => { + state.findUnique.load("edit") + }, []) + + const { data, loading } = state.findUnique + + if (loading || !data) { + return ( + + + + ) + } return ( <> @@ -12,24 +31,12 @@ function VisimisiDesa() { Visi Desa - - “Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana” - + Misi Desa - - - Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama. - Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi. - Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean goverment. - Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan. - Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat. - Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM. - Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup. - Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa. - Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata. - + +