diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9e3c2fb6..cfe37fea 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,6 +60,7 @@ model FileStorage { deletedAt DateTime? isActive Boolean @default(true) link String + category String // "image" / "document" / "other" Berita Berita[] PotensiDesa PotensiDesa[] Posyandu Posyandu[] @@ -86,28 +87,25 @@ model FileStorage { KolaborasiInovasi KolaborasiInovasi[] InfoTekno InfoTekno[] PengaduanMasyarakat PengaduanMasyarakat[] - - KegiatanDesa KegiatanDesa[] - - ProgramInovasi ProgramInovasi[] - - PejabatDesa PejabatDesa[] - - MediaSosial MediaSosial[] + KegiatanDesa KegiatanDesa[] + ProgramInovasi ProgramInovasi[] + PejabatDesa PejabatDesa[] + MediaSosial MediaSosial[] + DesaAntiKorupsi DesaAntiKorupsi[] } //========================================= MENU LANDING PAGE ========================================= // //========================================= PROFILE ========================================= // model PejabatDesa { - id String @id @default(cuid()) - name String @unique @db.VarChar(255) + id String @id @default(cuid()) + name String @unique @db.VarChar(255) position String image FileStorage? @relation(fields: [imageId], references: [id]) imageId String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } model ProgramInovasi { @@ -125,7 +123,7 @@ model ProgramInovasi { model MediaSosial { id String @id @default(cuid()) - name String + name String image FileStorage @relation(fields: [imageId], references: [id]) imageId String iconUrl String? @db.VarChar(255) @@ -135,6 +133,31 @@ model MediaSosial { isActive Boolean @default(true) } +//========================================= PROFILE ========================================= // +model DesaAntiKorupsi { + id String @id @default(cuid()) + name String @unique + deskripsi String @db.Text + kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id]) + kategoriId String + file FileStorage @relation(fields: [fileId], references: [id]) + fileId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model KategoriDesaAntiKorupsi { + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + DesaAntiKorupsi DesaAntiKorupsi[] +} + //========================================= MENU PPID ========================================= // //========================================= STRUKTUR PPID ========================================= // diff --git a/src/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi.ts b/src/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi.ts new file mode 100644 index 00000000..6eccb4e7 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi.ts @@ -0,0 +1,485 @@ +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 templateDesaAntiKorupsiForm = z.object({ + name: z.string().min(1, "Judul minimal 1 karakter"), + deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"), + kategoriId: z.string().min(1, "Kategori minimal 1"), + fileId: z.string().min(1, "File minimal 1"), +}); + +const defaultDesaAntiKorupsiForm = { + name: "", + deskripsi: "", + kategoriId: "", + fileId: "", +}; + +const desaAntikorupsi = proxy({ + create: { + form: { ...defaultDesaAntiKorupsiForm }, + loading: false, + async create() { + const cek = templateDesaAntiKorupsiForm.safeParse( + desaAntikorupsi.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + desaAntikorupsi.create.loading = true; + const res = await ApiFetch.api.landingpage.desaantikorupsi[ + "create" + ].post({ + ...desaAntikorupsi.create.form, + }); + + if (res.status === 200) { + desaAntikorupsi.findMany.load(); + return toast.success("Data berhasil ditambahkan"); + } + return toast.error("Gagal menambahkan data"); + } catch (error) { + console.log(error); + toast.error("Gagal menambahkan data"); + } finally { + desaAntikorupsi.create.loading = false; + } + }, + }, + findMany: { + data: null as Array< + Prisma.DesaAntiKorupsiGetPayload<{ + include: { + file: true; + kategori: true; + }; + }> + > | null, + async load() { + const res = await ApiFetch.api.landingpage.desaantikorupsi[ + "find-many" + ].get(); + if (res.status === 200) { + desaAntikorupsi.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.DesaAntiKorupsiGetPayload<{ + include: { + file: true; + kategori: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/landingpage/desaantikorupsi/${id}`); + if (res.ok) { + const data = await res.json(); + desaAntikorupsi.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + desaAntikorupsi.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data:", error); + desaAntikorupsi.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + desaAntikorupsi.delete.loading = true; + + const response = await fetch( + `/api/landingpage/desaantikorupsi/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "desa anti korupsi berhasil dihapus"); + await desaAntikorupsi.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus desa anti korupsi"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus desa anti korupsi"); + } finally { + desaAntikorupsi.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...defaultDesaAntiKorupsiForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + desaAntikorupsi.edit.loading = true; + + const response = await fetch(`/api/landingpage/desaantikorupsi/${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, + fileId: data.fileId, + }; + return data; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading desa anti korupsi:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } finally { + desaAntikorupsi.edit.loading = false; + } + }, + + async update() { + const cek = templateDesaAntiKorupsiForm.safeParse( + desaAntikorupsi.edit.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + desaAntikorupsi.edit.loading = true; + const response = await fetch( + `/api/landingpage/desaantikorupsi/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + deskripsi: this.form.deskripsi, + kategoriId: this.form.kategoriId, + fileId: this.form.fileId, + }), + } + ); + 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 desa anti korupsi"); + await desaAntikorupsi.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal mengupdate desa anti korupsi" + ); + } + } catch (error) { + console.error("Error updating desa anti korupsi:", error); + toast.error( + error instanceof Error + ? error.message + : "Gagal mengupdate desa anti korupsi" + ); + return false; + } finally { + desaAntikorupsi.edit.loading = false; + } + }, + reset() { + desaAntikorupsi.edit.id = ""; + desaAntikorupsi.edit.form = { ...defaultDesaAntiKorupsiForm }; + }, + }, +}); + +// ========================================= KATEGORI desa anti korupsi ========================================= // +const kategoriDesaAntiKorupsiForm = z.object({ + name: z.string().min(1, "Nama minimal 1 karakter"), +}); + +const kategoriDesaAntiKorupsiDefaultForm = { + name: "", +}; + +const kategoriDesaAntiKorupsi = proxy({ + create: { + form: { ...kategoriDesaAntiKorupsiDefaultForm }, + loading: false, + async create() { + const cek = kategoriDesaAntiKorupsiForm.safeParse( + kategoriDesaAntiKorupsi.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + kategoriDesaAntiKorupsi.create.loading = true; + const res = await ApiFetch.api.landingpage.kategoridak["create"].post( + kategoriDesaAntiKorupsi.create.form + ); + if (res.status === 200) { + kategoriDesaAntiKorupsi.findMany.load(); + return toast.success("Data berhasil ditambahkan"); + } + return toast.error("Gagal menambahkan data"); + } catch (error) { + console.log(error); + toast.error("Gagal menambahkan data"); + } finally { + kategoriDesaAntiKorupsi.create.loading = false; + } + }, + }, + findMany: { + data: null as Array<{ + id: string; + name: string; + }> | null, + async load() { + const res = await ApiFetch.api.landingpage.kategoridak["find-many"].get(); + if (res.status === 200) { + kategoriDesaAntiKorupsi.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.KategoriDesaAntiKorupsiGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/landingpage/kategoridak/${id}`); + if (res.ok) { + const data = await res.json(); + kategoriDesaAntiKorupsi.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + kategoriDesaAntiKorupsi.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data:", error); + kategoriDesaAntiKorupsi.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + kategoriDesaAntiKorupsi.delete.loading = true; + + const response = await fetch( + `/api/landingpage/kategoridak/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Kategori desa anti korupsi berhasil dihapus"); + await kategoriDesaAntiKorupsi.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus kategori desa anti korupsi"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus kategori desa anti korupsi"); + } finally { + kategoriDesaAntiKorupsi.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...kategoriDesaAntiKorupsiDefaultForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/landingpage/kategoridak/${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, + }; + return data; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading kategori desa anti korupsi:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + + async update() { + const cek = kategoriDesaAntiKorupsiForm.safeParse( + kategoriDesaAntiKorupsi.edit.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + kategoriDesaAntiKorupsi.edit.loading = true; + const response = await fetch( + `/api/landingpage/kategoridak/${kategoriDesaAntiKorupsi.edit.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: kategoriDesaAntiKorupsi.edit.form.name, + }), + } + ); + + // Clone the response to avoid 'body already read' error + const responseClone = response.clone(); + + try { + const result = await response.json(); + + if (!response.ok) { + console.error( + "Update failed with status:", + response.status, + "Response:", + result + ); + throw new Error( + result?.message || + `Gagal mengupdate kategori desa anti korupsi (${response.status})` + ); + } + + if (result.success) { + toast.success( + result.message || + "Berhasil memperbarui kategori desa anti korupsi" + ); + await kategoriDesaAntiKorupsi.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal mengupdate kategori desa anti korupsi" + ); + } + } catch (error) { + // If JSON parsing fails, try to get the response text for better error messages + try { + const text = await responseClone.text(); + console.error("Error response text:", text); + throw new Error(`Gagal memproses respons dari server: ${text}`); + } catch (textError) { + console.error("Error parsing response as text:", textError); + console.error("Original error:", error); + throw new Error("Gagal memproses respons dari server"); + } + } + } catch (error) { + console.error("Error updating kategori desa anti korupsi:", error); + toast.error( + error instanceof Error + ? error.message + : "Gagal mengupdate kategori desa anti korupsi" + ); + return false; + } finally { + kategoriDesaAntiKorupsi.edit.loading = false; + } + }, + reset() { + kategoriDesaAntiKorupsi.edit.id = ""; + kategoriDesaAntiKorupsi.edit.form = { + ...kategoriDesaAntiKorupsiDefaultForm, + }; + }, + }, +}); + +const korupsiState = proxy({ + desaAntikorupsi, + kategoriDesaAntiKorupsi, +}); +export default korupsiState; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx new file mode 100644 index 00000000..111db2ac --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx @@ -0,0 +1,62 @@ +/* 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 LayoutTabs({ children }: { children: React.ReactNode }) { + const router = useRouter() + const pathname = usePathname() + const tabs = [ + { + label: "List Desa Anti Korupsi", + value: "listDesaAntiKorupsi", + href: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi" + }, + { + label: "Kategori Desa Anti Korupsi", + value: "kategoriDesaAntiKorupsi", + href: "/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi" + }, + ]; + 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 ( + + Desa Anti Korupsi + + + {tabs.map((e, i) => ( + {e.label} + ))} + + {tabs.map((e, i) => ( + + {/* Konten dummy, bisa diganti tergantung routing */} + <> + + ))} + + {children} + + ); +} + +export default LayoutTabs; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/[id]/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/[id]/page.tsx new file mode 100644 index 00000000..1ef9ece9 --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/[id]/page.tsx @@ -0,0 +1,98 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi'; +import colors from '@/con/colors'; +import { Box, Button, Group, 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 EditKategoriDesaAntiKorupsi() { + const router = useRouter(); + const params = useParams(); + const id = params?.id as string; + const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi); + + const [formData, setFormData] = useState({ + name: "", + }); + + useEffect(() => { + const loadKategorikegiatan = async () => { + if (!id) return; + + try { + const data = await stateKategori.edit.load(id); + + if (data) { + // pastikan id-nya masuk ke state edit + stateKategori.edit.id = id; + setFormData({ + name: data.name || '', + }); + } + } catch (error) { + console.error("Error loading kategori desa anti korupsi:", error); + toast.error("Gagal memuat data kategori desa anti korupsi"); + } + }; + + loadKategorikegiatan(); + }, [id]); + + const handleSubmit = async () => { + try { + if (!formData.name.trim()) { + toast.error('Nama kategori desa anti korupsi tidak boleh kosong'); + return; + } + + stateKategori.edit.form = { + name: formData.name.trim(), + }; + + // Safety check tambahan: pastikan ID tidak kosong + if (!stateKategori.edit.id) { + stateKategori.edit.id = id; // fallback + } + + const success = await stateKategori.edit.update(); + + if (success) { + router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi"); + } + } catch (error) { + console.error("Error updating kategori desa anti korupsi:", error); + // toast akan ditampilkan dari fungsi update + } + }; + + return ( + + + + + + + + Edit Kategori Desa Anti Korupsi + setFormData({ ...formData, name: e.target.value })} + label={Nama Kategori Desa Anti Korupsi} + placeholder='Masukkan nama kategori desa anti korupsi' + /> + + + + + + + ); +} + +export default EditKategoriDesaAntiKorupsi; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create/page.tsx new file mode 100644 index 00000000..1ca81eee --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create/page.tsx @@ -0,0 +1,61 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +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 { useEffect } from 'react'; +import { useProxy } from 'valtio/utils'; +import korupsiState from '../../../../_state/landing-page/desa-anti-korupsi'; + +function CreateKategoriDesaAntiKorupsi() { + const router = useRouter(); + const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi) + + useEffect(() => { + stateKategori.findMany.load(); + }, []); + + const resetForm = () => { + stateKategori.create.form = { + name: "", + }; + } + + const handleSubmit = async () => { + await stateKategori.create.create(); + resetForm(); + router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi") + } + + return ( + + + + + + + + + Create Kategori Desa Anti Korupsi + { + stateKategori.create.form.name = val.target.value; + }} + label={Nama Kategori Desa Anti Korupsi} + placeholder='Masukkan nama kategori desa anti korupsi' + /> + + + + + + + + ); +} + +export default CreateKategoriDesaAntiKorupsi; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/page.tsx new file mode 100644 index 00000000..cd4f3cd3 --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/page.tsx @@ -0,0 +1,112 @@ +'use client' +import colors from '@/con/colors'; +import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconEdit, IconSearch, IconX } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../../_com/header'; +import JudulList from '../../../_com/judulList'; +import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; +import korupsiState from '../../../_state/landing-page/desa-anti-korupsi'; + + +function KategoriDesaAntiKorupsi() { + const [search, setSearch] = useState("") + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListKategoriKegiatan({ search }: { search: string }) { + const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi) + const [modalHapus, setModalHapus] = useState(false) + const [selectedId, setSelectedId] = useState(null) + const router = useRouter() + + const handleHapus = () => { + if (selectedId) { + stateKategori.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) + } + } + + useShallowEffect(() => { + stateKategori.findMany.load() + }, []) + + const filteredData = (stateKategori.findMany.data || []).filter(item => { + const keyword = search.toLowerCase(); + return ( + item.name.toLowerCase().includes(keyword) + ); + }); + + if (!stateKategori.findMany.data) { + return ( + + + + ) + } + + return ( + + + + + + + Nama Kategori + Edit + Delete + + + + {filteredData.map((item) => ( + + {item.name} + + + + + + + + ))} + +
+
+ {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus kategori kegiatan ini?' + /> +
+ ); +} + +export default KategoriDesaAntiKorupsi diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/layout.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/layout.tsx new file mode 100644 index 00000000..97e4e3a9 --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/layout.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import LayoutTabs from './_lib/layouTabs'; + +function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +export default Layout; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx new file mode 100644 index 00000000..ae79cd57 --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/[id]/edit/page.tsx @@ -0,0 +1,229 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import { useProxy } from 'valtio/utils'; + +import { Box, Button, Group, Paper, Select, Stack, Text, TextInput } from '@mantine/core'; +import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +import colors from '@/con/colors'; + +import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi'; +import ApiFetch from '@/lib/api-fetch'; +import { Dropzone } from '@mantine/dropzone'; +import { toast } from 'react-toastify'; +import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; + +interface FormDesaAntiKorupsi { + name: string; + deskripsi: string; + kategoriId: string; + fileId: string; +} + +function EditDesaAntiKorupsi() { + const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi) + const [previewFile, setPreviewFile] = useState(null); + const [file, setFile] = useState(null); + const params = useParams() + const router = useRouter() + const [formData, setFormData] = useState({ + name: '', + deskripsi: '', + kategoriId: '', + fileId: '', + }) + + useEffect(() => { + const loadDesaAntiKorupsi = async () => { + const id = params?.id as string; + if (!id) return; + + try { + const data = await desaAntiKorupsiState.edit.load(id); + if (data) { + // ⬇️ FIX PENTING: tambahkan ini + desaAntiKorupsiState.edit.id = id; + + desaAntiKorupsiState.edit.form = { + name: data.name, + deskripsi: data.deskripsi, + kategoriId: data.kategoriId, + fileId: data.fileId, + }; + + setFormData({ + name: data.name, + deskripsi: data.deskripsi, + kategoriId: data.kategoriId, + fileId: data.fileId, + }); + + if (data?.file?.link) { + setPreviewFile(data.file.link) + } + } + } catch (error) { + console.error("Error loading program penghijauan:", error); + toast.error("Gagal memuat data program penghijauan"); + } + } + + loadDesaAntiKorupsi(); + }, [params?.id]); + + + const handleSubmit = async () => { + + try { + // Update global state with form data + desaAntiKorupsiState.edit.form = { + ...desaAntiKorupsiState.edit.form, + name: formData.name, + deskripsi: formData.deskripsi, + kategoriId: formData.kategoriId || '', + fileId: formData.fileId // 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 uploaded = res.data?.data; + + if (!uploaded?.id) { + return toast.error("Gagal upload gambar"); + } + + // Update imageId in global state + desaAntiKorupsiState.edit.form.fileId = uploaded.id; + } + + await desaAntiKorupsiState.edit.update(); + toast.success("desa anti korupsi berhasil diperbarui!"); + router.push("/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi"); + } catch (error) { + console.error("Error updating desa anti korupsi:", error); + toast.error("Terjadi kesalahan saat memperbarui desa anti korupsi"); + } + }; + + return ( + + + + + + + Edit List Desa Anti Korupsi + {desaAntiKorupsiState.findUnique.data ? ( + + + { + setFormData({ + ...formData, + name: val.target.value + }) + }} + label={Judul} + placeholder='Masukkan judul' + /> + + Deskripsi + { + setFormData({ + ...formData, + deskripsi: val + }) + }} + /> + + { + stateKorupsi.create.form.kategoriId = val ?? ""; + }} + label={Kategori} + placeholder="Pilih kategori" + data={ + korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({ + value: v.id, + label: v.name, + })) || [] + } + /> + + + + + + + + ); +} + +export default CreateDesaAntiKorupsi; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/page.tsx new file mode 100644 index 00000000..ac20b635 --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/page.tsx @@ -0,0 +1,99 @@ +/* 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 { 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 korupsiState from '../../../_state/landing-page/desa-anti-korupsi'; + +function DesaAntiKorupsi() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListDesaAntiKorupsi({ search }: { search: string }) { + const listState = useProxy(korupsiState.desaAntikorupsi) + const router = useRouter(); + useEffect(() => { + listState.findMany.load() + }, []) + + const filteredData = (listState.findMany.data || []).filter(item => { + const keyword = search.toLowerCase(); + return ( + item.name.toLowerCase().includes(keyword) || + item.deskripsi.toLowerCase().includes(keyword) || + item.kategori?.name?.toLowerCase().includes(keyword) + ); + }); + + if (!listState.findMany.data) { + return ( + + + + ) + } + + return ( + + + + + + + + + Nama Desa Anti Korupsi + Deskripsi Desa Anti Korupsi + Kategori Desa Anti Korupsi + Detail + + + + {filteredData.map((item) => ( + + + + {item.name} + + + + + + {item.kategori?.name} + + + + + ))} + +
+
+
+
+
+ ) +} + +export default DesaAntiKorupsi; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/page.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/page.tsx deleted file mode 100644 index 9e5663e6..00000000 --- a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -function Page() { - return ( -
- Desa Anti Korupsi -
- ); -} - -export default Page; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/page.tsx index 1a8cd71f..4154486d 100644 --- a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/page.tsx @@ -50,7 +50,7 @@ function DetailKegiatanDesa() { - Detail kegiatanDesa Inovasi + Detail Kegiatan Desa Inovasi {kegiatanDesaState.findUnique.data ? ( diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index a4019f63..9a0b1ed9 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -12,7 +12,7 @@ export const navBar = [ { id: "Landing_Page_2", name: "Desa Anti Korupsi", - path: "/admin/landing-page/desa-anti-korupsi" + path: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi" }, { id: "Landing_Page_3", diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index 937d2a1d..ebb478c8 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -7,7 +7,9 @@ import { AppShellHeader, AppShellMain, AppShellNavbar, + Box, Burger, + Flex, Group, Image, NavLink, @@ -15,16 +17,16 @@ import { Text } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; -import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; +import { IconChevronLeft, IconChevronRight, IconDoorExit } from "@tabler/icons-react"; import _ from 'lodash'; import Link from "next/link"; -import { useSelectedLayoutSegments } from "next/navigation"; +import { useRouter, useSelectedLayoutSegments } from "next/navigation"; import { navBar } from "./_com/list_PageAdmin"; export default function Layout({ children }: { children: React.ReactNode }) { const [opened, { toggle }] = useDisclosure(); const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true); - + const router = useRouter() // Normalisasi semua segmen jadi lowercase const segments = useSelectedLayoutSegments().map(s => _.lowerCase(s)); @@ -44,6 +46,18 @@ export default function Layout({ children }: { children: React.ReactNode }) { > + + + + Dashboard Admin + + {!desktopOpened && ( @@ -55,6 +69,13 @@ export default function Layout({ children }: { children: React.ReactNode }) { hiddenFrom="sm" size={'sm'} /> + + { + router.push("/darmasaba") + }} color={colors["blue-button"]} radius={'xl'}> + + + - - - Dashboard Admin - diff --git a/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/create.ts b/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/create.ts index cc9f0ad8..abd40551 100644 --- a/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/create.ts +++ b/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/create.ts @@ -14,27 +14,15 @@ const fileStorageCreate = async (context: Context) => { const file = body.file; const name = body.name; - if (!file) { - return { - status: 400, - body: "No file uploaded", - }; - } + if (!file) return { status: 400, body: "No file uploaded" }; + if (!name) return { status: 400, body: "No name provided" }; + if (!UPLOAD_DIR) return { status: 500, body: "UPLOAD_DIR is not defined" }; - if (!name) { - return { - status: 400, - body: "No name provided", - }; - } + // Tentukan kategori berdasarkan mimeType + const isImage = file.type.startsWith("image/"); + const category = isImage ? "image" : "document"; - if (!UPLOAD_DIR) { - return { - status: 500, - body: "UPLOAD_DIR is not defined", - }; - } - const pathName = "allFile"; + const pathName = category === "image" ? "images" : "documents"; const rootPath = path.join(UPLOAD_DIR, pathName); await fs.mkdir(rootPath, { recursive: true }); @@ -44,9 +32,10 @@ const fileStorageCreate = async (context: Context) => { const data = await prisma.fileStorage.create({ data: { name: newName, - realName: file.name, // Add the original file name as realName + realName: file.name, path: rootPath, mimeType: file.type, + category, link: `/api/fileStorage/findUnique/${newName}`, }, }); @@ -56,9 +45,7 @@ const fileStorageCreate = async (context: Context) => { Buffer.from(await file.arrayBuffer()) ); - return { - data, - }; + return { data }; }; -export default fileStorageCreate; \ No newline at end of file +export default fileStorageCreate; diff --git a/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/findMany.ts b/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/findMany.ts index ec77d0d8..bf1628e1 100644 --- a/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/fileStorage/_lib/findMany.ts @@ -1,6 +1,12 @@ import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export const fileStorageFindMany = async () => { - const data = await prisma.fileStorage.findMany(); - return data; +export const fileStorageFindMany = async (context: Context) => { + const category = context.query?.category as string | undefined; + + const data = await prisma.fileStorage.findMany({ + where: category ? { category } : {}, + }); + + return { data }; }; diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/create.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/create.ts new file mode 100644 index 00000000..cfc49585 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/create.ts @@ -0,0 +1,43 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreate = { + name: string; + deskripsi: string; + fileId: string; + kategoriId: string; // minimal satu kategori +}; + +export default async function desaAntiKorupsiCreate(context: Context) { + const body = context.body as FormCreate; + + if (!body.kategoriId) { + throw new Error("kategoriId wajib diisi"); + } + + try { + // Create langsung data AdministrasiOnline + const result = await prisma.desaAntiKorupsi.create({ + data: { + name: body.name, + deskripsi: body.deskripsi, + fileId: body.fileId, + kategoriId: body.kategoriId, // relasi ke JenisLayanan + }, + include: { + kategori: true, // Include data relasi + }, + }); + + return { + success: true, + message: "Berhasil membuat desa anti korupsi", + data: result, + }; + } catch (error) { + console.error("Error creating desa anti korupsi:", error); + throw new Error( + "Gagal membuat desa anti korupsi: " + (error as Error).message + ); + } +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/del.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/del.ts new file mode 100644 index 00000000..82bf610a --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/del.ts @@ -0,0 +1,21 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function desaAntiKorupsiDelete(context: Context) { + const { params } = context; + const id = params?.id as string; + + if (!id) { + throw new Error("ID tidak ditemukan dalam parameter"); + } + + const deleted = await prisma.desaAntiKorupsi.delete({ + where: { id }, + }); + + return { + success: true, + message: "Berhasil menghapus desa anti korupsi", + data: deleted, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/findMany.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/findMany.ts new file mode 100644 index 00000000..7d35ce0f --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/findMany.ts @@ -0,0 +1,44 @@ +// /api/berita/findManyPaginated.ts +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +async function desaAntiKorupsiFindMany(context: Context) { + const page = Number(context.query.page) || 1; + const limit = Number(context.query.limit) || 10; + const skip = (page - 1) * limit; + + try { + const [data, total] = await Promise.all([ + prisma.desaAntiKorupsi.findMany({ + where: { isActive: true }, + include: { + kategori: true, + file: true, + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu + }), + prisma.desaAntiKorupsi.count({ + where: { isActive: true } + }) + ]); + + return { + success: true, + message: "Success fetch desa anti korupsi with pagination", + data, + page, + totalPages: Math.ceil(total / limit), + total, + }; + } catch (e) { + console.error("Find many paginated error:", e); + return { + success: false, + message: "Failed fetch desa anti korupsi with pagination", + }; + } +} + +export default desaAntiKorupsiFindMany; diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/findUnique.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/findUnique.ts new file mode 100644 index 00000000..f4d59198 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/findUnique.ts @@ -0,0 +1,29 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function desaAntiKorupsiFindUnique(context: Context) { + const { params } = context; + const id = params?.id as string; + + if (!id) { + throw new Error("ID tidak ditemukan dalam parameter"); + } + + const data = await prisma.desaAntiKorupsi.findUnique({ + where: { id }, + include: { + kategori: true, + file: true, + }, + }); + + if (!data) { + throw new Error("Desa anti korupsi tidak ditemukan"); + } + + return { + success: true, + message: "Data desa anti korupsi ditemukan", + data, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/index.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/index.ts new file mode 100644 index 00000000..eeef6eec --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/index.ts @@ -0,0 +1,41 @@ +import Elysia, { t } from "elysia"; +import desaAntiKorupsiDelete from "./del"; +import desaAntiKorupsiFindMany from "./findMany"; +import desaAntiKorupsiFindUnique from "./findUnique"; +import desaAntiKorupsiUpdate from "./updt"; +import desaAntiKorupsiCreate from "./create"; + +const DesaAntiKorupsi = new Elysia({ + prefix: "/desaantikorupsi", + tags: ["Landing Page/Desa Anti Korupsi"], +}) + + // ✅ Find all + .get("/find-many", desaAntiKorupsiFindMany) + + // ✅ Find by ID + .get("/:id", desaAntiKorupsiFindUnique) + + // ✅ Create + .post("/create", desaAntiKorupsiCreate, { + body: t.Object({ + name: t.String(), + deskripsi: t.String(), + fileId: t.String(), + kategoriId: t.String(), + }), + }) + + // ✅ Update + .put("/:id", desaAntiKorupsiUpdate, { + body: t.Object({ + name: t.String(), + deskripsi: t.String(), + fileId: t.String(), + kategoriId: t.String(), + }), + }) + // ✅ Delete + .delete("/del/:id", desaAntiKorupsiDelete); + +export default DesaAntiKorupsi; diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/create.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/create.ts new file mode 100644 index 00000000..2e8ef33f --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/create.ts @@ -0,0 +1,25 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + + +export default async function kategoriDesaAntiKorupsiCreate(context: Context) { + const body = context.body as {name: string}; + + if (!body.name) { + return { + success: false, + message: "Nama is required", + }; + } + + const kategoriDesaAntiKorupsi = await prisma.kategoriDesaAntiKorupsi.create({ + data: { + name: body.name, + }, + }); + return { + success: true, + message: "Success create kategori desa anti korupsi", + data: kategoriDesaAntiKorupsi + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/del.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/del.ts new file mode 100644 index 00000000..89cc52e1 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/del.ts @@ -0,0 +1,21 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kategoriDesaAntiKorupsiDelete(context: Context) { + const { params } = context; + const id = params?.id as string; + + if (!id) { + throw new Error("ID tidak ditemukan dalam parameter"); + } + + const deleted = await prisma.kategoriDesaAntiKorupsi.delete({ + where: { id }, + }); + + return { + success: true, + message: "Berhasil menghapus kategori desa anti korupsi", + data: deleted, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/findMany.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/findMany.ts new file mode 100644 index 00000000..bfc0f7f1 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/findMany.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; + +export default async function kategoriDesaAntiKorupsiFindMany() { + const data = await prisma.kategoriDesaAntiKorupsi.findMany(); + return { + success: true, + data: data.map((item: any) => { + return { + id: item.id, + name: item.name, + } + }), + }; +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/findUnique.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/findUnique.ts new file mode 100644 index 00000000..4258697d --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/findUnique.ts @@ -0,0 +1,47 @@ +import { Context } from "elysia"; +import prisma from "@/lib/prisma"; + +export default async function kategoriDesaAntiKorupsiFindUnique(context: Context) { + const url = new URL(context.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.kategoriDesaAntiKorupsi.findUnique({ + where: { id }, + }); + + if (!data) { + return { + success: false, + message: "Kategori desa anti korupsi tidak ditemukan", + } + } + + return { + success: true, + message: "Success find kategori desa anti korupsi", + data, + } + } catch (error) { + console.error("Find by ID error:", error); + return { + success: false, + message: "Gagal mengambil kategori desa anti korupsi: " + (error instanceof Error ? error.message : 'Unknown error'), + } + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/index.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/index.ts new file mode 100644 index 00000000..c16608e6 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/index.ts @@ -0,0 +1,29 @@ +import Elysia, { t } from "elysia"; +import kategoriDesaAntiKorupsiCreate from "./create"; +import kategoriDesaAntiKorupsiDelete from "./del"; +import kategoriDesaAntiKorupsiFindMany from "./findMany"; +import kategoriDesaAntiKorupsiFindUnique from "./findUnique"; +import kategoriDesaAntiKorupsiUpdate from "./updt"; + +const KategoriDesaAntiKorupsi = new Elysia({ + prefix: "/kategoridak", + tags: ["Landing Page/Desa Anti Korupsi"], +}) + .get("/find-many", kategoriDesaAntiKorupsiFindMany) + .get("/:id", async (context) => { + const response = await kategoriDesaAntiKorupsiFindUnique(context); + return response; + }) + .delete("/del/:id", kategoriDesaAntiKorupsiDelete) + .post("/create", kategoriDesaAntiKorupsiCreate, { + body: t.Object({ + name: t.String(), + }), + }) + .put("/:id", kategoriDesaAntiKorupsiUpdate, { + body: t.Object({ + name: t.String(), + }), + }); + +export default KategoriDesaAntiKorupsi; diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/updt.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/updt.ts new file mode 100644 index 00000000..26d75d1d --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/kategori-dak/updt.ts @@ -0,0 +1,44 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kategoriDesaAntiKorupsiUpdate(context: Context) { + const body = context.body as { name: string }; + const id = context.params?.id as string; + + // Validasi ID dan nama + if (!id) { + return { + success: false, + message: "ID is required", + }; + } + + if (!body.name) { + return { + success: false, + message: "Name is required", + }; + } + + try { + const kategoriDesaAntiKorupsi = await prisma.kategoriDesaAntiKorupsi.update({ + where: { id }, + data: { + name: body.name, + }, + }); + + return { + success: true, + message: "Success update kategori desa anti korupsi", + data: kategoriDesaAntiKorupsi, + }; + } catch (error) { + console.error("Update error:", error); + return { + success: false, + message: "Gagal update kategori desa anti korupsi", + error: error instanceof Error ? error.message : String(error), + }; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/updt.ts b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/updt.ts new file mode 100644 index 00000000..577d3c0b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/landing_page/desa-anti-korupsi/updt.ts @@ -0,0 +1,52 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormUpdateDesaAntiKorupsi = { + name?: string; + deskripsi?: string; + fileId?: string; + kategoriId?: string; // minimal satu kategori +}; + +export default async function desaAntiKorupsiUpdate(context: Context) { + const body = context.body as FormUpdateDesaAntiKorupsi; + + const id = context.params.id; + + if (!id) { + return { + success: false, + message: "ID desa anti korupsi wajib diisi", + }; + } + + try { + const updated = await prisma.desaAntiKorupsi.update({ + where: { id }, + data: { + name: body.name, + deskripsi: body.deskripsi, + fileId: body.fileId, + kategoriId: body.kategoriId, + }, + include: { + file: true, + kategori: true, + }, + }); + + return { + success: true, + message: "Desa anti korupsi berhasil diperbarui", + data: updated, + }; + } catch (error: any) { + console.error("❌ Error update desa anti korupsi:", error); + return { + success: false, + message: "Gagal memperbarui data desa anti korupsi", + error: error.message, + }; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/index.ts b/src/app/api/[[...slugs]]/_lib/landing_page/index.ts index 29ea04f4..791edd29 100644 --- a/src/app/api/[[...slugs]]/_lib/landing_page/index.ts +++ b/src/app/api/[[...slugs]]/_lib/landing_page/index.ts @@ -2,6 +2,8 @@ import Elysia from "elysia"; import MediaSosial from "./profile/media-sosial"; import ProgramInovasi from "./profile/program-inovasi"; import PejabatDesa from "./profile/pejabat-desa"; +import KategoriDesaAntiKorupsi from "./desa-anti-korupsi/kategori-dak"; +import DesaAntiKorupsi from "./desa-anti-korupsi"; const LandingPage = new Elysia({ prefix: "/api/landingpage", @@ -11,5 +13,7 @@ const LandingPage = new Elysia({ .use(MediaSosial) .use(ProgramInovasi) .use(PejabatDesa) +.use(KategoriDesaAntiKorupsi) +.use(DesaAntiKorupsi) export default LandingPage diff --git a/src/app/darmasaba/_com/Navbar.tsx b/src/app/darmasaba/_com/Navbar.tsx index 404193ab..d75eb141 100644 --- a/src/app/darmasaba/_com/Navbar.tsx +++ b/src/app/darmasaba/_com/Navbar.tsx @@ -26,6 +26,7 @@ export function Navbar() { }} > + { @@ -50,6 +51,7 @@ export function Navbar() { } + {(item || isSearch) && } diff --git a/src/app/darmasaba/_com/NavbarMainMenu.tsx b/src/app/darmasaba/_com/NavbarMainMenu.tsx index 35d64627..d06479b3 100644 --- a/src/app/darmasaba/_com/NavbarMainMenu.tsx +++ b/src/app/darmasaba/_com/NavbarMainMenu.tsx @@ -4,18 +4,20 @@ import colors from "@/con/colors" import stateNav from "@/state/state-nav" import { ActionIcon, Button, Container, Flex, Image, Stack } from "@mantine/core" import { useHover } from "@mantine/hooks" +import { IconSearch, IconUser } from "@tabler/icons-react" import { useTransitionRouter } from 'next-view-transitions' import { useSnapshot } from "valtio" import { MenuItem } from "../../../../types/menu-item" import { NavbarSearch } from "./NavBarSearch" import { NavbarSubMenu } from "./NavbarSubMenu" -import { IconSearch } from "@tabler/icons-react" +import { useRouter } from "next/navigation" export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) { const { item, isSearch } = useSnapshot(stateNav) const router = useTransitionRouter() + const next = useRouter() return { router.push("/darmasaba") stateNav.clear() - + }} > icon @@ -44,6 +46,11 @@ export function NavbarMainMenu({ listNavbar }: { {/* TODO: add icon search */} + { + next.push("/admin/landing-page/profile/program-inovasi") + }} color={colors["blue-button"]} radius={'xl'}> + + {item && } diff --git a/src/app/percobaan/page.tsx b/src/app/percobaan/page.tsx index f482f320..7f3c4cac 100644 --- a/src/app/percobaan/page.tsx +++ b/src/app/percobaan/page.tsx @@ -14,7 +14,7 @@ function Page() { const loadListFile = async () => { const { data } = await ApiFetch.api.fileStorage.findMany.get() - setListFile(data?.map((item) => item.link) || []) + setListFile(data?.data?.map((item) => item.link) || []) } useShallowEffect(() => { loadListFile() diff --git a/src/con/navbar-list-menu.ts b/src/con/navbar-list-menu.ts index 65382fff..409535c3 100644 --- a/src/con/navbar-list-menu.ts +++ b/src/con/navbar-list-menu.ts @@ -2,7 +2,7 @@ const navbarListMenu = [ { id: "1", name: "PPID", - href: "/darmasaba/ppid", + href: "/darmasaba/ppid/profile-ppid", children: [ { id: "1.1", @@ -51,7 +51,7 @@ const navbarListMenu = [ { id: "2", name: "Desa", - href: "/darmasaba/desa", + href: "/darmasaba/desa/profile", children: [ { id: "2.1", @@ -94,7 +94,7 @@ const navbarListMenu = [ { id: "3", name: "Kesehatan", - href: "/darmasaba/kesehatan", + href: "/darmasaba/kesehatan/posyandu", children: [ { id: "3.1", @@ -136,7 +136,7 @@ const navbarListMenu = [ { id: "4", name: "Keamanan", - href: "/darmasaba/keamanan", + href: "/darmasaba/keamanan/keamanan-lingkungan-pecalang-patwal", children: [ { id: "4.1", @@ -173,7 +173,7 @@ const navbarListMenu = [ { id: "5", name: "Ekonomi", - href: "/darmasaba/ekonomi", + href: "/darmasaba/ekonomi/pasar-desa", children: [ { id: "5.1", @@ -229,7 +229,7 @@ const navbarListMenu = [ }, { id: "6", name: "Inovasi", - href: "/darmasaba/inovasi", + href: "/darmasaba/inovasi/desa-digital-smart-village", children: [ { id: "6.1", @@ -266,7 +266,7 @@ const navbarListMenu = [ }, { id: "7", name: "Lingkungan", - href: "/darmasaba/lingkungan", + href: "/darmasaba/lingkungan/pengelolaan-sampah-bank-sampah", children: [ { id: "7.1", @@ -302,7 +302,7 @@ const navbarListMenu = [ }, { id: "8", name: "Pendidikan", - href: "/darmasaba/pendidikan", + href: "/darmasaba/pendidikan/info-sekolah-paud", children: [ { id: "8.1",