diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4c0d39cb..31430ab9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,6 +47,23 @@ model AppMenuChild { appMenuId String? } +// ========================================= 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 + Berita Berita[] + PotensiDesa PotensiDesa[] +} + //========================================= MENU PPID ========================================= // // ========================================= VISI MISI PPID ========================================= // model VisiMisiPPID { @@ -263,6 +280,21 @@ model KategoriBerita { isActive Boolean @default(true) } +// ========================================= 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) +} + // ========================================= PENGUMUMAN ========================================= // model Pengumuman { id String @id @default(cuid()) @@ -583,19 +615,3 @@ model DoctorSign { deletedAt DateTime @default(now()) isActive Boolean @default(true) } - -// === BARU - -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 - Berita Berita[] -} diff --git a/public/uploads/seeded-images/profile-ppid/perbekel.png b/public/perbekel.png similarity index 100% rename from public/uploads/seeded-images/profile-ppid/perbekel.png rename to public/perbekel.png diff --git a/public/uploads/seeded-images/profile-ppid/66f8e9e5-838e-4aaf-bbf2-9a31a6fc060e_perbekel.png b/public/uploads/seeded-images/profile-ppid/66f8e9e5-838e-4aaf-bbf2-9a31a6fc060e_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/66f8e9e5-838e-4aaf-bbf2-9a31a6fc060e_perbekel.png differ diff --git a/src/app/admin/(dashboard)/_com/createEditor.tsx b/src/app/admin/(dashboard)/_com/createEditor.tsx new file mode 100644 index 00000000..7878e59a --- /dev/null +++ b/src/app/admin/(dashboard)/_com/createEditor.tsx @@ -0,0 +1,85 @@ +// TestEditor.tsx +import { RichTextEditor, Link } from '@mantine/tiptap'; +import { useEditor } from '@tiptap/react'; +import Highlight from '@tiptap/extension-highlight'; +import StarterKit from '@tiptap/starter-kit'; +import Underline from '@tiptap/extension-underline'; +import TextAlign from '@tiptap/extension-text-align'; +import Superscript from '@tiptap/extension-superscript'; +import SubScript from '@tiptap/extension-subscript'; + +type CreateEditorProps = { + value: string; + onChange: (content: string) => void; +}; + +export default function CreateEditor({ value, onChange }: CreateEditorProps) { + const editor = useEditor({ + extensions: [ + StarterKit, + Underline, + Link, + Superscript, + SubScript, + Highlight, + TextAlign.configure({ types: ['heading', 'paragraph'] }), + ], + content: value, + onUpdate: () => { + if (editor) { + onChange(editor.getHTML()); + } + }, + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_com/editEditor.tsx b/src/app/admin/(dashboard)/_com/editEditor.tsx new file mode 100644 index 00000000..66317cc2 --- /dev/null +++ b/src/app/admin/(dashboard)/_com/editEditor.tsx @@ -0,0 +1,102 @@ +'use client' +import { RichTextEditor, Link } from '@mantine/tiptap'; +import { useEditor } from '@tiptap/react'; +import Highlight from '@tiptap/extension-highlight'; +import StarterKit from '@tiptap/starter-kit'; +import Underline from '@tiptap/extension-underline'; +import TextAlign from '@tiptap/extension-text-align'; +import Superscript from '@tiptap/extension-superscript'; +import SubScript from '@tiptap/extension-subscript'; +import { useEffect } from 'react'; + +type EditEditorProps = { + value: string; + onChange: (content: string) => void; +}; + +export default function EditEditor({ value, onChange }: EditEditorProps) { + const editor = useEditor({ + extensions: [ + StarterKit, + Underline, + Link, + Superscript, + SubScript, + Highlight, + TextAlign.configure({ types: ['heading', 'paragraph'] }), + ], + content: value, + // Hapus `immediatelyRender` dan `onMount` + }); + + // Sinkronisasi konten saat `value` berubah + useEffect(() => { + if (editor && value !== editor.getHTML()) { + editor.commands.setContent(value); + } + }, [value, editor]); + + // Sinkronisasi konten ke parent saat diubah + useEffect(() => { + if (!editor) return; + + const updateHandler = () => onChange(editor.getHTML()); + editor.on('update', updateHandler); + + return () => { + editor.off('update', updateHandler); + }; + }, [editor, onChange]); + + return ( + + + {/* Toolbar seperti sebelumnya */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_com/header.tsx b/src/app/admin/(dashboard)/_com/header.tsx new file mode 100644 index 00000000..1a867c81 --- /dev/null +++ b/src/app/admin/(dashboard)/_com/header.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Grid, GridCol, Paper, TextInput, Title } from '@mantine/core'; +import { IconSearch } from '@tabler/icons-react'; // Sesuaikan jika kamu pakai icon lain +import colors from '@/con/colors'; + + +const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = }: { title: string, placeholder?: string, searchIcon?: React.ReactNode }) => { + return ( + + + {title} + + + + + + + + ); +}; + +export default HeaderSearch; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_com/judulList.tsx b/src/app/admin/(dashboard)/_com/judulList.tsx new file mode 100644 index 00000000..4eaa5731 --- /dev/null +++ b/src/app/admin/(dashboard)/_com/judulList.tsx @@ -0,0 +1,30 @@ + +'use client' +import colors from '@/con/colors'; +import { Grid, GridCol, Button, Text } from '@mantine/core'; +import { IconCircleDashedPlus } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import React from 'react'; + +const JudulList = ({ title = "", href = "#" }) => { + const router = useRouter(); + + const handleNavigate = () => { + router.push(href); + }; + + return ( + + + {title} + + + + + + ); +}; + +export default JudulList; diff --git a/src/app/admin/(dashboard)/_state/desa/berita.ts b/src/app/admin/(dashboard)/_state/desa/berita.ts index 0431fd48..56b4fb01 100644 --- a/src/app/admin/(dashboard)/_state/desa/berita.ts +++ b/src/app/admin/(dashboard)/_state/desa/berita.ts @@ -1,4 +1,3 @@ - import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; @@ -57,10 +56,10 @@ const berita = proxy({ ); if (res.status === 200) { berita.findMany.load(); - return toast.success("success create"); + return toast.success("Berita berhasil disimpan!"); } - return toast.error("failed create"); + return toast.error("Gagal menyimpan berita"); } catch (error) { console.log((error as Error).message); } finally { @@ -88,6 +87,30 @@ const berita = proxy({ } }, }, + findUnique: { + data: null as + | Prisma.BeritaGetPayload<{ + include: { + image: true; + kategoriBerita: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/desa/berita/${id}`); + if (res.ok) { + const data = await res.json(); + berita.findUnique.data = data.data ?? null; + } else { + console.error('Failed to fetch berita:', res.statusText); + berita.findUnique.data = null; + } + } catch (error) { + console.error('Error fetching berita:', error); + berita.findUnique.data = null; + } + }, + }, delete: { loading: false, async byId(id: string) { diff --git a/src/app/admin/(dashboard)/_state/desa/potensi.ts b/src/app/admin/(dashboard)/_state/desa/potensi.ts new file mode 100644 index 00000000..c61ec4f1 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/desa/potensi.ts @@ -0,0 +1,223 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +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(50), + kategori: 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: "", +} + +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, + + 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 diff --git a/src/app/admin/(dashboard)/desa/berita/edit/[id]/page.tsx b/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx similarity index 74% rename from src/app/admin/(dashboard)/desa/berita/edit/[id]/page.tsx rename to src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx index 4f2da3e3..a753aede 100644 --- a/src/app/admin/(dashboard)/desa/berita/edit/[id]/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import { @@ -15,29 +14,27 @@ import { Title, } from "@mantine/core"; import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react"; +import { useParams, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; -import { useRouter, useParams } from "next/navigation"; -import { useProxy } from "valtio/utils"; import { toast } from "react-toastify"; +import { useProxy } from "valtio/utils"; +import EditEditor from "@/app/admin/(dashboard)/_com/editEditor"; +import colors from "@/con/colors"; import ApiFetch from "@/lib/api-fetch"; import { FileInput } from "@mantine/core"; -import stateDashboardBerita from "../../../../_state/desa/berita"; -import { Prisma } from "@prisma/client"; import { useShallowEffect } from "@mantine/hooks"; -import { BeritaEditor } from "../../_com/BeritaEditor"; -import colors from "@/con/colors"; +import { Prisma } from "@prisma/client"; +import stateDashboardBerita from "../../../../_state/desa/berita"; -function BeritaEdit() { +function EditBerita() { const beritaState = useProxy(stateDashboardBerita); const router = useRouter(); const params = useParams(); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); - const [editorInstance, setEditorInstance] = useState(null); - const [isEditorReady, setIsEditorReady] = useState(false); const [formData, setFormData] = useState({ judul: beritaState.berita.edit.form.judul || '', deskripsi: beritaState.berita.edit.form.deskripsi || '', @@ -51,7 +48,7 @@ function BeritaEdit() { const loadBerita = async () => { const id = params?.id as string; if (!id) return; - + try { const data = await stateDashboardBerita.berita.edit.load(id); // akses langsung, bukan dari proxy if (data) { @@ -62,7 +59,7 @@ function BeritaEdit() { content: data.content || '', imageId: data.imageId || '', }); - + if (data?.image?.link) { setPreviewImage(data.image.link); } @@ -72,57 +69,28 @@ function BeritaEdit() { toast.error("Gagal memuat data berita"); } }; - + loadBerita(); }, [params?.id]); // ✅ hapus beritaState dari dependency - - - - // Handle editor ready - const handleEditorReady = (editor: any) => { - setEditorInstance(editor); - setIsEditorReady(true); - - // Set initial content if exists - if (formData.content) { - editor.commands.setContent(formData.content); - } - }; const handleSubmit = async () => { - if (!isEditorReady || !editorInstance) { - return toast.error("Editor belum siap"); - } try { - const htmlContent = editorInstance.getHTML(); - if (!htmlContent || htmlContent === "

