diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e93d4996..1cd3f723 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -92,6 +92,8 @@ model FileStorage {
Pegawai Pegawai[]
DesaDigital DesaDigital[]
+
+ KolaborasiInovasi KolaborasiInovasi[]
}
//========================================= MENU PPID ========================================= //
@@ -1341,15 +1343,32 @@ model DesaDigital {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
+
// ========================================= PROGRAM KREATIF ========================================= //
model ProgramKreatif {
- id String @id @default(cuid())
+ id String @id @default(cuid())
name String
- slug String @db.Text //deskripsi singkat
- deskripsi String @db.Text //deskripsi panjang
+ slug String @db.Text //deskripsi singkat
+ deskripsi String @db.Text //deskripsi panjang
icon 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)
+}
+
+// ========================================= KOLABORASI INOVASI ========================================= //
+model KolaborasiInovasi {
+ id String @id @default(cuid())
+ name String
+ tahun Int
+ slug String @db.Text //deskripsi singkat
+ deskripsi String @db.Text //deskripsi panjang
+ kolaborator 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)
}
diff --git a/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts b/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts
new file mode 100644
index 00000000..4f82d04d
--- /dev/null
+++ b/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts
@@ -0,0 +1,234 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import ApiFetch from "@/lib/api-fetch";
+import { Prisma } from "@prisma/client";
+import { toast } from "react-toastify";
+import { proxy } from "valtio";
+import { z } from "zod";
+
+const templateForm = z.object({
+ name: z.string().min(1, "Nama minimal 1 karakter"),
+ tahun: z.number().min(4, "Tahun minimal 4 karakter"),
+ slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
+ deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
+ kolaborator: z.string().min(1, "Kolaborator minimal 1 karakter"),
+ imageId: z.string().min(1, "Image ID minimal 1 karakter"),
+})
+
+const defaultForm = {
+ name: "",
+ tahun: 0,
+ slug: "",
+ deskripsi: "",
+ kolaborator: "",
+ imageId: "",
+}
+
+const kolaborasiInovasiState = proxy({
+ create: {
+ form: { ...defaultForm },
+ loading: false,
+ async create() {
+ const cek = templateForm.safeParse(kolaborasiInovasiState.create.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ return toast.error(err);
+ }
+
+ try {
+ kolaborasiInovasiState.create.loading = true;
+ const res = await ApiFetch.api.inovasi.kolaborasiinovasi["create"].post(
+ kolaborasiInovasiState.create.form
+ );
+ if (res.status === 200) {
+ kolaborasiInovasiState.findMany.load();
+ return toast.success("success create");
+ }
+ console.log(res);
+ return toast.error("failed create");
+ } catch (error) {
+ console.log((error as Error).message);
+ } finally {
+ kolaborasiInovasiState.create.loading = false;
+ }
+ },
+ },
+ findMany: {
+ data: null as any[] | null,
+ page: 1,
+ totalPages: 1,
+ total: 0,
+ loading: false,
+ load: async (page = 1, limit = 10) => {
+ // Change to arrow function
+ kolaborasiInovasiState.findMany.loading = true; // Use the full path to access the property
+ kolaborasiInovasiState.findMany.page = page;
+ try {
+ const res = await ApiFetch.api.inovasi.kolaborasiinovasi["find-many"].get({
+ query: { page, limit },
+ });
+
+ if (res.status === 200 && res.data?.success) {
+ kolaborasiInovasiState.findMany.data = res.data.data || [];
+ kolaborasiInovasiState.findMany.total = res.data.total || 0;
+ kolaborasiInovasiState.findMany.totalPages = res.data.totalPages || 1;
+ } else {
+ console.error(
+ "Failed to load grafik berdasarkan jenis kelamin:",
+ res.data?.message
+ );
+ kolaborasiInovasiState.findMany.data = [];
+ kolaborasiInovasiState.findMany.total = 0;
+ kolaborasiInovasiState.findMany.totalPages = 1;
+ }
+ } catch (error) {
+ console.error("Error loading grafik berdasarkan jenis kelamin:", error);
+ kolaborasiInovasiState.findMany.data = [];
+ kolaborasiInovasiState.findMany.total = 0;
+ kolaborasiInovasiState.findMany.totalPages = 1;
+ } finally {
+ kolaborasiInovasiState.findMany.loading = false;
+ }
+ },
+ },
+ update: {
+ id: "",
+ form: { ...defaultForm },
+ loading: false,
+ async load(id: string) {
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+
+ try {
+ const response = await fetch(`/api/inovasi/kolaborasiinovasi/${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,
+ tahun: data.tahun,
+ slug: data.slug,
+ deskripsi: data.deskripsi,
+ kolaborator: data.kolaborator,
+ imageId: data.imageId,
+ };
+ return data;
+ } else {
+ throw new Error(result?.message || "Gagal mengambil data");
+ }
+ } catch (error) {
+ console.error("Error loading kolaborasi inovasi:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
+ }
+ },
+
+ async submit() {
+ const id = this.id;
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+ const cek = templateForm.safeParse(this.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ toast.error(err);
+ return null;
+ }
+ this.loading = true;
+ try {
+ const response = await fetch(`/api/inovasi/kolaborasiinovasi/${id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(this.form),
+ });
+ const result = await response.json();
+ if (!response.ok || !result?.success) {
+ throw new Error(result?.message || "Gagal update data");
+ }
+ toast.success("Berhasil update data!");
+ await kolaborasiInovasiState.findMany.load();
+ return result.data;
+ } catch (error) {
+ console.error("Error update data:", error);
+ toast.error("Gagal update data kolaborasi inovasi");
+ } finally {
+ this.loading = false;
+ }
+ },
+ },
+ findUnique: {
+ data: null as Prisma.KolaborasiInovasiGetPayload<{
+ omit: { isActive: true };
+ }> | null,
+ async load(id: string) {
+ try {
+ const res = await fetch(`/api/inovasi/kolaborasiinovasi/${id}`);
+ if (res.ok) {
+ const data = await res.json();
+ kolaborasiInovasiState.findUnique.data = data.data ?? null;
+ } else {
+ console.error("Failed to fetch data", res.status, res.statusText);
+ kolaborasiInovasiState.findUnique.data = null;
+ }
+ } catch (error) {
+ console.error("Error loading kolaborasi inovasi:", error);
+ kolaborasiInovasiState.findUnique.data = null;
+ }
+ },
+ },
+ delete: {
+ loading: false,
+ async byId(id: string) {
+ if (!id) return toast.warn("ID tidak valid");
+
+ try {
+ kolaborasiInovasiState.delete.loading = true;
+
+ const response = await fetch(`/api/inovasi/kolaborasiinovasi/del/${id}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ const result = await response.json();
+
+ if (response.ok && result?.success) {
+ toast.success(result.message || "Kolaborasi inovasi berhasil dihapus");
+ await kolaborasiInovasiState.findMany.load(); // refresh list
+ } else {
+ toast.error(result?.message || "Gagal menghapus kolaborasi inovasi");
+ }
+ } catch (error) {
+ console.error("Gagal delete:", error);
+ toast.error("Terjadi kesalahan saat menghapus kolaborasi inovasi");
+ } finally {
+ kolaborasiInovasiState.delete.loading = false;
+ }
+ },
+ },
+});
+
+export default kolaborasiInovasiState;
+
\ No newline at end of file
diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/edit/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/edit/page.tsx
similarity index 100%
rename from src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/edit/page.tsx
rename to src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/edit/page.tsx
diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/detail/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/page.tsx
similarity index 100%
rename from src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/detail/page.tsx
rename to src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/page.tsx
diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx
index 7344cdb1..ea59d136 100644
--- a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx
@@ -1,26 +1,84 @@
+/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
-import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
+import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import { useRouter } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import kolaborasiInovasiState from '../../_state/inovasi/kolaborasi-inovasi';
+import { useProxy } from 'valtio/utils';
function KolaborasiInovasi() {
+ const [search, setSearch] = useState('');
return (
}
+ value={search}
+ onChange={(e) => setSearch(e.currentTarget.value)}
/>
-
+
);
}
-function ListKolaborasiInovasi() {
+function ListKolaborasiInovasi({ search }: { search: string }) {
+ const listState = useProxy(kolaborasiInovasiState)
+ const { data, loading, page, totalPages, load } = listState.findMany
const router = useRouter();
+
+ useEffect(() => {
+ load(page, 10)
+ }, [page])
+
+ const filteredData = (data || []).filter(item => {
+ const keyword = search.toLowerCase();
+ return (
+ item.name.toLowerCase().includes(keyword) ||
+ item.deskripsi.toLowerCase().includes(keyword) ||
+ item.slug.toLowerCase().includes(keyword) ||
+ item.kolaborator.toLowerCase().includes(keyword)
+ );
+ });
+
+ if (loading || !data) {
+ return (
+
+
+
+ );
+ }
+ if (data.length === 0) {
+ return (
+
+
+
+
+
+
+
+ No
+ Nama Kolaborasi Inovasi
+ Tahun
+ Deskripsi Singkat
+ Detail
+
+
+
+ Tidak ada data kolaborasi inovasi yang tersedia
+
+
+
+ );
+ }
+
return (
@@ -31,26 +89,43 @@ function ListKolaborasiInovasi() {
+ No
Nama Kolaborasi Inovasi
- Image
+ Tahun
Deskripsi Singkat
Detail
-
+
-
- Kolaborasi Inovasi 1
- Image
- Deskripsi Singkat
-
-
-
-
+ {filteredData.map((item, index) => (
+
+ {index + 1}
+ {item.name}
+ {item.tahun}
+ {item.slug}
+
+
+
+
+ ))}
-
+
+
+ {
+ load(newPage, 10);
+ window.scrollTo(0, 0);
+ }}
+ total={totalPages}
+ mt="md"
+ mb="md"
+ />
+
+
);
}
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/index.ts
index 758d4203..b4ebd4bf 100644
--- a/src/app/api/[[...slugs]]/_lib/inovasi/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/index.ts
@@ -1,6 +1,7 @@
import Elysia from "elysia";
import DesaDigital from "./desa-digital";
import ProgramKreatif from "./program-kreatif";
+import KolaborasiInovasi from "./kolaborasi-inovasi";
const Inovasi = new Elysia({
prefix: "/api/inovasi",
@@ -8,5 +9,6 @@ const Inovasi = new Elysia({
})
.use(DesaDigital)
.use(ProgramKreatif)
+ .use(KolaborasiInovasi)
export default Inovasi;
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/create.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/create.ts
new file mode 100644
index 00000000..474b79a4
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/create.ts
@@ -0,0 +1,34 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+type FormCreateKolaborasiInovasi = {
+ name: string;
+ tahun: number;
+ slug: string;
+ deskripsi: string;
+ kolaborator: string;
+ imageId: string;
+}
+
+export default async function kolaborasiInovasiCreate(context: Context){
+ const body = context.body as FormCreateKolaborasiInovasi;
+
+ await prisma.kolaborasiInovasi.create({
+ data: {
+ name: body.name,
+ tahun: body.tahun,
+ slug: body.slug,
+ deskripsi: body.deskripsi,
+ kolaborator: body.kolaborator,
+ imageId: body.imageId,
+ }
+ })
+
+ return {
+ success: true,
+ message: "Success create kolaborasi inovasi",
+ data: {
+ ...body,
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/del.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/del.ts
new file mode 100644
index 00000000..272946d9
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/del.ts
@@ -0,0 +1,33 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+export default async function kolaborasiInovasiDelete(context: Context){
+ const { id } = context.params as { id: string };
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID kolaborasi inovasi diperlukan",
+ };
+ }
+
+ try {
+ const deleted = await prisma.kolaborasiInovasi.delete({
+ where: { id },
+ });
+
+ return {
+ success: true,
+ message: "Kolaborasi inovasi berhasil dihapus",
+ data: deleted,
+ };
+ } catch (error: any) {
+ console.error("Error delete kolaborasi inovasi:", error);
+ return {
+ success: false,
+ message: "Terjadi kesalahan saat menghapus kolaborasi inovasi",
+ error: error.message,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findMany.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findMany.ts
new file mode 100644
index 00000000..10c19973
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findMany.ts
@@ -0,0 +1,44 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+// Di findMany.ts
+export default async function kolaborasiInovasiFindMany(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.kolaborasiInovasi.findMany({
+ where: { isActive: true },
+ skip,
+ take: limit,
+ orderBy: { createdAt: 'desc' },
+ }),
+ prisma.kolaborasiInovasi.count({
+ where: { isActive: true }
+ })
+ ]);
+
+ const totalPages = Math.ceil(total / limit);
+
+ return {
+ success: true,
+ message: "Success fetch kolaborasi inovasi with pagination",
+ data,
+ page,
+ totalPages,
+ total,
+ };
+ } catch (e) {
+ console.error("Find many paginated error:", e);
+ return {
+ success: false,
+ message: "Failed fetch kolaborasi inovasi with pagination",
+ data: [],
+ page: 1,
+ totalPages: 1,
+ total: 0,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts
new file mode 100644
index 00000000..72872731
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts
@@ -0,0 +1,39 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+export default async function kolaborasiInovasiFindUnique(context: Context) {
+ const { id } = context.params as { id: string };
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID kolaborasi inovasi diperlukan",
+ };
+ }
+
+ try {
+ const kolaborasiInovasi = await prisma.kolaborasiInovasi.findUnique({
+ where: { id },
+ });
+
+ if (!kolaborasiInovasi) {
+ return {
+ success: false,
+ message: "Kolaborasi inovasi tidak ditemukan",
+ };
+ }
+
+ return {
+ success: true,
+ data: kolaborasiInovasi,
+ };
+ } catch (error: any) {
+ console.error("Error findUnique kolaborasi inovasi:", error);
+ return {
+ success: false,
+ message: "Gagal mengambil data kolaborasi inovasi",
+ error: error.message,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts
new file mode 100644
index 00000000..ea162530
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts
@@ -0,0 +1,45 @@
+import Elysia, { t } from "elysia";
+import kolaborasiInovasiFindMany from "./findMany";
+import kolaborasiInovasiFindUnique from "./findUnique";
+import kolaborasiInovasiCreate from "./create";
+import kolaborasiInovasiUpdate from "./updt";
+import kolaborasiInovasiDelete from "./del";
+
+const KolaborasiInovasi = new Elysia({
+ prefix: "/kolaborasiinovasi",
+ tags: ["Inovasi/Kolaborasi Inovasi"],
+})
+ .get("/find-many", kolaborasiInovasiFindMany)
+ .get("/:id", async (context) => {
+ const response = await kolaborasiInovasiFindUnique(context);
+ return response;
+ })
+ .post("/create", kolaborasiInovasiCreate, {
+ body: t.Object({
+ name: t.String(),
+ tahun: t.Number(),
+ slug: t.String(),
+ deskripsi: t.String(),
+ kolaborator: t.String(),
+ imageId: t.String(),
+ }),
+ })
+ .put(
+ "/:id",
+ async (context) => {
+ const response = await kolaborasiInovasiUpdate(context);
+ return response;
+ },
+ {
+ body: t.Object({
+ name: t.String(),
+ tahun: t.Number(),
+ slug: t.String(),
+ deskripsi: t.String(),
+ kolaborator: t.String(),
+ imageId: t.String(),
+ }),
+ }
+ )
+ .delete("/del/:id", kolaborasiInovasiDelete);
+export default KolaborasiInovasi;
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts
new file mode 100644
index 00000000..774853b9
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts
@@ -0,0 +1,51 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+type FormUpdateKolaborasiInovasi = {
+ id: string;
+ name?: string;
+ tahun?: number;
+ slug?: string;
+ deskripsi?: string;
+ kolaborator?: string;
+ imageId?: string;
+};
+export default async function kolaborasiInovasiUpdate(context: Context) {
+ const body = context.body as FormUpdateKolaborasiInovasi;
+ const id = context.params?.id; // ambil dari URL param
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID kolaborasi inovasi wajib diisi",
+ };
+ }
+
+ try {
+ const updated = await prisma.kolaborasiInovasi.update({
+ where: { id },
+ data: {
+ name: body.name,
+ tahun: body.tahun,
+ slug: body.slug,
+ deskripsi: body.deskripsi,
+ kolaborator: body.kolaborator,
+ imageId: body.imageId,
+ },
+ });
+
+ return {
+ success: true,
+ message: "Kolaborasi inovasi berhasil diupdate",
+ data: updated,
+ };
+ } catch (error: any) {
+ console.error("Error update kolaborasi inovasi:", error);
+ return {
+ success: false,
+ message: "Gagal mengupdate kolaborasi inovasi",
+ error: error.message,
+ };
+ }
+ }
\ No newline at end of file