diff --git a/src/app/admin/(dashboard)/_state/desa/berita.ts b/src/app/admin/(dashboard)/_state/desa/berita.ts index 4782cde3..64245619 100644 --- a/src/app/admin/(dashboard)/_state/desa/berita.ts +++ b/src/app/admin/(dashboard)/_state/desa/berita.ts @@ -1,10 +1,11 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ + import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; import { proxy } from "valtio"; import { z } from "zod"; +// 1. Schema validasi dengan Zod const templateForm = z.object({ judul: z.string().min(3, "Judul minimal 3 karakter"), deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), @@ -13,6 +14,16 @@ const templateForm = z.object({ imageId: z.string().nonempty(), }); +// 2. Default value form berita (hindari uncontrolled input) +const defaultForm = { + judul: "", + deskripsi: "", + imageId: "", + content: "", + kategoriBeritaId: "", +}; + +// 3. Kategori proxy const category = proxy({ findMany: { data: null as @@ -27,19 +38,10 @@ const category = proxy({ }, }); -type BeritaForm = Prisma.BeritaGetPayload<{ - select: { - judul: true; - deskripsi: true; - imageId: true; - content: true; - kategoriBeritaId: true; - }; -}>; - +// 4. Berita proxy const berita = proxy({ create: { - form: {} as BeritaForm, + form: { ...defaultForm }, // ✅ ini kunci fix-nya loading: false, async create() { const cek = templateForm.safeParse(berita.create.form); @@ -49,6 +51,7 @@ const berita = proxy({ .join("\n")}] required`; return toast.error(err); } + try { berita.create.loading = true; const res = await ApiFetch.api.desa.berita["create"].post( @@ -56,7 +59,7 @@ const berita = proxy({ ); if (res.status === 200) { berita.findMany.load(); - return toast.success("succes create"); + return toast.success("success create"); } return toast.error("failed create"); @@ -66,28 +69,65 @@ const berita = proxy({ berita.create.loading = false; } }, + resetForm() { + berita.create.form = { ...defaultForm }; + }, }, + findMany: { data: null as - | Prisma.BeritaGetPayload<{ - include: { - image: true, - kategoriBerita: true - } - }>[] + | Prisma.BeritaGetPayload<{ + include: { + image: true; + kategoriBerita: true; + }; + }>[] | null, async load() { const res = await ApiFetch.api.desa.berita["find-many"].get(); if (res.status === 200) { - berita.findMany.data = (res.data?.data as any) ?? []; + berita.findMany.data = (res.data?.data ) ?? []; } }, }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + berita.delete.loading = true; + + const response = await fetch(`/api/desa/berita/delete/${id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Berita berhasil dihapus"); + await berita.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus berita"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus berita"); + } finally { + berita.delete.loading = false; + } + }, + }, + }); +// 5. State global const stateDashboardBerita = proxy({ category, berita, }); -export default stateDashboardBerita; \ No newline at end of file +export default stateDashboardBerita; diff --git a/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx b/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx index c912c9d7..4efd681f 100644 --- a/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx +++ b/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx @@ -34,6 +34,7 @@ import { useEffect } from 'react'; TextAlign.configure({ types: ['heading', 'paragraph'] }), ], content: '', + immediatelyRender: false }); useEffect(() => { diff --git a/src/app/admin/(dashboard)/desa/berita/page.tsx b/src/app/admin/(dashboard)/desa/berita/page.tsx index 51a3ab45..af5f0801 100644 --- a/src/app/admin/(dashboard)/desa/berita/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/page.tsx @@ -1,16 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' -import { Box, Button, Center, FileInput, Group, Image, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +import { ActionIcon, Box, Button, Center, FileInput, Flex, Image, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { Prisma } from '@prisma/client'; -import { IconImageInPicture } from '@tabler/icons-react'; +import { IconEdit, IconImageInPicture, IconX } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; import stateDashboardBerita from '../../_state/desa/berita'; import { BeritaEditor } from './_com/BeritaEditor'; -import colors from '@/con/colors'; -import { useState } from 'react'; -import { toast } from 'react-toastify'; -import ApiFetch from '@/lib/api-fetch'; function Page() { return ( @@ -175,32 +176,32 @@ function BeritaCreate() { return ( - + + { + beritaState.berita.create.form.judul = val.target.value; + }} + label={Judul} + placeholder="masukkan judul" + /> { beritaState.berita.create.form.kategoriBeritaId = val.id; }} /> - { - beritaState.berita.create.form.judul = val.target.value; - }} - label={"Judul"} - placeholder="masukkan judul" - /> { beritaState.berita.create.form.deskripsi = val.target.value; }} - label={"Deskripsi"} + label={Deskripsi} placeholder="masukkan deskripsi" /> Upload Gambar} value={file} onChange={async (e) => { if (!e) return; @@ -218,12 +219,13 @@ function BeritaCreate() { )} - - setEditorInstance(ed)} - /> - + + Konten + setEditorInstance(ed)} + /> + @@ -240,6 +242,10 @@ function BeritaList() { beritaState.berita.findMany.load() }, []) + + + const router = useRouter() + if (!beritaState.berita.findMany.data) return {Array.from({ length: 10 }).map((v, k) => )} @@ -249,28 +255,41 @@ function BeritaList() { List Berita - {beritaState.berita.findMany.data?.map((item) => ( - - - - Kategori - - {item.kategoriBerita?.name} - - Judul - - {item.judul} - - Deskripsi - - {item.deskripsi} - - Gambar - - gambar - - - ))} + {beritaState.berita.findMany.data?.map((item) => ( + + + + beritaState.berita.delete.byId(item.id)} + disabled={beritaState.berita.delete.loading} + color={colors['blue-button']} variant='transparent'> + + + { + router.push("/desa/berita/edit"); + }} color={colors['blue-button']} variant='transparent'> + + + + + Kategori + + {item.kategoriBerita?.name} + + Judul + + {item.judul} + + Deskripsi + + {item.deskripsi} + + Gambar + + gambar + + + ))} @@ -278,30 +297,71 @@ function BeritaList() { ) } -function SelectCategory({ onChange }: { +function SelectCategory({ + onChange, +}: { onChange: (value: Prisma.KategoriBeritaGetPayload<{ select: { - name: true, - id: true - } - }>) => void + name: true; + id: true; + }; + }>) => void; }) { - const beritaState = useProxy(stateDashboardBerita) - useShallowEffect(() => { - beritaState.category.findMany.load() - }, []) + const categoryState = useProxy(stateDashboardBerita.category); - if (!beritaState.category.findMany.data) return - return - Kategori} + placeholder="Pilih kategori" + data={categoryState.findMany.data.map((item) => ({ + label: item.name, + value: item.id, + }))} + onChange={(val) => { + const selected = categoryState.findMany.data?.find((item) => item.id === val); + if (selected) { + onChange(selected); + } + }} + searchable + nothingFoundMessage="Tidak ditemukan" + /> + ); } + +// function SelectCategory({ onChange }: { +// onChange: (value: Prisma.KategoriBeritaGetPayload<{ +// select: { +// name: true, +// id: true +// } +// }>) => void +// }) { +// const beritaState = useProxy(stateDashboardBerita) +// useShallowEffect(() => { +// beritaState.category.findMany.load() +// }, []) + +// if (!beritaState.category.findMany.data) return +// return +//