") { - return toast.warn("Konten tidak boleh kosong"); - } - - // Update form data with editor content - const updatedFormData = { - ...formData, - content: htmlContent - }; - // Update global state with form data beritaState.berita.edit.form = { - judul: updatedFormData.judul, - deskripsi: updatedFormData.deskripsi, - content: updatedFormData.content, - kategoriBeritaId: updatedFormData.kategoriBeritaId || '', - imageId: beritaState.berita.edit.form.imageId // Keep existing imageId if not changed + ...beritaState.berita.edit.form, + judul: formData.judul, + deskripsi: formData.deskripsi, + content: formData.content, + kategoriBeritaId: formData.kategoriBeritaId || '', + imageId: formData.imageId // Keep existing imageId if not changed }; // Jika ada file baru, upload if (file) { - const res = await ApiFetch.api.fileStorage.create.post({ - file, - name: file.name, - }); - + const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name }); const uploaded = res.data?.data; + if (!uploaded?.id) { return toast.error("Gagal upload gambar"); } @@ -131,7 +99,6 @@ function BeritaEdit() { beritaState.berita.edit.form.imageId = uploaded.id; } - await beritaState.berita.edit.update(); toast.success("Berita berhasil diperbarui!"); router.push("/admin/desa/berita"); @@ -142,14 +109,18 @@ function BeritaEdit() { }; return ( - - router.back()}/> + + + + Edit Berita setFormData({...formData, judul: e.target.value})} + onChange={(e) => setFormData({ ...formData, judul: e.target.value })} label={Judul} placeholder="masukkan judul" /> @@ -166,7 +137,7 @@ function BeritaEdit() { setFormData({...formData, deskripsi: e.target.value})} + onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })} label={Deskripsi} placeholder="masukkan deskripsi" /> @@ -194,15 +165,16 @@ function BeritaEdit() { Konten - setFormData({...formData, content})} + { + setFormData((prev) => ({ ...prev, content: htmlContent })); + beritaState.berita.edit.form.content = htmlContent; + }} /> - + @@ -237,9 +209,9 @@ function SelectCategory({ return ; } - + const selectedValue = value || defaultValue; - + 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" + /> + ); + } +} diff --git a/src/app/admin/(dashboard)/desa/berita/page.tsx b/src/app/admin/(dashboard)/desa/berita/page.tsx index 3b397ad7..01a96511 100644 --- a/src/app/admin/(dashboard)/desa/berita/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/page.tsx @@ -1,146 +1,38 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' 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 { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { Prisma } from '@prisma/client'; -import { IconEdit, IconImageInPicture, IconX } from '@tabler/icons-react'; +import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; -import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; import stateDashboardBerita from '../../_state/desa/berita'; -import { BeritaEditor } from './_com/BeritaEditor'; + function Page() { return ( - Berita - + + + Berita + + + + } + /> + + + ); } -function BeritaCreate() { - const beritaState = useProxy(stateDashboardBerita); - const [previewImage, setPreviewImage] = useState(null); - const [file, setFile] = useState(null); - const [editorInstance, setEditorInstance] = useState(null); - const resetForm = () => { - // Reset state di valtio - beritaState.berita.create.form = { - judul: "", - deskripsi: "", - kategoriBeritaId: "", - imageId: "", - content: "", - }; - - // Reset state lokal - setPreviewImage(null); - setFile(null); - if (editorInstance) { - editorInstance.commands.setContent(""); // Kosongkan editor - } - }; - - const handleSubmit = async () => { - if (!file) { - return toast.warn("Pilih file gambar terlebih dahulu"); - } - if (!editorInstance) return toast.error("Editor belum siap"); - - const htmlContent = editorInstance.getHTML(); - if (!htmlContent || htmlContent === "

