diff --git a/bun.lockb b/bun.lockb
index 0da4afd1..6da03f4d 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index eb085cc2..a737af3f 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"framer-motion": "^12.23.5",
"get-port": "^7.1.0",
"jotai": "^2.12.3",
+ "list": "^2.0.19",
"lodash": "^4.17.21",
"motion": "^12.4.1",
"nanoid": "^5.1.5",
diff --git a/src/app/admin/(dashboard)/_state/desa/berita.ts b/src/app/admin/(dashboard)/_state/desa/berita.ts
index b314fcd4..d49562b4 100644
--- a/src/app/admin/(dashboard)/_state/desa/berita.ts
+++ b/src/app/admin/(dashboard)/_state/desa/berita.ts
@@ -263,6 +263,59 @@ const berita = proxy({
berita.edit.form = { ...defaultForm };
},
},
+ findFirst: {
+ data: null as Prisma.BeritaGetPayload<{
+ include: {
+ image: true;
+ kategoriBerita: true;
+ };
+ }> | null,
+ loading: false,
+ async load() {
+ this.loading = true;
+ try {
+ const res = await ApiFetch.api.desa.berita["find-first"].get();
+ if (res.status === 200 && res.data?.success) {
+ // Add type assertion to ensure type safety
+ berita.findFirst.data = res.data.data as Prisma.BeritaGetPayload<{
+ include: {
+ image: true;
+ kategoriBerita: true;
+ };
+ }> | null;
+ }
+ } catch (err) {
+ console.error("Gagal fetch berita terbaru:", err);
+ } finally {
+ this.loading = false;
+ }
+ },
+ },
+ findRecent: {
+ data: [] as Prisma.BeritaGetPayload<{
+ include: {
+ image: true;
+ kategoriBerita: true;
+ };
+ }>[],
+ loading: false,
+
+ async load() {
+ try {
+ this.loading = true;
+ const res = await ApiFetch.api.desa.berita["find-recent"].get();
+ if (res.status === 200 && res.data?.success) {
+ this.data = res.data.data ?? [];
+ }
+ } catch (error) {
+ console.error("Gagal fetch berita recent:", error);
+ } finally {
+ this.loading = false;
+ }
+ },
+ }
+
+
});
diff --git a/src/app/admin/(dashboard)/_state/desa/pengumuman.ts b/src/app/admin/(dashboard)/_state/desa/pengumuman.ts
index 91b4e311..3c9ae6ae 100644
--- a/src/app/admin/(dashboard)/_state/desa/pengumuman.ts
+++ b/src/app/admin/(dashboard)/_state/desa/pengumuman.ts
@@ -68,11 +68,11 @@ const pengumuman = proxy({
},
findMany: {
data: null as
- | Prisma.PengumumanGetPayload<{
- include: {
+ | Prisma.PengumumanGetPayload<{
+ include: {
CategoryPengumuman: true;
- }
- }>[]
+ };
+ }>[]
| null,
async load() {
const res = await ApiFetch.api.desa.pengumuman["find-many"].get();
@@ -82,30 +82,28 @@ const pengumuman = proxy({
}
},
},
- // findUnique: {
- // data: null as
- // | Prisma.PengumumanGetPayload<{
- // include: {
- // CategoryPengumuman: true;
- // }
- // }>
- // | null,
- // async load(id: string) {
- // try {
- // const res = await fetch(`/api/desa/pengumuman/${id}`);
- // if (res.ok) {
- // const data = await res.json();
- // pengumuman.findUnique.data = data.data ?? null;
- // } else {
- // console.error('Failed to fetch pengumuman:', res.statusText);
- // pengumuman.findUnique.data = null;
- // }
- // } catch (error) {
- // console.error('Error fetching pengumuman:', error);
- // pengumuman.findUnique.data = null;
- // }
- // },
- // },
+ findUnique: {
+ data: null as Prisma.PengumumanGetPayload<{
+ include: {
+ CategoryPengumuman: true;
+ };
+ }> | null,
+ async load(id: string) {
+ try {
+ const res = await fetch(`/api/desa/pengumuman/${id}`);
+ if (res.ok) {
+ const data = await res.json();
+ pengumuman.findUnique.data = data.data ?? null;
+ } else {
+ console.error("Failed to fetch pengumuman:", res.statusText);
+ pengumuman.findUnique.data = null;
+ }
+ } catch (error) {
+ console.error("Error fetching pengumuman:", error);
+ pengumuman.findUnique.data = null;
+ }
+ },
+ },
delete: {
loading: false,
async byId(id: string) {
@@ -237,6 +235,55 @@ const pengumuman = proxy({
}
},
},
+ findFirst: {
+ data: null as Prisma.PengumumanGetPayload<{
+ include: {
+ CategoryPengumuman: true;
+ };
+ }> | null,
+ loading: false,
+ async load() {
+ this.loading = true;
+ try {
+ const res = await ApiFetch.api.desa.pengumuman["find-first"].get();
+ if (res.status === 200 && res.data?.success) {
+ // Add type assertion to ensure type safety
+ pengumuman.findFirst.data = res.data
+ .data as Prisma.PengumumanGetPayload<{
+ include: {
+ CategoryPengumuman: true;
+ };
+ }> | null;
+ }
+ } catch (err) {
+ console.error("Gagal fetch pengumuman terbaru:", err);
+ } finally {
+ this.loading = false;
+ }
+ },
+ },
+ findRecent: {
+ data: [] as Prisma.PengumumanGetPayload<{
+ include: {
+ CategoryPengumuman: true;
+ };
+ }>[],
+ loading: false,
+
+ async load() {
+ try {
+ this.loading = true;
+ const res = await ApiFetch.api.desa.pengumuman["find-recent"].get();
+ if (res.status === 200 && res.data?.success) {
+ this.data = res.data.data ?? [];
+ }
+ } catch (error) {
+ console.error("Gagal fetch pengumuman recent:", error);
+ } finally {
+ this.loading = false;
+ }
+ },
+ },
});
const stateDesaPengumuman = proxy({
diff --git a/src/app/api/[[...slugs]]/_lib/desa/berita/findFirst.ts b/src/app/api/[[...slugs]]/_lib/desa/berita/findFirst.ts
new file mode 100644
index 00000000..383a4cec
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/berita/findFirst.ts
@@ -0,0 +1,30 @@
+import prisma from '@/lib/prisma';
+
+export default async function beritaFindFirst() {
+ try {
+ const result = await prisma.berita.findFirst({
+ where: {
+ isActive: true, // opsional kalau kamu punya field ini
+ },
+ orderBy: {
+ createdAt: 'desc', // ambil yang paling terbaru
+ },
+ include: {
+ image: true,
+ kategoriBerita: true,
+ }
+ });
+
+ return {
+ success: true,
+ message: 'Berhasil ambil berita terbaru',
+ data: result,
+ };
+ } catch (error) {
+ console.error('[findFirstBerita] Error:', error);
+ return {
+ success: false,
+ message: 'Gagal ambil berita terbaru',
+ };
+ }
+}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/berita/findRecent.ts b/src/app/api/[[...slugs]]/_lib/desa/berita/findRecent.ts
new file mode 100644
index 00000000..5e79b4cc
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/berita/findRecent.ts
@@ -0,0 +1,19 @@
+import prisma from "@/lib/prisma";
+
+export default async function findRecentBerita() {
+ const result = await prisma.berita.findMany({
+ orderBy: {
+ createdAt: "desc",
+ },
+ take: 4, // ambil 4 data terbaru
+ include: {
+ image: true,
+ kategoriBerita: true,
+ },
+ });
+
+ return {
+ success: true,
+ data: result,
+ };
+}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/berita/index.ts b/src/app/api/[[...slugs]]/_lib/desa/berita/index.ts
index 3fdf3b63..3959faf0 100644
--- a/src/app/api/[[...slugs]]/_lib/desa/berita/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/desa/berita/index.ts
@@ -5,6 +5,8 @@ import beritaCreate from "./create";
import beritaDelete from "./del";
import beritaUpdate from "./updt";
import findBeritaById from "./find-by-id";
+import beritaFindFirst from "./findFirst";
+import findRecentBerita from "./findRecent";
const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
.get("/category/find-many", kategoriBeritaFindMany)
@@ -22,6 +24,8 @@ const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
kategoriBeritaId: t.Union([t.String(), t.Null()]),
}),
})
+ .get("/find-first", beritaFindFirst)
+ .get("/find-recent", findRecentBerita)
.delete("/delete/:id", beritaDelete)
.put(
"/:id",
@@ -39,5 +43,6 @@ const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
}),
}
);
+
export default Berita;
diff --git a/src/app/api/[[...slugs]]/_lib/desa/pengumuman/findFirst.ts b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/findFirst.ts
new file mode 100644
index 00000000..94ef45a3
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/findFirst.ts
@@ -0,0 +1,29 @@
+import prisma from '@/lib/prisma';
+
+export default async function pengumumanFindFirst() {
+ try {
+ const result = await prisma.pengumuman.findFirst({
+ where: {
+ isActive: true, // opsional kalau kamu punya field ini
+ },
+ orderBy: {
+ createdAt: 'desc', // ambil yang paling terbaru
+ },
+ include: {
+ CategoryPengumuman: true,
+ }
+ });
+
+ return {
+ success: true,
+ message: 'Berhasil ambil pengumuman terbaru',
+ data: result,
+ };
+ } catch (error) {
+ console.error('[findFirstPengumuman] Error:', error);
+ return {
+ success: false,
+ message: 'Gagal ambil pengumuman terbaru',
+ };
+ }
+}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/pengumuman/findRecent.ts b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/findRecent.ts
new file mode 100644
index 00000000..a37fbf84
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/findRecent.ts
@@ -0,0 +1,18 @@
+import prisma from "@/lib/prisma";
+
+export default async function pengumumanFindRecent() {
+ const result = await prisma.pengumuman.findMany({
+ orderBy: {
+ createdAt: "desc",
+ },
+ take: 4, // ambil 4 data terbaru
+ include: {
+ CategoryPengumuman: true,
+ },
+ });
+
+ return {
+ success: true,
+ data: result,
+ };
+}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/pengumuman/index.ts b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/index.ts
index 0b059ea5..3bcb683e 100644
--- a/src/app/api/[[...slugs]]/_lib/desa/pengumuman/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/desa/pengumuman/index.ts
@@ -6,6 +6,8 @@ import pengumumanCategoryFindMany from "./category";
import pengumumanDelete from "./del";
import pengumumanFindById from "./find-by-id";
import pengumumanUpdate from "./updt";
+import pengumumanFindFirst from "./findFirst";
+import pengumumanFindRecent from "./findRecent";
const Pengumuman = new Elysia({ prefix: "/pengumuman", tags: ["Desa/Pengumuman"] })
.get("/category/find-many", pengumumanCategoryFindMany)
@@ -20,6 +22,8 @@ const Pengumuman = new Elysia({ prefix: "/pengumuman", tags: ["Desa/Pengumuman"]
categoryPengumumanId: t.Union([t.String(), t.Null()]),
}),
})
+ .get("/find-first", pengumumanFindFirst)
+ .get("/find-recent", pengumumanFindRecent)
.put("/:id", pengumumanUpdate, {
body: t.Object({
id: t.String(),
diff --git a/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/informasi-desa/page.tsx b/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/informasi-desa/page.tsx
index 9df154bb..4ac2d9c8 100644
--- a/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/informasi-desa/page.tsx
+++ b/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/informasi-desa/page.tsx
@@ -1,27 +1,136 @@
+/* eslint-disable react-hooks/exhaustive-deps */
'use client'
+import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import colors from '@/con/colors';
-import { Box, Paper, Stack, Text } from '@mantine/core';
-import { IconBell } from '@tabler/icons-react';
-import { motion } from 'framer-motion';
+import { Box, Card, Divider, Grid, GridCol, Image, Paper, Stack, Text, Title } from '@mantine/core';
+import dayjs from 'dayjs';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import { useEffect } from 'react';
+import { useProxy } from 'valtio/utils';
+import BackButton from '../../../desa/layanan/_com/BackButto';
+import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
+
+dayjs.extend(relativeTime);
function InformasiDesa() {
+ const stateBerita = useProxy(stateDashboardBerita.berita)
+ const statePengumuman = useProxy(stateDesaPengumuman.pengumuman)
+
+ useEffect(() => {
+ stateBerita.findFirst.load();
+ stateBerita.findRecent.load();
+ statePengumuman.findFirst.load();
+ statePengumuman.findRecent.load();
+ }, []);
+
+ const dataBerita = stateBerita.findFirst.data
+ const dataPengumuman = statePengumuman.findFirst.data
+
+
return (
-
-
-
-
-
- ,
-
- Informasi Desa
- Akses berita dan pengumuman terbaru seputar kegiatan desa
-
-
+
+
+
+
+
+
+ Informasi Desa
+
+
+
+
+ {dataBerita && (
+
+
+
+
+
+
+
+ {dataBerita.kategoriBerita?.name} • {dayjs(dataBerita.createdAt).fromNow()}
+ {dataBerita.judul}
+
+
+
+
+
+ )}
+
+ Berita Terbaru
+
+ {stateBerita.findRecent.data.map((item) => (
+
+
+
+
+
+
+
+ {item.judul}
+
+
+ {item.deskripsi}
+
+
+ {dayjs(item.createdAt).fromNow()}
+
+
+
+
+ ))}
+
+
+
+
+ {dataPengumuman && (
+
+
+ {dataPengumuman.judul}
+ {dataPengumuman.CategoryPengumuman?.name} • {dayjs(dataPengumuman.createdAt).fromNow()}
+
+
+
+
+
+ )}
+
+ Pengumuman Terbaru
+
+ {statePengumuman.findRecent.data.map((item) => (
+
+
+
+
+ {item.judul}
+
+
+ {item.deskripsi}
+
+
+ {dayjs(item.createdAt).fromNow()}
+
+
+
+
+ ))}
+
+
+
-
+
+
);
}
diff --git a/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/page.tsx b/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/page.tsx
index bd8ddc34..13145970 100644
--- a/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/page.tsx
+++ b/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/page.tsx
@@ -1,13 +1,16 @@
'use client'
import colors from '@/con/colors';
-import { Box, SimpleGrid, Stack, Text } from '@mantine/core';
+import { Box, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
+import { IconBell } from '@tabler/icons-react';
+import { motion } from 'framer-motion';
+import { useRouter } from 'next/navigation';
import BackButton from '../../desa/layanan/_com/BackButto';
import AdministrasiOnline from './administrasi-online/page';
-import InformasiDesa from './informasi-desa/page';
import PengaduanMasyarakat from './pengaduan-masyarakat/page';
function Page() {
+ const router = useRouter()
return (
@@ -29,7 +32,23 @@ function Page() {
-
+
+
+ router.push('/darmasaba/inovasi/layanan-online-desa/informasi-desa')}
+ >
+
+
+
+
+ Informasi Desa
+ Akses berita dan pengumuman terbaru seputar kegiatan desa
+
+
+
+
diff --git a/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/pengaduan-masyarakat/page.tsx b/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/pengaduan-masyarakat/page.tsx
index ce78eb08..e1641ea9 100644
--- a/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/pengaduan-masyarakat/page.tsx
+++ b/src/app/darmasaba/(pages)/inovasi/layanan-online-desa/pengaduan-masyarakat/page.tsx
@@ -82,7 +82,7 @@ function PengaduanMasyarakat() {
>
- ,
+
Pengaduan Masyarakat
Sampaikan keluhan dan aspirasi Anda melalui platform digital kami
diff --git a/src/app/darmasaba/_com/Navbar.tsx b/src/app/darmasaba/_com/Navbar.tsx
index 1c1fb433..404193ab 100644
--- a/src/app/darmasaba/_com/Navbar.tsx
+++ b/src/app/darmasaba/_com/Navbar.tsx
@@ -34,7 +34,7 @@ export function Navbar() {
}}
size={80} radius={"xl"}
>
-
+
stateNav.mobileOpen = !stateNav.mobileOpen} color={colors["blue-button"]} opened={mobileOpen} />