From 80a7df663e926cd2ac69819177197c86505f84c5 Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 22 Jul 2025 11:24:19 +0800 Subject: [PATCH] API & UI Menu Lingkungan, Submenu Gotong Royong --- .../migration.sql | 144 ++++++ prisma/schema.prisma | 31 ++ .../_state/lingkungan/gotong-royong.ts | 476 ++++++++++++++++++ .../gotong-royong/_lib/layoutTabs.tsx | 62 +++ .../lingkungan/gotong-royong/create/page.tsx | 68 --- .../lingkungan/gotong-royong/detail/page.tsx | 66 --- .../lingkungan/gotong-royong/edit/page.tsx | 68 --- .../kategori-kegiatan/[id]/page.tsx | 98 ++++ .../kategori-kegiatan/create/page.tsx | 61 +++ .../gotong-royong/kategori-kegiatan/page.tsx | 112 +++++ .../kegiatan-desa/[id]/edit/page.tsx | 242 +++++++++ .../gotong-royong/kegiatan-desa/[id]/page.tsx | 133 +++++ .../kegiatan-desa/create/page.tsx | 215 ++++++++ .../gotong-royong/kegiatan-desa/page.tsx | 97 ++++ .../lingkungan/gotong-royong/layout.tsx | 9 + .../lingkungan/gotong-royong/page.tsx | 71 --- .../edit/page.tsx | 86 ++++ .../page.tsx | 53 +- .../nilai-konservasi-adat/edit/page.tsx | 88 +++- .../nilai-konservasi-adat/page.tsx | 53 +- src/app/admin/_com/list_PageAdmin.tsx | 2 +- .../_lib/lingkungan/gotong-royong/create.ts | 51 ++ .../_lib/lingkungan/gotong-royong/del.ts | 21 + .../_lib/lingkungan/gotong-royong/findMany.ts | 44 ++ .../lingkungan/gotong-royong/findUnique.ts | 29 ++ .../_lib/lingkungan/gotong-royong/index.ts | 55 ++ .../gotong-royong/kategori-kegiatan/create.ts | 25 + .../gotong-royong/kategori-kegiatan/del.ts | 33 ++ .../kategori-kegiatan/findMany.ts | 15 + .../kategori-kegiatan/findUnique.ts | 47 ++ .../gotong-royong/kategori-kegiatan/index.ts | 30 ++ .../gotong-royong/kategori-kegiatan/updt.ts | 44 ++ .../_lib/lingkungan/gotong-royong/updt.ts | 60 +++ .../api/[[...slugs]]/_lib/lingkungan/index.ts | 2 + 34 files changed, 2401 insertions(+), 290 deletions(-) create mode 100644 prisma/migrations/20250721095104_nico_21_jul_25/migration.sql create mode 100644 src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/_lib/layoutTabs.tsx delete mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/create/page.tsx delete mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/detail/page.tsx delete mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/edit/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/[id]/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/create/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/create/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/page.tsx create mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/layout.tsx delete mode 100644 src/app/admin/(dashboard)/lingkungan/gotong-royong/page.tsx create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/create.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/del.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findMany.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findUnique.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/index.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/create.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/del.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findMany.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findUnique.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/index.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/updt.ts create mode 100644 src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/updt.ts diff --git a/prisma/migrations/20250721095104_nico_21_jul_25/migration.sql b/prisma/migrations/20250721095104_nico_21_jul_25/migration.sql new file mode 100644 index 00000000..c81499ee --- /dev/null +++ b/prisma/migrations/20250721095104_nico_21_jul_25/migration.sql @@ -0,0 +1,144 @@ +-- CreateTable +CREATE TABLE "ProgramPenghijauan" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "icon" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "ProgramPenghijauan_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "DataLingkunganDesa" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "jumlah" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "icon" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "DataLingkunganDesa_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "KegiatanDesa" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsiSingkat" TEXT NOT NULL, + "deskripsiLengkap" TEXT NOT NULL, + "tanggal" TIMESTAMP(3) NOT NULL, + "lokasi" TEXT NOT NULL, + "partisipan" INTEGER NOT NULL, + "imageId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "kategoriKegiatanId" TEXT NOT NULL, + + CONSTRAINT "KegiatanDesa_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "KategoriKegiatan" ( + "id" TEXT NOT NULL, + "nama" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "KategoriKegiatan_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TujuanEdukasiLingkungan" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "TujuanEdukasiLingkungan_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MateriEdukasiLingkungan" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "MateriEdukasiLingkungan_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ContohEdukasiLingkungan" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "ContohEdukasiLingkungan_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FilosofiTriHita" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "FilosofiTriHita_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BentukKonservasiBerdasarkanAdat" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "BentukKonservasiBerdasarkanAdat_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "NilaiKonservasiAdat" ( + "id" TEXT NOT NULL, + "judul" TEXT NOT NULL, + "deskripsi" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "isActive" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "NilaiKonservasiAdat_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "KegiatanDesa" ADD CONSTRAINT "KegiatanDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "KegiatanDesa" ADD CONSTRAINT "KegiatanDesa_kategoriKegiatanId_fkey" FOREIGN KEY ("kategoriKegiatanId") REFERENCES "KategoriKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a85f13e2..e02db313 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -86,6 +86,8 @@ model FileStorage { KolaborasiInovasi KolaborasiInovasi[] InfoTekno InfoTekno[] PengaduanMasyarakat PengaduanMasyarakat[] + + KegiatanDesa KegiatanDesa[] } //========================================= MENU PPID ========================================= // @@ -1499,6 +1501,35 @@ model DataLingkunganDesa { isActive Boolean @default(true) } +// ========================================= GOTONG ROYONG ========================================= // +model KegiatanDesa { + id String @id @default(uuid()) + judul String + deskripsiSingkat String + deskripsiLengkap String + tanggal DateTime + lokasi String + partisipan Int + image FileStorage @relation(fields: [imageId], references: [id]) + imageId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + kategoriKegiatan KategoriKegiatan @relation(fields: [kategoriKegiatanId], references: [id]) + kategoriKegiatanId String +} + +model KategoriKegiatan { + id String @id @default(cuid()) + nama String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + KegiatanDesa KegiatanDesa[] +} + // ========================================= EDUKASI LINGKUNGAN ========================================= // model TujuanEdukasiLingkungan { id String @id @default(cuid()) diff --git a/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts b/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts new file mode 100644 index 00000000..b2cfcb7f --- /dev/null +++ b/src/app/admin/(dashboard)/_state/lingkungan/gotong-royong.ts @@ -0,0 +1,476 @@ +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 templateKegiatanDesaForm = z.object({ + judul: z.string().min(1, "Judul minimal 1 karakter"), + deskripsiSingkat: z.string().min(1, "Deskripsi singkat minimal 1 karakter"), + deskripsiLengkap: z.string().min(1, "Deskripsi lengkap minimal 1 karakter"), + tanggal: z.date(), + lokasi: z.string().min(1, "Lokasi minimal 1 karakter"), + partisipan: z.number().min(1, "Partisipan minimal 1"), + imageId: z.string().min(1, "Gambar wajib dipilih"), + kategoriKegiatanId: z.string().min(1, "Kategori kegiatan minimal 1"), +}); + +const defaultKegiatanDesaForm = { + judul: "", + deskripsiSingkat: "", + deskripsiLengkap: "", + tanggal: new Date(), + lokasi: "", + partisipan: 0, + imageId: "", + kategoriKegiatanId: "", +}; + +const kegiatanDesa = proxy({ + create: { + form: { ...defaultKegiatanDesaForm }, + loading: false, + async create() { + const cek = templateKegiatanDesaForm.safeParse(kegiatanDesa.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + kegiatanDesa.create.loading = true; + const res = await ApiFetch.api.lingkungan.kegiatandesa["create"].post({ + ...kegiatanDesa.create.form, + tanggal: kegiatanDesa.create.form.tanggal.toISOString(), // ✅ convert Date -> string + }); + + if (res.status === 200) { + kegiatanDesa.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 { + kegiatanDesa.create.loading = false; + } + }, + }, + findMany: { + data: null as Array< + Prisma.KegiatanDesaGetPayload<{ + include: { + image: true; + kategoriKegiatan: true; + }; + }> + > | null, + async load() { + const res = await ApiFetch.api.lingkungan.kegiatandesa["find-many"].get(); + if (res.status === 200) { + kegiatanDesa.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.KegiatanDesaGetPayload<{ + include: { + image: true; + kategoriKegiatan: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/lingkungan/kegiatandesa/${id}`); + if (res.ok) { + const data = await res.json(); + kegiatanDesa.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + kegiatanDesa.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data:", error); + kegiatanDesa.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + kegiatanDesa.delete.loading = true; + + const response = await fetch(`/api/lingkungan/kegiatandesa/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "kegiatan desa berhasil dihapus"); + await kegiatanDesa.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus pasar desa"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus pasar desa"); + } finally { + kegiatanDesa.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...defaultKegiatanDesaForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + kegiatanDesa.edit.loading = true; + + const response = await fetch(`/api/lingkungan/kegiatandesa/${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 = { + judul: data.judul, + deskripsiSingkat: data.deskripsiSingkat, + deskripsiLengkap: data.deskripsiLengkap, + tanggal: data.tanggal, + lokasi: data.lokasi, + partisipan: data.partisipan, + imageId: data.imageId, + kategoriKegiatanId: data.kategoriKegiatanId, + }; + return data; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading kegiatan desa:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } finally { + kegiatanDesa.edit.loading = false; + } + }, + + async update() { + const cek = templateKegiatanDesaForm.safeParse(kegiatanDesa.edit.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + kegiatanDesa.edit.loading = true; + const response = await fetch(`/api/lingkungan/kegiatandesa/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + judul: this.form.judul, + deskripsiSingkat: this.form.deskripsiSingkat, + deskripsiLengkap: this.form.deskripsiLengkap, + tanggal: typeof this.form.tanggal === "string" + ? this.form.tanggal + : this.form.tanggal.toISOString(), + lokasi: this.form.lokasi, + partisipan: this.form.partisipan, + imageId: this.form.imageId, + kategoriKegiatanId: this.form.kategoriKegiatanId, + }), + }); + 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 kegiatan desa"); + await kegiatanDesa.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal mengupdate kegiatan desa"); + } + } catch (error) { + console.error("Error updating kegiatan desa:", error); + toast.error( + error instanceof Error ? error.message : "Gagal mengupdate kegiatan desa" + ); + return false; + } finally { + kegiatanDesa.edit.loading = false; + } + }, + reset() { + kegiatanDesa.edit.id = ""; + kegiatanDesa.edit.form = { ...defaultKegiatanDesaForm }; + }, + }, +}); + +// ========================================= KATEGORI kegiatan ========================================= // +const kategoriKegiatanForm = z.object({ + nama: z.string().min(1, "Nama minimal 1 karakter"), +}); + +const kategoriKegiatanDefaultForm = { + nama: "", +}; + +const kategoriKegiatan = proxy({ + create: { + form: { ...kategoriKegiatanDefaultForm }, + loading: false, + async create() { + const cek = kategoriKegiatanForm.safeParse(kategoriKegiatan.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + kategoriKegiatan.create.loading = true; + const res = await ApiFetch.api.lingkungan.kegiatandesa.kategorikegiatan["create"].post( + kategoriKegiatan.create.form + ); + if (res.status === 200) { + kategoriKegiatan.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 { + kategoriKegiatan.create.loading = false; + } + }, + }, + findMany: { + data: null as Array<{ + id: string; + nama: string; + }> | null, + async load() { + const res = await ApiFetch.api.lingkungan.kegiatandesa.kategorikegiatan["find-many"].get(); + if (res.status === 200) { + kategoriKegiatan.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.KategoriKegiatanGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/lingkungan/kegiatanDesa/kategorikegiatan/${id}`); + if (res.ok) { + const data = await res.json(); + kategoriKegiatan.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + kategoriKegiatan.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching data:", error); + kategoriKegiatan.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + kategoriKegiatan.delete.loading = true; + + const response = await fetch(`/api/lingkungan/kegiatandesa/kategorikegiatan/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Kategori kegiatan berhasil dihapus"); + await kategoriKegiatan.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus kategori kegiatan"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus kategori kegiatan"); + } finally { + kategoriKegiatan.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...kategoriKegiatanDefaultForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/lingkungan/kegiatandesa/kategorikegiatan/${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; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading kategori kegiatan:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + + async update() { + const cek = kategoriKegiatanForm.safeParse(kategoriKegiatan.edit.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + kategoriKegiatan.edit.loading = true; + const response = await fetch( + `/api/lingkungan/kegiatandesa/kategorikegiatan/${kategoriKegiatan.edit.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nama: kategoriKegiatan.edit.form.nama, + }), + } + ); + + // 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 kegiatan (${response.status})` + ); + } + + if (result.success) { + toast.success( + result.message || "Berhasil memperbarui kategori kegiatan" + ); + await kategoriKegiatan.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal mengupdate kategori kegiatan" + ); + } + } 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 kegiatan:", error); + toast.error( + error instanceof Error + ? error.message + : "Gagal mengupdate kategori kegiatan" + ); + return false; + } finally { + kategoriKegiatan.edit.loading = false; + } + }, + reset() { + kategoriKegiatan.edit.id = ""; + kategoriKegiatan.edit.form = { ...kategoriKegiatanDefaultForm }; + }, + }, +}); + +const gotongRoyongState = proxy({ + kegiatanDesa, + kategoriKegiatan, +}); +export default gotongRoyongState; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/_lib/layoutTabs.tsx new file mode 100644 index 00000000..b9cb795c --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/_lib/layoutTabs.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: "Kegiatan Desa", + value: "kegiatanDesa", + href: "/admin/lingkungan/gotong-royong/kegiatan-desa" + }, + { + label: "Kategori Kegiatan", + value: "kategoriKegiatan", + href: "/admin/lingkungan/gotong-royong/kategori-kegiatan" + }, + ]; + 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 ( + + Gotong Royong + + + {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)/lingkungan/gotong-royong/create/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/create/page.tsx deleted file mode 100644 index e7b57fe6..00000000 --- a/src/app/admin/(dashboard)/lingkungan/gotong-royong/create/page.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import React from 'react'; -import { KeamananEditor } from '../../../keamanan/_com/keamananEditor'; - -function CreateGotongRoyong() { - const router = useRouter() - return ( - - - - - - - - Create Gotong Royong - Judul Gotong Royong} - placeholder="masukkan judul gotong royong" - /> - Kategori Gotong Royong} - placeholder="masukkan kategori gotong royong" - /> - - Deskripsi Gotong Royong - - - {/* 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 ? ( - - ) : ( -
- -
- )} */} - - Gambar - - - -
-
-
- ); -} - -export default CreateGotongRoyong; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/detail/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/detail/page.tsx deleted file mode 100644 index 06e3a53d..00000000 --- a/src/app/admin/(dashboard)/lingkungan/gotong-royong/detail/page.tsx +++ /dev/null @@ -1,66 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core'; -import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import React from 'react'; -// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; - -function DetailGotongRoyong() { - const router = useRouter(); - return ( - - - - - - - Detail Gotong Royong - - - - - Judul Gotong Royong - Test Judul - - - Kategori Gotong Royong - Test Kategori - - - Deskripsi Gotong Royong - Test Deskripsi - - - Gambar - gambar - - - - - - - - - - - - - {/* Modal Hapus - setModalHapus(false)} - onConfirm={handleHapus} - text="Apakah anda yakin ingin menghapus potensi ini?" - /> */} - - ); -} - -export default DetailGotongRoyong; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/edit/page.tsx deleted file mode 100644 index acff7b2f..00000000 --- a/src/app/admin/(dashboard)/lingkungan/gotong-royong/edit/page.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import React from 'react'; -import { KeamananEditor } from '../../../keamanan/_com/keamananEditor'; - -function EditGotongRoyong() { - const router = useRouter() - return ( - - - - - - - - Edit Gotong Royong - Judul Gotong Royong} - placeholder="masukkan judul gotong royong" - /> - Kategori Gotong Royong} - placeholder="masukkan kategori gotong royong" - /> - - Deskripsi Gotong Royong - - - {/* 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 ? ( - - ) : ( -
- -
- )} */} - - Gambar - - - -
-
-
- ); -} - -export default EditGotongRoyong; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/[id]/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/[id]/page.tsx new file mode 100644 index 00000000..3ef19b4f --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/[id]/page.tsx @@ -0,0 +1,98 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong'; +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 EditKategoriKegiatan() { + const router = useRouter(); + const params = useParams(); + const id = params?.id as string; + const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan); + + const [formData, setFormData] = useState({ + nama: "", + }); + + 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({ + nama: data.nama || '', + }); + } + } catch (error) { + console.error("Error loading kategori kegiatan:", error); + toast.error("Gagal memuat data kategori kegiatan"); + } + }; + + loadKategorikegiatan(); + }, [id]); + + const handleSubmit = async () => { + try { + if (!formData.nama.trim()) { + toast.error('Nama kategori kegiatan tidak boleh kosong'); + return; + } + + stateKategori.edit.form = { + nama: formData.nama.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/lingkungan/gotong-royong/kategori-kegiatan"); + } + } catch (error) { + console.error("Error updating kategori kegiatan:", error); + // toast akan ditampilkan dari fungsi update + } + }; + + return ( + + + + + + + + Edit Kategori kegiatan + setFormData({ ...formData, nama: e.target.value })} + label={Nama Kategori kegiatan} + placeholder='Masukkan nama kategori kegiatan' + /> + + + + + + + ); +} + +export default EditKategoriKegiatan; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/create/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/create/page.tsx new file mode 100644 index 00000000..e87b3a78 --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/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 gotongRoyongState from '../../../../_state/lingkungan/gotong-royong'; + +function CreateKategoriKegiatan() { + const router = useRouter(); + const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan) + + useEffect(() => { + stateKategori.findMany.load(); + }, []); + + const resetForm = () => { + stateKategori.create.form = { + nama: "", + }; + } + + const handleSubmit = async () => { + await stateKategori.create.create(); + resetForm(); + router.push("/admin/lingkungan/gotong-royong/kategori-kegiatan") + } + + return ( + + + + + + + + + Create Kategori Kegiatan + { + stateKategori.create.form.nama = val.target.value; + }} + label={Nama Kategori Kegiatan} + placeholder='Masukkan nama kategori kegiatan' + /> + + + + + + + + ); +} + +export default CreateKategoriKegiatan; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/page.tsx new file mode 100644 index 00000000..daf3fbd8 --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kategori-kegiatan/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 gotongRoyongState from '../../../_state/lingkungan/gotong-royong'; + + +function KategoriKegiatan() { + const [search, setSearch] = useState("") + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListKategoriKegiatan({ search }: { search: string }) { + const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan) + 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.nama.toLowerCase().includes(keyword) + ); + }); + + if (!stateKategori.findMany.data) { + return ( + + + + ) + } + + return ( + + + + + + + Nama Kategori + Edit + Delete + + + + {filteredData.map((item) => ( + + {item.nama} + + + + + + + + ))} + +
+
+ {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus kategori kegiatan ini?' + /> +
+ ); +} + +export default KategoriKegiatan; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx new file mode 100644 index 00000000..3b278668 --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx @@ -0,0 +1,242 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; +import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +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'; + + +interface FormKegiatanDesa { + judul: string; + deskripsiSingkat: string; + deskripsiLengkap: string; + tanggal: string; + lokasi: string; + partisipan: number; + imageId: string; + kategoriKegiatanId: string; +} + +function EditGotongRoyong() { + const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa) + const params = useParams() + const router = useRouter() + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + + const [formData, setFormData] = useState({ + judul: '', + deskripsiSingkat: '', + deskripsiLengkap: '', + tanggal: '', + lokasi: '', + partisipan: 0, + imageId: '', + kategoriKegiatanId: '', + }) + + const formatDateForInput = (dateString: string) => { + if (!dateString) return ''; + const date = new Date(dateString); + return date.toISOString().split('T')[0]; + }; + + useEffect(() => { + const loadKegiatanDesa = async () => { + const id = params?.id as string; + if (!id) return; + + try { + const data = await kegiatanDesaState.edit.load(id); + if (data) { + setFormData({ + judul: data.judul || '', + deskripsiSingkat: data.deskripsiSingkat || '', + deskripsiLengkap: data.deskripsiLengkap || '', + tanggal: data.tanggal || '', + lokasi: data.lokasi || '', + partisipan: data.partisipan || 0, + imageId: data.imageId || '', + kategoriKegiatanId: data.kategoriKegiatanId || '', + }); + } + } catch (error) { + console.error("Error loading kegiatan desa:", error); + toast.error("Gagal memuat data kegiatan desa"); + } + } + gotongRoyongState.kategoriKegiatan.findMany.load() + loadKegiatanDesa(); + }, [params?.id]); + + const handleSubmit = async () => { + try { + kegiatanDesaState.edit.form = { + ...kegiatanDesaState.edit.form, + judul: formData.judul.trim(), + deskripsiSingkat: formData.deskripsiSingkat.trim(), + deskripsiLengkap: formData.deskripsiLengkap.trim(), + tanggal: new Date(formData.tanggal.trim()), + lokasi: formData.lokasi.trim(), + partisipan: formData.partisipan, + imageId: formData.imageId, + kategoriKegiatanId: formData.kategoriKegiatanId, + } + 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 + kegiatanDesaState.edit.form.imageId = uploaded.id; + } + await kegiatanDesaState.edit.update() + router.push("/admin/lingkungan/gotong-royong/kegiatan-desa"); + } catch (error) { + console.error("Error updating kegiatan desa:", error); + toast.error("Terjadi kesalahan saat memperbarui kegiatan desa"); + } + } + + return ( + + + + + + + + Edit Kegiatan Desa + Judul Kegiatan Desa} + placeholder="masukkan judul kegiatan desa" + onChange={(e) => setFormData({ ...formData, judul: e.target.value })} + /> + Deskripsi Singkat Kegiatan Desa} + placeholder="masukkan deskripsi singkat kegiatan desa" + onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })} + /> + { + stateKegiatanDesa.create.form.kategoriKegiatanId = val ?? ""; + }} + label={Kategori Kegiatan} + placeholder="Pilih kategori produk" + data={ + gotongRoyongState.kategoriKegiatan.findMany.data?.map((v) => ({ + value: v.id, + label: v.nama, + })) || [] + } + /> + + + + + + + + ); +} + +export default CreateKegiatanDesa; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/page.tsx new file mode 100644 index 00000000..a014029d --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/page.tsx @@ -0,0 +1,97 @@ +/* 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 HeaderSearch from '../../../_com/header'; +import JudulList from '../../../_com/judulList'; +import { useEffect, useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import gotongRoyongState from '../../../_state/lingkungan/gotong-royong'; + +function KegiatanDesa() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListKegiatanDesa({ search }: { search: string }) { + const listState = useProxy(gotongRoyongState.kegiatanDesa) + const router = useRouter(); + useEffect(() => { + listState.findMany.load() + }, []) + + const filteredData = (listState.findMany.data || []).filter(item => { + const keyword = search.toLowerCase(); + return ( + item.judul.toLowerCase().includes(keyword) || + item.lokasi.toLowerCase().includes(keyword) || + item.kategoriKegiatan?.nama?.toLowerCase().includes(keyword) + ); + }); + + if (!listState.findMany.data) { + return ( + + + + ) + } + + return ( + + + + + + + + + Judul Kegiatan Desa + Kategori Kegiatan Desa + Lokasi Kegiatan Desa + Detail + + + + {filteredData.map((item) => ( + + + + {item.judul} + + + {item.kategoriKegiatan?.nama} + {item.lokasi} + + + + + ))} + +
+
+
+
+
+ ) +} + +export default KegiatanDesa; diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/layout.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/layout.tsx new file mode 100644 index 00000000..285fc16e --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/layout.tsx @@ -0,0 +1,9 @@ +import LayoutTabs from "./_lib/layoutTabs"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/page.tsx deleted file mode 100644 index 28a109bf..00000000 --- a/src/app/admin/(dashboard)/lingkungan/gotong-royong/page.tsx +++ /dev/null @@ -1,71 +0,0 @@ -'use client' -import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; -import HeaderSearch from '../../_com/header'; -import colors from '@/con/colors'; -import JudulList from '../../_com/judulList'; -import { useRouter } from 'next/navigation'; - -function GotongRoyong() { - return ( - - } - /> - - - ); -} - -function ListGotongRoyong() { - const router = useRouter(); - return ( - - - - - - - - - Judul Gotong Royong - Kategori Gotong Royong - Image - Deskripsi Gotong Royong - Detail - - - - - - - Judul - - - Kategori - - - - Deskripsi - - - - - - -
-
-
-
-
- ) -} - -export default GotongRoyong; diff --git a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit/page.tsx index 8b137891..af38497f 100644 --- a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit/page.tsx @@ -1 +1,87 @@ +'use client' +import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali'; +import colors from '@/con/colors'; +import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconArrowBack } from '@tabler/icons-react'; +import dynamic from 'next/dynamic'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { useProxy } from 'valtio/utils'; +const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), { + ssr: false, +}); + +function EditBentukKonservasiBerdasarkanAdat() { + const router = useRouter() + const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat) + const [judul, setJudul] = useState(''); + const [content, setContent] = useState(''); + + useShallowEffect(() => { + if (!bentukKonservasiState.findById.data) { + bentukKonservasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu + } + }, []); + + useEffect(() => { + if (bentukKonservasiState.findById.data) { + setJudul(bentukKonservasiState.findById.data.judul ?? '') + setContent(bentukKonservasiState.findById.data.deskripsi ?? '') + } + }, [bentukKonservasiState.findById.data]) + + const submit = () => { + if (bentukKonservasiState.findById.data) { + bentukKonservasiState.findById.data.judul = judul; + bentukKonservasiState.findById.data.deskripsi = content; + bentukKonservasiState.update.save(bentukKonservasiState.findById.data) + } + router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat') + } + return ( + + + + + + + + + Edit Bentuk Konservasi Berdasarkan Adat + Judul + + Content + + + + + + + + + + ); +} + +export default EditBentukKonservasiBerdasarkanAdat; diff --git a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/page.tsx b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/page.tsx index 69da2f21..6763cb25 100644 --- a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/page.tsx @@ -1,11 +1,54 @@ -import React from 'react'; +'use client' +import colors from '@/con/colors'; +import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconEdit } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; +import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali'; function Page() { + const router = useRouter() + const listBentukKonservasiBerdasarkanAdat = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat) + useShallowEffect(() => { + listBentukKonservasiBerdasarkanAdat.findById.load('edit') + }, []) + + if (!listBentukKonservasiBerdasarkanAdat.findById.data) { + return ( + + + + ) + } return ( -
- Page -
- ); + + + + + Preview Bentuk Konservasi Berdasarkan Adat + + + + + + + + + + + + + + + + + + + + ) } export default Page; diff --git a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit/page.tsx index 69da2f21..d0708a30 100644 --- a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit/page.tsx @@ -1,11 +1,87 @@ -import React from 'react'; +'use client' +import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali'; +import colors from '@/con/colors'; +import { Box, Button, Group, Paper, Stack, Text, Title } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconArrowBack } from '@tabler/icons-react'; +import dynamic from 'next/dynamic'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { useProxy } from 'valtio/utils'; -function Page() { +const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), { + ssr: false, +}); + +function EditNilaiKonservasiAdat() { + const router = useRouter() + const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat) + const [judul, setJudul] = useState(''); + const [content, setContent] = useState(''); + + useShallowEffect(() => { + if (!nilaiKonservasiState.findById.data) { + nilaiKonservasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu + } + }, []); + + useEffect(() => { + if (nilaiKonservasiState.findById.data) { + setJudul(nilaiKonservasiState.findById.data.judul ?? '') + setContent(nilaiKonservasiState.findById.data.deskripsi ?? '') + } + }, [nilaiKonservasiState.findById.data]) + + const submit = () => { + if (nilaiKonservasiState.findById.data) { + nilaiKonservasiState.findById.data.judul = judul; + nilaiKonservasiState.findById.data.deskripsi = content; + nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data) + } + router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat') + } return ( -
- Page -
+ + + + + + + + + Edit Nilai Konservasi Adat + Judul + + Content + + + + + + + + + ); } -export default Page; +export default EditNilaiKonservasiAdat; diff --git a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/page.tsx b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/page.tsx index 69da2f21..22065533 100644 --- a/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/page.tsx @@ -1,11 +1,54 @@ -import React from 'react'; +'use client' +import colors from '@/con/colors'; +import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconEdit } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; +import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali'; function Page() { + const router = useRouter() + const listNilaiKonservasiAdat = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat) + useShallowEffect(() => { + listNilaiKonservasiAdat.findById.load('edit') + }, []) + + if (!listNilaiKonservasiAdat.findById.data) { + return ( + + + + ) + } return ( -
- Page -
- ); + + + + + Preview Nilai Konservasi Adat + + + + + + + + + + + + + + + + + + + + ) } export default Page; diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index 07e7dfdd..0523b834 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -323,7 +323,7 @@ export const navBar = [ { id: "Lingkungan_4", name: "Gotong Royong", - path: "/admin/lingkungan/gotong-royong" + path: "/admin/lingkungan/gotong-royong/kegiatan-desa" }, { id: "Lingkungan_5", diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/create.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/create.ts new file mode 100644 index 00000000..627e89d5 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/create.ts @@ -0,0 +1,51 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreate = { + judul: string; + deskripsiSingkat: string; + deskripsiLengkap: string; + tanggal: string; + lokasi: string; + partisipan?: number; + imageId: string; + kategoriKegiatanId: string; // minimal satu kategori +}; + +export default async function kegiatanDesaCreate(context: Context) { + const body = context.body as FormCreate; + + if (!body.kategoriKegiatanId) { + throw new Error("kategoriKegiatanId wajib diisi"); + } + + try { + // Create langsung data AdministrasiOnline + const result = await prisma.kegiatanDesa.create({ + data: { + judul: body.judul, + deskripsiSingkat: body.deskripsiSingkat, + deskripsiLengkap: body.deskripsiLengkap, + tanggal: body.tanggal, + lokasi: body.lokasi, + partisipan: body.partisipan ?? 0, + imageId: body.imageId, + kategoriKegiatanId: body.kategoriKegiatanId, // relasi ke JenisLayanan + }, + include: { + kategoriKegiatan: true, // Include data relasi + }, + }); + + return { + success: true, + message: "Berhasil membuat kegiatan desa", + data: result, + }; + } catch (error) { + console.error("Error creating kegiatan desa:", error); + throw new Error( + "Gagal membuat kegiatan desa: " + (error as Error).message + ); + } +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/del.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/del.ts new file mode 100644 index 00000000..04d0d4d3 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/del.ts @@ -0,0 +1,21 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kegiatanDesaDelete(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.kegiatanDesa.delete({ + where: { id }, + }); + + return { + success: true, + message: "Berhasil menghapus kegiatan desa", + data: deleted, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findMany.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findMany.ts new file mode 100644 index 00000000..58a29693 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findMany.ts @@ -0,0 +1,44 @@ +// /api/berita/findManyPaginated.ts +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +async function kegiatanDesaFindMany(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.kegiatanDesa.findMany({ + where: { isActive: true }, + include: { + kategoriKegiatan: true, + image: true, + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu + }), + prisma.kegiatanDesa.count({ + where: { isActive: true } + }) + ]); + + return { + success: true, + message: "Success fetch kegiatan desa 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 kegiatan desa with pagination", + }; + } +} + +export default kegiatanDesaFindMany; diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findUnique.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findUnique.ts new file mode 100644 index 00000000..7e275ed2 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/findUnique.ts @@ -0,0 +1,29 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kegiatanDesaFindUnique(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.kegiatanDesa.findUnique({ + where: { id }, + include: { + kategoriKegiatan: true, + image: true, + }, + }); + + if (!data) { + throw new Error("Kegiatan desa tidak ditemukan"); + } + + return { + success: true, + message: "Data kegiatan desa ditemukan", + data, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/index.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/index.ts new file mode 100644 index 00000000..ab9b8132 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/index.ts @@ -0,0 +1,55 @@ +import Elysia, { t } from "elysia"; +import KegiatanDesaCreate from "./create"; +import KegiatanDesaDelete from "./del"; +import KegiatanDesaFindMany from "./findMany"; +import KegiatanDesaFindUnique from "./findUnique"; +import KegiatanDesaUpdate from "./updt"; +import KategoriKegiatan from "./kategori-kegiatan"; + +const KegiatanDesa = new Elysia({ + prefix: "/kegiatandesa", + tags: ["Lingkungan/Gotong Royong/Kegiatan Desa"], +}) + + // ✅ Find all + .get("/find-many", KegiatanDesaFindMany) + + // ✅ Find by ID + .get("/:id", KegiatanDesaFindUnique) + + // ✅ Create + .post("/create", KegiatanDesaCreate, { + body: t.Object({ + judul: t.String(), + deskripsiSingkat: t.String(), + deskripsiLengkap: t.String(), + tanggal: t.String(), + lokasi: t.String(), + partisipan: t.Optional(t.Number()), + imageId: t.String(), + kategoriKegiatanId: t.String(), + }), + }) + + // ✅ Update + .put("/:id", KegiatanDesaUpdate, { + body: t.Object({ + judul: t.Optional(t.String()), + deskripsiSingkat: t.Optional(t.String()), + deskripsiLengkap: t.Optional(t.String()), + tanggal: t.Optional(t.String()), + lokasi: t.Optional(t.String()), + partisipan: t.Optional(t.Number()), + imageId: t.Optional(t.String()), + kategoriKegiatanId: t.Optional(t.String()), + }), + }) + + + // ✅ Kategori kegiatan routes (nested) + .use(KategoriKegiatan) + + // ✅ Delete + .delete("/del/:id", KegiatanDesaDelete); + +export default KegiatanDesa; diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/create.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/create.ts new file mode 100644 index 00000000..f62d0413 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/create.ts @@ -0,0 +1,25 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + + +export default async function kategoriKegiatanCreate(context: Context) { + const body = context.body as {nama: string}; + + if (!body.nama) { + return { + success: false, + message: "Nama is required", + }; + } + + const kategoriKegiatan = await prisma.kategoriKegiatan.create({ + data: { + nama: body.nama, + }, + }); + return { + success: true, + message: "Success create kategori kegiatan", + data: kategoriKegiatan + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/del.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/del.ts new file mode 100644 index 00000000..580276e8 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/del.ts @@ -0,0 +1,33 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +const kategoriKegiatanDelete = async (context: Context) => { + const id = context.params.id; + if (!id) { + return { + success: false, + message: "ID is required", + } + } + + const kategoriKegiatan = await prisma.kategoriKegiatan.delete({ + where: { + id: id, + }, + }) + + if(!kategoriKegiatan) { + return { + success: false, + message: "Kategori Kegiatan tidak ditemukan", + } + } + + return { + success: true, + message: "Success delete kategori kegiatan", + data: kategoriKegiatan, + } +} + +export default kategoriKegiatanDelete diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findMany.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findMany.ts new file mode 100644 index 00000000..18fc7787 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findMany.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; + +export default async function kategoriKegiatanFindMany() { + const data = await prisma.kategoriKegiatan.findMany(); + return { + success: true, + data: data.map((item: any) => { + return { + id: item.id, + nama: item.nama, + } + }), + }; +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findUnique.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findUnique.ts new file mode 100644 index 00000000..03ea2563 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/findUnique.ts @@ -0,0 +1,47 @@ +import { Context } from "elysia"; +import prisma from "@/lib/prisma"; + +export default async function kategoriKegiatanFindUnique(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.kategoriKegiatan.findUnique({ + where: { id }, + }); + + if (!data) { + return { + success: false, + message: "Kategori kegiatan tidak ditemukan", + } + } + + return { + success: true, + message: "Success find kategori kegiatan", + data, + } + } catch (error) { + console.error("Find by ID error:", error); + return { + success: false, + message: "Gagal mengambil kategori kegiatan: " + (error instanceof Error ? error.message : 'Unknown error'), + } + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/index.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/index.ts new file mode 100644 index 00000000..4cd01286 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/index.ts @@ -0,0 +1,30 @@ +import Elysia, { t } from "elysia"; +import kategoriKegiatanCreate from "./create"; +import kategoriKegiatanDelete from "./del"; +import kategoriKegiatanFindMany from "./findMany"; +import kategoriKegiatanFindUnique from "./findUnique"; +import kategoriKegiatanUpdate from "./updt"; + +const KategoriKegiatan = new Elysia({ + prefix: "/kategorikegiatan", + tags: ["Lingkungan/Kategori Kegiatan"], +}) + .get("/find-many", kategoriKegiatanFindMany) + .get("/:id", async (context) => { + const response = await kategoriKegiatanFindUnique(context); + return response; + }) + .delete("/del/:id", kategoriKegiatanDelete) + .post("/create", kategoriKegiatanCreate, { + body: t.Object({ + nama: t.String(), + }), + }) + .put("/:id", kategoriKegiatanUpdate, { + body: t.Object({ + id: t.String(), + nama: t.String(), + }), + }); + +export default KategoriKegiatan; diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/updt.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/updt.ts new file mode 100644 index 00000000..9b7d07ba --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/kategori-kegiatan/updt.ts @@ -0,0 +1,44 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function kategoriKegiatanUpdate(context: Context) { + const body = context.body as { nama: string }; + const id = context.params?.id as string; + + // Validasi ID dan nama + if (!id) { + return { + success: false, + message: "ID is required", + }; + } + + if (!body.nama) { + return { + success: false, + message: "Nama is required", + }; + } + + try { + const kategoriKegiatan = await prisma.kategoriKegiatan.update({ + where: { id }, + data: { + nama: body.nama, + }, + }); + + return { + success: true, + message: "Success update kategori kegiatan", + data: kategoriKegiatan, + }; + } catch (error) { + console.error("Update error:", error); + return { + success: false, + message: "Gagal update kategori kegiatan", + error: error instanceof Error ? error.message : String(error), + }; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/updt.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/updt.ts new file mode 100644 index 00000000..4be0463e --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/gotong-royong/updt.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormUpdateKegiatanDesa = { + judul?: string; + deskripsiSingkat?: string; + deskripsiLengkap?: string; + tanggal?: string; + lokasi?: string; + partisipan?: number; + imageId?: string; + kategoriKegiatanId?: string; // minimal satu kategori +}; + +export default async function kegiatanDesaUpdate(context: Context) { + const body = context.body as FormUpdateKegiatanDesa; + + const id = context.params.id; + + if (!id) { + return { + success: false, + message: "ID kegiatan desa wajib diisi", + }; + } + + try { + const updated = await prisma.kegiatanDesa.update({ + where: { id }, + data: { + judul: body.judul, + deskripsiSingkat: body.deskripsiSingkat, + deskripsiLengkap: body.deskripsiLengkap, + tanggal: body.tanggal ? new Date(body.tanggal) : undefined, + lokasi: body.lokasi, + partisipan: body.partisipan ?? 0, + imageId: body.imageId, + updatedAt: new Date(), + }, + include: { + image: true, + kategoriKegiatan: true, + }, + }); + + return { + success: true, + message: "Kegiatan desa berhasil diperbarui", + data: updated, + }; + } catch (error: any) { + console.error("❌ Error update kegiatan desa:", error); + return { + success: false, + message: "Gagal memperbarui data kegiatan desa", + error: error.message, + }; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts index c9a9bdb6..df51e961 100644 --- a/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts @@ -4,6 +4,7 @@ import ProgramPenghijauan from "./program-penghijauan"; import DataLingkunganDesa from "./data-lingkungan-desa"; import EdukasiLingkungan from "./edukasi-lingkungan"; import KonservasiAdatBali from "./konservasi-adat-bali"; +import KegiatanDesa from "./gotong-royong"; const Lingkungan = new Elysia({ prefix: "/api/lingkungan", @@ -15,6 +16,7 @@ const Lingkungan = new Elysia({ .use(DataLingkunganDesa) .use(EdukasiLingkungan) .use(KonservasiAdatBali) +.use(KegiatanDesa) export default Lingkungan;