From 22de1aa1f3eaaddbd42827defc69837b5f936f32 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 25 Feb 2026 15:41:01 +0800 Subject: [PATCH] fix-admin-menu-desa-potensi --- prisma/schema.prisma | 12 ++--- .../desa/potensi/list-potensi/[id]/page.tsx | 20 ++++++- .../desa/potensi/list-potensi/page.tsx | 15 +++++- .../_lib/desa/potensi/find-unique.ts | 11 ++-- .../_lib/desa/potensi/kategori-potensi/del.ts | 53 +++++++++++++++---- 5 files changed, 89 insertions(+), 22 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 041fac02..5a9168cd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -633,25 +633,25 @@ model KategoriBerita { // ========================================= POTENSI DESA ========================================= // model PotensiDesa { id String @id @default(cuid()) - name String - deskripsi String + name String @unique @db.VarChar(255) + deskripsi String @db.Text kategori KategoriPotensi? @relation(fields: [kategoriId], references: [id]) - kategoriId String? + kategoriId String @db.VarChar(36) image FileStorage? @relation(fields: [imageId], references: [id]) imageId String? content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) + deletedAt DateTime? isActive Boolean @default(true) } model KategoriPotensi { id String @id @default(cuid()) - nama String + nama String @unique @db.VarChar(100) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) + deletedAt DateTime? isActive Boolean @default(true) PotensiDesa PotensiDesa[] } diff --git a/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/page.tsx b/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/page.tsx index 3ae6dac8..35fe407a 100644 --- a/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/list-potensi/[id]/page.tsx @@ -8,6 +8,7 @@ import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; +import DOMPurify from 'dompurify'; export default function DetailPotensi() { const router = useRouter(); @@ -77,7 +78,17 @@ export default function DetailPotensi() { Deskripsi - + @@ -102,7 +113,12 @@ export default function DetailPotensi() { diff --git a/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx b/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx index f2cb3d15..5ed853da 100644 --- a/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx +++ b/src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx @@ -27,6 +27,7 @@ import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; import potensiDesaState from '../../../_state/desa/potensi'; import { useDebouncedValue } from '@mantine/hooks'; +import DOMPurify from 'dompurify'; function Potensi() { const [search, setSearch] = useState(""); @@ -137,7 +138,12 @@ function ListPotensi({ search }: { search: string }) { fz="sm" lh={1.5} lineClamp={2} - dangerouslySetInnerHTML={{ __html: item.deskripsi }} + dangerouslySetInnerHTML={{ + __html: DOMPurify.sanitize(item.deskripsi, { + ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'ul', 'ol', 'li'], + ALLOWED_ATTR: [] + }) + }} style={{ wordBreak: 'break-word' }} /> @@ -199,7 +205,12 @@ function ListPotensi({ search }: { search: string }) { 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 a17eda6a..dc431265 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/find-unique.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/find-unique.ts @@ -21,8 +21,13 @@ export default async function findUnique( }, { status: 400 }); } - const data = await prisma.potensiDesa.findUnique({ - where: { id }, + // ✅ Filter by isActive and deletedAt + const data = await prisma.potensiDesa.findFirst({ + where: { + id, + isActive: true, + deletedAt: null, + }, include: { image: true, kategori: true @@ -48,5 +53,5 @@ export default async function findUnique( message: "Gagal mengambil potensi desa: " + (error instanceof Error ? error.message : 'Unknown error'), }, { status: 500 }); } - + } \ 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 index d91cef98..356d73f0 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/del.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/potensi/kategori-potensi/del.ts @@ -2,15 +2,50 @@ import prisma from "@/lib/prisma"; import { Context } from "elysia"; export default async function kategoriPotensiDelete(context: Context) { - const id = context.params.id as string; + try { + const id = context.params?.id as string; - await prisma.kategoriPotensi.delete({ - where: { id }, - }); + if (!id) { + return Response.json({ + success: false, + message: "ID tidak boleh kosong", + }, { status: 400 }); + } - return { - status: 200, - success: true, - message: "Sukses Menghapus kategori potensi", - }; + // ✅ Cek apakah kategori masih digunakan oleh potensi desa + const existingPotensi = await prisma.potensiDesa.findFirst({ + where: { + kategoriId: id, + isActive: true, + deletedAt: null, + }, + }); + + if (existingPotensi) { + return Response.json({ + success: false, + message: "Kategori masih digunakan oleh potensi desa. Tidak dapat dihapus.", + }, { status: 400 }); + } + + // Soft delete + await prisma.kategoriPotensi.update({ + where: { id }, + data: { + deletedAt: new Date(), + isActive: false, + }, + }); + + return { + success: true, + message: "Kategori potensi berhasil dihapus", + }; + } catch (error) { + console.error("Delete kategori error:", error); + return Response.json({ + success: false, + message: "Gagal menghapus kategori: " + (error instanceof Error ? error.message : 'Unknown error'), + }, { status: 500 }); + } } \ No newline at end of file