Penambahan fungsi search disetiap menu & submenu,
Menu Landing Page Menu PPID Menu Desa
This commit is contained in:
@@ -143,7 +143,7 @@ model MediaSosial {
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
//========================================= PROFILE ========================================= //
|
||||
//========================================= DESA ANTI KORUPSI ========================================= //
|
||||
model DesaAntiKorupsi {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
|
||||
@@ -89,26 +89,26 @@ function ListArtikelKesehatan({ search }: { search: string }) {
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Judul</TableTh>
|
||||
<TableTh>Konten</TableTh>
|
||||
<TableTh>Aksi</TableTh>
|
||||
<TableTh style={{ minWidth: 200 }}>Judul</TableTh>
|
||||
<TableTh style={{ minWidth: 200 }}>Konten</TableTh>
|
||||
<TableTh style={{ minWidth: 200 }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<TableTd style={{ minWidth: 200 }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.title}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<TableTd style={{ minWidth: 200 }} >
|
||||
<Text truncate fz="sm" c="dimmed" lineClamp={1}>
|
||||
{item.content}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<TableTd style={{ minWidth: 200 }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
|
||||
@@ -223,7 +223,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
||||
|
||||
{/* Chart */}
|
||||
<Box mt="lg" style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Paper withBorder bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
||||
{mounted && diseaseChartData.length > 0 ? (
|
||||
<Center>
|
||||
|
||||
@@ -111,9 +111,7 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text truncate fz="sm" c="dimmed">
|
||||
{item.deskripsiSingkat}
|
||||
</Text>
|
||||
<Text truncate="end" fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
|
||||
538
src/app/api/[[...slugs]]/_lib/search/findMany.ts
Normal file
538
src/app/api/[[...slugs]]/_lib/search/findMany.ts
Normal file
@@ -0,0 +1,538 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function searchFindMany(context: Context) {
|
||||
const { query, page = 1, limit = 10, type } = context.query as any;
|
||||
|
||||
// Convert to numbers
|
||||
const pageNum = parseInt(String(page), 10) || 1;
|
||||
const limitNum = parseInt(String(limit), 10) || 10;
|
||||
const skip = (pageNum - 1) * limitNum;
|
||||
|
||||
if (!query || query.trim() === "") {
|
||||
return { data: [], nextPage: null };
|
||||
}
|
||||
|
||||
// 🔍 kalau type dikirim → cari spesifik modul
|
||||
//========================================= MENU LANDING PAGE ========================================= //
|
||||
//========================================= PROFILE ========================================= //
|
||||
if (type === "pejabatdesa") {
|
||||
const data = await prisma.pejabatDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return {
|
||||
data: data.map((b) => ({ type: "pejabatdesa", ...b })),
|
||||
nextPage: data.length < limitNum ? null : pageNum + 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "programinovasi") {
|
||||
const data = await prisma.programInovasi.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "mediasosial") {
|
||||
const data = await prisma.mediaSosial.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//========================================= DESA ANTI KORUPSI ========================================= //
|
||||
|
||||
if (type === "desaantikorupsi") {
|
||||
const data = await prisma.desaAntiKorupsi.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//========================================= SDGS Desa ========================================= //
|
||||
if (type === "sdgsdesa") {
|
||||
const data = await prisma.sdgsDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//========================================= APBDes ========================================= //
|
||||
if (type === "apbdes") {
|
||||
const data = await prisma.aPBDes.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//========================================= PRESTASI DESA ========================================= //
|
||||
if (type === "prestasidesa") {
|
||||
const data = await prisma.prestasiDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//========================================= INDEKS KEPUASAAN MASYARAKAT ========================================= //
|
||||
if (type === "responden") {
|
||||
const data = await prisma.responden.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
//========================================= STRUKTUR PPID ========================================= //
|
||||
if (type === "strukturppid") {
|
||||
const data = await prisma.strukturPPID.findMany({
|
||||
where: {
|
||||
PegawaiPPID: { namaLengkap: { contains: query, mode: "insensitive" } },
|
||||
},
|
||||
include: {
|
||||
PosisiOrganisasiPPID: true,
|
||||
PegawaiPPID: true,
|
||||
},
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= VISI MISI PPID ========================================= //
|
||||
if (type === "visimisippid") {
|
||||
const data = await prisma.visiMisiPPID.findMany({
|
||||
where: {
|
||||
visi: { contains: query, mode: "insensitive" },
|
||||
misi: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// ========================================= DASAR HUKUM PPID ========================================= //
|
||||
if (type === "dasarhukumppid") {
|
||||
const data = await prisma.dasarHukumPPID.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= PROFILE PPID ========================================= //
|
||||
if (type === "profileppid") {
|
||||
const data = await prisma.profilePPID.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= DAFTAR INFORMASI PUBLIK ========================================= //
|
||||
if (type === "daftarinformasipublik") {
|
||||
const data = await prisma.daftarInformasiPublik.findMany({
|
||||
where: { jenisInformasi: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//=========================================PERMOHONAN INFORMASI PUBLIK========================= //
|
||||
if (type === "permohonaninformasipublik") {
|
||||
const data = await prisma.permohonanInformasiPublik.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
//=========================================PERMOHONAN INFORMASI KEBERATAN PUBLIK========================= //
|
||||
if (type === "permohonaninformasikeberatanpublik") {
|
||||
const data = await prisma.formulirPermohonanKeberatan.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= IKM ========================================= //
|
||||
if (type === "ikm") {
|
||||
const data = await prisma.indeksKepuasanMasyarakat.findMany({
|
||||
where: { label: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= MENU DESA ========================================= //
|
||||
// ========================================= PROFILE DESA ========================================= //
|
||||
if (type === "sejarahdesa") {
|
||||
const data = await prisma.sejarahDesa.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "visimisidesa") {
|
||||
const data = await prisma.visiMisiDesa.findMany({
|
||||
where: { visi: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "lambangdesa") {
|
||||
const data = await prisma.lambangDesa.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "maskotdesa") {
|
||||
const data = await prisma.maskotDesa.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "profilperbekel") {
|
||||
const data = await prisma.profilPerbekel.findMany({
|
||||
where: { biodata: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "perbekeldarmasaba") {
|
||||
const data = await prisma.perbekelDariMasaKeMasa.findMany({
|
||||
where: { nama: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= BERITA ========================================= //
|
||||
if (type === "berita") {
|
||||
const data = await prisma.berita.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "kategoriBerita") {
|
||||
const data = await prisma.kategoriBerita.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= POTENSI DESA ========================================= //
|
||||
if (type === "potensi") {
|
||||
const data = await prisma.potensiDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= PENGUMUMAN ========================================= //
|
||||
if (type === "pengumuman") {
|
||||
const data = await prisma.pengumuman.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// ========================================= GALLERY ========================================= //
|
||||
if (type === "galleryFoto") {
|
||||
const data = await prisma.galleryFoto.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "galleryVideo") {
|
||||
const data = await prisma.galleryVideo.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
// 🌍 GLOBAL SEARCH — cari di beberapa modul sekaligus
|
||||
const [
|
||||
pejabatdesa,
|
||||
programinovasi,
|
||||
mediasosial,
|
||||
desaantikorupsi,
|
||||
sdgsdesa,
|
||||
apbdes,
|
||||
prestasidesa,
|
||||
responden,
|
||||
strukturppid,
|
||||
visimisippid,
|
||||
dasarhukumppid,
|
||||
profileppid,
|
||||
daftarinformasipublik,
|
||||
permohonaninformasipublik,
|
||||
permohonaninformasikeberatanpublik,
|
||||
ikm,
|
||||
sejarahdesa,
|
||||
visimisidesa,
|
||||
lambangdesa,
|
||||
maskotdesa,
|
||||
profilperbekel,
|
||||
perbekeldarmasaba,
|
||||
berita,
|
||||
kategoriBerita,
|
||||
potensi,
|
||||
pengumuman,
|
||||
galleryFoto,
|
||||
galleryVideo,
|
||||
pelayananSuratKeterangan,
|
||||
pelayananPerizinanBerusaha,
|
||||
pelayananTelunjukSaktiDesa,
|
||||
pelayananPendudukNonPermanent,
|
||||
penghargaan
|
||||
] = await Promise.all([
|
||||
prisma.pejabatDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.programInovasi.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.mediaSosial.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.desaAntiKorupsi.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.sdgsDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.aPBDes.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.prestasiDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.responden.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
// ✅ FIXED
|
||||
prisma.strukturPPID.findMany({
|
||||
where: {
|
||||
PegawaiPPID: { namaLengkap: { contains: query, mode: "insensitive" } },
|
||||
},
|
||||
include: { PegawaiPPID: true },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.visiMisiPPID.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ visi: { contains: query, mode: "insensitive" } },
|
||||
{ misi: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.dasarHukumPPID.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.profilePPID.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.daftarInformasiPublik.findMany({
|
||||
where: { jenisInformasi: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.permohonanInformasiPublik.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.formulirPermohonanKeberatan.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.indeksKepuasanMasyarakat.findMany({
|
||||
where: { label: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.sejarahDesa.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.visiMisiDesa.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ visi: { contains: query, mode: "insensitive" } },
|
||||
{ misi: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.lambangDesa.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.maskotDesa.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.profilPerbekel.findMany({
|
||||
where: { biodata: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.perbekelDariMasaKeMasa.findMany({
|
||||
where: { nama: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.berita.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.kategoriBerita.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.potensiDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.pengumuman.findMany({
|
||||
where: { judul: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.galleryFoto.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.galleryVideo.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.pelayananSuratKeterangan.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.pelayananPerizinanBerusaha.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.pelayananTelunjukSaktiDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.pelayananPendudukNonPermanen.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.penghargaan.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
take: limitNum,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data: [
|
||||
...pejabatdesa.map((b) => ({ type: "pejabatdesa", ...b })),
|
||||
...programinovasi.map((b) => ({ type: "programinovasi", ...b })),
|
||||
...mediasosial.map((b) => ({ type: "mediaSosial", ...b })),
|
||||
...desaantikorupsi.map((b) => ({ type: "desaantikorupsi", ...b })),
|
||||
...sdgsdesa.map((b) => ({ type: "sdgsdesa", ...b })),
|
||||
...apbdes.map((b) => ({ type: "apbdes", ...b })),
|
||||
...prestasidesa.map((b) => ({ type: "prestasidesa", ...b })),
|
||||
...responden.map((b) => ({ type: "responden", ...b })),
|
||||
...strukturppid.map((b) => ({ type: "strukturppid", ...b })),
|
||||
...visimisippid.map((b) => ({ type: "visimisippid", ...b })),
|
||||
...dasarhukumppid.map((b) => ({ type: "dasarhukumppid", ...b })),
|
||||
...profileppid.map((b) => ({ type: "profileppid", ...b })),
|
||||
...daftarinformasipublik.map((b) => ({
|
||||
type: "daftarinformasipublik",
|
||||
...b,
|
||||
})),
|
||||
...permohonaninformasipublik.map((b) => ({
|
||||
type: "permohonaninformasipublik",
|
||||
...b,
|
||||
})),
|
||||
...permohonaninformasikeberatanpublik.map((b) => ({
|
||||
type: "permohonaninformasikeberatanpublik",
|
||||
...b,
|
||||
})),
|
||||
...ikm.map((b) => ({ type: "ikm", ...b })),
|
||||
...sejarahdesa.map((b) => ({ type: "sejarahdesa", ...b })),
|
||||
...visimisidesa.map((b) => ({ type: "visimisidesa", ...b })),
|
||||
...lambangdesa.map((b) => ({ type: "lambangdesa", ...b })),
|
||||
...maskotdesa.map((b) => ({ type: "maskotdesa", ...b })),
|
||||
...profilperbekel.map((b) => ({ type: "profilperbekel", ...b })),
|
||||
...perbekeldarmasaba.map((b) => ({ type: "perbekeldarmasaba", ...b })),
|
||||
...berita.map((b) => ({ type: "berita", ...b })),
|
||||
...kategoriBerita.map((b) => ({ type: "kategoriBerita", ...b })),
|
||||
...potensi.map((b) => ({ type: "potensi", ...b })),
|
||||
...pengumuman.map((b) => ({ type: "pengumuman", ...b })),
|
||||
...galleryFoto.map((b) => ({ type: "galleryFoto", ...b })),
|
||||
...galleryVideo.map((b) => ({ type: "galleryVideo", ...b })),
|
||||
...pelayananSuratKeterangan.map((b) => ({ type: "pelayananSuratKeterangan", ...b })),
|
||||
...pelayananPerizinanBerusaha.map((b) => ({ type: "pelayananPerizinanBerusaha", ...b })),
|
||||
...pelayananTelunjukSaktiDesa.map((b) => ({ type: "pelayananTelunjukSaktiDesa", ...b })),
|
||||
...pelayananPendudukNonPermanent.map((b) => ({ type: "pelayananPendudukNonPermanent", ...b })),
|
||||
...penghargaan.map((b) => ({ type: "penghargaan", ...b })),
|
||||
],
|
||||
nextPage: null, // bisa dibuat lebih kompleks kalau perlu
|
||||
};
|
||||
}
|
||||
10
src/app/api/[[...slugs]]/_lib/search/index.ts
Normal file
10
src/app/api/[[...slugs]]/_lib/search/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Elysia from "elysia";
|
||||
import searchFindMany from "./findMany";
|
||||
|
||||
const Search = new Elysia({
|
||||
prefix: "/api/search",
|
||||
tags: ["Search"],
|
||||
})
|
||||
.get("/findMany", searchFindMany);
|
||||
|
||||
export default Search;
|
||||
74
src/app/api/[[...slugs]]/_lib/search/searchState.ts
Normal file
74
src/app/api/[[...slugs]]/_lib/search/searchState.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client';
|
||||
import { proxy, subscribe } from 'valtio';
|
||||
import { debounce } from 'lodash';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
|
||||
interface SearchResult {
|
||||
type?: string; // optional biar gak error
|
||||
id: string | number;
|
||||
title?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const searchState = proxy({
|
||||
query: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
type: '', // kosong = global search
|
||||
results: [] as SearchResult[],
|
||||
nextPage: null as number | null,
|
||||
loading: false,
|
||||
|
||||
async fetch() {
|
||||
if (!searchState.query) return;
|
||||
searchState.loading = true;
|
||||
|
||||
try {
|
||||
const res = await ApiFetch.api.search.findMany.get({
|
||||
query: {
|
||||
query: searchState.query,
|
||||
page: searchState.page,
|
||||
limit: searchState.limit,
|
||||
type: searchState.type,
|
||||
},
|
||||
});
|
||||
|
||||
const data = (res.data?.data || []).map((item: any) => ({
|
||||
type: item.type ?? 'unknown', // pastikan selalu ada type
|
||||
...item,
|
||||
}));
|
||||
|
||||
if (searchState.page === 1) {
|
||||
searchState.results = data;
|
||||
} else {
|
||||
searchState.results.push(...data);
|
||||
}
|
||||
|
||||
searchState.nextPage = res.data?.nextPage || null;
|
||||
} catch (e) {
|
||||
console.error('Search fetch error:', e);
|
||||
} finally {
|
||||
searchState.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async next() {
|
||||
if (!searchState.nextPage || searchState.loading) return;
|
||||
searchState.page = searchState.nextPage;
|
||||
await searchState.fetch();
|
||||
},
|
||||
});
|
||||
|
||||
// 🔁 Auto debounce search trigger
|
||||
const debouncedFetch = debounce(() => {
|
||||
if (!searchState.query) return;
|
||||
searchState.page = 1;
|
||||
searchState.fetch();
|
||||
}, 500);
|
||||
|
||||
subscribe(searchState, () => {
|
||||
debouncedFetch();
|
||||
});
|
||||
|
||||
export default searchState;
|
||||
@@ -25,6 +25,7 @@ import LandingPage from "./_lib/landing_page";
|
||||
import Pendidikan from "./_lib/pendidikan";
|
||||
import User from "./_lib/user";
|
||||
import Role from "./_lib/user/role";
|
||||
import Search from "./_lib/search";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
|
||||
@@ -95,6 +96,7 @@ const ApiServer = new Elysia()
|
||||
.use(Pendidikan)
|
||||
.use(User)
|
||||
.use(Role)
|
||||
.use(Search)
|
||||
|
||||
.onError(({ code }) => {
|
||||
if (code === "NOT_FOUND") {
|
||||
|
||||
@@ -121,13 +121,13 @@ function Page() {
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text fz="sm" c="dimmed">
|
||||
Diposting: 12 Februari 2025 · Dinas Kesehatan
|
||||
Diposting: {v.createdAt.toLocaleDateString()}
|
||||
</Text>
|
||||
<Divider />
|
||||
<Text fz="sm" lh={1.5}>
|
||||
<Text fz="sm" lh={1.5} lineClamp={3} truncate="end">
|
||||
{v.deskripsiSingkat}
|
||||
</Text>
|
||||
<Button variant="light" radius="md" size="md" onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${v.id}`)}>
|
||||
<Button variant="light" radius="md" size="md" onClick={() => router.push(`/darmasaba/kesehatan/info-wabah-penyakit/${v.id}`)}>
|
||||
Selengkapnya
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -97,18 +97,23 @@ function Page() {
|
||||
shadow="sm"
|
||||
withBorder
|
||||
bg={colors['white-trans-1']}
|
||||
style={{ transition: 'all 0.3s ease' }}
|
||||
style={{
|
||||
transition: 'all 0.3s ease',
|
||||
transform: 'translateY(0)',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'translateY(-5px)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
|
||||
>
|
||||
<Stack align="center" gap="md">
|
||||
<Center>
|
||||
<Image
|
||||
src={v.image.link}
|
||||
alt={v.name}
|
||||
w={160}
|
||||
h={160}
|
||||
fit="contain"
|
||||
h={180}
|
||||
w="100%"
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
fit="cover"
|
||||
style={{ aspectRatio: '4/3' }}
|
||||
/>
|
||||
</Center>
|
||||
<Stack gap={4} w="100%">
|
||||
@@ -151,8 +156,11 @@ function Page() {
|
||||
styles={{
|
||||
control: {
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': { backgroundColor: colors['blue-button'], color: 'white' },
|
||||
},
|
||||
}}
|
||||
|
||||
/>
|
||||
</Center>
|
||||
|
||||
|
||||
@@ -28,11 +28,31 @@ export default function Page() {
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py="xl" px={{ base: "md", md: 100 }}>
|
||||
<Text fz="lg" fw="bold" c={colors["blue-button"]}>
|
||||
Tidak ada posyandu yang ditemukan
|
||||
</Text>
|
||||
</Box>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
|
||||
<Text
|
||||
ta="left"
|
||||
fz={{ base: "1.8rem", md: "2.5rem" }}
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
>
|
||||
Posyandu Desa Darmasaba
|
||||
</Text>
|
||||
<TextInput
|
||||
placeholder="Cari posyandu berdasarkan nama..."
|
||||
aria-label="Pencarian Posyandu"
|
||||
radius="xl"
|
||||
size="md"
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "100%", md: "35%" }}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import stateNav from "@/state/state-nav";
|
||||
import { Container, Stack, TextInput, Tooltip } from "@mantine/core";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { Container, Stack, Tooltip } from "@mantine/core";
|
||||
import GlobalSearch from "./globalSearch";
|
||||
|
||||
export function NavbarSearch() {
|
||||
return (
|
||||
@@ -12,14 +12,7 @@ export function NavbarSearch() {
|
||||
>
|
||||
<Stack pt="xl">
|
||||
<Tooltip label="Type to search across the site" position="bottom-start" withArrow>
|
||||
<TextInput
|
||||
autoFocus
|
||||
size="lg"
|
||||
variant="filled"
|
||||
radius="xl"
|
||||
placeholder="Search anything..."
|
||||
leftSection={<IconSearch size={20} />}
|
||||
/>
|
||||
<GlobalSearch />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
97
src/app/darmasaba/_com/globalSearch.tsx
Normal file
97
src/app/darmasaba/_com/globalSearch.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
'use client';
|
||||
import { TextInput, Loader, Stack, Box, Text } from '@mantine/core';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import searchState from '@/app/api/[[...slugs]]/_lib/search/searchState';
|
||||
|
||||
// Mapping type ke URL
|
||||
const getDetailUrl = (item: { type?: string; id: string | number; [key: string]: unknown }) => {
|
||||
const { type, id, kategori } = item;
|
||||
|
||||
const typeUrlMap: Record<string, string> = {
|
||||
programinovasi: `/darmasaba/program-inovasi/${id}`,
|
||||
desaantikorupsi: '/darmasaba/desa-anti-korupsi',
|
||||
sdgsdesa: '/darmasaba/sdgs-desa',
|
||||
apbdes: '/darmasaba/apbdes',
|
||||
prestasidesa: '/darmasaba/prestasi-desa',
|
||||
pejabatdesa: '/darmasaba/profile/pejabat-desa',
|
||||
strukturppid: '/darmasaba/ppid/struktur-ppid',
|
||||
visimisippid: '/darmasaba/ppid/visi-misi',
|
||||
dasarhukumppid: '/darmasaba/ppid/dasar-hukum',
|
||||
profileppid: '/darmasaba/ppid/profile',
|
||||
daftarinformasipublik: '/darmasaba/ppid/daftar-informasi-publik',
|
||||
perbekeldarmasaba: '/darmasaba/desa/profile',
|
||||
berita: `/darmasaba/desa/berita/${kategori}/${id}`,
|
||||
pengumuman: `/darmasaba/desa/pengumuman/${kategori}/${id}`,
|
||||
sejarahdesa: '/darmasaba/desa/profile',
|
||||
visimisidesa: '/darmasaba/desa/profile',
|
||||
lambangdesa: '/darmasaba/desa/profile',
|
||||
maskotdesa: '/darmasaba/desa/profile',
|
||||
profilperbekel: '/darmasaba/desa/profile',
|
||||
potensi: '/darmasaba/desa/potensi-desa',
|
||||
galleryFoto: '/darmasaba/desa/gallery/foto',
|
||||
galleryVideo: '/darmasaba/desa/gallery/video',
|
||||
pelayananSuratKeterangan: '/darmasaba/desa/layanan',
|
||||
pelayananPerizinanBerusaha: '/darmasaba/desa/layanan',
|
||||
pelayananTelunjukSaktiDesa: '/darmasaba/desa/layanan',
|
||||
pelayananPendudukNonPermanent: '/darmasaba/desa/layanan',
|
||||
penghargaan: '/darmasaba/desa/penghargaan',
|
||||
};
|
||||
|
||||
return type ? typeUrlMap[type] || '/darmasaba' : '/darmasaba';
|
||||
};
|
||||
|
||||
|
||||
export default function GlobalSearch() {
|
||||
const snap = useSnapshot(searchState);
|
||||
const router = useRouter();
|
||||
|
||||
// Infinite scroll listener
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const bottom =
|
||||
window.innerHeight + window.scrollY >= document.body.offsetHeight - 200;
|
||||
if (bottom && !snap.loading) searchState.next();
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, [snap.loading]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<TextInput
|
||||
placeholder="Cari apapun..."
|
||||
value={snap.query}
|
||||
onChange={(e) => (searchState.query = e.currentTarget.value)}
|
||||
/>
|
||||
|
||||
{snap.results.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
p="sm"
|
||||
style={{
|
||||
borderBottom: '1px solid #eee',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = '#f5f5f5')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
|
||||
onClick={() => {
|
||||
const url = getDetailUrl(item);
|
||||
router.push(url);
|
||||
}}
|
||||
>
|
||||
<Text size="sm" fw={500}>
|
||||
{item.judul || item.namaPasar || item.nama || item.name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
dari modul: {item.type}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{snap.loading && <Loader size="sm" />}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user