From f63249327de7c32d414401338b22b3c5ad6a5e7f Mon Sep 17 00:00:00 2001 From: nico Date: Mon, 25 Aug 2025 16:40:03 +0800 Subject: [PATCH] Sinkronisasi UI & API Admin - User Menu Inovasi --- prisma/schema.prisma | 15 +- .../_state/inovasi/desa-digital.ts | 33 ++- .../(dashboard)/_state/inovasi/info-tekno.ts | 33 ++- .../_state/inovasi/kolaborasi-inovasi.ts | 67 +++-- .../_state/inovasi/mitra-kolaborasi.ts | 229 ++++++++++++++++++ .../desa-digital-smart-village/page.tsx | 50 ++-- .../info-teknologi-tepat-guna/page.tsx | 49 ++-- .../kolaborasi-inovasi/_lib/layoutTabs.tsx | 62 +++++ .../kolaborasi-inovasi/create/page.tsx | 155 ------------ .../inovasi/kolaborasi-inovasi/layout.tsx | 12 + .../[id]/edit/page.tsx | 54 +---- .../[id]/page.tsx | 10 +- .../list-kolaborasi-inovasi/create/page.tsx | 113 +++++++++ .../{ => list-kolaborasi-inovasi}/page.tsx | 69 +++--- .../mitra-kolaborasi/[id]/page.tsx | 148 +++++++++++ .../mitra-kolaborasi/create/page.tsx | 136 +++++++++++ .../mitra-kolaborasi/page.tsx | 187 ++++++++++++++ src/app/admin/_com/list_PageAdmin.tsx | 2 +- .../_lib/inovasi/desa-digital/findMany.ts | 69 ++++-- .../api/[[...slugs]]/_lib/inovasi/index.ts | 4 +- .../_lib/inovasi/info-teknologi/findMany.ts | 76 ++++-- .../_lib/inovasi/kolaborasi-inovasi/create.ts | 63 ++--- .../inovasi/kolaborasi-inovasi/findMany.ts | 29 ++- .../inovasi/kolaborasi-inovasi/findUnique.ts | 3 - .../_lib/inovasi/kolaborasi-inovasi/index.ts | 3 +- .../mitra-kolaborasi/create.ts | 26 ++ .../mitra-kolaborasi/del.ts | 54 +++++ .../mitra-kolaborasi/findMany.ts | 53 ++++ .../mitra-kolaborasi/findUnique.ts | 49 ++++ .../mitra-kolaborasi/index.ts | 37 +++ .../mitra-kolaborasi/updt.ts | 97 ++++++++ .../_lib/inovasi/kolaborasi-inovasi/updt.ts | 3 +- .../desa-digital-smart-village/page.tsx | 133 +++++----- .../inovasi/kolaborasi-inovasi/page.tsx | 120 ++++++--- 34 files changed, 1732 insertions(+), 511 deletions(-) create mode 100644 src/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi.ts create mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/_lib/layoutTabs.tsx delete mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/create/page.tsx create mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/layout.tsx rename src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/{ => list-kolaborasi-inovasi}/[id]/edit/page.tsx (72%) rename src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/{ => list-kolaborasi-inovasi}/[id]/page.tsx (89%) create mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/create/page.tsx rename src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/{ => list-kolaborasi-inovasi}/page.tsx (57%) create mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/[id]/page.tsx create mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create/page.tsx create mode 100644 src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/page.tsx create mode 100644 src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create.ts create mode 100644 src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/del.ts create mode 100644 src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findMany.ts create mode 100644 src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findUnique.ts create mode 100644 src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/index.ts create mode 100644 src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/updt.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 265af1ec..d2e4cc06 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -85,7 +85,6 @@ model FileStorage { KontakItem KontakItem[] Pegawai Pegawai[] DesaDigital DesaDigital[] - KolaborasiInovasi KolaborasiInovasi[] InfoTekno InfoTekno[] PengaduanMasyarakat PengaduanMasyarakat[] KegiatanDesa KegiatanDesa[] @@ -100,6 +99,8 @@ model FileStorage { DataPerpustakaan DataPerpustakaan[] PegawaiPPID PegawaiPPID[] PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[] + + MitraKolaborasi MitraKolaborasi[] } //========================================= MENU LANDING PAGE ========================================= // @@ -1644,14 +1645,22 @@ model KolaborasiInovasi { 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) } +model MitraKolaborasi { + id String @id @default(cuid()) + name 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) +} // ========================================= INFO TEKHNOLOGI TEPAT GUNA ========================================= // model InfoTekno { id String @id @default(cuid()) diff --git a/src/app/admin/(dashboard)/_state/inovasi/desa-digital.ts b/src/app/admin/(dashboard)/_state/inovasi/desa-digital.ts index 32aa09c1..83d4d5cd 100644 --- a/src/app/admin/(dashboard)/_state/inovasi/desa-digital.ts +++ b/src/app/admin/(dashboard)/_state/inovasi/desa-digital.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; @@ -55,10 +56,34 @@ const desaDigitalState = proxy({ }; }>[] | null, - async load() { - const res = await ApiFetch.api.inovasi.desadigital["find-many"].get(); - if (res.status === 200) { - desaDigitalState.findMany.data = res.data?.data ?? []; + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + desaDigitalState.findMany.loading = true; // ✅ Akses langsung via nama path + desaDigitalState.findMany.page = page; + desaDigitalState.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.inovasi.desadigital["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + desaDigitalState.findMany.data = res.data.data ?? []; + desaDigitalState.findMany.totalPages = res.data.totalPages ?? 1; + } else { + desaDigitalState.findMany.data = []; + desaDigitalState.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch desa digital paginated:", err); + desaDigitalState.findMany.data = []; + desaDigitalState.findMany.totalPages = 1; + } finally { + desaDigitalState.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/inovasi/info-tekno.ts b/src/app/admin/(dashboard)/_state/inovasi/info-tekno.ts index b6db48ec..710bbcd8 100644 --- a/src/app/admin/(dashboard)/_state/inovasi/info-tekno.ts +++ b/src/app/admin/(dashboard)/_state/inovasi/info-tekno.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; @@ -55,10 +56,34 @@ const infoTeknoState = proxy({ }; }>[] | null, - async load() { - const res = await ApiFetch.api.inovasi.infotekno["find-many"].get(); - if (res.status === 200) { - infoTeknoState.findMany.data = res.data?.data ?? []; + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + infoTeknoState.findMany.loading = true; // ✅ Akses langsung via nama path + infoTeknoState.findMany.page = page; + infoTeknoState.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.inovasi.infotekno["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + infoTeknoState.findMany.data = res.data.data ?? []; + infoTeknoState.findMany.totalPages = res.data.totalPages ?? 1; + } else { + infoTeknoState.findMany.data = []; + infoTeknoState.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch info teknologi paginated:", err); + infoTeknoState.findMany.data = []; + infoTeknoState.findMany.totalPages = 1; + } finally { + infoTeknoState.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts b/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts index 5f7fd327..50ad7168 100644 --- a/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts +++ b/src/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi.ts @@ -6,12 +6,11 @@ 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"), + name: z.string().min(1, "Nama kolaborasi inovasi harus diisi"), + tahun: z.number().min(1900, "Tahun tidak valid").max(new Date().getFullYear() + 1, "Tahun tidak boleh lebih dari tahun depan"), + slug: z.string().min(1, "Slug harus dihasilkan otomatis"), + deskripsi: z.string().min(1, "Deskripsi harus diisi"), + kolaborator: z.string().min(1, "Kolaborator harus diisi"), }) const defaultForm = { @@ -20,7 +19,6 @@ const defaultForm = { slug: "", deskripsi: "", kolaborator: "", - imageId: "", } const kolaborasiInovasiState = proxy({ @@ -28,27 +26,37 @@ const kolaborasiInovasiState = proxy({ 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 { + // Validate form + const validation = templateForm.safeParse(kolaborasiInovasiState.create.form); + if (!validation.success) { + const errorMessages = validation.error.issues + .map(issue => `- ${issue.path.join('.')}: ${issue.message}`) + .join('\n'); + return toast.error(`Validasi gagal:\n${errorMessages}`); + } + 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"); + await kolaborasiInovasiState.findMany.load(); + return { success: true, data: res.data }; } - console.log(res); - return toast.error("failed create"); + + console.error('Create failed:', res); + toast.error(res.data?.message || "Gagal menyimpan data"); + return { success: false, error: res.data }; } catch (error) { - console.log((error as Error).message); + console.error('Error in create:', error); + toast.error("Terjadi kesalahan saat menyimpan data"); + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }; } finally { kolaborasiInovasiState.create.loading = false; } @@ -60,13 +68,21 @@ const kolaborasiInovasiState = proxy({ 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 + search: "", + year: "", + load: async (page = 1, limit = 10, search = "", year?: string) => { + kolaborasiInovasiState.findMany.loading = true; kolaborasiInovasiState.findMany.page = page; + kolaborasiInovasiState.findMany.search = search; + kolaborasiInovasiState.findMany.year = year || ""; + try { + const query: any = { page, limit }; + if (search) query.search = search; + if (year) query.year = year; + const res = await ApiFetch.api.inovasi.kolaborasiinovasi["find-many"].get({ - query: { page, limit }, + query, }); if (res.status === 200 && res.data?.success) { @@ -124,7 +140,6 @@ const kolaborasiInovasiState = proxy({ slug: data.slug, deskripsi: data.deskripsi, kolaborator: data.kolaborator, - imageId: data.imageId, }; return data; } else { @@ -179,7 +194,7 @@ const kolaborasiInovasiState = proxy({ }, findUnique: { data: null as Prisma.KolaborasiInovasiGetPayload<{ - include: { image: true }; + omit: { isActive: true }; }> | null, async load(id: string) { try { diff --git a/src/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi.ts b/src/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi.ts new file mode 100644 index 00000000..d931135e --- /dev/null +++ b/src/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi.ts @@ -0,0 +1,229 @@ +/* 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 mitraKolaborasiForm = z.object({ + name: z.string().min(1, { message: "Name is required" }), + imageId: z.string().nonempty(), +}); + +const defaultForm = { + name: "", + imageId: "", +}; + +const mitraKolaborasi = proxy({ + create: { + form: { ...defaultForm }, + loading: false, + async create() { + const cek = mitraKolaborasiForm.safeParse(mitraKolaborasi.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + mitraKolaborasi.create.loading = true; + const res = await ApiFetch.api.inovasi.mitrakolaborasi["create"].post( + mitraKolaborasi.create.form + ); + if (res.status === 200) { + mitraKolaborasi.findMany.load(); + return toast.success("mitraKolaborasi berhasil disimpan!"); + } + return toast.error("Gagal menyimpan mitraKolaborasi"); + } catch (error) { + console.log((error as Error).message); + } finally { + mitraKolaborasi.create.loading = false; + } + }, + resetForm() { + mitraKolaborasi.create.form = { ...defaultForm }; + }, + }, + findMany: { + data: null as + | Prisma.MitraKolaborasiGetPayload<{ + include: { + image: true; + }; + }>[] + | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + mitraKolaborasi.findMany.loading = true; // ✅ Akses langsung via nama path + mitraKolaborasi.findMany.page = page; + mitraKolaborasi.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.inovasi.mitrakolaborasi["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + mitraKolaborasi.findMany.data = res.data.data ?? []; + mitraKolaborasi.findMany.totalPages = res.data.totalPages ?? 1; + } else { + mitraKolaborasi.findMany.data = []; + mitraKolaborasi.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch mitraKolaborasi paginated:", err); + mitraKolaborasi.findMany.data = []; + mitraKolaborasi.findMany.totalPages = 1; + } finally { + mitraKolaborasi.findMany.loading = false; + } + }, + }, + findUnique: { + data: null as Prisma.MitraKolaborasiGetPayload<{ + include: { + image: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/inovasi/mitrakolaborasi/${id}`); + if (res.ok) { + const data = await res.json(); + mitraKolaborasi.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch mitraKolaborasi:", res.statusText); + mitraKolaborasi.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching mitraKolaborasi:", error); + mitraKolaborasi.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + mitraKolaborasi.delete.loading = true; + const response = await fetch(`/api/inovasi/mitrakolaborasi/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "mitraKolaborasi berhasil dihapus"); + await mitraKolaborasi.findMany.load(); // refresh list + } else { + toast.error(result.message || "Gagal menghapus mitraKolaborasi"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus mitraKolaborasi"); + } finally { + mitraKolaborasi.delete.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/mitrakolaborasi/${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, + imageId: data.imageId, + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading mitraKolaborasi:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = mitraKolaborasiForm.safeParse(mitraKolaborasi.update.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + try { + mitraKolaborasi.update.loading = true; + const response = await fetch(`/api/inovasi/mitrakolaborasi/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + imageId: this.form.imageId, + }), + }); + 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(result.message || "mitraKolaborasi berhasil diupdate"); + await mitraKolaborasi.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal mengupdate mitraKolaborasi"); + } + } catch (error) { + console.error("Error updating mitraKolaborasi:", error); + toast.error( + error instanceof Error ? error.message : "Gagal mengupdate mitraKolaborasi" + ); + return false; + } finally { + mitraKolaborasi.update.loading = false; + } + }, + reset() { + mitraKolaborasi.update.id = ""; + mitraKolaborasi.update.form = { ...defaultForm }; + }, + }, +}); + +export default mitraKolaborasi; diff --git a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx index bfc71f1e..c563b15e 100644 --- a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx +++ b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx @@ -1,6 +1,6 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { Box, Button, Center, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Pagination } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -29,19 +29,22 @@ function DesaDigitalSmartVillage() { function ListDesaDigitalSmartVillage({ search }: { search: string }) { const state = useProxy(desaDigitalState) const router = useRouter() + + const { + data, + page, + totalPages, + loading, + load, + } = state.findMany + useShallowEffect(() => { - state.findMany.load() - }, []) + load(page, 10, search) + }, [page, search]) - const filteredData = (state.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.deskripsi.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!state.findMany.data) { + if (loading || !data) { return ( @@ -68,18 +71,27 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) { {item.name} - + - - - - - ))} + + + + + ))} +
+ load(newPage)} // ini penting! + total={totalPages} + mt="md" + mb="md" + /> +
); } diff --git a/src/app/admin/(dashboard)/inovasi/info-teknologi-tepat-guna/page.tsx b/src/app/admin/(dashboard)/inovasi/info-teknologi-tepat-guna/page.tsx index d6cd23b8..b3ec93a5 100644 --- a/src/app/admin/(dashboard)/inovasi/info-teknologi-tepat-guna/page.tsx +++ b/src/app/admin/(dashboard)/inovasi/info-teknologi-tepat-guna/page.tsx @@ -1,6 +1,6 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -29,19 +29,22 @@ function InfoTeknologiTepatGuna() { function ListInfoTeknologiTepatGuna({ search }: { search: string }) { const state = useProxy(infoTeknoState) const router = useRouter() + + const { + data, + page, + totalPages, + loading, + load, + } = state.findMany + useShallowEffect(() => { - state.findMany.load() - }, []) + load(page, 10, search) + }, [page, search]) - const filteredData = (state.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.deskripsi.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!state.findMany.data) { + if (loading || !data) { return ( @@ -68,17 +71,25 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) { {item.name} - + - - - - - ))} + + + + + ))} +
+ load(newPage)} // ini penting! + total={totalPages} + my="md" + /> +
); diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/_lib/layoutTabs.tsx new file mode 100644 index 00000000..4a661c4e --- /dev/null +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/_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 LayoutTabsKolaborasi({ children }: { children: React.ReactNode }) { + const router = useRouter() + const pathname = usePathname() + const tabs = [ + { + label: "List Kolaborasi Inovasi", + value: "listkolaborasiinovasi", + href: "/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi" + }, + { + label: "Mitra Kolaborasi", + value: "mitarakolaborasi", + href: "/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi" + } + ]; + 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 ( + + Kolaborasi Inovasi + + + {tabs.map((e, i) => ( + {e.label} + ))} + + {tabs.map((e, i) => ( + + {/* Konten dummy, bisa diganti tergantung routing */} + <> + + ))} + + {children} + + ); +} + +export default LayoutTabsKolaborasi; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/create/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/create/page.tsx deleted file mode 100644 index 5aa364f0..00000000 --- a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/create/page.tsx +++ /dev/null @@ -1,155 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import { useProxy } from 'valtio/utils'; -import CreateEditor from '../../../_com/createEditor'; -import kolaborasiInovasiState from '../../../_state/inovasi/kolaborasi-inovasi'; -import { useState } from 'react'; -import { toast } from 'react-toastify'; -import ApiFetch from '@/lib/api-fetch'; -import { Dropzone } from '@mantine/dropzone'; - - -function CreateProgramKreatifDesa() { - const stateCreate = useProxy(kolaborasiInovasiState) - const router = useRouter(); - const [previewImage, setPreviewImage] = useState(null); - const [file, setFile] = useState(null); - - const resetForm = () => { - stateCreate.create.form = { - name: "", - tahun: 0, - slug: "", - deskripsi: "", - kolaborator: "", - imageId: "", - } - - setPreviewImage(null); - setFile(null); - } - - const handleSubmit = async () => { - if (!file) { - return toast.warn("Pilih file gambar terlebih dahulu"); - } - - // Upload gambar dulu - const res = await ApiFetch.api.fileStorage.create.post({ - file, - name: file.name, - }); - - const uploaded = res.data?.data; - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } - - // Simpan ID gambar ke form - stateCreate.create.form.imageId = uploaded.id; - - // Submit data berita - await stateCreate.create.create(); - - // Reset form setelah submit - resetForm(); - router.push("/admin/inovasi/kolaborasi-inovasi") - } - return ( - - - - - - - - Create Kolaborasi Inovasi - Nama Kolaborasi Inovasi} - placeholder="masukkan nama kolaborasi inovasi" - onChange={(val) => stateCreate.create.form.name = val.target.value} - /> - Tahun} - placeholder="masukkan tahun" - onChange={(val) => stateCreate.create.form.tahun = parseInt(val.target.value)} - /> - stateCreate.create.form.slug = e.currentTarget.value} - label={Deskripsi Singkat Kolaborasi Inovasi} - placeholder='Masukkan deskripsi singkat kolaborasi inovasi' - /> - stateCreate.create.form.kolaborator = e.currentTarget.value} - label={Kolaborator} - placeholder='Masukkan kolaborator' - /> - - - Gambar - - { - const newImages = files.map((file) => ({ - file, - preview: URL.createObjectURL(file), - label: '', - })); - setFile(newImages[0].file); - setPreviewImage(newImages[0].preview); // ← ini yang kurang - }} - - > - - - - - - - - - - -
- - Drag images here or click to select files - - - Attach as many files as you like, each file should not exceed 5mb - -
-
-
-
- {previewImage ? ( - - ) : ( -
- -
- )} -
-
- - Deskripsi Kolaborasi Inovasi - stateCreate.create.form.deskripsi = htmlContent} - /> - - - - -
-
-
- ); -} - -export default CreateProgramKreatifDesa; diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/layout.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/layout.tsx new file mode 100644 index 00000000..5e639e29 --- /dev/null +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/layout.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import LayoutTabsKolaborasi from './_lib/layoutTabs'; + +function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +export default Layout; diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/edit/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/[id]/edit/page.tsx similarity index 72% rename from src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/edit/page.tsx rename to src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/[id]/edit/page.tsx index 0955a093..4022cdc7 100644 --- a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/[id]/edit/page.tsx @@ -2,41 +2,34 @@ "use client"; import EditEditor from "@/app/admin/(dashboard)/_com/editEditor"; +import kolaborasiInovasiState from "@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi"; import colors from "@/con/colors"; -import ApiFetch from "@/lib/api-fetch"; import { Box, Button, - Center, - FileInput, - Image, Paper, Stack, Text, TextInput, Title } from "@mantine/core"; -import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react"; +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"; -import kolaborasiInovasiState from "@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi"; function EditKolaborasiInovasi() { const kolaborasiState = useProxy(kolaborasiInovasiState); const router = useRouter(); const params = useParams(); - const [previewImage, setPreviewImage] = useState(null); - const [file, setFile] = useState(null); const [formData, setFormData] = useState({ name: kolaborasiState.update.form.name || '', deskripsi: kolaborasiState.update.form.deskripsi || '', tahun: kolaborasiState.update.form.tahun || '', slug: kolaborasiState.update.form.slug || '', kolaborator: kolaborasiState.update.form.kolaborator || '', - imageId: kolaborasiState.update.form.imageId || '' }); // Load berita by id saat pertama kali @@ -54,13 +47,7 @@ function EditKolaborasiInovasi() { tahun: data.tahun || '', slug: data.slug || '', kolaborator: data.kolaborator || '', - imageId: data.imageId || '', }); - if (data.image) { - if (data?.image?.link) { - setPreviewImage(data.image.link); - } - } } } catch (error) { console.error("Error loading berita:", error); @@ -82,22 +69,7 @@ function EditKolaborasiInovasi() { tahun: Number(formData.tahun), slug: formData.slug, kolaborator: formData.kolaborator, - imageId: formData.imageId // Keep existing imageId if not changed }; - - // Jika ada file baru, upload - if (file) { - const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name }); - const uploaded = res.data?.data; - - if (!uploaded?.id) { - return toast.error("Gagal upload gambar"); - } - - // Update imageId in global state - kolaborasiState.update.form.imageId = uploaded.id; - } - await kolaborasiState.update.submit(); toast.success("Berita berhasil diperbarui!"); router.push("/admin/inovasi/kolaborasi-inovasi"); @@ -144,28 +116,6 @@ function EditKolaborasiInovasi() { label={Kolaborator} placeholder="masukkan kolaborator" /> - - Upload Gambar Baru (Opsional)} - value={file} - onChange={async (e) => { - if (!e) return; - setFile(e); - const base64 = await e.arrayBuffer().then((buf) => - "data:image/png;base64," + Buffer.from(buf).toString("base64") - ); - setPreviewImage(base64); - }} - /> - - {previewImage ? ( - - ) : ( -
- -
- )} - Konten Deskripsi - - Gambar - gambar - Kolaborator {kolaborasiState.findUnique.data?.kolaborator} diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/create/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/create/page.tsx new file mode 100644 index 00000000..4a0d2615 --- /dev/null +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/create/page.tsx @@ -0,0 +1,113 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; +import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi'; +import colors from '@/con/colors'; +import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { YearPickerInput } from '@mantine/dates'; +import { IconArrowBack } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; + + + +function CreateProgramKreatifDesa() { + const stateCreate = useProxy(kolaborasiInovasiState) + const router = useRouter(); + + const resetForm = () => { + stateCreate.create.form = { + name: "", + tahun: 0, + slug: "", + deskripsi: "", + kolaborator: "", + } + } + + // Generate slug from name + useEffect(() => { + const { name } = stateCreate.create.form; + if (name) { + const slug = name + .toLowerCase() + .replace(/[^\w\s-]/g, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-'); + stateCreate.create.form.slug = slug; + } + }, [stateCreate.create.form.name]); + + const handleSubmit = async () => { + try { + // Submit data kolaborasi inovasi + await stateCreate.create.create(); + + // Reset form setelah submit + resetForm(); + router.push("/admin/inovasi/kolaborasi-inovasi"); + toast.success("Berhasil menambahkan kolaborasi inovasi"); + } catch (error) { + console.error("Error creating kolaborasi inovasi:", error); + toast.error("Terjadi kesalahan saat menyimpan data"); + } + } + return ( + + + + + + + + Create Kolaborasi Inovasi + Nama Kolaborasi Inovasi} + placeholder="masukkan nama kolaborasi inovasi" + onChange={(val) => stateCreate.create.form.name = val.target.value} + /> + { + const year = dateString ? new Date(dateString).getFullYear() : 0; + stateCreate.create.form.tahun = year; + }} + /> + + Deskripsi + { + stateCreate.create.form.deskripsi = val; + }} + /> + + stateCreate.create.form.kolaborator = e.currentTarget.value} + label={Kolaborator} + placeholder='Masukkan kolaborator' + /> + + Deskripsi Kolaborasi Inovasi + stateCreate.create.form.deskripsi = htmlContent} + /> + + + + + + + + ); +} + +export default CreateProgramKreatifDesa; diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/page.tsx similarity index 57% rename from src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx rename to src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/page.tsx index 60fc9a59..b065f34b 100644 --- a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/page.tsx +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi/page.tsx @@ -3,11 +3,11 @@ import colors from '@/con/colors'; 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 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 kolaborasiInovasiState from '../../../_state/inovasi/kolaborasi-inovasi'; import { useProxy } from 'valtio/utils'; function KolaborasiInovasi() { @@ -28,22 +28,21 @@ function KolaborasiInovasi() { 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 { + data, + loading, + page, + totalPages, + load, + } = listState.findMany - 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) - ); - }); + useEffect(() => { + load(page, 10, search) + }, [page, search]) + + const filteredData = data || [] if (loading || !data) { return ( @@ -64,11 +63,11 @@ function ListKolaborasiInovasi({ search }: { search: string }) { - No - Nama Kolaborasi Inovasi - Tahun - Deskripsi Singkat - Detail + No + Nama Kolaborasi Inovasi + Tahun + Deskripsi Singkat + Detail
@@ -89,21 +88,21 @@ function ListKolaborasiInovasi({ search }: { search: string }) { - No - Nama Kolaborasi Inovasi - Tahun - Deskripsi Singkat - Detail + No + Nama Kolaborasi Inovasi + Tahun + Deskripsi Singkat + Detail {filteredData.map((item, index) => ( - {index + 1} - {item.name} - {item.tahun} - {item.slug} - + {index + 1} + {item.name} + {item.tahun} + {item.slug} + @@ -116,17 +115,13 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
{ - load(newPage, 10); - window.scrollTo(0, 0); - }} + onChange={(newPage) => load(newPage)} // ini penting! total={totalPages} mt="md" mb="md" />
- - + ); } diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/[id]/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/[id]/page.tsx new file mode 100644 index 00000000..99f3fa7a --- /dev/null +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/[id]/page.tsx @@ -0,0 +1,148 @@ +'use client' +/* eslint-disable react-hooks/exhaustive-deps */ +import mitraKolaborasi from '@/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Dropzone } from '@mantine/dropzone'; +import { IconArrowBack, IconImageInPicture, 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'; + + +function EditFoto() { + const state = useProxy(mitraKolaborasi) + const router = useRouter(); + const params = useParams(); + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + const [formData, setFormData] = useState({ + name: state.update.form.name || '', + imageId: state.update.form.imageId || '' + }); + + useEffect(() => { + const loadFoto = async () => { + const id = params?.id as string; + if (!id) return; + try { + const data = await state.update.load(id); + if (data) { + setFormData({ + name: data.name || '', + imageId: data.imageId || '' + }); + if (data?.image?.link) { + setPreviewImage(data.image.link); + } + } + } catch (error) { + console.error('Error loading foto:', error); + toast.error('Gagal memuat data foto'); + } + }; + loadFoto(); + }, [params?.id]); + + const handleSubmit = async () => { + try { + state.update.form = { + ...state.update.form, + name: formData.name, + imageId: formData.imageId + }; + 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"); + } + state.update.form.imageId = uploaded.id; + } + await state.update.update(); + toast.success('Mitra berhasil diperbarui!'); + router.push('/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi'); + } catch (error) { + console.error('Error updating mitra:', error); + toast.error('Terjadi kesalahan saat memperbarui mitra'); + } + }; + + return ( + + + + + + + + Edit Mitra + Nama Mitra} + placeholder='Masukkan nama mitra' + value={formData.name} + onChange={(e) => + (formData.name = e.target.value) + } + /> + + Upload Foto + { + const selectedFile = files[0]; // Ambil file pertama + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview + } + }} + onReject={() => toast.error('File tidak valid.')} + maxSize={5 * 1024 ** 2} // Maks 5MB + accept={{ 'image/*': [] }} + > + + + + + + + + + + + +
+ + Drag gambar ke sini atau klik untuk pilih file + + + Maksimal 5MB dan harus format gambar + +
+
+
+ + {previewImage ? ( + + ) : ( +
+ +
+ )} +
+ + + +
+
+
+ ); +} + +export default EditFoto; diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create/page.tsx new file mode 100644 index 00000000..f51df81d --- /dev/null +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create/page.tsx @@ -0,0 +1,136 @@ +'use client' +import mitraKolaborasi from '@/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Dropzone } from '@mantine/dropzone'; +import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; + + + +function CreateFoto() { + const state = useProxy(mitraKolaborasi) + const router = useRouter(); + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + + const resetForm = () => { + state.create.form = { + name: "", + imageId: "", + }; + + setPreviewImage(null) + setFile(null) + }; + + const handleSubmit = async () => { + if (!file) { + return toast.warn("Pilih file gambar terlebih dahulu"); + } + + 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"); + } + + state.create.form.imageId = uploaded.id; + await state.create.create(); + resetForm(); + router.push("/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi") + }; + + return ( + + + + + + + + Create Mitra + Nama Mitra} + placeholder='Masukkan nama mitra' + value={state.create.form.name} + onChange={(val) => { + state.create.form.name = val.target.value; + }} + /> + + Gambar + + { + const selectedFile = files[0]; // Ambil file pertama + if (selectedFile) { + setFile(selectedFile); + setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview + } + }} + onReject={() => toast.error('File tidak valid.')} + maxSize={5 * 1024 ** 2} // Maks 5MB + accept={{ 'image/*': [] }} + > + + + + + + + + + + + +
+ + Drag gambar ke sini atau klik untuk pilih file + + + Maksimal 5MB dan harus format gambar + +
+
+
+ + {/* Tampilkan preview kalau ada */} + {previewImage && ( + + + + )} + +
+
+ + + +
+
+
+ ); +} + +export default CreateFoto; diff --git a/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/page.tsx b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/page.tsx new file mode 100644 index 00000000..1f3887cb --- /dev/null +++ b/src/app/admin/(dashboard)/inovasi/kolaborasi-inovasi/mitra-kolaborasi/page.tsx @@ -0,0 +1,187 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import colors from '@/con/colors'; +import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { IconEdit, IconSearch, IconX } 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 mitraKolaborasi from '../../../_state/inovasi/mitra-kolaborasi'; +import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; + +function MitraKolaborasi() { + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListMitraKolaborasi({ search }: { search: string }) { + const listState = useProxy(mitraKolaborasi) + const router = useRouter(); + const [modalHapus, setModalHapus] = useState(false) + const [selectedId, setSelectedId] = useState(null) + + const handleHapus = () => { + if (selectedId) { + mitraKolaborasi.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) + router.push("/admin/inovasi/kolaborasi-inovasi") + } + } + + const { + data, + loading, + page, + totalPages, + load, + } = listState.findMany + + useEffect(() => { + load(page, 10, search) + }, [page, search]) + + const filteredData = data || [] + + if (loading || !data) { + return ( + + + + ); + } + if (data.length === 0) { + return ( + + + + +
+ + + No + Nama Mitra + Image + Delete + Edit + + +
+ Tidak ada data mitra kolaborasi yang tersedia +
+ + + ); + } + + return ( + + + + + + + No + Nama Mitra + Image + Delete + Edit + + + + {filteredData.map((item, index) => ( + + {index + 1} + {item.name} + + + + + + + + + + + + + ))} + +
+
+
+ load(newPage)} // ini penting! + total={totalPages} + mt="md" + mb="md" + /> +
+ {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus mitra kolaborasi ini?' + /> +
+ ); +} + +export default MitraKolaborasi; diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index ce110221..ac60f979 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -286,7 +286,7 @@ export const navBar = [ { id: "Inovasi_4", name: "Kolaborasi Inovasi", - path: "/admin/inovasi/kolaborasi-inovasi" + path: "/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi" }, { id: "Inovasi_5", diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/desa-digital/findMany.ts b/src/app/api/[[...slugs]]/_lib/inovasi/desa-digital/findMany.ts index 93338696..bfa4fdec 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/desa-digital/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/desa-digital/findMany.ts @@ -1,23 +1,54 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function desaDigitalFindMany() { - try { - const data = await prisma.desaDigital.findMany({ - include: { - image: true, - }, - }); +export default async function desaDigitalFindMany(context: Context) { + // Ambil parameter dari query + const page = Number(context.query.page) || 1; + const limit = Number(context.query.limit) || 10; + const search = (context.query.search as string) || ''; + const skip = (page - 1) * limit; - return { - success: true, - message: "Success fetch desa digital", - data, - }; - } catch (error) { - console.error("Find many error:", error); - return { - success: false, - message: "Failed fetch desa digital", - }; - } + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + { deskripsi: { contains: search, mode: 'insensitive' } }, + ]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.desaDigital.findMany({ + where, + include: { + image: true, + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.desaDigital.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil desa digital dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }; + } catch (e) { + console.error("Error di findMany paginated:", e); + return { + success: false, + message: "Gagal mengambil data desa digital", + }; + } } \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/index.ts index 5abb4978..952a3835 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/index.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/index.ts @@ -5,6 +5,7 @@ import KolaborasiInovasi from "./kolaborasi-inovasi"; import InfoTekno from "./info-teknologi"; import AjukanIdeInovatif from "./ajukan-ide-inovatif"; import LayananOnlineDesa from "./layanan-online-desa"; +import MitraKolaborasi from "./kolaborasi-inovasi/mitra-kolaborasi"; const Inovasi = new Elysia({ prefix: "/api/inovasi", @@ -16,5 +17,6 @@ const Inovasi = new Elysia({ .use(InfoTekno) .use(AjukanIdeInovatif) .use(LayananOnlineDesa) - + .use(MitraKolaborasi) + export default Inovasi; diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/info-teknologi/findMany.ts b/src/app/api/[[...slugs]]/_lib/inovasi/info-teknologi/findMany.ts index 90311299..6334d2ce 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/info-teknologi/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/info-teknologi/findMany.ts @@ -1,23 +1,61 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function infoTeknoFindMany() { - try { - const data = await prisma.infoTekno.findMany({ - include: { - image: true, - }, - }); +// Di findMany.ts +export default async function infoTeknoFindMany(context: Context) { + const page = Number(context.query.page) || 1; + const limit = Number(context.query.limit) || 10; + const search = (context.query.search as string) || ''; + const skip = (page - 1) * limit; - return { - success: true, - message: "Success fetch info teknologi", - data, - }; - } catch (error) { - console.error("Find many error:", error); - return { - success: false, - message: "Failed fetch info teknologi", - }; - } + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + { deskripsi: { contains: search, mode: 'insensitive' } }, + ]; + } + + + try { + const [data, total] = await Promise.all([ + prisma.infoTekno.findMany({ + where, + include: { + image: true, + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.infoTekno.count({ + where + }) + ]); + + const totalPages = Math.ceil(total / limit); + + return { + success: true, + message: "Success fetch info teknologi with pagination", + data, + page, + totalPages, + total, + }; + } catch (e) { + console.error("Find many paginated error:", e); + return { + success: false, + message: "Failed fetch info teknologi 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/create.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/create.ts index 474b79a4..52f8c0b4 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/create.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/create.ts @@ -1,34 +1,37 @@ import prisma from "@/lib/prisma"; import { Context } from "elysia"; +import { Prisma } from "@prisma/client"; -type FormCreateKolaborasiInovasi = { - name: string; - tahun: number; - slug: string; - deskripsi: string; - kolaborator: string; - imageId: string; +// Define validation schema +type FormCreateKolaborasiInovasi = Prisma.KolaborasiInovasiGetPayload<{ + select: { + name: true; + tahun: true; + slug: true; + deskripsi: true; + kolaborator: true; + }; +}>; + +export default async function kolaborasiInovasiCreate(context: Context) { + const body = context.body as FormCreateKolaborasiInovasi; + + // Create new kolaborasi inovasi + await prisma.kolaborasiInovasi.create({ + data: { + name: body.name, + tahun: body.tahun, + slug: body.slug, + deskripsi: body.deskripsi, + kolaborator: body.kolaborator, + }, + }); + + return { + success: true, + message: "Berhasil membuat kolaborasi inovasi", + data: { + ...body, + }, + }; } - -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/findMany.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findMany.ts index 10c19973..20d9a329 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findMany.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import prisma from "@/lib/prisma"; import { Context } from "elysia"; @@ -5,18 +6,42 @@ import { Context } from "elysia"; export default async function kolaborasiInovasiFindMany(context: Context) { const page = Number(context.query.page) || 1; const limit = Number(context.query.limit) || 10; + const search = (context.query.search as string) || ''; + const year = (context.query.year as string) || ''; const skip = (page - 1) * limit; + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan filter tahun (jika ada) + if (year) { + const startDate = new Date(parseInt(year), 0, 1); // 1 Januari tahun tersebut + const endDate = new Date(parseInt(year) + 1, 0, 1); // 1 Januari tahun berikutnya + where.createdAt = { + gte: startDate, + lt: endDate, + }; + } + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + { slug: { contains: search, mode: 'insensitive' } }, + ]; + } + + try { const [data, total] = await Promise.all([ prisma.kolaborasiInovasi.findMany({ - where: { isActive: true }, + where, skip, take: limit, orderBy: { createdAt: 'desc' }, }), prisma.kolaborasiInovasi.count({ - where: { isActive: true } + where }) ]); diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts index 5349bdcb..72872731 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/findUnique.ts @@ -15,9 +15,6 @@ export default async function kolaborasiInovasiFindUnique(context: Context) { try { const kolaborasiInovasi = await prisma.kolaborasiInovasi.findUnique({ where: { id }, - include: { - image: true, - } }); if (!kolaborasiInovasi) { diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts index ea162530..d0e44c67 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/index.ts @@ -21,7 +21,6 @@ const KolaborasiInovasi = new Elysia({ slug: t.String(), deskripsi: t.String(), kolaborator: t.String(), - imageId: t.String(), }), }) .put( @@ -37,7 +36,7 @@ const KolaborasiInovasi = new Elysia({ slug: t.String(), deskripsi: t.String(), kolaborator: t.String(), - imageId: t.String(), + }), } ) diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create.ts new file mode 100644 index 00000000..ea670867 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create.ts @@ -0,0 +1,26 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreate = { + name: string; + imageId: string; +}; + +export default async function mitraKolaborasiCreate(context: Context) { + const body = context.body as FormCreate; + + await prisma.mitraKolaborasi.create({ + data: { + name: body.name, + imageId: body.imageId, + }, + }); + + return { + success: true, + message: "Berhasil membuat mitra kolaborasi", + data: { + ...body, + }, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/del.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/del.ts new file mode 100644 index 00000000..04dc8f4f --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/del.ts @@ -0,0 +1,54 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; +import fs from "fs/promises"; +import path from "path"; + +export default async function mitraKolaborasiDelete(context: Context) { + const id = context.params?.id as string; + + if (!id) { + return { + status: 400, + body: "ID tidak diberikan", + }; + } + + const mitraKolaborasi = await prisma.mitraKolaborasi.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!mitraKolaborasi) { + return { + status: 404, + body: "Mitra kolaborasi tidak ditemukan", + }; + } + + if (mitraKolaborasi.image) { + try { + const filePath = path.join( + mitraKolaborasi.image.path, + mitraKolaborasi.image.name + ); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: mitraKolaborasi.image.id }, + }); + } catch (error) { + console.error("Gagal hapus file image:", error); + } + } + + await prisma.mitraKolaborasi.delete({ + where: { id }, + }); + + return { + success: true, + message: "Mitra kolaborasi berhasil dihapus", + status: 200, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findMany.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findMany.ts new file mode 100644 index 00000000..fef63fdc --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findMany.ts @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +export default async function mitraKolaborasiFindMany(context: Context) { + // Ambil parameter dari query + const page = Number(context.query.page) || 1; + const limit = Number(context.query.limit) || 10; + const search = (context.query.search as string) || ''; + const skip = (page - 1) * limit; + + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search) { + where.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + ]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.mitraKolaborasi.findMany({ + where, + include: { + image: true, + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.mitraKolaborasi.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil mitra kolaborasi dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }; + } catch (e) { + console.error("Error di findMany paginated:", e); + return { + success: false, + message: "Gagal mengambil data mitra kolaborasi", + }; + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findUnique.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findUnique.ts new file mode 100644 index 00000000..80265c5b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/findUnique.ts @@ -0,0 +1,49 @@ +import prisma from "@/lib/prisma"; + +export default async function mitraKolaborasiFindUnique(request: Request) { + const url = new URL(request.url); + const pathSegments = url.pathname.split("/"); + const id = pathSegments[pathSegments.length - 1]; + + if (!id) { + return Response.json({ + success: false, + message: "ID tidak ditemukan", + }, {status: 400}); + } + + try { + if (typeof id !== 'string') { + return Response.json({ + success: false, + message: "ID tidak valid", + }, {status: 400}); + } + + const data = await prisma.mitraKolaborasi.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!data) { + return Response.json({ + success: false, + message: "Mitra kolaborasi tidak ditemukan", + }, {status: 404}); + } + + return Response.json({ + success: true, + message: "Success fetch mitra kolaborasi by ID", + data, + }, {status: 200}); + } catch (error) { + console.error("Find by ID error:", error); + return Response.json({ + success: false, + message: "Gagal mengambil mitra kolaborasi: " + (error instanceof Error ? error.message : 'Unknown error'), + }, {status: 500}); + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/index.ts new file mode 100644 index 00000000..29c35638 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/index.ts @@ -0,0 +1,37 @@ +import Elysia, { t } from "elysia"; +import mitraKolaborasiCreate from "./create"; +import mitraKolaborasiDelete from "./del"; +import mitraKolaborasiUpdate from "./updt"; +import mitraKolaborasiFindUnique from "./findUnique"; +import mitraKolaborasiFindMany from "./findMany"; + +const MitraKolaborasi = new Elysia({ + prefix: "/mitrakolaborasi", + tags: ["Inovasi/Mitra Kolaborasi"], +}) + .post("/create", mitraKolaborasiCreate, { + body: t.Object({ + name: t.String(), + imageId: t.String(), + }), + }) + .get("/find-many", mitraKolaborasiFindMany) + .get("/:id", async (context) => { + const response = await mitraKolaborasiFindUnique(context.request); + return response; + }) + .delete("/del/:id", mitraKolaborasiDelete) + .put( + "/:id", + async (context) => { + const response = await mitraKolaborasiUpdate(context); + return response; + }, + { + body: t.Object({ + name: t.String(), + imageId: t.String(), + }), + } + ); +export default MitraKolaborasi; diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/updt.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/updt.ts new file mode 100644 index 00000000..f3edbecc --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/mitra-kolaborasi/updt.ts @@ -0,0 +1,97 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; +import fs from "fs/promises"; +import path from "path"; + +type FormUpdate = { + id: string; + name: string; + imageId: string; +} +export default async function mitraKolaborasiUpdate(context: Context) { + try { + const id = context.params?.id as string; + const body = (await context.body) as Omit; + + const { + name, + imageId + } = body; + + if (!id) { + return new Response(JSON.stringify({ + success: false, + message: "ID tidak ditemukan", + }), { + status: 400, + headers: { + 'Content-Type': 'application/json' + } + }) + } + + const existing = await prisma.mitraKolaborasi.findUnique({ + where: {id}, + include: { + image: true, + } + }) + + if (!existing) { + return new Response(JSON.stringify({ + success: false, + message: "mitra kolaborasi tidak ditemukan", + }), { + status: 404, + headers: { + 'Content-Type': 'application/json' + } + }) + } + + if (existing.imageId && existing.imageId !== imageId) { + const oldImage = existing.image; + if (oldImage) { + try { + const filePath = path.join(oldImage.path, oldImage.name); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: oldImage.id }, + }); + } catch (error) { + console.error("Gagal hapus gambar lama:", error); + } + } + } + + const updated = await prisma.mitraKolaborasi.update({ + where: { id }, + data: { + name, + imageId, + } + }) + + return new Response(JSON.stringify({ + success: true, + message: "Mitra kolaborasi berhasil diupdate", + data: updated, + }), { + status: 200, + headers: { + 'Content-Type': 'application/json' + } + }) + } catch (error) { + console.error("Error updating mitra kolaborasi:", error); + return new Response(JSON.stringify({ + success: false, + message: "Terjadi kesalahan saat mengupdate mitra kolaborasi", + }), { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + }) + } + } \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts index 774853b9..f24d825f 100644 --- a/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts +++ b/src/app/api/[[...slugs]]/_lib/inovasi/kolaborasi-inovasi/updt.ts @@ -9,7 +9,7 @@ type FormUpdateKolaborasiInovasi = { slug?: string; deskripsi?: string; kolaborator?: string; - imageId?: string; + }; export default async function kolaborasiInovasiUpdate(context: Context) { const body = context.body as FormUpdateKolaborasiInovasi; @@ -31,7 +31,6 @@ export default async function kolaborasiInovasiUpdate(context: Context) { slug: body.slug, deskripsi: body.deskripsi, kolaborator: body.kolaborator, - imageId: body.imageId, }, }); diff --git a/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx b/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx index 027aef03..5a83dbaa 100644 --- a/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx +++ b/src/app/darmasaba/(pages)/inovasi/desa-digital-smart-village/page.tsx @@ -1,79 +1,61 @@ +'use client' import colors from '@/con/colors'; -import { Stack, Box, Text, List, ListItem, Paper, SimpleGrid, Image } from '@mantine/core'; -import React from 'react'; +import { Stack, Box, Text, Paper, SimpleGrid, Image, Skeleton, Center, Pagination, Grid, GridCol, TextInput } from '@mantine/core'; +import React, { useState } from 'react'; import BackButton from '../../desa/layanan/_com/BackButto'; +import { useProxy } from 'valtio/utils'; +import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconSearch } from '@tabler/icons-react'; -const data = [ - { - id: 1, - image: '/api/img/administrasi-digital.png', - judul: 'Layanan Administrasi Digital', - deskripsi: - Sistem pengurusan dokumen kependudukan online. - Pembuatan KTP, KK, Surat Keterangan secara daring. - Antrian dan pembayaran pajak berbasis aplikasi mobile. - Sistem informasi kependudukan terintegrasi. - - }, - { - id: 2, - image: '/api/img/edukasi-digital.png', - judul: 'Edukasi Digital', - deskripsi: - Ruang Belajar Digital dengan akses internet gratis. - Pelatihan komputer dan literasi digital untuk semua usia. - Kursus online keterampilan digital (desain, pemrograman, marketing). - Beasiswa pendidikan teknologi untuk pemuda desa. - Perpustakaan digital dengan koleksi buku elektronik. - - }, - { - id: 3, - image: '/api/img/ekonomi-digital.png', - judul: 'Ekonomi Digital', - deskripsi: - Marketplace produk UMKM Darmasaba. - Platform pemasaran hasil pertanian dan kerajinan lokal. - Sistem pembayaran digital untuk pelaku usaha desa. - Inkubator bisnis digital untuk wirausaha muda. - Pelatihan e-commerce dan digital marketing. - - }, - { - id: 4, - image: '/api/img/kesehatan-daring.png', - judul: 'Kesehatan Daring', - deskripsi: - Telemedicine dengan dokter dan puskesmas. - Monitoring kesehatan berbasis aplikasi. - Pendaftaran antrian puskesmas online. - Edukasi kesehatan melalui platform digital. - Rekam medis elektronik. - - }, - { - id: 5, - image: '/api/img/pertanian-cerdas.png', - judul: 'Pertanian Cerdas', - deskripsi: - Sistem informasi cuaca dan prediksi pertanian. - Konsultasi pertanian online dengan ahli. - Penjualan hasil pertanian melalui platform digital. - Pelatihan pertanian modern berbasis teknologi. - - }, -] function Page() { + const [search, setSearch] = useState("") + const state = useProxy(desaDigitalState) + const { + data, + page, + totalPages, + loading, + load, + } = state.findMany + + useShallowEffect(() => { + load(page, 3, search) + }, [page, search]) + + const filteredData = data || [] + + if (loading || !data) { + return ( + + + + ) + } return ( - - Desa Digital / Smart Village - - Mewujudkan Desa Darmasaba sebagai pusat inovasi digital yang memberdayakan masyarakat, meningkatkan kesejahteraan, dan menciptakan peluang ekonomi berbasis teknologi. + + + + Desa Digital / Smart Village + + + + setSearch(e.target.value)} + leftSection={} + w={{ base: "50%", md: "100%" }} + /> + + + Mewujudkan Desa Darmasaba sebagai pusat inovasi digital yang memberdayakan masyarakat, meningkatkan kesejahteraan, dan menciptakan peluang ekonomi berbasis teknologi. @@ -84,13 +66,13 @@ function Page() { md: 3 }} > - {data.map((v, k) => { + {filteredData.map((v, k) => { return ( - - {v.judul} - - {v.deskripsi} + + {v.name} + + ) @@ -98,6 +80,15 @@ function Page() { +
+ load(newPage)} // ini penting! + total={totalPages} + mt="md" + mb="md" + /> +
); } diff --git a/src/app/darmasaba/(pages)/inovasi/kolaborasi-inovasi/page.tsx b/src/app/darmasaba/(pages)/inovasi/kolaborasi-inovasi/page.tsx index 19f56a25..8110e378 100644 --- a/src/app/darmasaba/(pages)/inovasi/kolaborasi-inovasi/page.tsx +++ b/src/app/darmasaba/(pages)/inovasi/kolaborasi-inovasi/page.tsx @@ -1,26 +1,51 @@ +'use client' +import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi'; import colors from '@/con/colors'; -import { Stack, Box, Text, Paper, Flex, Group, SimpleGrid, Button, Image, Center } from '@mantine/core'; -import React from 'react'; +import { Box, Center, Grid, GridCol, Group, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconSearch } from '@tabler/icons-react'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; -import { IconChevronDown } from '@tabler/icons-react'; - -const data = [ - { - id: 1, - bulan: 'JANUARI 2025', - judul: 'Darmasaba Smart Waste', - deskripsi: 'Sistem manajemen sampah terpadu yang memudahkan warga untuk memilah dan mendaur ulang sampah.', - kolaborator: 'Kolaborator: DLH Kabupaten Badung, Komunitas Peduli Lingkungan, TPS3R Pudak Mesari' - }, - { - id: 2, - bulan: 'FEBRUARI 2025', - judul: 'Darmasaba Digital Market', - deskripsi: 'Platform e-commerce untuk produk UMKM desa yang menghubungkan produsen lokal dengan pasar global.', - kolaborator: 'Kolaborator: Kementerian Desa PDTT, UMKM Darmasaba' - } -] function Page() { + const state = useProxy(kolaborasiInovasiState) + const [search, setSearch] = useState(''); + const [selectedYear, setSelectedYear] = useState(null); + + // Get unique years from the data + const years = Array.from( + new Set( + state.findMany.data?.map(item => + new Date(item.createdAt).getFullYear().toString() + ) || [] + ) + ) + .sort((a, b) => b.localeCompare(a)) // Sort descending (newest first) + .map(year => ({ + value: year, + label: year, + })); + + const { + data, + page, + totalPages, + loading, + load, + } = state.findMany + + useShallowEffect(() => { + load(page, 10, search, selectedYear || '') + }, [page, search, selectedYear]) + + if (loading || !data) { + return ( + + + + ); + } + return ( <> @@ -28,27 +53,46 @@ function Page() { - - Kolaborasi Inovasi - + + + + Kolaborasi Inovasi + + + + setSearch(e.target.value)} + leftSection={} + w={{ base: "50%", md: "100%" }} + /> + + - - - Tahun - - - +