") return toast.warn("Konten tidak boleh kosong"); - - beritaState.berita.create.form.content = htmlContent; - - // Upload gambar dulu - 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"); - } - - // Simpan ID gambar ke form - beritaState.berita.create.form.imageId = uploaded.id; - - // Submit data berita - await beritaState.berita.create.create(); - - toast.success("Berita berhasil disimpan!"); - - // Reset form setelah submit - resetForm(); - }; - - return ( - - - - Create Berita - { - beritaState.berita.create.form.judul = val.target.value; - }} - label={Judul} - placeholder="masukkan judul" - /> - { - beritaState.berita.create.form.kategoriBeritaId = val.id; - }} - /> - { - beritaState.berita.create.form.deskripsi = val.target.value; - }} - label={Deskripsi} - placeholder="masukkan deskripsi" - /> - - 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 - setEditorInstance(ed)} - /> - - -
-
-
- ); -} @@ -228,9 +120,7 @@ function BeritaList() { if (!beritaState.berita.findMany.data) { return ( - {Array.from({ length: 10 }).map((_, k) => ( - - ))} + ) } @@ -239,41 +129,49 @@ function BeritaList() { - List Berita - - {beritaState.berita.findMany.data?.map((item) => ( - - - - { - setSelectedId(item.id) - setModalHapus(true) - }} - disabled={beritaState.berita.delete.loading} - color={colors['blue-button']} variant='transparent' - > - - - router.push(`/admin/desa/berita/edit/${item.id}`)} - color={colors['blue-button']} variant='transparent' - > - - - - Kategori - {item.kategoriBerita?.name} - Judul - {item.judul} - Deskripsi - {item.deskripsi} - Gambar - gambar - - - ))} - + + + List Berita + + + + + + + + + + Judul + Kategori + Image + Detail + + + + + {beritaState.berita.findMany.data?.map((item) => ( + + + + {item.judul} + + + {item.kategoriBerita?.name} + + + + + + + + + ))} + +
@@ -288,45 +186,4 @@ function BeritaList() { ) } - -function SelectCategory({ - onChange, -}: { - onChange: (value: Prisma.KategoriBeritaGetPayload<{ - select: { - name: true; - id: true; - }; - }>) => void; -}) { - const categoryState = useProxy(stateDashboardBerita.category); - - useShallowEffect(() => { - categoryState.findMany.load(); - }, []); - - if (!categoryState.findMany.data) { - return ; - } - - return ( -