From ab887c30e60577596e6897acfff81d29f9b4f615 Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 5 Aug 2025 17:43:04 +0800 Subject: [PATCH] Sinkronisasi Admin dan User, Menu Landing Page, Submenu Potensi --- prisma/schema.prisma | 128 ++-- .../(dashboard)/_state/desa/layananDesa.ts | 41 +- .../admin/(dashboard)/_state/desa/potensi.ts | 674 ++++++++++++------ .../pelayanan_telunjuk_sakti_desa/page.tsx | 92 ++- .../desa/potensi/_lib/layoutTabs.tsx | 63 ++ .../(dashboard)/desa/potensi/create/page.tsx | 128 ---- .../potensi/kategori-potensi/[id]/page.tsx | 80 +++ .../potensi/kategori-potensi/create/page.tsx | 55 ++ .../desa/potensi/kategori-potensi/page.tsx | 128 ++++ .../admin/(dashboard)/desa/potensi/layout.tsx | 14 + .../[id] => list-potensi/[id]/edit}/page.tsx | 122 +++- .../{detail => list-potensi}/[id]/page.tsx | 9 +- .../desa/potensi/list-potensi/create/page.tsx | 176 +++++ .../desa/potensi/list-potensi/page.tsx | 158 ++++ .../admin/(dashboard)/desa/potensi/page.tsx | 100 --- src/app/admin/_com/list_PageAdmin.tsx | 2 +- src/app/api/[[...slugs]]/_lib/desa/index.ts | 2 + .../find-many.ts | 52 +- .../[[...slugs]]/_lib/desa/potensi/create.ts | 4 +- .../api/[[...slugs]]/_lib/desa/potensi/del.ts | 1 + .../_lib/desa/potensi/find-many.ts | 63 +- .../_lib/desa/potensi/find-unique.ts | 1 + .../[[...slugs]]/_lib/desa/potensi/index.ts | 4 +- .../desa/potensi/kategori-potensi/create.ts | 27 + .../_lib/desa/potensi/kategori-potensi/del.ts | 16 + .../desa/potensi/kategori-potensi/findMany.ts | 11 + .../potensi/kategori-potensi/findUnique.ts | 46 ++ .../desa/potensi/kategori-potensi/index.ts | 33 + .../desa/potensi/kategori-potensi/updt.ts | 28 + .../[[...slugs]]/_lib/desa/potensi/updt.ts | 7 +- .../_com/pelayananTelunjukSaktiDesa.tsx | 2 +- .../(pages)/desa/potensi/[id]/page.tsx | 77 ++ .../_com/main-page/landing-page/index.tsx | 4 +- .../_com/main-page/layanan/index.tsx | 65 +- .../_com/main-page/potensi/index.tsx | 78 +- 35 files changed, 1793 insertions(+), 698 deletions(-) create mode 100644 src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx delete mode 100644 src/app/admin/(dashboard)/desa/potensi/create/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/potensi/kategori-potensi/[id]/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/potensi/kategori-potensi/create/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/potensi/layout.tsx rename src/app/admin/(dashboard)/desa/potensi/{edit/[id] => list-potensi/[id]/edit}/page.tsx (52%) rename src/app/admin/(dashboard)/desa/potensi/{detail => list-potensi}/[id]/page.tsx (93%) create mode 100644 src/app/admin/(dashboard)/desa/potensi/list-potensi/create/page.tsx create mode 100644 src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx delete mode 100644 src/app/admin/(dashboard)/desa/potensi/page.tsx create mode 100644 src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/create.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/del.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findMany.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findUnique.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/index.ts create mode 100644 src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/updt.ts create mode 100644 src/app/darmasaba/(pages)/desa/potensi/[id]/page.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8a06ca04..cb636ff4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -50,59 +50,58 @@ model AppMenuChild { // ========================================= FILE STORAGE ========================================= // model FileStorage { - id String @id @default(cuid()) - name String @unique - realName String - path String - mimeType String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime? - isActive Boolean @default(true) - link String - category String // "image" / "document" / "other" - Berita Berita[] - PotensiDesa PotensiDesa[] - Posyandu Posyandu[] - StrukturPPID StrukturPPID[] - GalleryFoto GalleryFoto[] - - Pelapor Pelapor[] - Penghargaan Penghargaan[] - ProfileDesaImage ProfileDesaImage[] - ProfilePPID ProfilePPID[] - ProfilPerbekel ProfilPerbekel[] - Puskesmas Puskesmas[] - ProgramKesehatan ProgramKesehatan[] - PenangananDarurat PenangananDarurat[] - KontakDarurat KontakDarurat[] - InfoWabahPenyakit InfoWabahPenyakit[] - KeamananLingkungan KeamananLingkungan[] - MenuTipsKeamanan MenuTipsKeamanan[] - PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage") + id String @id @default(cuid()) + name String @unique + realName String + path String + mimeType String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + isActive Boolean @default(true) + link String + category String // "image" / "document" / "other" + Berita Berita[] + PotensiDesa PotensiDesa[] + Posyandu Posyandu[] + StrukturPPID StrukturPPID[] + GalleryFoto GalleryFoto[] + + Pelapor Pelapor[] + Penghargaan Penghargaan[] + ProfileDesaImage ProfileDesaImage[] + ProfilePPID ProfilePPID[] + ProfilPerbekel ProfilPerbekel[] + Puskesmas Puskesmas[] + ProgramKesehatan ProgramKesehatan[] + PenangananDarurat PenangananDarurat[] + KontakDarurat KontakDarurat[] + InfoWabahPenyakit InfoWabahPenyakit[] + KeamananLingkungan KeamananLingkungan[] + MenuTipsKeamanan MenuTipsKeamanan[] + PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage") PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2") - PasarDesa PasarDesa[] - KontakDaruratKeamanan KontakDaruratKeamanan[] - KontakItem KontakItem[] - Pegawai Pegawai[] - DesaDigital DesaDigital[] - KolaborasiInovasi KolaborasiInovasi[] - InfoTekno InfoTekno[] - PengaduanMasyarakat PengaduanMasyarakat[] - KegiatanDesa KegiatanDesa[] - ProgramInovasi ProgramInovasi[] - PejabatDesa PejabatDesa[] - MediaSosial MediaSosial[] - DesaAntiKorupsi DesaAntiKorupsi[] - SDGSDesa SDGSDesa[] - APBDesImage APBDes[] @relation("APBDesImage") - APBDesFile APBDes[] @relation("APBDesFile") - PrestasiDesa PrestasiDesa[] + PasarDesa PasarDesa[] + KontakDaruratKeamanan KontakDaruratKeamanan[] + KontakItem KontakItem[] + Pegawai Pegawai[] + DesaDigital DesaDigital[] + KolaborasiInovasi KolaborasiInovasi[] + InfoTekno InfoTekno[] + PengaduanMasyarakat PengaduanMasyarakat[] + KegiatanDesa KegiatanDesa[] + ProgramInovasi ProgramInovasi[] + PejabatDesa PejabatDesa[] + MediaSosial MediaSosial[] + DesaAntiKorupsi DesaAntiKorupsi[] + SDGSDesa SDGSDesa[] + APBDesImage APBDes[] @relation("APBDesImage") + APBDesFile APBDes[] @relation("APBDesFile") + PrestasiDesa PrestasiDesa[] DataPerpustakaan DataPerpustakaan[] PegawaiPPID PegawaiPPID[] - } //========================================= MENU LANDING PAGE ========================================= // @@ -612,17 +611,28 @@ model KategoriBerita { // ========================================= POTENSI DESA ========================================= // model PotensiDesa { - id String @id @default(cuid()) - name String - deskripsi String - kategori String - image FileStorage @relation(fields: [imageId], references: [id]) - imageId String - content String @db.Text - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + id String @id @default(cuid()) + name String + deskripsi String + kategori KategoriPotensi? @relation(fields: [kategoriId], references: [id]) + kategoriId String? + image FileStorage? @relation(fields: [imageId], references: [id]) + imageId String? + content String @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model KategoriPotensi { + id String @id @default(cuid()) + nama String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + PotensiDesa PotensiDesa[] } // ========================================= PENGUMUMAN ========================================= // diff --git a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts index ff593a5e..f174c8ee 100644 --- a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts +++ b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts @@ -336,15 +336,38 @@ const pelayananTelunjukSaktiDesa = proxy({ }, }, findMany: { - data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{ - omit: { isActive: true }; - }>[], - async load() { - const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[ - "find-many" - ].get(); - if (res.status === 200) { - pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? []; + data: null as any[] | null, + page: 1, + 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 + pelayananTelunjukSaktiDesa.findMany.page = page; + try { + const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[ + "find-many" + ].get({ + query: { page, limit }, + }); + + 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; + } else { + console.error("Failed to load telunjuk sakti desa:", res.data?.message); + pelayananTelunjukSaktiDesa.findMany.data = []; + pelayananTelunjukSaktiDesa.findMany.total = 0; + pelayananTelunjukSaktiDesa.findMany.totalPages = 1; + } + } catch (error) { + console.error("Error loading telunjuk sakti desa:", error); + pelayananTelunjukSaktiDesa.findMany.data = []; + pelayananTelunjukSaktiDesa.findMany.total = 0; + pelayananTelunjukSaktiDesa.findMany.totalPages = 1; + } finally { + pelayananTelunjukSaktiDesa.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/desa/potensi.ts b/src/app/admin/(dashboard)/_state/desa/potensi.ts index 91e898a0..30eca0c1 100644 --- a/src/app/admin/(dashboard)/_state/desa/potensi.ts +++ b/src/app/admin/(dashboard)/_state/desa/potensi.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; @@ -5,219 +6,470 @@ import { proxy } from "valtio"; import { z } from "zod"; const templateForm = z.object({ - name: z.string().min(1).max(50), - deskripsi: z.string().min(1).max(5000), - kategori: z.string().min(1).max(50), - imageId: z.string().min(1).max(50), - content: z.string().min(1).max(5000), -}) + name: z.string().min(1).max(50), + deskripsi: z.string().min(1).max(5000), + kategoriId: z.string().min(1).max(50), + imageId: z.string().min(1).max(50), + content: z.string().min(1).max(5000), +}); const defaultForm = { - name: "", - deskripsi: "", - kategori: "", - imageId: "", - content: "", -} + name: "", + deskripsi: "", + kategoriId: "", + imageId: "", + content: "", +}; + +const potensiDesa = proxy({ + create: { + form: { ...defaultForm }, + loading: false, + async create() { + const cek = templateForm.safeParse(potensiDesa.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + potensiDesa.create.loading = true; + const res = await ApiFetch.api.desa.potensi["create"].post( + potensiDesa.create.form + ); + if (res.status === 200) { + potensiDesa.findMany.load(); + return toast.success("Potensi berhasil disimpan!"); + } + return toast.error("Gagal menyimpan potensi"); + } catch (error) { + console.log((error as Error).message); + } finally { + potensiDesa.create.loading = false; + } + }, + }, + findMany: { + data: null as any[] | null, + page: 1, + totalPages: 1, + total: 0, + loading: false, + load: async (page = 1, limit = 10) => { // Change to arrow function + potensiDesa.findMany.loading = true; // Use the full path to access the property + potensiDesa.findMany.page = page; + try { + const res = await ApiFetch.api.desa.potensi[ + "find-many" + ].get({ + query: { page, limit }, + }); + + if (res.status === 200 && res.data?.success) { + potensiDesa.findMany.data = res.data.data || []; + potensiDesa.findMany.total = res.data.total || 0; + potensiDesa.findMany.totalPages = res.data.totalPages || 1; + } else { + console.error("Failed to load potensi desa:", res.data?.message); + potensiDesa.findMany.data = []; + potensiDesa.findMany.total = 0; + potensiDesa.findMany.totalPages = 1; + } + } catch (error) { + console.error("Error loading potensi desa:", error); + potensiDesa.findMany.data = []; + potensiDesa.findMany.total = 0; + potensiDesa.findMany.totalPages = 1; + } finally { + potensiDesa.findMany.loading = false; + } + }, + }, + findUnique: { + data: null as Prisma.PotensiDesaGetPayload<{ + include: { + image: true; + kategori: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/desa/potensi/${id}`); + if (res.ok) { + const data = await res.json(); + potensiDesa.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch potensi:", res.statusText); + potensiDesa.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching potensi:", error); + potensiDesa.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + potensiDesa.delete.loading = true; + + const response = await fetch(`/api/desa/potensi/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Potensi berhasil dihapus"); + await potensiDesa.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus potensi"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus potensi"); + } finally { + potensiDesa.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...defaultForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/desa/potensi/${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 = { + name: data.name, + deskripsi: data.deskripsi, + kategoriId: data.kategoriId, + imageId: data.imageId || "", + content: data.content, + }; + return data; // Return the loaded data + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading potensi:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + + async update() { + const cek = templateForm.safeParse(potensiDesa.edit.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + potensiDesa.edit.loading = true; + + const response = await fetch(`/api/desa/potensi/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + deskripsi: this.form.deskripsi, + kategoriId: this.form.kategoriId, + imageId: this.form.imageId, + content: this.form.content, + }), + }); + + 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("Berhasil update potensi"); + await potensiDesa.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal update potensi"); + } + } catch (error) { + console.error("Error updating potensi:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update potensi" + ); + return false; + } finally { + potensiDesa.edit.loading = false; + } + }, + reset() { + potensiDesa.edit.id = ""; + potensiDesa.edit.form = { ...defaultForm }; + }, + }, +}); + +const templateKategoriPotensi = z.object({ + nama: z.string().min(1, "Nama harus diisi"), +}); + +const defaultKategoriPotensi = { + nama: "", +}; + +const kategoriPotensi = proxy({ + create: { + form: { ...defaultKategoriPotensi }, + loading: false, + async create() { + const cek = templateKategoriPotensi.safeParse( + kategoriPotensi.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + kategoriPotensi.create.loading = true; + const res = await ApiFetch.api.desa.kategoripotensi["create"].post( + kategoriPotensi.create.form + ); + if (res.status === 200) { + kategoriPotensi.findMany.load(); + return toast.success("Data Kategori Potensi Berhasil Dibuat"); + } + console.log(res); + return toast.error("failed create"); + } catch (error) { + console.log(error); + return toast.error("failed create"); + } finally { + kategoriPotensi.create.loading = false; + } + }, + }, + findMany: { + data: [] as Prisma.KategoriPotensiGetPayload<{ + omit: { + isActive: true; + }; + }>[], + loading: false, + async load() { + const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get(); + if (res.status === 200) { + kategoriPotensi.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.KategoriPotensiGetPayload<{ + omit: { + isActive: true; + }; + }> | null, + loading: false, + async load(id: string) { + try { + const res = await fetch(`/api/desa/kategoripotensi/${id}`); + if (res.ok) { + const data = await res.json(); + kategoriPotensi.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + kategoriPotensi.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data:", error); + kategoriPotensi.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async delete(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + kategoriPotensi.delete.loading = true; + + const response = await fetch(`/api/desa/kategoripotensi/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success( + result.message || "Data Kategori Potensi berhasil dihapus" + ); + await kategoriPotensi.findMany.load(); // refresh list + } else { + toast.error( + result?.message || "Gagal menghapus Data Kategori Potensi" + ); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus Data Kategori Potensi"); + } finally { + kategoriPotensi.delete.loading = false; + } + }, + }, + update: { + id: "", + form: { ...defaultKategoriPotensi }, + loading: false, + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/desa/kategoripotensi/${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, + }; + return data; // Return the loaded data + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading kategori potensi:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = templateKategoriPotensi.safeParse( + kategoriPotensi.update.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + kategoriPotensi.update.loading = true; + + const response = await fetch(`/api/desa/kategoripotensi/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nama: this.form.nama, + }), + }); + + 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("Berhasil update data kategori potensi"); + await kategoriPotensi.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal update data kategori potensi" + ); + } + } catch (error) { + console.error("Error updating data kategori potensi:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update data kategori potensi" + ); + return false; + } finally { + kategoriPotensi.update.loading = false; + } + }, + reset() { + kategoriPotensi.update.id = ""; + kategoriPotensi.update.form = { ...defaultKategoriPotensi }; + }, + }, +}); const potensiDesaState = proxy({ - create: { - form: { ...defaultForm }, - loading: false, - async create() { - const cek = templateForm.safeParse(potensiDesaState.create.form); - if (!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - return toast.error(err); - } - try { - potensiDesaState.create.loading = true; - const res = await ApiFetch.api.desa.potensi["create"].post(potensiDesaState.create.form); - if (res.status === 200) { - potensiDesaState.findMany.load(); - return toast.success("Potensi berhasil disimpan!"); - } - return toast.error("Gagal menyimpan potensi"); - } catch (error) { - console.log((error as Error).message); - } finally { - potensiDesaState.create.loading = false; - } - } - }, - findMany: { - data: null as - | Prisma.PotensiDesaGetPayload<{ - include: { - image: true; - } - }>[] - | null, - async load() { - const res = await ApiFetch.api.desa.potensi["find-many"].get(); - if (res.status === 200) { - potensiDesaState.findMany.data = res.data?.data ?? []; - } - } - }, - findUnique: { - data: null as - | Prisma.PotensiDesaGetPayload<{ - include: { - image: true; - } - }> | null, - async load(id: string) { - try { - const res = await fetch(`/api/desa/potensi/${id}`); - if (res.ok) { - const data = await res.json(); - potensiDesaState.findUnique.data = data.data ?? null; - } else { - console.error('Failed to fetch potensi:', res.statusText); - potensiDesaState.findUnique.data = null; - } - } catch (error) { - console.error('Error fetching potensi:', error); - potensiDesaState.findUnique.data = null; - } - }, - }, - delete: { - loading: false, - async byId(id: string) { - if (!id) return toast.warn("ID tidak valid"); - - try { - potensiDesaState.delete.loading = true; - - const response = await fetch(`/api/desa/potensi/del/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - }); - - const result = await response.json(); - - if (response.ok && result?.success) { - toast.success(result.message || "Potensi berhasil dihapus"); - await potensiDesaState.findMany.load(); // refresh list - } else { - toast.error(result?.message || "Gagal menghapus potensi"); - } - } catch (error) { - console.error("Gagal delete:", error); - toast.error("Terjadi kesalahan saat menghapus potensi"); - } finally { - potensiDesaState.delete.loading = false; - } - }, - }, - edit: { - id: "", - form: { ...defaultForm }, - loading: false, + potensiDesa, + kategoriPotensi, +}); - async load(id: string) { - if (!id) { - toast.warn("ID tidak valid"); - return null; - } - - try { - const response = await fetch(`/api/desa/potensi/${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 = { - name: data.name, - deskripsi: data.deskripsi, - kategori: data.kategori, - imageId: data.imageId || "", - content: data.content, - }; - return data; // Return the loaded data - } else { - throw new Error(result?.message || "Gagal memuat data"); - } - } catch (error) { - console.error("Error loading potensi:", error); - toast.error(error instanceof Error ? error.message : "Gagal memuat data"); - return null; - } - }, - - async update() { - const cek = templateForm.safeParse(potensiDesaState.edit.form); - if (!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - toast.error(err); - return false; - } - - try { - potensiDesaState.edit.loading = true; - - const response = await fetch(`/api/desa/potensi/${this.id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name: this.form.name, - deskripsi: this.form.deskripsi, - kategori: this.form.kategori, - imageId: this.form.imageId, - content: this.form.content, - }), - }); - - 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("Berhasil update potensi"); - await potensiDesaState.findMany.load(); // refresh list - return true; - } else { - throw new Error(result.message || "Gagal update potensi"); - } - } catch (error) { - console.error("Error updating potensi:", error); - toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update potensi"); - return false; - } finally { - potensiDesaState.edit.loading = false; - } - }, - reset() { - potensiDesaState.edit.id = ""; - potensiDesaState.edit.form = { ...defaultForm }; - } - } -}) - -export default potensiDesaState - - \ No newline at end of file +export default potensiDesaState; 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 c995523a..bee8a092 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,10 +1,10 @@ +/* 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 { useShallowEffect } from '@mantine/hooks'; +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 { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; import JudulList from '../../../_com/judulList'; @@ -30,24 +30,68 @@ function ListPelayananTelunjukSakti({ search }: { search: string }) { const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa) const router = useRouter() - useShallowEffect(() => { - telunjukSaktiState.findMany.load() + const { + data, + page, + totalPages, + loading, + load, + } = telunjukSaktiState.findMany; + + useEffect(() => { + load(page, 10) }, []) - const filteredData = (telunjukSaktiState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.deskripsi.toLowerCase().includes(keyword) - ); - }); + 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 (!telunjukSaktiState.findMany.data) { + if (loading || !data) { return ( - + - ) + ); + } + + if (data.length === 0) { + return ( + + + + + + + Nama + Link + Detail + + + + + + + Tidak ada data + + + + +
+
+
+ ); } return ( @@ -75,9 +119,9 @@ function ListPelayananTelunjukSakti({ search }: { search: string }) { - - - + + + @@ -92,6 +136,18 @@ function ListPelayananTelunjukSakti({ search }: { search: string }) { +
+ { + load(newPage, 10); + window.scrollTo(0, 0); + }} + total={totalPages} + mt="md" + mb="md" + /> +
); } diff --git a/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx new file mode 100644 index 00000000..1b3d5978 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx @@ -0,0 +1,63 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import colors from '@/con/colors'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { usePathname, useRouter } from 'next/navigation'; +import React, { useEffect, useState } from 'react'; + +function LayoutTabsPotensi({ children }: { children: React.ReactNode }) { + const router = useRouter() + const pathname = usePathname() + const tabs = [ + { + label: "List Potensi", + value: "list_potensi", + href: "/admin/desa/potensi/list-potensi" + }, + { + label: "Kategori Potensi", + value: "kategori_potensi", + href: "/admin/desa/potensi/kategori-potensi" + }, + + ]; + const curentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const handleTabChange = (value: string | null) => { + const tab = tabs.find(t => t.value === value) + if (tab) { + router.push(tab.href) + } + setActiveTab(value) + } + + useEffect(() => { + const match = tabs.find(tab => tab.href === pathname) + if (match) { + setActiveTab(match.value) + } + }, [pathname]) + + return ( + + Potensi + + + {tabs.map((e, i) => ( + {e.label} + ))} + + {tabs.map((e, i) => ( + + {/* Konten dummy, bisa diganti tergantung routing */} + <> + + ))} + + {children} + + ); +} + +export default LayoutTabsPotensi; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/potensi/create/page.tsx b/src/app/admin/(dashboard)/desa/potensi/create/page.tsx deleted file mode 100644 index 4dc249d4..00000000 --- a/src/app/admin/(dashboard)/desa/potensi/create/page.tsx +++ /dev/null @@ -1,128 +0,0 @@ -'use client'; - -import colors from '@/con/colors'; -import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; -import { useState } from 'react'; -import { toast } from 'react-toastify'; -import { useProxy } from 'valtio/utils'; -import potensiDesaState from '../../../_state/desa/potensi'; -import { useRouter } from 'next/navigation'; -import CreateEditor from '../../../_com/createEditor'; - - -function CreatePotensi() { - const potensiState = useProxy(potensiDesaState); - const [previewImage, setPreviewImage] = useState(null); - const [file, setFile] = useState(null); - const router = useRouter(); - - 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'); - } - - potensiState.create.form.imageId = uploaded.id; - - await potensiState.create.create(); - - resetForm(); - router.push('/admin/desa/potensi'); - }; - - const resetForm = () => { - potensiState.create.form = { - name: '', - deskripsi: '', - kategori: '', - imageId: '', - content: '', - }; - - setPreviewImage(null); - setFile(null); - }; - - return ( - - - - - - - - Create Potensi - - (potensiState.create.form.name = val.target.value)} - label={Judul} - placeholder="masukkan judul" - /> - - (potensiState.create.form.deskripsi = val.target.value)} - label={Deskripsi} - placeholder="masukkan deskripsi" - /> - - (potensiState.create.form.kategori = val.target.value)} - label={Kategori} - placeholder="masukkan kategori" - /> - - Upload Gambar} - value={file} - onChange={async (e) => { - if (!e) return; - setFile(e); - const base64 = await e.arrayBuffer().then((buf) => - 'data:image/png;base64,' + Buffer.from(buf).toString('base64') - ); - setPreviewImage(base64); - }} - /> - - {previewImage ? ( - - ) : ( -
- -
- )} - - - Konten - { - potensiState.create.form.content = htmlContent; - }} - /> - - - -
-
-
- ); -} - -export default CreatePotensi; 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 new file mode 100644 index 00000000..612a577a --- /dev/null +++ b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/[id]/page.tsx @@ -0,0 +1,80 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'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 { 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 EditKategoriPotensi() { + const editState = useProxy(potensiDesaState.kategoriPotensi) + const router = useRouter(); + const params = useParams(); + const [formData, setFormData] = useState({ + 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 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 + setFormData({ ...formData, nama: e.target.value })} + label={Nama Kategori Potensi} + placeholder="masukkan nama kategori potensi" + /> + + + + + + ); +} + +export default EditKategoriPotensi; 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 new file mode 100644 index 00000000..9c65118a --- /dev/null +++ b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/create/page.tsx @@ -0,0 +1,55 @@ +'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 { IconArrowBack } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; + + + +function CreateKategoriPotensi() { + const createState = useProxy(potensiDesaState.kategoriPotensi) + const router = useRouter(); + + const resetForm = () => { + createState.create.form = { + nama: "", + }; + }; + + const handleSubmit = async () => { + await createState.create.create(); + resetForm(); + router.push("/admin/desa/potensi/kategori-potensi") + }; + + return ( + + + + + + + + Create Kategori Potensi + Nama Kategori Potensi} + placeholder='Masukkan nama kategori Potensi' + value={createState.create.form.nama} + onChange={(val) => { + createState.create.form.nama = val.target.value; + }} + /> + + + + + + + ); +} + +export default CreateKategoriPotensi; diff --git a/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx new file mode 100644 index 00000000..db465690 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx @@ -0,0 +1,128 @@ +/* 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 { 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'; + + + +function KategoriPotensi() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListKategoriPotensi({ search }: { search: string }) { + const listDataState = useProxy(potensiDesaState.kategoriPotensi) + const router = useRouter(); + const [modalHapus, setModalHapus] = useState(false) + const [selectedId, setSelectedId] = useState(null) + + useEffect(() => { + listDataState.findMany.load() + }, []) + + const handleDelete = () => { + if (selectedId) { + listDataState.delete.delete(selectedId) + setModalHapus(false) + setSelectedId(null) + + listDataState.findMany.load() + } + } + + const filteredData = (listDataState.findMany.data || []).filter(item => { + const keyword = search.toLowerCase(); + return ( + item.nama.toLowerCase().includes(keyword) + ); + }); + + if (!listDataState.findMany.data) { + return ( + + + + ) + } + return ( + + + + + + + + + No + Nama + Edit + Hapus + + + + {filteredData.map((item, index) => ( + + + + {index + 1} + + + {item.nama} + + + + + + + + ))} + +
+
+
+
+ + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleDelete} + text='Apakah anda yakin ingin menghapus kategori Potensi ini?' + /> +
+ ) +} + +export default KategoriPotensi; diff --git a/src/app/admin/(dashboard)/desa/potensi/layout.tsx b/src/app/admin/(dashboard)/desa/potensi/layout.tsx new file mode 100644 index 00000000..b677970c --- /dev/null +++ b/src/app/admin/(dashboard)/desa/potensi/layout.tsx @@ -0,0 +1,14 @@ +'use client' +import React from 'react'; +import LayoutTabsPotensi from './_lib/layoutTabs'; + +function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} + +export default Layout; + diff --git a/src/app/admin/(dashboard)/desa/potensi/edit/[id]/page.tsx b/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx similarity index 52% rename from src/app/admin/(dashboard)/desa/potensi/edit/[id]/page.tsx rename to src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx index 4dff08b3..38f0d20f 100644 --- a/src/app/admin/(dashboard)/desa/potensi/edit/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/edit/page.tsx @@ -1,21 +1,22 @@ +/* eslint-disable react-hooks/exhaustive-deps */ 'use client' 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, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from "@mantine/core"; -import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react"; -import { useParams } from "next/navigation"; -import { useRouter } from "next/navigation"; -import { useState, useEffect } from "react"; +import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from "@mantine/core"; +import { Dropzone } from "@mantine/dropzone"; +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 EditPotensi() { - const potensiState = useProxy(potensiDesaState) + const potensiState = useProxy(potensiDesaState.potensiDesa) const router = useRouter() const params = useParams() @@ -24,23 +25,24 @@ function EditPotensi() { const [formData, setFormData] = useState({ name: '', deskripsi: '', - kategori: '', + kategoriId: '', content: '', imageId: '' }); useEffect(() => { + potensiDesaState.kategoriPotensi.findMany.load() const loadPotensi = async () => { const id = params?.id as string; if (!id) return; try { - const data = await potensiDesaState.edit.load(id); // ambil data dari API + const data = await potensiState.edit.load(id); // ambil data dari API if (data) { setFormData({ name: data.name || '', deskripsi: data.deskripsi || '', - kategori: data.kategori || '', + kategoriId: data.kategoriId || '', content: data.content || '', imageId: data.imageId || '', }); @@ -65,7 +67,7 @@ function EditPotensi() { ...potensiState.edit.form, name: formData.name, deskripsi: formData.deskripsi, - kategori: formData.kategori, + kategoriId: formData.kategoriId, content: formData.content, }; @@ -119,40 +121,82 @@ function EditPotensi() { label={Deskripsi} placeholder="masukkan deskripsi" /> - { - const val = e.target.value; - setFormData((prev) => ({ ...prev, kategori: val })); - potensiState.edit.form.kategori = val; - }} - label={Kategori} - placeholder="masukkan kategori" + Kategori} + placeholder='Pilih kategori' + value={potensiState.create.form.kategoriId || ""} + onChange={(val) => { + potensiState.create.form.kategoriId = val ?? ""; + }} + data={potensiDesaState.kategoriPotensi.findMany.data?.map((item) => ({ + value: item.id, + label: item.nama, + }))} + /> + + + 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 + + )} + +
+
+ + + Konten + { + potensiState.create.form.content = htmlContent; + }} + /> + + + + + + + ); +} + +export default CreatePotensi; diff --git a/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx b/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx new file mode 100644 index 00000000..b335c40e --- /dev/null +++ b/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx @@ -0,0 +1,158 @@ +/* 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 { 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)} + /> + + + ); +} + +function ListPotensi({ search }: { search: string }) { + const potensiState = useProxy(potensiDesaState) + const router = useRouter() + + const { + data, + page, + totalPages, + loading, + load, + } = potensiState.potensiDesa.findMany; + + useEffect(() => { + potensiState.kategoriPotensi.findMany.load() + load(page, 10) + }, []) + + 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) + ); + }); + + // 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) => ( + + + + {item.name} + + {item.kategori?.nama} + + + + + + + + + + ))} + +
+
+
+
+
+ { + load(newPage, 10); + window.scrollTo(0, 0); + }} + total={totalPages} + mt="md" + mb="md" + /> +
+
+ ) +} + +export default Potensi; diff --git a/src/app/admin/(dashboard)/desa/potensi/page.tsx b/src/app/admin/(dashboard)/desa/potensi/page.tsx deleted file mode 100644 index 8c74ce53..00000000 --- a/src/app/admin/(dashboard)/desa/potensi/page.tsx +++ /dev/null @@ -1,100 +0,0 @@ - -'use client' -import colors from '@/con/colors'; -import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { useShallowEffect } from '@mantine/hooks'; -import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import { useProxy } from 'valtio/utils'; -import HeaderSearch from '../../_com/header'; -import JudulList from '../../_com/judulList'; -import potensiDesaState from '../../_state/desa/potensi'; -import { useState } from 'react'; - - -function Potensi() { - const [search, setSearch] = useState(""); - return ( - - } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - /> - - - ); -} - -function ListPotensi({ search }: { search: string }) { - const potensiState = useProxy(potensiDesaState) - const router = useRouter() - - useShallowEffect(() => { - potensiState.findMany.load() - }, []) - -const filteredData = (potensiState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.kategori.toLowerCase().includes(keyword) - ); -}); - - if (!potensiState.findMany.data) { - return ( - - - - ) - } - - return ( - - - - - - - - - Judul - Kategori - Image - Detail - - - - {filteredData.map((item) => ( - - - - {item.name} - - {item.kategori} - - - - - - - - ))} - -
-
-
-
-
- ) -} - -export default Potensi; diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index 816c22c2..a8ba8dce 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -98,7 +98,7 @@ export const navBar = [ { id: "Desa_2", name: "Potensi", - path: "/admin/desa/potensi" + path: "/admin/desa/potensi/list-potensi" }, { id: "Desa_3", diff --git a/src/app/api/[[...slugs]]/_lib/desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/index.ts index 1955ed81..1c039fc2 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/index.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/index.ts @@ -7,6 +7,7 @@ import GalleryFoto from "./gallery/foto"; import GalleryVideo from "./gallery/video"; import LayananDesa from "./layanan"; import Penghargaan from "./penghargaan"; +import KategoriPotensi from "./potensi/kategori-potensi"; const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] }) @@ -18,5 +19,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] }) .use(GalleryVideo) .use(LayananDesa) .use(Penghargaan) + .use(KategoriPotensi) export default Desa; 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 7881f161..29d84dd9 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,21 +1,43 @@ import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function pelayananTelunjukSaktiDesaFindMany() { +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 = await prisma.pelayananTelunjukSaktiDesa.findMany({ - where: { isActive: true }, - }); - - return { - success: true, - message: "Success fetch pelayanan telunjuk sakti desa", - data, - }; + 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 error:", e); - return { - success: false, - message: "Failed fetch pelayanan telunjuk sakti desa", - }; + 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 diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/create.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/create.ts index 54754aa4..cfbdb4e8 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/create.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/create.ts @@ -6,7 +6,7 @@ type FormCreate = Prisma.PotensiDesaGetPayload<{ select: { name: true; deskripsi: true; - kategori: true; + kategoriId: true; imageId: true; content: true; } @@ -18,7 +18,7 @@ export default async function potensiDesaCreate(context: Context) { data: { name: body.name, deskripsi: body.deskripsi, - kategori: body.kategori, + kategoriId: body.kategoriId, imageId: body.imageId, content: body.content, }, diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/del.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/del.ts index fd6e9956..d3e17c64 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/del.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/del.ts @@ -17,6 +17,7 @@ const potensiDesaDelete = async (context: Context) => { where: { id }, include: { image: true, + kategori: true }, }); 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 bf78eacb..1f449980 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/find-many.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/find-many.ts @@ -1,26 +1,47 @@ import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -async function potensiDesaFindMany() { +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; + try { - const data = await prisma.potensiDesa.findMany({ - where: { isActive: true }, - include: { - image: true, - }, - }); - - return { - success: true, - message: "Success fetch potensi desa", - data, - }; + const [data, total] = await Promise.all([ + prisma.potensiDesa.findMany({ + where: { isActive: true }, + skip, + take: limit, + include: { + image: true, + kategori: true + }, + orderBy: { createdAt: 'desc' }, + }), + prisma.potensiDesa.count({ + where: { isActive: true } + }) + ]); + + const totalPages = Math.ceil(total / limit); + + return { + success: true, + message: "Success fetch potensi desa with pagination", + data, + page, + totalPages, + total, + }; } catch (e) { - console.error("Find many error:", e); - return { - success: false, - message: "Failed fetch potensi desa", - }; + console.error("Find many paginated error:", e); + return { + success: false, + message: "Failed fetch potensi desa with pagination", + data: [], + page: 1, + totalPages: 1, + total: 0, + }; } -} - -export default potensiDesaFindMany \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/find-unique.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/find-unique.ts index d5944f87..a17eda6a 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/find-unique.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/find-unique.ts @@ -25,6 +25,7 @@ export default async function findUnique( where: { id }, include: { image: true, + kategori: true }, }); diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/index.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/index.ts index 2cf0b658..ac9de421 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/index.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/index.ts @@ -14,7 +14,7 @@ const PotensiDesa = new Elysia({ body: t.Object({ name: t.String(), deskripsi: t.String(), - kategori: t.String(), + kategoriId: t.String(), imageId: t.String(), content: t.String(), }), @@ -35,7 +35,7 @@ const PotensiDesa = new Elysia({ body: t.Object({ name: t.String(), deskripsi: t.String(), - kategori: t.String(), + kategoriId: t.String(), imageId: t.String(), content: t.String(), }), diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/create.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/create.ts new file mode 100644 index 00000000..ec2a5631 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/create.ts @@ -0,0 +1,27 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreate = { + nama: string; +} + +export default async function kategoriPotensiCreate(context: Context) { + const body = (await context.body) as FormCreate; + + try { + const result = await prisma.kategoriPotensi.create({ + data: { + nama: body.nama + }, + }); + return { + success: true, + message: "Berhasil membuat kategori potensi", + data: result, + }; + } catch (error) { + console.error("Error creating kategori potensi:", error); + throw new Error("Gagal membuat kategori potensi: " + (error as Error).message); + } +} + \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/del.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/del.ts new file mode 100644 index 00000000..804fff7f --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/del.ts @@ -0,0 +1,16 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kategoriPotensiDelete(context: Context) { + const id = context.params.id as string; + + await prisma.kategoriPotensi.delete({ + where: { id }, + }); + + return { + status: 200, + success: true, + message: "Success delete kategori potensi", + }; +} \ No newline at end of file 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 new file mode 100644 index 00000000..6e3e8588 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findMany.ts @@ -0,0 +1,11 @@ +import prisma from "@/lib/prisma"; + +export default async function kategoriPotensiFindMany() { + const data = await prisma.kategoriPotensi.findMany(); + + return { + success: true, + message: "Success get all kategori potensi", + data, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findUnique.ts new file mode 100644 index 00000000..564720f6 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/findUnique.ts @@ -0,0 +1,46 @@ +import prisma from "@/lib/prisma"; + +export default async function kategoriBukuFindUnique(request: Request) { + const url = new URL(request.url); + const pathSegments = url.pathname.split('/'); + const id = pathSegments[pathSegments.length - 1]; + + if (!id) { + return { + success: false, + message: "ID is required", + } + } + + try { + if (typeof id !== 'string') { + return { + success: false, + message: "ID is required", + } + } + + const data = await prisma.kategoriPotensi.findUnique({ + where: { id }, + }); + + if (!data) { + return { + success: false, + message: "Data not found", + } + } + + return { + success: true, + message: "Success get kategori potensi", + data, + } + } catch (error) { + console.error("Find by ID error:", error); + return { + success: false, + message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'), + } + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/index.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/index.ts new file mode 100644 index 00000000..3ee6466b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/index.ts @@ -0,0 +1,33 @@ +import Elysia, { t } from "elysia"; +import kategoriPotensiCreate from "./create"; +import kategoriPotensiDelete from "./del"; +import kategoriPotensiFindMany from "./findMany"; +import kategoriPotensiFindUnique from "./findUnique"; +import kategoriPotensiUpdate from "./updt"; + +const KategoriPotensi = new Elysia({ + prefix: "/kategoripotensi", + tags: ["Desa / Potensi"], +}) + + .post("/create", kategoriPotensiCreate, { + body: t.Object({ + nama: t.String(), + }), + }) + + .get("/findMany", kategoriPotensiFindMany) + .get("/:id", async (context) => { + const response = await kategoriPotensiFindUnique( + new Request(context.request) + ); + return response; + }) + .put("/:id", kategoriPotensiUpdate, { + body: t.Object({ + nama: t.String(), + }), + }) + .delete("/del/:id", kategoriPotensiDelete); + +export default KategoriPotensi; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/updt.ts new file mode 100644 index 00000000..3018f756 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/updt.ts @@ -0,0 +1,28 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormUpdate = { + nama: string; +} + +export default async function kategoriPotensiUpdate(context: Context) { + const body = (await context.body) as FormUpdate; + const id = context.params.id as string; + + try { + const result = await prisma.kategoriPotensi.update({ + where: { id }, + data: { + nama: body.nama, + }, + }); + return { + success: true, + message: "Berhasil mengupdate kategori potensi", + data: result, + }; + } catch (error) { + console.error("Error updating kategori potensi:", error); + throw new Error("Gagal mengupdate kategori potensi: " + (error as Error).message); + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/potensi/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/potensi/updt.ts index 5edc9506..dd33ab8d 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/updt.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/updt.ts @@ -9,7 +9,7 @@ type FormUpdate = Prisma.PotensiDesaGetPayload<{ id: true; name: true; deskripsi: true; - kategori: true; + kategoriId: true; imageId: true; content: true; }; @@ -23,7 +23,7 @@ export default async function potensiDesaUpdate(context: Context) { const { name, deskripsi, - kategori, + kategoriId, imageId, content, } = body; @@ -39,6 +39,7 @@ export default async function potensiDesaUpdate(context: Context) { where: { id }, include: { image: true, + kategori: true } }); @@ -69,7 +70,7 @@ export default async function potensiDesaUpdate(context: Context) { data: { name, deskripsi, - kategori, + kategoriId, imageId, content, }, diff --git a/src/app/darmasaba/(pages)/desa/layanan/_com/pelayananTelunjukSaktiDesa.tsx b/src/app/darmasaba/(pages)/desa/layanan/_com/pelayananTelunjukSaktiDesa.tsx index 23ac113f..3808f720 100644 --- a/src/app/darmasaba/(pages)/desa/layanan/_com/pelayananTelunjukSaktiDesa.tsx +++ b/src/app/darmasaba/(pages)/desa/layanan/_com/pelayananTelunjukSaktiDesa.tsx @@ -28,7 +28,7 @@ function PelayananTelunjukSaktiDesa() { loadData() }, []) - const data = state.pelayananTelunjukSaktiDesa.findMany.data as Array<{ + const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{ name: string; id: string; deskripsi: string; diff --git a/src/app/darmasaba/(pages)/desa/potensi/[id]/page.tsx b/src/app/darmasaba/(pages)/desa/potensi/[id]/page.tsx new file mode 100644 index 00000000..a976980a --- /dev/null +++ b/src/app/darmasaba/(pages)/desa/potensi/[id]/page.tsx @@ -0,0 +1,77 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi'; +import colors from '@/con/colors'; +import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core'; +import { useParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import BackButton from '../../layanan/_com/BackButto'; + + +function Page() { + const params = useParams<{ id: string }>(); + const id = Array.isArray(params.id) ? params.id[0] : params.id; + const state = useProxy(potensiDesaState.potensiDesa) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const loadData = async () => { + if (!id) return; + try { + setLoading(true); + await state.findUnique.load(id); + } catch (error) { + console.error('Error loading data:', error); + } finally { + setLoading(false); + } + } + loadData() + }, [id]) + + if (loading) { + return ( +
+ +
+ ); + } + + if (!state.findUnique.data) { + return ( +
+ Data tidak ditemukan +
+ ); + } + + + return ( + + + + + + {state.findUnique.data?.name} + + + Informasi dan Pelayanan Administrasi Digital + + + + + + + {state.findUnique.data?.deskripsi || ''} + + + + ); +} + +export default Page; diff --git a/src/app/darmasaba/_com/main-page/landing-page/index.tsx b/src/app/darmasaba/_com/main-page/landing-page/index.tsx index 3d7dea33..c5a09ea5 100644 --- a/src/app/darmasaba/_com/main-page/landing-page/index.tsx +++ b/src/app/darmasaba/_com/main-page/landing-page/index.tsx @@ -176,7 +176,7 @@ function LandingPage() { @@ -206,7 +206,7 @@ function LandingPage() { ApiFetch.api.layanan.get().then(({ data }) => data?.data), - { - fallbackData: [], - } - ); - - const router = useTransitionRouter() return ( @@ -125,7 +114,7 @@ function Slider() { - @@ -135,19 +124,25 @@ function Slider() { )); return ( - - {slides} - + + {loading ? ( + + ) : ( + + {slides} + + )} + ); } diff --git a/src/app/darmasaba/_com/main-page/potensi/index.tsx b/src/app/darmasaba/_com/main-page/potensi/index.tsx index 19774cea..fda522d1 100644 --- a/src/app/darmasaba/_com/main-page/potensi/index.tsx +++ b/src/app/darmasaba/_com/main-page/potensi/index.tsx @@ -1,8 +1,8 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-unused-vars */ "use client"; +import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi"; import colors from "@/con/colors"; -import images from "@/con/images"; -import ApiFetch from "@/lib/api-fetch"; import { BackgroundImage, Box, @@ -11,45 +11,13 @@ import { Group, SimpleGrid, Stack, - Text, - Title, + Text } from "@mantine/core"; import _ from "lodash"; import { motion } from "motion/react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import useSWR from "swr"; - -const datamap = [ - { - id: 1, - images: "/api/img/tps.png", - name: "TPS3R Pudak Mesari", - deskripsi: "TPS 3R Pudak Mesari Darmasaba layak mendapat penghargaan demikian apresiasi dari Delterra Sosial Indonesia nie Semeton Darmasaba!, Hal tersebut dikarenakan walaupun baru berdiri namun TPS 3R kebanggaan Desa Darmasaba tersebut sudah berjalan dengan sangat baik. ", - link: "/darmasaba/desa/potensi/tps3r-pudak-mesari" - }, - { - id: 2, - images: "/api/img/ack.png", - name: "Bumdes Pudak Mesari", - deskripsi: "Bumdes Pudak Mesari sangat membantu warga desa Darmasaba dalam mengelola dan membangun sebuah desa yang lebih baik lagi", - link: "/darmasaba/desa/potensi/bumdes-pudak-mesari" - }, - { - id: 3, - images: "/api/img/taman-beji.jpg", - name: "Taman Beji Cengana", - deskripsi: "Tirta Klebutan di Pura Taman Beji Cengana di Desa Adat Darmasaba, Badung, selain dipercaya nunas Taksu serta pembersihan diri. Tersemat juga asal usul cerita ditemukannya Tirta Klebutan yang tepat berada di pinggir Tukad Cengana tersebut.", - link: "/darmasaba/desa/potensi/taman-beji-cengana" - }, - { - id: 4, - images: "/api/img/waterpark.png", - name: "Gumuh Sari Water Park", - deskripsi: "Gumuh Sari Rekreasi atau waterpark, tempat wisata yang asyik dan seru untuk kamu sekeluarga! Tempat liburan di Bali memang seakan nggak ada habisnya. Selalu ada aja destinasi wisata seru yang bisa jadi wishlist. Ada banyak banget tempat wisata yang kamu kunjungi di Bali, mulai dari wisata alam, wisata modern, sampai wisata air.", - link: "/darmasaba/desa/potensi/gumuh-sari-water-park" - }, -] +import { useTransitionRouter } from "next-view-transitions"; +import { useEffect, useState } from "react"; +import { useProxy } from "valtio/utils"; @@ -59,11 +27,25 @@ const textHeading = { }; function Potensi() { - const router = useRouter() - const { data, isLoading } = useSWR("/", (url) => - ApiFetch.api.potensi.get().then(({ data }) => data?.data) - ); - if (isLoading) return loading ...; + const router = useTransitionRouter() + const [loading, setLoading] = useState(false) + const state = useProxy(potensiDesaState.potensiDesa) + + useEffect(()=> { + const loadData = async () => { + try { + setLoading(true); + await state.findMany.load() + } catch (error) { + console.error('Error loading data:', error); + } finally { + setLoading(false); + } + } + loadData() + }, []) + + const data = (state.findMany.data || []).slice(0, 4); return ( router.push(datamap[k].link)} + onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)} > - {datamap[k].name} + {v.name} - {datamap[k].deskripsi} + {v.deskripsi} @@ -134,7 +116,7 @@ function Potensi() { -