diff --git a/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts b/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts index f0b29679..e5cfb7e0 100644 --- a/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.ts +++ b/src/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa.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"; @@ -53,22 +54,47 @@ const pasarDesa = proxy({ }, }, findMany: { - data: null as Array< - Prisma.PasarDesaGetPayload<{ - include: { - image: true; - KategoriToPasar: { - include: { - kategori: true; + data: null as + | Prisma.PasarDesaGetPayload<{ + include: { + image: true; + KategoriToPasar: { + include: { + kategori: true; + }; }; }; - }; - }> - > | null, - async load() { - const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get(); - if (res.status === 200) { - pasarDesa.findMany.data = res.data?.data ?? []; + }>[] + | null, + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "", categoryId?: string) => { + pasarDesa.findMany.loading = true; + pasarDesa.findMany.page = page; + pasarDesa.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + if (categoryId) query.categoryId = categoryId; + + const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + pasarDesa.findMany.data = res.data.data ?? []; + pasarDesa.findMany.totalPages = res.data.totalPages ?? 1; + } else { + pasarDesa.findMany.data = []; + pasarDesa.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch keamanan lingkungan paginated:", err); + pasarDesa.findMany.data = []; + pasarDesa.findMany.totalPages = 1; + } finally { + pasarDesa.findMany.loading = false; } }, }, @@ -272,14 +298,41 @@ const kategoriProduk = proxy({ }, }, findMany: { - data: null as Array<{ - id: string; - nama: string; - }> | null, - async load() { - const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get(); - if (res.status === 200) { - kategoriProduk.findMany.data = res.data?.data ?? []; + data: null as + | Prisma.KategoriProdukGetPayload<{ + omit: { + isActive: true; + }; + }>[] + | null, + page: 1, + totalPages: 1, + loading: false, + search2: "", + load: async (page = 1, limit = 10, search2 = "") => { + kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path + kategoriProduk.findMany.page = page; + kategoriProduk.findMany.search2 = search2; + + try { + const query: any = { page, limit }; + if (search2) query.search2 = search2; + + const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + kategoriProduk.findMany.data = res.data.data ?? []; + kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1; + } else { + kategoriProduk.findMany.data = []; + kategoriProduk.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch kategori produk paginated:", err); + kategoriProduk.findMany.data = []; + kategoriProduk.findMany.totalPages = 1; + } finally { + kategoriProduk.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/page.tsx b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/page.tsx index 0e85744d..28598ac1 100644 --- a/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/pasar-desa/kategori-produk/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 } from '@mantine/core'; +import { Box, Button, Center, Pagination, 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'; @@ -13,31 +13,39 @@ import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa'; function PasarDesa() { - const [search, setSearch] = useState("") + const [search2, setSearch2] = useState("") return ( } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} + value={search2} + onChange={(e) => setSearch2(e.currentTarget.value)} /> - + ); } -function ListPasarDesa({ search }: { search: string }) { +function ListPasarDesa({ search2 }: { search2: string }) { const statePasar = useProxy(pasarDesaState.kategoriProduk) const [modalHapus, setModalHapus] = useState(false) const [selectedId, setSelectedId] = useState(null) // const params = useParams() const router = useRouter() + const { + data, + page, + totalPages, + loading, + load, + } = statePasar.findMany + useShallowEffect(() => { - statePasar.findMany.load() - }, []) + load(page, 10, search2) + }, [page, search2]) const handleHapus = () => { if (selectedId) { @@ -47,14 +55,9 @@ function ListPasarDesa({ search }: { search: string }) { } } - const filteredData = (statePasar.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.nama.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!statePasar.findMany.data) { + if (loading || !data) { return ( @@ -99,6 +102,14 @@ function ListPasarDesa({ search }: { search: string }) { +
+ load(newPage)} + total={totalPages} + my={"md"} + /> +
{/* Modal Konfirmasi Hapus */} { - statePasar.findMany.load() - }, []) + load(page, 10, search) + }, [page, search]) - const filteredData = (statePasar.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.nama.toLowerCase().includes(keyword) || - item.harga.toString().toLowerCase().includes(keyword) || - item.rating.toString().toLowerCase().includes(keyword) || - item.alamatUsaha.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!statePasar.findMany.data) { + if (loading || !data) { return ( @@ -86,6 +86,14 @@ function ListPasarDesa({ search }: { search: string }) { +
+ load(newPage)} + total={totalPages} + my={"md"} + /> +
); } diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts index 026e6970..ba74e7cc 100644 --- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/findMany.ts @@ -1,26 +1,84 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// /api/berita/findManyPaginated.ts import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function pasarDesaFindMany() { - const data = await prisma.pasarDesa.findMany({ - where: { - isActive: true, // Opsional filter - }, - orderBy: { - createdAt: "desc", - }, - include: { - image: true, - KategoriToPasar: { +async function pasarDesaFindMany(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 categoryId = context.query.categoryId as string | undefined; + const skip = (page - 1) * limit; + + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan filter kategori (jika ada) + if (categoryId) { + where.KategoriToPasar = { + some: { + kategoriId: categoryId + } + }; + } + + // Tambahkan pencarian (jika ada) + if (search) { + where.AND = where.AND || []; + where.AND.push({ + OR: [ + { nama: { contains: search, mode: 'insensitive' } }, + { alamatUsaha: { contains: search, mode: 'insensitive' } }, + { + KategoriToPasar: { + some: { + kategori: { + nama: { contains: search, mode: 'insensitive' } + } + } + } + } + ] + }); + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.pasarDesa.findMany({ + where, include: { - kategori: true, + image: true, + KategoriToPasar: { + include: { + kategori: true + } + } }, - }, - }, - }); + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.pasarDesa.count({ where }), + ]); - return { - success: true, - message: "Berhasil mengambil semua data pasar desa", - data, - }; + return { + success: true, + message: "Berhasil ambil pasar desa 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 pasar desa", + }; + } } + +export default pasarDesaFindMany; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts index 543ed67f..25fb8d3c 100644 --- a/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pasar-desa/kategori-produk/findMany.ts @@ -1,15 +1,60 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +// /api/berita/findManyPaginated.ts import prisma from "@/lib/prisma"; +import { Context } from "elysia"; -export default async function kategoriProdukFindMany() { - const data = await prisma.kategoriProduk.findMany(); - return { - success: true, - data: data.map((item: any) => { - return { - id: item.id, - nama: item.nama, - } - }), +async function kategoriProdukFindMany(context: Context) { + // Ambil parameter dari query + const page = Number(context.query.page) || 1; + const limit = Number(context.query.limit) || 10; + const search2 = (context.query.search as string) || ''; + const skip = (page - 1) * limit; + + // Buat where clause + const where: any = { isActive: true }; + + // Tambahkan pencarian (jika ada) + if (search2) { + where.OR = [ + { nama: { contains: search2, mode: 'insensitive' } }, + {KategoriToPasar : { + some: { + kategori: { + nama: { contains: search2, mode: 'insensitive' } + } + } + }} + ]; + } + + try { + // Ambil data dan total count secara paralel + const [data, total] = await Promise.all([ + prisma.kategoriProduk.findMany({ + where, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.kategoriProduk.count({ where }), + ]); + + return { + success: true, + message: "Berhasil ambil kategori produk dengan pagination", + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), }; -} \ No newline at end of file + } catch (e) { + console.error("Error di findMany paginated:", e); + return { + success: false, + message: "Gagal mengambil data kategori produk", + }; + } +} + +export default kategoriProdukFindMany; \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx index 3ceabef0..3e8f3a6f 100644 --- a/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/pasar-desa/page.tsx @@ -1,90 +1,76 @@ 'use client' +import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa'; import colors from '@/con/colors'; -import { Box, Combobox, Flex, Image, InputBase, InputPlaceholder, Paper, SimpleGrid, Stack, Text, TextInput, useCombobox } from '@mantine/core'; +import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; import { IconMapPinFilled, IconSearch, IconShoppingCartFilled, IconStarFilled } from '@tabler/icons-react'; import { motion } from 'motion/react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; -const groceries = [ - 'Makanan', - 'Minuman', - 'Pakaian', - 'Alat Dapur', - 'Alat Mandi', - 'Furniture', -]; - -const dataBarang = [ - { - id: 1, - image: '/api/img/semat.png', - judul: 'Semat Bambu / Semat Banten', - harga: 'Rp. 3000 / pcs', - bintang: '4.9', - alamat: 'Jl. Kecubung no.6' - }, - { - id: 2, - image: '/api/img/kerupuk.png', - judul: 'Kerupuk Babi', - harga: 'Rp. 12000 / pcs', - bintang: '4.9', - alamat: 'Jl. Kenari no.7' - }, - { - id: 3, - image: '/api/img/beras.png', - judul: 'beras Merah Organik', - harga: 'Rp. 40000 / 1 kg', - bintang: '4.9', - alamat: 'Jl. Mawar no.8' - }, - { - id: 4, - image: '/api/img/genteng.png', - judul: 'Genteng', - harga: 'Rp. 3600 / pcs', - bintang: '4.9', - alamat: 'Jl. Kecubung no.16' - }, - -] function Page() { - const [search, setSearch] = useState(''); - const combobox = useCombobox({ - onDropdownClose: () => { - combobox.resetSelectedOption(); - combobox.focusTarget(); - setSearch(''); - }, - - onDropdownOpen: () => { - combobox.focusSearchInput(); - }, - }); - - const [value, setValue] = useState(null); - - const options = groceries - .filter((item) => item.toLowerCase().includes(search.toLowerCase().trim())) - .map((item) => ( - - {item} - - )); const router = useRouter() + const state = useProxy(pasarDesaState.pasarDesa) + const [search, setSearch] = useState(''); + const [selectedCategory, setSelectedCategory] = useState(null); + const { + data, + page, + loading, + totalPages, + load, + } = state.findMany + + useShallowEffect(() => { + pasarDesaState.kategoriProduk.findMany.load() + }, []) + + // Filter data based on selected category + const filteredData = selectedCategory + ? data?.filter(item => + item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory) + ) + : data; + + useShallowEffect(() => { + load(page, 4, search, selectedCategory || undefined) + }, [page, search, selectedCategory]) + + + if (loading || !data) { + return ( + + + + ) + } + return ( - - Pasar Desa - - + + + + Pasar Desa + + + + setSearch(e.target.value)} + leftSection={} + w={{ base: "50%", md: "100%" }} + /> + + + Pasar Desa Online merupakan Media Promosi yang bertujuan untuk membantu warga desa dalam memasarkan dan memperkenalkan produknya kepada masyarakat. @@ -98,48 +84,23 @@ function Page() { }} > - { - setValue(val); - combobox.closeDropdown(); - }} - > - - } - onClick={() => combobox.toggleDropdown()} - rightSectionPointerEvents="none" - > - {value || Kategori} - - - - - setSearch(event.currentTarget.value)} - placeholder="Search groceries" - /> - - {options.length > 0 ? options : Nothing found} - - - - - - } +