Compare commits

...

3 Commits

89 changed files with 7588 additions and 4349 deletions

View File

@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import { LatLngExpression } from 'leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import { useEffect } from 'react';
type MarkerData = {
position: [number, number];
popup: string;
};
type Props = {
center: [number, number];
markers: MarkerData[];
zoom?: number;
scrollWheelZoom?: boolean;
className?: string;
style?: React.CSSProperties;
};
export default function LeafletMultiMarkerMap({
center,
markers,
zoom = 13,
scrollWheelZoom = true,
className = '',
style = { height: '100%', width: '100%', zIndex: 0 },
}: Props) {
// Fix for default marker icons in Next.js
useEffect(() => {
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
});
}, []);
return (
<div className={className} style={style}>
<MapContainer
center={center as LatLngExpression}
zoom={zoom}
scrollWheelZoom={scrollWheelZoom}
style={{ height: '100%', width: '100%' }}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{markers.map((marker, index) => (
<Marker key={index} position={marker.position as LatLngExpression}>
<Popup>{marker.popup}</Popup>
</Marker>
))}
</MapContainer>
</div>
);
}

View File

@@ -56,13 +56,17 @@ const dataLingkunganDesaState = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
dataLingkunganDesaState.findMany.page = page;
dataLingkunganDesaState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

@@ -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";
@@ -67,10 +68,46 @@ const kegiatanDesa = proxy({
};
}>
> | null,
async load() {
const res = await ApiFetch.api.lingkungan.kegiatandesa["find-many"].get();
if (res.status === 200) {
kegiatanDesa.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "", kategori = "") => {
// Change to arrow function
kegiatanDesa.findMany.loading = true; // Use the full path to access the property
kegiatanDesa.findMany.page = page;
kegiatanDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
if (kategori) query.kategori = kategori;
const res = await ApiFetch.api.lingkungan.kegiatandesa[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
kegiatanDesa.findMany.data = res.data.data || [];
kegiatanDesa.findMany.total = res.data.total || 0;
kegiatanDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load kegiatan desa:",
res.data?.message
);
kegiatanDesa.findMany.data = [];
kegiatanDesa.findMany.total = 0;
kegiatanDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading kegiatan desa:", error);
kegiatanDesa.findMany.data = [];
kegiatanDesa.findMany.total = 0;
kegiatanDesa.findMany.totalPages = 1;
} finally {
kegiatanDesa.findMany.loading = false;
}
},
},
@@ -244,6 +281,35 @@ const kegiatanDesa = proxy({
kegiatanDesa.edit.form = { ...defaultKegiatanDesaForm };
},
},
findFirst: {
data: null as Prisma.KegiatanDesaGetPayload<{
include: {
image: true;
kategoriKegiatan: true;
};
}> | null,
loading: false,
// findFirst.load()
async load(kategori?: string) {
this.loading = true;
try {
const res = await ApiFetch.api.lingkungan.kegiatandesa["find-first"].get({
query: kategori ? { kategori } : {},
});
if (res.status === 200 && res.data?.success) {
this.data = res.data.data || null;
} else {
this.data = null;
}
} catch (err) {
console.error("Gagal fetch kegiatan desa terbaru:", err);
this.data = null;
} finally {
this.loading = false;
}
},
},
});
// ========================================= KATEGORI kegiatan ========================================= //
@@ -269,9 +335,7 @@ const kategoriKegiatan = proxy({
}
try {
kategoriKegiatan.create.loading = true;
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
"create"
].post(kategoriKegiatan.create.form);
const res = await ApiFetch.api.lingkungan.kategorikegiatan["create"].post(kategoriKegiatan.create.form);
if (res.status === 200) {
kategoriKegiatan.findMany.load();
return toast.success("Data berhasil ditambahkan");
@@ -305,9 +369,7 @@ const kategoriKegiatan = proxy({
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/lingkungan/kategorikegiatan/${id}`
);
const res = await fetch(`/api/lingkungan/kategorikegiatan/${id}`);
if (res.ok) {
const data = await res.json();
kategoriKegiatan.findUnique.data = data.data ?? null;
@@ -367,15 +429,12 @@ const kategoriKegiatan = proxy({
}
try {
const response = await fetch(
`/api/lingkungan/kategorikegiatan/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const response = await fetch(`/api/lingkungan/kategorikegiatan/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

View File

@@ -52,15 +52,19 @@ const pengelolaanSampah = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
pengelolaanSampah.findMany.page = page;
pengelolaanSampah.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
"find-many"
].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

@@ -56,13 +56,17 @@ const programPenghijauanState = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
programPenghijauanState.findMany.page = page;
programPenghijauanState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

@@ -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";
@@ -51,13 +52,46 @@ const jenjangPendidikan = proxy({
id: string;
nama: string;
}> | null,
async load() {
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
"find-many"
].get();
if (res.status === 200) {
jenjangPendidikan.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
jenjangPendidikan.findMany.loading = true; // Use the full path to access the property
jenjangPendidikan.findMany.page = page;
jenjangPendidikan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
jenjangPendidikan.findMany.data = res.data.data || [];
jenjangPendidikan.findMany.total = res.data.total || 0;
jenjangPendidikan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load jenjang pendidikan:",
res.data?.message
);
jenjangPendidikan.findMany.data = [];
jenjangPendidikan.findMany.total = 0;
jenjangPendidikan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading jenjang pendidikan:", error);
jenjangPendidikan.findMany.data = [];
jenjangPendidikan.findMany.total = 0;
jenjangPendidikan.findMany.totalPages = 1;
} finally {
jenjangPendidikan.findMany.loading = false;
}
},
},
@@ -304,13 +338,46 @@ const lembagaPendidikan = proxy({
};
}>
> | null,
async load() {
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan[
"find-many"
].get();
if (res.status === 200) {
lembagaPendidikan.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
lembagaPendidikan.findMany.loading = true; // Use the full path to access the property
lembagaPendidikan.findMany.page = page;
lembagaPendidikan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
lembagaPendidikan.findMany.data = res.data.data || [];
lembagaPendidikan.findMany.total = res.data.total || 0;
lembagaPendidikan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load lembaga pendidikan:",
res.data?.message
);
lembagaPendidikan.findMany.data = [];
lembagaPendidikan.findMany.total = 0;
lembagaPendidikan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading lembaga pendidikan:", error);
lembagaPendidikan.findMany.data = [];
lembagaPendidikan.findMany.total = 0;
lembagaPendidikan.findMany.totalPages = 1;
} finally {
lembagaPendidikan.findMany.loading = false;
}
},
},
@@ -558,12 +625,45 @@ const siswa = proxy({
};
}>
> | null,
async load() {
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
"find-many"
].get();
if (res.status === 200) {
siswa.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
siswa.findMany.loading = true; // Use the full path to access the property
siswa.findMany.page = page;
siswa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
siswa.findMany.data = res.data.data || [];
siswa.findMany.total = res.data.total || 0;
siswa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load siswa:",
res.data?.message
);
siswa.findMany.data = [];
siswa.findMany.total = 0;
siswa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading siswa:", error);
siswa.findMany.data = [];
siswa.findMany.total = 0;
siswa.findMany.totalPages = 1;
} finally {
siswa.findMany.loading = false;
}
},
},
@@ -798,12 +898,45 @@ const pengajar = proxy({
};
}>
> | null,
async load() {
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
"find-many"
].get();
if (res.status === 200) {
pengajar.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
pengajar.findMany.loading = true; // Use the full path to access the property
pengajar.findMany.page = page;
pengajar.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
pengajar.findMany.data = res.data.data || [];
pengajar.findMany.total = res.data.total || 0;
pengajar.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load pengajar:",
res.data?.message
);
pengajar.findMany.data = [];
pengajar.findMany.total = 0;
pengajar.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pengajar:", error);
pengajar.findMany.data = [];
pengajar.findMany.total = 0;
pengajar.findMany.totalPages = 1;
} finally {
pengajar.findMany.loading = false;
}
},
},
@@ -815,7 +948,9 @@ const pengajar = proxy({
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/pendidikan/infosekolahpaud/pengajar/${id}`);
const res = await fetch(
`/api/pendidikan/infosekolahpaud/pengajar/${id}`
);
if (res.ok) {
const data = await res.json();
pengajar.findUnique.data = data.data ?? null;
@@ -948,7 +1083,8 @@ const pengajar = proxy({
result
);
throw new Error(
result?.message || `Gagal mengupdate pengajar (${response.status})`
result?.message ||
`Gagal mengupdate pengajar (${response.status})`
);
}

View File

@@ -42,18 +42,10 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
const router = useRouter();
useEffect(() => {
load(page, 10)
}, [page])
load(page, 10, search)
}, [page, search])
const filteredData = (data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.jumlah.toLowerCase().includes(keyword) ||
item.icon.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
const iconMap: Record<string, React.FC<any>> = {
ekowisata: IconLeaf,

View File

@@ -126,7 +126,9 @@ function ListProgramPenghijauan({ search }: { search: string }) {
<TableTr key={item.id}>
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
<TableTd style={{ width: '35%', wordWrap: 'break-word' }} dangerouslySetInnerHTML={{ __html: item.judul }}></TableTd>
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>
<Text lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.judul }}/>
</TableTd>
<TableTd style={{ width: '10%' }}>
{iconMap[item.icon] && (
<Box title={item.icon}>

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -6,17 +7,28 @@ export default async function dataLingkunganDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
const search = (context.query.search as string) || '';
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.dataLingkunganDesa.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.dataLingkunganDesa.count({
where: { isActive: true }
where,
})
]);

View File

@@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function KegiatanDesaFindFirst(context: Context) {
const kategori = (context.query.kategori as string) || '';
const where: any = { isActive: true };
if (kategori) {
where.kategoriKegiatan = {
name: { equals: kategori, mode: 'insensitive' }
};
}
try {
const data = await prisma.kegiatanDesa.findFirst({
where,
include: {
image: true,
kategoriKegiatan: true,
},
orderBy: { createdAt: 'desc' },
});
return {
success: true,
message: "Berhasil ambil gotong royong terbaru",
data,
};
} catch (error) {
console.error('Error di findFirst:', error);
return {
success: false,
message: 'Gagal memuat gotong royong terbaru',
};
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,38 @@ import { Context } from "elysia";
async function kegiatanDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const kategori = (context.query.kategori as string) || ''; // 🔥 Parameter kategori baru
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Filter berdasarkan kategori (jika ada)
if (kategori) {
where.kategoriKegiatan = {
nama: {
equals: kategori,
mode: 'insensitive' // Tidak case-sensitive
}
};
}
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ judul: { contains: search, mode: 'insensitive' } },
{ deskripsiSingkat: { contains: search, mode: 'insensitive' } },
{ deskripsiLengkap: { contains: search, mode: 'insensitive' } },
{ kategoriKegiatan: { nama: { contains: search, mode: 'insensitive' } } }
];
}
try {
const [data, total] = await Promise.all([
prisma.kegiatanDesa.findMany({
where: { isActive: true },
where,
include: {
kategoriKegiatan: true,
image: true,
@@ -20,7 +47,7 @@ async function kegiatanDesaFindMany(context: Context) {
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.kegiatanDesa.count({
where: { isActive: true }
where,
})
]);

View File

@@ -4,6 +4,7 @@ import KegiatanDesaDelete from "./del";
import KegiatanDesaFindMany from "./findMany";
import KegiatanDesaFindUnique from "./findUnique";
import KegiatanDesaUpdate from "./updt";
import KegiatanDesaFindFirst from "./findFirst";
const KegiatanDesa = new Elysia({
prefix: "/kegiatandesa",
@@ -16,6 +17,9 @@ const KegiatanDesa = new Elysia({
// ✅ Find by ID
.get("/:id", KegiatanDesaFindUnique)
// ✅ Find First
.get("/find-first", KegiatanDesaFindFirst)
// ✅ Create
.post("/create", KegiatanDesaCreate, {
body: t.Object({

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,18 +6,28 @@ import { Context } from "elysia";
export default async function pengelolaanSampahFindMany(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;
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.pengelolaanSampah.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pengelolaanSampah.count({
where: { isActive: true }
where,
})
]);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -6,17 +7,26 @@ export default async function programPenghijauanFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
const search = (context.query.search as string) || '';
const where: any = { isActive: true };
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ judul: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.programPenghijauan.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.programPenghijauan.count({
where: { isActive: true }
where,
})
]);

View File

@@ -1,15 +1,56 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function jenjangPendidikanFindMany() {
const data = await prisma.jenjangPendidikan.findMany();
return {
success: true,
data: data.map((item: any) => {
return {
id: item.id,
nama: item.nama,
}
}),
export default async function jenjangPendidikanFindMany(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;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: "insensitive" } },
{ lembagas: { contains: search, mode: "insensitive" } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.jenjangPendidikan.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: "desc" },
}),
prisma.jenjangPendidikan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil jenjang pendidikan dengan pagination",
data: data.map((item: any) => {
return {
id: item.id,
nama: item.nama,
lembagas: item.lembagas,
};
}),
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
}
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data jenjang pendidikan",
};
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,26 @@ import { Context } from "elysia";
async function lembagaPendidikanFindMany(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;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: "insensitive" } },
{ siswa: { contains: search, mode: "insensitive" } },
{ pengajar: { contains: search, mode: "insensitive" } },
{ jenjangPendidikan: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.lembaga.findMany({
where: { isActive: true },
where,
include: {
jenjangPendidikan: true,
siswa: true,
@@ -20,8 +35,8 @@ async function lembagaPendidikanFindMany(context: Context) {
take: limit,
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.kegiatanDesa.count({
where: { isActive: true }
prisma.lembaga.count({
where,
})
]);
@@ -30,6 +45,7 @@ async function lembagaPendidikanFindMany(context: Context) {
message: "Success fetch lembaga pendidikan with pagination",
data,
page,
limit,
totalPages: Math.ceil(total / limit),
total,
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -6,11 +7,23 @@ async function pengajarFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
const search = (context.query.search as string) || "";
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: "insensitive" } },
{ lembaga: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.pengajar.findMany({
where: { isActive: true },
where,
include: {
lembaga: true,
},
@@ -19,7 +32,7 @@ async function pengajarFindMany(context: Context) {
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.pengajar.count({
where: { isActive: true }
where,
})
]);
@@ -28,6 +41,7 @@ async function pengajarFindMany(context: Context) {
message: "Success fetch pengajar with pagination",
data,
page,
limit,
totalPages: Math.ceil(total / limit),
total,
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -6,11 +7,23 @@ async function siswaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
const search = (context.query.search as string) || "";
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: "insensitive" } },
{ lembaga: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.siswa.findMany({
where: { isActive: true },
where,
include: {
lembaga: true,
},
@@ -19,7 +32,7 @@ async function siswaFindMany(context: Context) {
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.siswa.count({
where: { isActive: true }
where,
})
]);
@@ -28,6 +41,7 @@ async function siswaFindMany(context: Context) {
message: "Success fetch siswa with pagination",
data,
page,
limit,
totalPages: Math.ceil(total / limit),
total,
};

View File

@@ -1,9 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import { useParams } from 'next/navigation';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../_com/BackButto';
@@ -34,46 +34,44 @@ function Page() {
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.suratKeterangan.findUnique.load(id);
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
setData(result);
} catch (error) {
console.error('Error loading data:', error);
console.error('Terjadi kesalahan saat memuat data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, [id]);
if (loading) {
return (
<Center>
<Skeleton height={500} />
<Center h="100vh" bg={colors.Bg}>
<Skeleton height={500} radius="md" animate />
</Center>
);
}
if (!data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
<Center h="100vh" bg={colors.Bg}>
<Text fz="xl" c="dimmed">Maaf, data layanan tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }}>
<Container w={{ base: "100%", md: "60%" }}>
<Text
fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }}
fz={{ base: "2rem", md: "2.5rem", lg: "3rem" }}
ta="center"
fw="bold"
>
@@ -81,19 +79,32 @@ function Page() {
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Stack gap="md">
<Text
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
fz={{ base: "h4", md: "h2" }}
pb={20}
fz={{ base: "md", md: "lg" }}
c="dark.7"
ta="justify"
/>
{data.image2?.link && (
<Center>
<Image src={data.image2.link} alt={data.name} />
<Image
src={data.image2.link}
alt={data.name}
radius="md"
maw={500}
mx="auto"
style={{ boxShadow: '0 0 20px rgba(28,110,164,0.2)' }}
/>
</Center>
)}
<Group justify='center' mt="md">
<Button radius="lg" fz="h4" bg={colors['blue-button']}>
<Group justify="center" mt="md">
<Button
radius="xl"
size="lg"
variant="gradient"
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
>
Ajukan Permohonan
</Button>
</Group>

View File

@@ -1,62 +1,95 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client";
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { ActionIcon, Box, Divider, Flex, Skeleton, Text } from '@mantine/core';
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPendudukNonPermanent() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
const state = useProxy(stateLayananDesa);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananPendudukNonPermanen.findById.load('1')
setLoading(true);
await state.pelayananPendudukNonPermanen.findById.load('1');
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false)
setLoading(false);
}
}
loadData()
}, [])
};
loadData();
}, []);
const data = state.pelayananPendudukNonPermanen.findById.data;
const data = state.pelayananPendudukNonPermanen.findById.data
return (
<Box>
<Box py="lg">
{loading ? (
<Skeleton h={500} />
<Stack gap="lg">
<Skeleton height={40} radius="md" />
<Skeleton height={200} radius="md" />
<Skeleton height={30} radius="md" />
</Stack>
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{data?.name}</Text>
<Stack gap="xl">
<Box>
<Text fz={{ base: "xl", md: "2xl" }} fw={700} lh={1.3} c="dark">
{data?.name || "Judul belum tersedia"}
</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"} dangerouslySetInnerHTML={{__html: data?.deskripsi || ''}} />
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
<Box>
{data?.deskripsi ? (
<Text
fz={{ base: "sm", md: "md" }}
lh={1.7}
ta="justify"
c="dimmed"
dangerouslySetInnerHTML={{ __html: data?.deskripsi }}
/>
) : (
<Text fz="sm" c="gray">Deskripsi belum tersedia.</Text>
)}
</Box>
<Divider color={colors["blue-button"]} size="sm" />
<Flex justify="space-between" align="center" wrap="wrap" gap="md">
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">
25 Mei 2021 Darmasaba
</Text>
<Group gap="md">
<Tooltip label="Bagikan ke Facebook" withArrow>
<ActionIcon size="lg" radius="xl" variant="subtle" color="blue">
<IconBrandFacebook size={24} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</Tooltip>
<Tooltip label="Bagikan ke Instagram" withArrow>
<ActionIcon size="lg" radius="xl" variant="subtle" color="pink">
<IconBrandInstagram size={24} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</Tooltip>
<Tooltip label="Bagikan ke Twitter" withArrow>
<ActionIcon size="lg" radius="xl" variant="subtle" color="blue">
<IconBrandTwitter size={24} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</Tooltip>
<Tooltip label="Bagikan ke WhatsApp" withArrow>
<ActionIcon size="lg" radius="xl" variant="subtle" color="green">
<IconBrandWhatsapp size={24} />
</ActionIcon>
</Flex>
</Box>
</Tooltip>
</Group>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
<Divider color={colors["blue-button"]} size="sm" />
</Stack>
)}
</Box>
);

View File

@@ -1,7 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Button, Center, Group, Skeleton, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core';
import { Box, Button, Center, Group, Loader, Stack, Stepper, StepperCompleted, StepperStep, Text, Title } from '@mantine/core';
import { IconArrowLeft, IconArrowRight, IconCheck } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -18,7 +19,7 @@ function PelayananPerizinanBerusaha() {
setLoading(true);
await state.pelayananPerizinanBerusaha.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false);
}
@@ -28,76 +29,87 @@ function PelayananPerizinanBerusaha() {
const data = state.pelayananPerizinanBerusaha.findById.data;
if (!data) {
if (!data && !loading) {
return (
<Center>
<Text>Data tidak tersedia</Text>
<Center mih={300}>
<Stack align="center" gap="sm">
<Text fz="lg" fw={500} c="dimmed">Belum ada informasi layanan yang tersedia</Text>
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">Kunjungi OSS</Button>
</Stack>
</Center>
);
}
return (
<Box>
<Box px={{ base: 'md', md: 'xl' }} py="lg">
{loading ? (
<Center>
<Skeleton h={250} />
<Center mih={300}>
<Loader size="lg" color="blue" />
</Center>
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text
py={10}
ta={"justify"}
fz={{ base: "sm", md: 'h3' }}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '' }}
/>
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
<Box p={"xl"} w={{ base: "100%", md: "100%" }}>
<Stepper active={active} onStepClick={setActive} orientation="vertical"
styles={{
separator: {
marginLeft: 25
},
step: {
padding: '12px 0'
}
}}>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
Pendaftaran akun pada portal OSS
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI">
Memilih KBLI dengan jenis usaha yang akan didaftarkan
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
Proses verifikasi dan persetujuan oleh instansi terkait
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
</StepperStep>
<StepperCompleted>
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
</StepperCompleted>
</Stepper>
<Stack gap="lg">
<Box>
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
Perizinan Berusaha Berbasis Risiko melalui OSS
</Title>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
</Text>
</Box>
<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>Back</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>
Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
resmi OSS <a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.
<Text fz={{ base: 'sm', md: 'md' }} ta="justify" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '' }} />
<Box>
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>Alur pendaftaran NIB:</Text>
<Stepper active={active} onStepClick={setActive} orientation="vertical" color="blue" radius="md"
styles={{
step: { padding: '14px 0' },
stepBody: { marginLeft: 8 }
}}
>
<StepperStep label="Langkah 1" description="Daftar Akun">
<Text fz="sm">Membuat akun di portal OSS</Text>
</StepperStep>
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text>
</StepperStep>
<StepperStep label="Langkah 3" description="Pilih KBLI">
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text>
</StepperStep>
<StepperStep label="Langkah 4" description="Unggah Dokumen">
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text>
</StepperStep>
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text>
</StepperStep>
<StepperStep label="Langkah 6" description="Terbit NIB">
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text>
</StepperStep>
<StepperCompleted>
<Center>
<Stack align="center" gap="xs">
<IconCheck size={40} color="green" />
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text>
</Stack>
</Center>
</StepperCompleted>
</Stepper>
<Group justify="center" mt="lg">
<Button variant="light" leftSection={<IconArrowLeft size={18} />} onClick={prevStep} disabled={active === 0}>
Kembali
</Button>
<Button rightSection={<IconArrowRight size={18} />} onClick={nextStep}>
Lanjut
</Button>
</Group>
</Box>
<Text fz="sm" ta="justify" c="dimmed" mt="md">
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{" "}
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">oss.go.id</a> atau hubungi instansi pemerintah terkait.
</Text>
</Box>
</Box>
</Stack>
)}
</Box>
);

View File

@@ -1,96 +1,124 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client";
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Group, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { BackgroundImage, Box, Button, Center, Group, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananSuratKeterangan({ search }: { search: string }) {
const [loading, setLoading] = useState(false);
const router = useRouter()
const state = useProxy(stateLayananDesa)
const router = useRouter();
const state = useProxy(stateLayananDesa);
const filteredData = useMemo(() => {
if (!state.suratKeterangan.findMany.data) return [];
return state.suratKeterangan.findMany.data.filter(item => {
return state.suratKeterangan.findMany.data.filter((item) => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
return item.name?.toLowerCase().includes(keyword);
});
}, [state.suratKeterangan.findMany.data, search]);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.suratKeterangan.findMany.load()
await state.suratKeterangan.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
};
loadData();
}, []);
return (
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
<Box pb="xl">
<Group justify="space-between" align="center" mb="md">
<Group gap="xs">
<IconFileDescription size={28} stroke={1.8} color={colors["blue-button"]} />
<Text fz={{ base: "h4", md: "h2" }} fw={700}>
Layanan Surat Keterangan
</Text>
</Group>
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
<IconInfoCircle size={22} stroke={1.8} color={colors["blue-button"]} />
</Tooltip>
</Group>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
py="lg"
cols={{ base: 1, sm: 2, md: 3 }}
spacing="lg"
>
{loading ? (
<Center>
<Skeleton h={250} />
Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} h={250} radius="lg" />
))
) : filteredData.length === 0 ? (
<Center py="xl">
<Stack align="center" gap="xs">
<IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} />
<Text c="dimmed" ta="center">
Tidak ada layanan surat keterangan yang ditemukan
</Text>
</Stack>
</Center>
) : (
filteredData.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(`/darmasaba/desa/layanan/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})
filteredData.map((v, k) => (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={250}
radius="lg"
pos="relative"
style={{
boxShadow: "0 4px 20px rgba(0,0,0,0.1)",
overflow: "hidden",
}}
>
<Box
pos="absolute"
w="100%"
h="100%"
bg="rgba(0,0,0,0.45)"
style={{ borderRadius: 16 }}
/>
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
<Text
c="white"
fw={600}
fz="lg"
ta="center"
lineClamp={2}
>
{v.name}
</Text>
<Group justify="center">
<Button
size="md"
radius="xl"
bg={colors["blue-button"]}
px="lg"
onClick={() => router.push(`/darmasaba/desa/layanan/${v.id}`)}
style={{
boxShadow: "0 0 12px rgba(59,130,246,0.6)",
transition: "all 0.2s ease",
}}
>
Lihat Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
))
)}
</SimpleGrid>
</Box>
);
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Flex, Skeleton, Text, Title } from '@mantine/core';
import { Box, Card, Flex, Grid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconExternalLink } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -11,22 +12,22 @@ interface ServiceItem {
}
function PelayananTelunjukSaktiDesa() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
const state = useProxy(stateLayananDesa);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananTelunjukSaktiDesa.findMany.load()
setLoading(true);
await state.pelayananTelunjukSaktiDesa.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false)
setLoading(false);
}
}
loadData()
}, [])
};
loadData();
}, []);
const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{
name: string;
@@ -37,28 +38,63 @@ function PelayananTelunjukSaktiDesa() {
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}>
}>;
return (
<Box>
<Title fz="h2" py={10} mb="md">Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. Layanan Telunjuk Sakti Desa meliputi :</Title>
<Title order={2} mb="lg" fz={{ base: 22, md: 28 }} fw={700} style={{ lineHeight: 1.4 }}>
Layanan Telunjuk Sakti Desa <br />
<Text span c="dimmed" fz="lg" fw={400}>
Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman
</Text>
</Title>
{loading ? (
<Skeleton h={500} />
<Skeleton h={400} radius="lg" />
) : data.length === 0 ? (
<Card shadow="sm" radius="lg" withBorder>
<Text c="dimmed" ta="center" py="xl">
Belum ada layanan tersedia untuk saat ini
</Text>
</Card>
) : (
data.map((v, k) => {
return (
<Box key={k}>
<Box py={10}>
<Flex gap={"3"} align={"center"}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{v.name}
<Grid gutter="lg">
{data.map((v, k) => (
<Grid.Col span={{ base: 12, sm: 6, lg: 4 }} key={k}>
<Card
shadow="md"
radius="xl"
withBorder
p="lg"
style={{
transition: 'all 0.3s ease',
background: 'linear-gradient(145deg, #ffffff, #f8f9fa)',
}}
>
<Stack gap="sm">
<Text fw={700} fz="lg" lh={1.4}>
{v.name}
</Text>
<Text span fz={{ base: "h4", md: "h3" }}>
<a href={v.link} target="_blank" rel="noopener noreferrer" style={{ color: '#228be6' }}>{v.deskripsi}</a>
</Text>
</Flex>
</Box>
</Box>
)
})
<Flex gap="xs" align="center">
<IconExternalLink size={18} stroke={1.5} />
<Text
component="a"
href={v.link}
target="_blank"
rel="noopener noreferrer"
fz="sm"
c="blue"
td="underline"
style={{ cursor: 'pointer' }}
>
{v.deskripsi}
</Text>
</Flex>
</Stack>
</Card>
</Grid.Col>
))}
</Grid>
)}
</Box>
);

View File

@@ -12,7 +12,7 @@ import PelayananPendudukNonPermanent from "./_com/pelayananPendudukNonPermanent"
export default function Page() {
const [search, setSearch] = useState("")
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
@@ -40,14 +40,16 @@ export default function Page() {
</Stack>
</Container>
<Box px={{ base: "md", md: 100 }}>
{/* Bagian Pelayanan Surat Keterangan */}
<PelayananSuratKeterangan search={search} />
{/* Bagian Pelayanan Perizinan Berusaha */}
<PelayananPerizinanBerusaha/>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<PelayananTelunjukSaktiDesa/>
{/* Bagian Pelayanan Penduduk Non Permanent */}
<PelayananPendudukNonPermanent/>
<Stack gap={"xl"}>
{/* Bagian Pelayanan Surat Keterangan */}
<PelayananSuratKeterangan search={search} />
{/* Bagian Pelayanan Perizinan Berusaha */}
<PelayananPerizinanBerusaha />
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<PelayananTelunjukSaktiDesa />
{/* Bagian Pelayanan Penduduk Non Permanent */}
<PelayananPendudukNonPermanent />
</Stack>
</Box>
</Stack>
)

View File

@@ -2,18 +2,18 @@
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Center, Container, Image, Loader, Paper, Stack, Text, Title } from '@mantine/core';
import { IconMoodSad } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(potensiDesaState.potensiDesa)
const [loading, setLoading] = useState(true)
const state = useProxy(potensiDesaState.potensiDesa);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
@@ -22,54 +22,66 @@ function Page() {
setLoading(true);
await state.findUnique.load(id);
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [id])
};
loadData();
}, [id]);
if (loading) {
return (
<Center>
<Skeleton height={500} />
<Center h="80vh">
<Stack align="center" gap="md">
<Loader size="lg" color="blue" />
<Text c="dimmed" fz="sm">Sedang memuat informasi...</Text>
</Stack>
</Center>
);
}
if (!state.findUnique.data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
<Center h="80vh">
<Stack align="center" gap="sm">
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
<Title order={3}>Data Tidak Ditemukan</Title>
<Text c="dimmed" fz="sm">Mohon periksa kembali atau coba beberapa saat lagi</Text>
</Stack>
</Center>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
{state.findUnique.data?.name}
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Informasi dan Pelayanan Administrasi Digital
</Text>
</Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} />
</Container>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
{state.findUnique.data?.deskripsi || ''}
</Text>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "60%" }}>
<Paper radius="2xl" shadow="lg" p="xl" withBorder>
<Stack gap="lg" align="center">
<Title ta="center" fz={{ base: "2rem", md: "3rem" }} c={colors["blue-button"]} fw={800}>
{state.findUnique.data?.name}
</Title>
<Text ta="center" fw={600} fz={{ base: "md", md: "lg" }} c="dimmed">
Informasi & Pelayanan Potensi Desa Digital
</Text>
<Image
src={state.findUnique.data?.image?.link || ''}
alt={state.findUnique.data?.name || 'Potensi Desa'}
radius="lg"
fit="cover"
w="100%"
h={{ base: 220, md: 400 }}
fallbackSrc="https://placehold.co/800x400?text=Gambar+tidak+tersedia"
/>
<Text py="md" fz={{ base: "sm", md: "md" }} ta="justify" lh={1.8}>
{state.findUnique.data?.deskripsi || 'Belum ada deskripsi untuk potensi desa ini.'}
</Text>
</Stack>
</Paper>
</Container>
</Stack>
);
}

View File

@@ -1,27 +1,27 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { IconEye } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
import BackButton from '../layanan/_com/BackButto';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import BackButton from '../layanan/_com/BackButto';
function Page() {
const router = useTransitionRouter()
const [loading, setLoading] = useState(false)
const state = useProxy(potensiDesaState)
useEffect(()=> {
useEffect(() => {
state.kategoriPotensi.findMany.load()
const loadData = async () => {
try {
setLoading(true)
await state.potensiDesa.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false)
}
@@ -30,104 +30,121 @@ function Page() {
}, [])
const data = state.potensiDesa.findMany.data
// const kategoriData = state.kategoriPotensi.findMany.data
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} >
<Stack pos="relative" bg={colors.Bg} py="xl" gap={48}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: "md", md: 100 }}>
<Box>
<Stack gap={0}>
<Flex justify={"space-between"} align={"center"} >
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
<Stack gap="sm" maw={600}>
<Text fz={{ base: "2rem", md: "3rem" }} fw={900} c={colors["blue-button"]} lh={1.2}>
Potensi Desa Darmasaba
</Text>
<Text fz="lg" c="dimmed" ta="justify">
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
</Text>
</Stack>
<Paper
radius="2xl"
px="xl"
py="md"
bg={colors["blue-button"]}
shadow="xl"
withBorder
>
<Flex justify="center" align="center" gap="xl">
<Box>
<Text fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
Potensi Desa
<Text ta="center" fz="2rem" fw={800} c="white">
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
</Text>
<Text ta={"justify"} >
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba.
<Text ta="center" fz="sm" c="white" fw={500}>
Potensi
</Text>
</Box>
<Box>
<Text ta="center" fz="2rem" fw={800} c="white">
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0}
</Text>
<Text ta="center" fz="sm" c="white" fw={500}>
Wisata
</Text>
</Box>
<Paper radius={"md"} px={"xl"} py={5} bg={colors["blue-button"]} >
<Flex justify={"space-between"} align={"center"} gap={"xl"}>
<Box>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata' ).length || 0}
</Text>
<Text ta={"center"} fz={"sm"} c={"white"}>Potensi</Text>
</Box>
<Box>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata' ).length || 0}
</Text>
<Text ta={"center"} fz={"sm"} c={"white"}>Wisata</Text>
</Box>
</Flex>
</Paper>
</Flex>
</Stack>
</Box>
</Paper>
</Flex>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
<SimpleGrid py={48} cols={{ base: 1, sm: 2, lg: 3 }} spacing="2xl">
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
data?.map((v, k) => {
return (
Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} h={360} radius="xl" />
))
) : data && data.length > 0 ? (
data.map((v, k) => (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
h={360}
radius="xl"
style={{ overflow: 'hidden', position: 'relative' }}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
pos="absolute"
inset={0}
bg="linear-gradient(180deg, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.7) 100%)"
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
<Group>
<Paper radius={"lg"} py={7} px={10}>
<Text>{v.kategori?.nama}</Text>
<Paper radius="lg" py={6} px={12} shadow="md" withBorder bg="rgba(255,255,255,0.85)">
<Text fz="sm" fw={600}>{v.kategori?.nama}</Text>
</Paper>
</Group>
<Box p={"lg"}>
<Box>
<Text
fw={"bold"}
c={"white"}
size={"1.8rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
fw={800}
c="white"
fz="xl"
ta="center"
lineClamp={2}
lh={1.3}
>
{v.name}
</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}>
Detail
</Button>
<Tooltip label="Lihat detail potensi" withArrow>
<Button
radius="xl"
size="md"
leftSection={<IconEye size={18} />}
bg={colors["blue-button"]}
variant="gradient"
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
>
Lihat Detail
</Button>
</Tooltip>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
))
) : (
<Center h={240}>
<Stack align="center" gap="xs">
<Text fz="lg" fw={600} c="dimmed">
Belum ada potensi desa
</Text>
<Text fz="sm" c="dimmed">
Data potensi akan tampil di sini setelah tersedia.
</Text>
</Stack>
</Center>
)}
</SimpleGrid>
</Box>
</Stack>
);
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Stack, Paper, Image, Text, Center, Skeleton } from '@mantine/core';
import React, { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
import colors from '@/con/colors'
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'
import { useEffect } from 'react'
import { useProxy } from 'valtio/utils'
function LambangDesa() {
const state = useProxy(stateProfileDesa.lambangDesa)
@@ -17,26 +17,60 @@ function LambangDesa() {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="lg">
<Skeleton h={420} radius="xl" />
</Box>
)
}
return (
<Box pb={70}>
<Stack align='center' gap={0}>
<Box pb={30}>
<Box pb={90}>
<Stack align="center" gap="lg">
<Box pb="lg">
<Center>
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
<Image
src={"/darmasaba-icon.png"}
alt="Lambang resmi desa"
w={{ base: 180, md: 280 }}
radius="md"
/>
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Lambang Desa</Text>
<Text
c={colors['blue-button']}
ta="center"
fw={800}
fz={{ base: 28, md: 40 }}
mt="sm"
style={{ letterSpacing: '-0.5px' }}
>
Lambang Desa
</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Text fz={{ base: "md", md: "h3"}} ta={"justify"} dangerouslySetInnerHTML={{__html: data.deskripsi}} />
<Paper
p="xl"
radius="xl"
withBorder
shadow="sm"
w="100%"
style={{
background: 'linear-gradient(135deg, #ffffff 0%, #f3f7ff 100%)',
borderColor: '#e0e9ff',
}}
>
<Tooltip label="Deskripsi lambang desa" position="top-start" withArrow>
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.8}
c="dark"
ta="justify"
style={{ fontWeight: 400 }}
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/>
</Tooltip>
</Paper>
</Stack>
</Box >
);
</Box>
)
}
export default LambangDesa;
export default LambangDesa

View File

@@ -1,59 +1,95 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Card, Center, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text, Tooltip } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
import { IconPhoto } from '@tabler/icons-react';
import colors from '@/con/colors';
function MaskotDesa() {
const state = useProxy(stateProfileDesa.maskotDesa)
const state = useProxy(stateProfileDesa.maskotDesa);
useEffect(() => {
state.findUnique.load("edit")
}, [])
state.findUnique.load('edit');
}, []);
const { data, loading } = state.findUnique
const { data, loading } = state.findUnique;
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
<Center mih={500}>
<Stack align="center" gap="sm">
<Loader size="lg" color="blue" />
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text>
</Stack>
</Center>
);
}
return (
<Box pb={70}>
<Stack align='center' gap={0}>
<Box pb={30}>
<Center>
<Image src={"/pudak-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Maskot Desa</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.deskripsi }} />
<Group wrap="wrap" gap="md">
{data.images.map((img, index) => (
<Card key={index} p="xs" w={220}>
<Image
src={img.image.link}
alt={img.label}
w={200}
h={200}
fit="cover"
radius="md"
style={{ border: '1px solid #ccc', objectFit: 'cover' }}
/>
<Text ta="center" mt="xs" fw="bold">{img.label}</Text>
</Card>
))}
<Box pb={80}>
<Stack align="center" gap="xl">
<Stack align="center" gap={10}>
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} />
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
</Stack>
<Paper
p={{ base: 'md', md: 'xl' }}
radius="lg"
shadow="sm"
withBorder
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
>
<Text
fz={{ base: 'sm', md: 'lg' }}
lh={1.7}
ta="justify"
c="dark"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/>
<Group justify="center" gap="lg" mt="lg">
{data.images.length > 0 ? (
data.images.map((img, index) => (
<Tooltip key={index} label={img.label} position="bottom" withArrow>
<Card
radius="lg"
shadow="md"
withBorder
w={220}
p="sm"
style={{
transition: 'transform 200ms ease, box-shadow 200ms ease',
}}
className="hover:scale-105 hover:shadow-lg"
>
<Image
src={img.image.link}
alt={img.label}
w="100%"
h={200}
fit="cover"
radius="md"
/>
<Text ta="center" mt="sm" fw={600} fz="sm" c="dark">
{img.label}
</Text>
</Card>
</Tooltip>
))
) : (
<Stack align="center" gap="xs" mt="lg">
<IconPhoto size={48} stroke={1.5} color="gray" />
<Text c="dimmed" fz="sm">Belum ada gambar maskot yang ditambahkan</Text>
</Stack>
)}
</Group>
</Paper>
</Stack>
</Box >
</Box>
);
}
export default MaskotDesa;

View File

@@ -1,75 +1,133 @@
import colors from '@/con/colors';
'use client'
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { motion } from 'framer-motion';
import { IconSparkles } from '@tabler/icons-react';
import colors from '@/con/colors';
const dataText = [
{
id: 1,
title: "SANTUN",
description: "Memberikan pelayanan yang baik, penuh rasa empati, sopan, dan beretika"
title: "Santun",
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
},
{
id: 2,
title: "ADAPTIF",
description: "Cepat menyesuaikan diri menghadapi perubahan dan bertindak proaktif"
title: "Adaptif",
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
},
{
id: 3,
title: "INOVATIF",
description: "Selalu berusaha menciptakan pembaruan atau kreasi baru"
title: "Inovatif",
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
},
{
id: 4,
title: "PROFESIONAL",
description: "Memiliki pengetahuan, terampil dan bertanggung jawab"
title: "Profesional",
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
},
{
id: 5,
title: "GESIT",
description: "Inisiatif dan cekatan dalam bekerja"
title: "Gesit",
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
},
]
];
const letters = ["S", "I", "G", "A", "P"];
function MotoDesa() {
return (
<Box pb={70}>
<Stack align='center' gap={0} >
<Box pb={30}>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h1", md: "2.5rem" }}>Moto Desa Darmasaba</Text>
</Box>
<Flex gap={50} pb={50} pt={20} justify={"space-evenly"} align={"center"} >
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>S</Text>
</ActionIcon>
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>I</Text>
</ActionIcon>
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>G</Text>
</ActionIcon>
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>A</Text>
</ActionIcon>
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>P</Text>
</ActionIcon>
</Flex>
<Paper mb={50} p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<SimpleGrid
cols={{
base: 1,
md: 2,
<Box pb={80} px={{ base: "md", md: "xl" }}>
<Stack align="center" gap="lg">
<Box>
<Text
ta="center"
fw={800}
fz={{ base: "2rem", md: "2.8rem" }}
style={{
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
}}
>
{dataText.map((v, k) => {
return (
<Box key={k}>
<Text fw={"bold"} fz={{ base: "lg", md: "h3" }}>{v.title}</Text>
<Text fz={{ base: "sm", md: "md" }}>{v.description}</Text>
</Box>
)
})}
Moto Desa Darmasaba
</Text>
</Box>
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
{letters.map((letter, i) => (
<motion.div
key={i}
whileHover={{ scale: 1.15, rotate: 5 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 300 }}
>
<ActionIcon
radius="xl"
size={90}
variant="gradient"
gradient={{ from: "blue", to: "cyan" }}
style={{
boxShadow: `0 0 18px ${colors['blue-button']}`,
backdropFilter: "blur(6px)",
}}
>
<Text c="white" fw={800} fz="xl">
{letter}
</Text>
</ActionIcon>
</motion.div>
))}
</Flex>
<Paper
radius="lg"
p="xl"
withBorder
style={{
background: "linear-gradient(145deg, #ffffffcc, #f5faffcc)",
boxShadow: "0 8px 24px rgba(0,0,0,0.08)",
}}
>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
{dataText.map((v) => (
<motion.div
key={v.id}
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<Stack gap={4}>
<Flex align="center" gap="sm">
<IconSparkles size={20} color={colors['blue-button']} />
<Text fw={700} fz={{ base: "lg", md: "xl" }} c={colors['blue-button']}>
{v.title}
</Text>
</Flex>
<Text fz={{ base: "sm", md: "md" }} c="gray.7">
{v.description}
</Text>
</Stack>
</motion.div>
))}
</SimpleGrid>
</Paper>
<Text mb={40} c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h4", md: "h3" }}>&quot;Berkomitmen memberikan pelayanan terbaik dengan prinsip SIGAP untuk masyarakat Desa Darmasaba&quot;</Text>
<Text
ta="center"
fw={700}
fz={{ base: "md", md: "xl" }}
c="blue.8"
mt="md"
style={{
maxWidth: 720,
lineHeight: 1.6,
}}
>
&quot;Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
<Text span fw={800} c="cyan.6">
SIGAP
</Text>{" "}
untuk masyarakat Desa Darmasaba.&quot;
</Text>
</Stack>
</Box>
);

View File

@@ -2,9 +2,10 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Divider, Tooltip } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
import { IconUser, IconBriefcase, IconUsers, IconTargetArrow } from '@tabler/icons-react';
function ProfilPerbekel() {
const state = useProxy(stateProfileDesa.profilPerbekel)
@@ -17,77 +18,155 @@ function ProfilPerbekel() {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py={20} px="md">
<Skeleton h={500} radius="lg" />
</Box>
)
}
return (
<Box pb={70}>
<Stack align='center' gap={0}>
<Box pb={30}>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Profil Perbekel</Text>
</Box>
<Box pb={80} px="md">
<Stack align="center" gap={0} mb={40}>
<Text
c={colors['blue-button']}
ta="center"
fw="bold"
fz={{ base: "2rem", md: "2.8rem" }}
style={{ letterSpacing: "0.5px" }}
>
Profil Perbekel
</Text>
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
</Stack>
<SimpleGrid
cols={{
base: 1,
md: 2,
}}
pb={50}
>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
<Box>
<Paper bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Paper
bg={colors['white-trans-1']}
w="100%"
radius="xl"
shadow="md"
withBorder
>
<Stack gap={0}>
<Image
pt={{ base: 0, md: 120 }}
px={"lg"}
pt={{ base: 0, md: 100 }}
px="lg"
src={data.image?.link || "/perbekel.png"}
sizes='100%'
alt=''
alt="Foto Perbekel"
radius="lg"
onError={(e) => {
e.currentTarget.src = "/perbekel.png";
}}
/>
<Paper
bg={colors['blue-button']}
px={"lg"}
px="lg"
radius="0 0 var(--mantine-radius-xl) var(--mantine-radius-xl)"
className="glass3"
py={{ base: 20, md: 50 }}
>
<Text c={colors['white-1']} fz={{ base: "md", md: "h3" }}>Perbekel Desa Darmasaba</Text>
<Text c={colors['white-1']} fw={"bolder"} fz={{ base: "md", md: "h2" }}>
I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P.
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
Perbekel Desa Darmasaba
</Text>
<Text
c={colors['white-1']}
fw="bolder"
fz={{ base: "xl", md: "h2" }}
mt={8}
>
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
</Text>
</Paper>
</Stack>
</Paper>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Box>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Biodata</Text>
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.biodata }} />
</Box>
<Box>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman</Text>
<Text fz={{ base: "1rem", md: "1.5rem" }} dangerouslySetInnerHTML={{ __html: data.pengalaman }} />
</Box>
<Paper
p="xl"
bg={colors['white-trans-1']}
w="100%"
radius="xl"
shadow="md"
withBorder
>
<Stack gap="xl">
<Box>
<Tooltip label="Informasi pribadi perbekel" withArrow>
<Stack gap={6}>
<Stack align="center" gap={6}>
<IconUser size={22} color={colors['blue-button']} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text>
</Stack>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
ta="justify"
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.biodata }}
/>
</Stack>
</Tooltip>
</Box>
<Box>
<Tooltip label="Pengalaman kerja perbekel" withArrow>
<Stack gap={6}>
<Stack align="center" gap={6}>
<IconBriefcase size={22} color={colors['blue-button']} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
</Stack>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
ta="justify"
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
/>
</Stack>
</Tooltip>
</Box>
</Stack>
</Paper>
</SimpleGrid >
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Box>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman Organisasi</Text>
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }} />
</Box>
<Box pb={20}>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Program Kerja Unggulan</Text>
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.programUnggulan }} />
</SimpleGrid>
<Paper
p="xl"
bg={colors['white-trans-1']}
w="100%"
radius="xl"
shadow="md"
withBorder
>
<Stack gap="xl">
<Box>
<Stack align="center" gap={6} >
<IconUsers size={22} color={colors['blue-button']} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text>
</Stack>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
ta="justify"
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
/>
</Box>
</Box>
<Box>
<Stack align="center" gap={6} mb={6}>
<IconTargetArrow size={22} color={colors['blue-button']} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Program Kerja Unggulan</Text>
</Stack>
<Box px={10}>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
ta="justify"
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
/>
</Box>
</Box>
</Stack>
</Paper>
</Box >
</Box>
);
}

View File

@@ -7,39 +7,69 @@ import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function SejarahDesa() {
const state = useProxy(stateProfileDesa.sejarahDesa)
const state = useProxy(stateProfileDesa.sejarahDesa);
useEffect(() => {
state.findUnique.load("edit")
}, [])
state.findUnique.load('edit');
}, []);
const { data, loading } = state.findUnique
const { data, loading } = state.findUnique;
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="lg">
<Skeleton h={500} radius="lg" />
</Box>
)
);
}
return (
<>
<Box pb={70}>
<Stack align='center' gap={0}>
<Box pb={30}>
<Center>
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Sejarah Desa</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.deskripsi }} />
</Paper>
<Box py="xl">
<Stack align="center" gap="xl">
<Stack align="center" gap="sm">
<Center>
<Image
src="/darmasaba-icon.png"
alt="Ikon Desa Darmasaba"
w={{ base: 180, md: 260 }}
radius="md"
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
/>
</Center>
<Center>
<Text
c={colors['blue-button']}
ta="center"
fw={700}
fz={{ base: '2rem', md: '2.8rem' }}
style={{ letterSpacing: '-0.5px' }}
>
Sejarah Desa
</Text>
</Center>
</Stack>
</Box >
</>
<Paper
p="xl"
radius="lg"
bg="white"
shadow="sm"
style={{
background: 'linear-gradient(135deg, #ffffff 0%, #f9fbfd 100%)',
border: `1px solid ${colors['blue-button']}20`,
}}
>
<Stack gap="md">
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.8}
ta="justify"
style={{ color: '#2a2a2a' }}
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/>
</Stack>
</Paper>
</Stack>
</Box>
);
}

View File

@@ -1,59 +1,102 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Center, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import { useProxy } from 'valtio/utils';
import { Box, Center, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconUser } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
function SemuaPerbekel() {
const state = useProxy(stateProfileDesa.mantanPerbekel)
const state = useProxy(stateProfileDesa.mantanPerbekel);
useShallowEffect(() => {
state.findMany.load()
}, [])
useShallowEffect(() => {
state.findMany.load();
}, []);
const {data, loading} = state.findMany
const { data, loading } = state.findMany;
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
if (loading || !data) {
return (
<Box py="xl">
<Skeleton h={500} radius="xl" />
</Box>
);
}
if (data.length === 0) {
return (
<Center py="xl">
<Stack align="center" gap="sm">
<IconUser size={48} stroke={1.5} />
<Title fw="bold" order={2}>Belum ada data Perbekel</Title>
<Text c="dimmed" fz="sm" ta="center">Data mantan Perbekel akan muncul di sini ketika sudah tersedia</Text>
</Stack>
</Center>
);
}
return (
<Box pb={50}>
<Stack align='center'>
<Box pb={30}>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h1", md: "2.5rem" }}>Perbekel Dari Masa Ke Masa</Text>
<Box pb={80}>
<Stack align="center" gap="lg">
<Box>
<Text
ta="center"
fw={900}
fz={{ base: "2rem", md: "2.5rem" }}
variant="gradient"
gradient={{ from: "blue", to: "cyan", deg: 45 }}
>
Perbekel Dari Masa ke Masa
</Text>
</Box>
<SimpleGrid
cols={{
base: 1,
md: 3,
}}
>
{data.map((v, k) => {
return (
<Box key={k}>
<Paper h={620} mb={50} radius={26} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Box>
<Center>
<Image src={v.image?.link} alt='' />
</Center>
</Box>
<Box>
<Stack gap={"sm"} py={10}>
<Text ta={"center"} fw={"bold"} fz={{ base: "h3", md: "h3" }}>{v.nama}</Text>
<Text ta={"center"} fz={{ base: "h5", md: "h4" }}>{v.daerah}</Text>
<Text ta={"center"} fz={{ base: "h5", md: "h4" }}>{v.periode}</Text>
</Stack>
</Box>
</Paper>
</Box>
)
})
}
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
{data.map((v: any, k: number) => (
<Paper
key={k}
radius="2xl"
shadow="md"
withBorder
p="lg"
bg="white"
style={{
transition: "all 250ms ease",
}}
className="hover:shadow-xl hover:scale-[1.02]"
>
<Stack gap="md" align="center">
<Box w="100%" h={300} style={{ overflow: "hidden", borderRadius: "1rem" }}>
<Image
src={v.image?.link}
alt={v.nama || "Foto Perbekel"}
fit="cover"
h="100%"
w="100%"
/>
</Box>
<Stack gap={4} align="center">
<Tooltip label="Nama Perbekel" withArrow>
<Text fw={700} fz="lg" ta="center">
{v.nama}
</Text>
</Tooltip>
<Tooltip label="Wilayah menjabat" withArrow>
<Text c="dimmed" fz="sm" ta="center">
{v.daerah}
</Text>
</Tooltip>
<Tooltip label="Periode jabatan" withArrow>
<Text c="blue" fw={600} fz="sm" ta="center">
{v.periode}
</Text>
</Tooltip>
</Stack>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
</Box>

View File

@@ -6,43 +6,90 @@ import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function VisimisiDesa() {
const state = useProxy(stateProfileDesa.visiMisiDesa)
function VisiMisiDesa() {
const state = useProxy(stateProfileDesa.visiMisiDesa);
useEffect(() => {
state.findUnique.load("edit")
}, [])
useEffect(() => {
state.findUnique.load('edit');
}, []);
const { data, loading } = state.findUnique
const { data, loading } = state.findUnique;
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
if (loading || !data) {
return (
<>
<Box pb={30}>
<Stack align='center' gap={0}>
<Box pb={30}>
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Text c={colors['blue-button']} ta={"center"} fw={"lighter"} fz={"2.5rem"}>Visi Desa</Text>
<Text fz={"1.5rem"} ta={"center"} fw={"bold"} dangerouslySetInnerHTML={{ __html: data.visi }} />
</Paper>
<Paper my={20} p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Text c={colors['blue-button']} ta={"center"} fw={"lighter"} fz={"2.5rem"}>Misi Desa</Text>
<Box>
<Text fz={"1.5rem"} fw={"bold"} dangerouslySetInnerHTML={{ __html: data.misi }} />
</Box>
</Paper>
</Stack>
</Box >
</>
<Box py="xl">
<Skeleton h={500} radius="lg" />
</Box>
);
}
return (
<Box py="xl">
<Stack align="center" gap="xl">
<Image
src="/darmasaba-icon.png"
alt="Lambang Desa Darmasaba"
w={{ base: 160, md: 240 }}
radius="md"
/>
<Paper
p="xl"
radius="lg"
shadow="md"
withBorder
w="100%"
style={{
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
}}
>
<Text
c={colors['blue-button']}
ta="center"
fw={700}
fz={{ base: '2rem', md: '2.5rem' }}
mb="md"
>
Visi Desa
</Text>
<Text
fz={{ base: '1.125rem', md: '1.375rem' }}
ta="center"
fw={500}
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.visi }}
/>
</Paper>
<Paper
p="xl"
radius="lg"
shadow="md"
withBorder
w="100%"
style={{
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
}}
>
<Text
c={colors['blue-button']}
ta="center"
fw={700}
fz={{ base: '2rem', md: '2.5rem' }}
mb="md"
>
Misi Desa
</Text>
<Text
fz={{ base: '1.125rem', md: '1.375rem' }}
fw={500}
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.misi }}
/>
</Paper>
</Stack>
</Box>
);
}
export default VisimisiDesa;
export default VisiMisiDesa;

View File

@@ -5,112 +5,130 @@ import { IconArrowRight } from '@tabler/icons-react';
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<SimpleGrid
px={{ base: 20, md: 100 }}
cols={{
base: 1,
md: 2
}}
cols={{ base: 1, md: 2 }}
spacing="xl"
>
{/* Content 1 */}
<Box pt={{base: 0, md: 35}}>
<Text c={colors["blue-button"]} fz={{ base: "h4", md: "h3" }} >
Info Keamanan dan Pencegahan Kriminalitas
<Box pt={{ base: 0, md: 35 }}>
<Text c={colors['blue-button']} fz={{ base: 'h4', md: 'h3' }}>
Community Safety & Crime Prevention
</Text>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Kontak Darurat
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
Emergency Contacts
</Text>
<Group>
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
Lihat Detail
<Button
rightSection={<IconArrowRight />}
mt={20}
radius="xl"
size="md"
bg={colors['blue-button']}
c={colors['white-1']}
>
View Details
</Button>
</Group>
</Box>
{/* Content 2 */}
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Paper p={"lg"}>
<Text c={colors["blue-button"]} fw={'bold'} fz={{ base: "h4", md: "h3" }} >
Tips menjaga keamanan lingkungan
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
<Paper p="lg" radius="xl" shadow="md">
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
How to Keep Your Neighborhood Safe
</Text>
<Text fz={{ base: "h5", md: "h4" }} c={colors["blue-button"]} >
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
Practical safety habits everyone can apply daily to reduce risks.
</Text>
<Group>
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
Lihat Detail
<Button
rightSection={<IconArrowRight />}
mt={20}
radius="xl"
size="md"
bg={colors['blue-button']}
c={colors['white-1']}
>
Learn More
</Button>
</Group>
</Paper>
<Paper p={"lg"}>
<Text c={colors["blue-button"]} fw={'bold'} fz={{ base: "h4", md: "h3" }} >
Mengenali tanda-tanda kegiatan kriminal
<Paper p="lg" radius="xl" shadow="md">
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
Recognizing Criminal Activities
</Text>
<Text fz={{ base: "h5", md: "h4" }} c={colors["blue-button"]} >
the printing and typesetting industry. the printing and typesetting industry.
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
Key warning signs and behavior patterns you should stay aware of.
</Text>
<Group>
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
Lihat Detail
<Button
rightSection={<IconArrowRight />}
mt={20}
radius="xl"
size="md"
bg={colors['blue-button']}
c={colors['white-1']}
>
Learn More
</Button>
</Group>
</Paper>
</SimpleGrid>
{/* Content 3 */}
<Paper p={'xl'}>
<Text fz={{ base: "h3", md: "h2" }} c={colors["blue-button"]} fw={"bold"}>
Program Keamanan Rutin
<Paper p="xl" radius="xl" shadow="lg">
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold">
Ongoing Security Programs
</Text>
<Stack pt={30} gap={'lg'}>
<Box>
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
<Flex align={'center'} justify={'space-between'}>
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
<IconArrowRight size={30} color={colors['white-1']} />
<Stack pt={30} gap="lg">
{['Night Patrol', 'Neighborhood Watch', 'Emergency Preparedness'].map((program, i) => (
<Paper key={i} p="md" bg={colors['blue-button']} radius="md" shadow="sm">
<Flex align="center" justify="space-between">
<Text fz="h3" c={colors['white-1']}>
{program}
</Text>
<IconArrowRight size={28} color={colors['white-1']} />
</Flex>
</Paper>
</Box>
<Box>
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
<Flex align={'center'} justify={'space-between'}>
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
<IconArrowRight size={30} color={colors['white-1']} />
</Flex>
</Paper>
</Box>
<Box>
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
<Flex align={'center'} justify={'space-between'}>
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
<IconArrowRight size={30} color={colors['white-1']} />
</Flex>
</Paper>
</Box>
))}
<Box pt={25}>
<Button fullWidth rightSection={<IconArrowRight size={20} color={colors['white-1']} />}>Lihat program lainnya</Button>
<Button
fullWidth
radius="xl"
size="md"
bg={colors['blue-button']}
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
>
Explore More Programs
</Button>
</Box>
</Stack>
</Paper>
{/* Content 4 */}
<Box>
<Paper p={'xl'} >
<Box style={{ maxWidth: "560px", width: "100%", aspectRatio: "16/9" }}>
<iframe width="100%"
<Paper p="xl" radius="xl" shadow="lg">
<Box style={{ maxWidth: 560, width: '100%', aspectRatio: '16/9' }}>
<iframe
width="100%"
height="100%"
src="https://www.youtube.com/embed/p5OkdBgasWA?si=6lFKXeE9LN5zcJQq" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" ></iframe>
src="https://www.youtube.com/embed/p5OkdBgasWA?si=6lFKXeE9LN5zcJQq"
title="Community Safety Patrol"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
</Box>
<Text py={10} fz={{ base: "h3", md: "h2" }} fw={'bold'} c={colors['blue-button']}>
Patroli Malam Darmasaba
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
Darmasaba Night Patrol
</Text>
<Text fz={'h4'} >
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
<Text fz="h4" c={colors['blue-button']}>
A closer look at how the community works together to maintain safety.
</Text>
<Box pt={10}>
<Button bg={colors['blue-button']} rightSection={<IconArrowRight size={20} color={colors['white-1']} />}>
Lihat Video Lainnya
<Button
radius="xl"
size="md"
bg={colors['blue-button']}
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
>
Watch More Videos
</Button>
</Box>
</Paper>

View File

@@ -2,158 +2,155 @@
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Button, Center, Divider, Group, Image, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Divider, Group, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function Page() {
const state = useProxy(artikelKesehatanState)
const params = useParams()
const state = useProxy(artikelKesehatanState);
const params = useParams();
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
state.findUnique.load(params?.id as string);
}, []);
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Stack py="xl" px="md">
<Skeleton h={420} radius="lg" />
<Skeleton h={20} mt="md" w="60%" />
<Skeleton h={20} w="40%" />
<Skeleton h={200} radius="md" />
</Stack>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Paper radius={10}>
<Box style={{ borderTopLeftRadius: 10, borderTopRightRadius: 10 }} bg={colors['blue-button']}>
<Text p={'md'} fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw={"bold"}>
Detail Lengkap Fasilitas Kesehatan
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
<Paper radius="xl" shadow="md" withBorder>
<Box style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} bg={colors['blue-button']}>
<Text p="md" fz={{ base: 'h3', md: 'h2' }} c={colors['white-1']} fw="bold">
{state.findUnique.data.title || 'Detail Artikel Kesehatan'}
</Text>
</Box>
<Box p={'md'} >
<Stack gap={'xs'}>
<Center bg={'#DEE3E3FF'}>
<Image
w={'100%'}
src={'/api/img/dbd.png'}
alt='' />
</Center>
<Box>
<Text c={'#9A9D9DFF'} fz={{ base: 'h6', md: 'h5' }}>
{new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })}
</Text>
</Box>
{/* Pendahuluan */}
<Text fz={'h4'} fw={"bold"}>
Pendahuluan
</Text>
<Divider />
<Text pb={10} fz={'h4'} ta={'justify'}>
{state.findUnique.data.introduction?.content}
</Text>
{/* Kenali Gejala DBD */}
<Text fz={'h4'} fw={"bold"}>
Kenali Gejala DBD
</Text>
<Divider />
<Text fz={'h4'}>
{state.findUnique.data.symptom?.title}
</Text>
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
{/* Cara Mencegah DBD */}
<Text fz={'h4'} fw={"bold"}>
{state.findUnique.data.prevention?.title}
</Text>
<Divider />
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
{/* Pertolongan Pertama Pada Penderita DBD */}
<Text fz={'h4'} fw={"bold"}>
{state.findUnique.data.firstaid?.title}
</Text>
<Divider />
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
{/* Mitos dan Fakta tentang DBD */}
<Text fz={'h4'} fw={"bold"}>
{state.findUnique.data.mythvsfact?.title}
</Text>
<Divider />
<Box pb={10}>
<Table highlightOnHover withTableBorder withColumnBorders>
<TableThead>
<TableTr>
<TableTh fz={'h4'}>Mitos</TableTh>
<TableTh fz={'h4'}>Fakta</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{state.findUnique.data?.mythvsfact ? (
<TableTr>
<TableTd>
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
</TableTd>
<TableTd>
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
</TableTd>
</TableTr>
) : (
<TableTr>
<TableTd colSpan={3} style={{ textAlign: 'center' }}>Tidak ada data mitos dan fakta</TableTd>
</TableTr>
)}
</TableTbody>
</Table>
</Box>
{/* Kapan Harus ke Dokter */}
<Text fz={'h4'} fw={"bold"}>
Kapan Harus ke Dokter?
</Text>
<Divider />
<Text fz={'h4'}>
Segera bawa penderita ke fasilitas kesehatan jika mengalami:
</Text>
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} />
{/* Kasus DBD di Wilayah Abiansemal */}
<Text fz={'h4'} fw={"bold"}>
Kasus DBD di Wilayah Abiansemal
</Text>
<Divider />
<Paper p={'lg'} bg={colors['blue-button-trans']}>
<Text fz={'h3'} c={colors['white-1']} fw={'bold'}>Informasi Lebih Lanjut</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Hotline DBD : <Text span fz={'h4'}>(0361) 123456</Text>
<Box p="lg">
<Stack gap="lg">
<Group gap="xs">
<IconCalendar size={18} color={colors['blue-button']} />
<Text c="dimmed" fz="sm">
{new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
WhatsApp Center : <Text span fz={'h4'}>081234567890</Text>
</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Email : <Text span fz={'h4'}>
p2p@dinkes.badungkab.go.id</Text>
</Text>
</Paper>
{/* Referensi */}
<Text fz={'h4'} fw={"bold"}>
Referensi
</Text>
<Divider />
<List pb={10} type='ordered'>
<ListItem fz={'h4'}>Kementerian Kesehatan RI. (2024). Pedoman Pencegahan dan Pengendalian DBD.</ListItem>
<ListItem fz={'h4'}>World Health Organization. (2024). Dengue Guidelines for Diagnosis, Treatment, Prevention and Control.</ListItem>
<ListItem fz={'h4'}>Dinas Kesehatan Kabupaten Badung. (2025). Laporan Surveilans DBD Triwulan I 2025.</ListItem>
</List>
<Group>
<Button fz={'lg'} bg={colors['blue-button']}>
Download Infografis Pencegahan DBD (PDF)
</Button>
<Button fz={'lg'} bg={'green'}>
Bagikan Artikel Ini
</Button>
</Group>
<Stack gap="lg">
<Box>
<Text fz="h4" fw="bold">Pendahuluan</Text>
<Divider my="xs" />
<Text fz="md" lh={1.6} ta="justify">
{state.findUnique.data.introduction?.content}
</Text>
</Box>
<Box>
<Text fz="h4" fw="bold">Kenali Gejala DBD</Text>
<Divider my="xs" />
<Text fz="md" fw="semibold">{state.findUnique.data.symptom?.title}</Text>
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.prevention?.title}</Text>
<Divider my="xs" />
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.firstaid?.title}</Text>
<Divider my="xs" />
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.mythvsfact?.title}</Text>
<Divider my="xs" />
<Box pb="md">
<Table highlightOnHover withTableBorder withColumnBorders striped>
<TableThead>
<TableTr>
<TableTh fz="sm" fw="bold">Mitos</TableTh>
<TableTh fz="sm" fw="bold">Fakta</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{state.findUnique.data?.mythvsfact ? (
<TableTr>
<TableTd>
<Text fz="sm" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
</TableTd>
<TableTd>
<Text fz="sm" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
</TableTd>
</TableTr>
) : (
<TableTr>
<TableTd colSpan={2} ta="center" c="dimmed">Belum ada data mitos dan fakta</TableTd>
</TableTr>
)}
</TableTbody>
</Table>
</Box>
</Box>
<Box>
<Text fz="h4" fw="bold">Kapan Harus ke Dokter?</Text>
<Divider my="xs" />
<Group gap="xs" mb="xs">
<IconAlertCircle size={18} color="red" />
<Text fz="md">Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
</Group>
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} />
</Box>
<Box>
<Text fz="h4" fw="bold">Kasus DBD di Wilayah Abiansemal</Text>
<Divider my="xs" />
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
<Group gap="xs" mb="sm">
<IconInfoCircle size={20} color={colors['white-1']} />
<Text fz="h4" c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Text>
</Group>
<Stack gap={4}>
<Text fz="sm" c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
<Text fz="sm" c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
<Text fz="sm" c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
</Stack>
</Paper>
</Box>
<Box>
<Text fz="h4" fw="bold">Referensi</Text>
<Divider my="xs" />
<List spacing="xs" size="sm" type="ordered">
<ListItem>Kementerian Kesehatan RI. (2024). Pedoman Pencegahan dan Pengendalian DBD.</ListItem>
<ListItem>World Health Organization. (2024). Dengue Guidelines for Diagnosis, Treatment, Prevention and Control.</ListItem>
<ListItem>Dinas Kesehatan Kabupaten Badung. (2025). Laporan Surveilans DBD Triwulan I 2025.</ListItem>
</List>
</Box>
</Stack>
</Stack>
</Box>
</Paper>

View File

@@ -1,8 +1,9 @@
'use client'
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
import { Anchor, Box, Divider, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Anchor, Box, Card, Divider, Group, Image, Loader, Paper, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
@@ -16,36 +17,71 @@ function ArtikelKesehatanPage() {
if(!state.findMany.data){
return (
<Box py={10}>
<Skeleton h={500}/>
<Box py="xl" ta="center">
<Loader size="lg" color={colors['blue-button']} />
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
</Box>
)
}
if(state.findMany.data.length === 0){
return (
<Box py="xl" ta="center">
<Image src="/empty-state.svg" alt="Tidak ada data" w={220} mx="auto" />
<Text mt="md" fw="bold" fz="lg">Belum ada artikel kesehatan</Text>
<Text c="dimmed" fz="sm">Artikel akan tampil di sini setelah tersedia.</Text>
</Box>
)
}
return (
<Box>
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
<Stack gap={'xs'}>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
{state.findMany.data.map((item) => (
<Box key={item.id}>
<Image pt={5} src={'/api/img/dbd.png'} alt="" />
<Text fz={'h4'} fw={'bold'} >
{item.title}
</Text>
<Text fz={'h6'} pb={10}>
Diposting: {new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} | Dinas Kesehatan
</Text>
<Text fz={'h4'} pb={10} lineClamp={2}>
{item.content}
</Text>
<Anchor c={'black'} onClick={()=> router.push(`/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan-page/${item.id}`)} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} >
Baca Selengkapnya {'>>'}
</Text>
</Anchor>
</Box>
))}
<Divider color={colors['blue-button']} px={'xl'} />
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
<Stack gap="xl">
<Title order={2} ta="center" c={colors['blue-button']}>Artikel Kesehatan</Title>
<Divider color={colors['blue-button']} />
<Stack gap="lg">
{state.findMany.data.map((item) => (
<Card
key={item.id}
withBorder
radius="lg"
shadow="sm"
p="lg"
style={{ transition: 'transform 200ms ease' }}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'translateY(-4px)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
>
<Card.Section>
<Image src={'/api/img/dbd.png'} alt={item.title} height={200} fit="cover" />
</Card.Section>
<Stack gap="xs" mt="md">
<Text fw="bold" fz="xl" c="dark">{item.title}</Text>
<Group gap="xs">
<IconCalendar size={16} color={colors['blue-button']} />
<Text fz="sm" c="dimmed">
{new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} Dinas Kesehatan
</Text>
</Group>
<Text fz="md" c="dark" lineClamp={3}>
{item.content}
</Text>
<Tooltip label="Baca artikel lengkap">
<Anchor
onClick={()=> router.push(`/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan-page/${item.id}`)}
variant="light"
c={colors['blue-button']}
>
<Group gap="xs">
<Text fw="bold" fz="md">Baca Selengkapnya</Text>
<IconChevronRight size={18} />
</Group>
</Anchor>
</Tooltip>
</Stack>
</Card>
))}
</Stack>
</Stack>
</Paper>
</Box>

View File

@@ -1,160 +1,353 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Divider, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { ActionIcon, Anchor, AspectRatio, Badge, Box, Button, Card, Chip, CopyButton, Divider, Grid, Group, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowRight, IconBrandWhatsapp, IconCheck, IconCopy, IconDeviceLandlinePhone, IconFileDownload, IconHeart, IconInfoCircle, IconMail, IconMapPin, IconMoodEmpty, IconSearch, IconStethoscope, IconUser, IconUsersGroup, IconWallet } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useMemo } from 'react';
import { useProxy } from 'valtio/utils';
interface Kontak {
telepon: string;
whatsapp: string;
email: string;
}
interface LayananItem {
nama: string;
keterangan?: string;
}
interface LayananUnggulan {
items: LayananItem[];
}
interface Lokasi {
mapsEmbed: string;
}
interface TarifDanLayanan {
layanan: string;
tarif: string | number;
gratisBpjs?: boolean;
}
function Page() {
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const params = useParams()
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const params = useParams();
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
state.findUnique.load(params?.id as string);
}, []);
if (!state.findUnique.data) {
const data = state.findUnique.data as any; // Temporary any to fix type issues
const nama = data?.name || 'Fasilitas Kesehatan';
const alamat = data?.informasiumum?.alamat || '-';
const jam = data?.informasiumum?.jamOperasional || '-';
const layananUnggulan = (data?.layananunggulan as LayananUnggulan)?.items || [];
const tenaga = data?.dokterdantenagamedis || null;
const fasilitasPendukungHtml = data?.fasilitaspendukung?.content || '';
const tarif = (data?.tarifdanlayanan as TarifDanLayanan) || null;
const kontak = (data?.kontak as Kontak) || {
telepon: '(0361) 123456',
whatsapp: '081234567890',
email: 'info@fasilitas-kesehatan.id'
};
const lokasi = (data?.lokasi as Lokasi) || {
mapsEmbed: 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3945.272172359321!2d115.21836257533302!3d-8.569807186941553!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23e9d99b9395f%3A0xb002795fdcb33b30!2sUPTD%20Puskesmas%20Abiansemal%20III!5e0!3m2!1sid!2sid!4v1744792682341!5m2!1sid!2sid'
};
const gratisBpjs = (data?.tarifdanlayanan as TarifDanLayanan)?.gratisBpjs ?? true;
const formatRupiah = useMemo(
() => (v?: number | string) => {
if (v === null || v === undefined || v === '') return '-';
const n = typeof v === 'string' ? Number(v) : v;
if (Number.isNaN(n as number)) return String(v);
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(n as number);
},
[]
);
if (state.findUnique.loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Stack bg={colors.Bg} mih="100vh" p="lg" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={32} w={220} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={64} radius="lg" />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Grid gutter="lg">
<Grid.Col span={{ base: 12, md: 8 }}>
<Skeleton h={320} radius="lg" />
</Grid.Col>
<Grid.Col span={{ base: 12, md: 4 }}>
<Skeleton h={320} radius="lg" />
</Grid.Col>
</Grid>
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={420} radius="lg" />
</Box>
</Stack>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
<Group justify="space-between">
<Group gap="xs">
<BackButton />
</Group>
<Group gap="xs">
<Badge variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="xl" size="lg" leftSection={<IconHeart size={16} />}>Pelayanan Aktif</Badge>
<Badge variant="light" radius="xl" size="lg" leftSection={<IconInfoCircle size={16} />}>Jam: {jam}</Badge>
</Group>
</Group>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Paper radius={10}>
<Box style={{ borderTopLeftRadius: 10, borderTopRightRadius: 10 }} bg={colors['blue-button']}>
<Text p={'md'} fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw={"bold"}>
Detail Lengkap Fasilitas Kesehatan
</Text>
</Box>
<Box p={'md'} >
<Stack gap={'xs'}>
{/* Informasi Umum */}
<Text fz={'h4'} fw={"bold"}>
Informasi Umum
</Text>
<Divider />
<Text fz={'h4'} fw={"bold"}>
Nama Fasilitas : <Text span fz={'h4'}>{state.findUnique.data?.name}</Text>
</Text>
<Text fz={'h4'} fw={"bold"}>
Alamat : <Text span fz={'h4'}>{state.findUnique.data?.informasiumum.alamat}</Text>
</Text>
<Text pb={10} fz={'h4'} fw={"bold"}>
Jam Operasional: : <Text span fz={'h4'}>{state.findUnique.data?.informasiumum.jamOperasional}</Text>
</Text>
{/* Layanan Unggulan */}
<Text fz={'h4'} fw={"bold"}>
Layanan Unggulan
</Text>
<Divider />
{/* Tenaga Medis */}
<Text fz={'h4'} fw={"bold"}>
Dokter & Tenaga Medis
</Text>
<Box px={{ base: 'md', md: 100 }}>
<Card radius="xl" p="lg" withBorder style={{ backdropFilter: 'blur(6px)' }}>
<Stack gap="sm">
<Title order={2}>{nama}</Title>
<Group gap="md" wrap="wrap">
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconMapPin size={18} /></ThemeIcon>
<Text>{alamat}</Text>
</Group>
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconDeviceLandlinePhone size={18} /></ThemeIcon>
<Text>{kontak.telepon}</Text>
<CopyButton value={kontak.telepon}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin nomor'}>
<ActionIcon variant="subtle" onClick={copy}>{copied ? <IconCheck size={18} /> : <IconCopy size={18} />}</ActionIcon>
</Tooltip>
)}
</CopyButton>
</Group>
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconBrandWhatsapp size={18} /></ThemeIcon>
<Text>{kontak.whatsapp}</Text>
<CopyButton value={kontak.whatsapp}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin WhatsApp'}>
<ActionIcon variant="subtle" onClick={copy}>{copied ? <IconCheck size={18} /> : <IconCopy size={18} />}</ActionIcon>
</Tooltip>
)}
</CopyButton>
</Group>
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconMail size={18} /></ThemeIcon>
<Text>{kontak.email}</Text>
<CopyButton value={kontak.email}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin email'}>
<ActionIcon variant="subtle" onClick={copy}>{copied ? <IconCheck size={18} /> : <IconCopy size={18} />}</ActionIcon>
</Tooltip>
)}
</CopyButton>
</Group>
</Group>
<Group gap="xs" mt="sm" wrap="wrap">
<Chip defaultChecked radius="xl" variant="light" icon={<IconStethoscope size={16} />}>Layanan Medis</Chip>
<Chip radius="xl" variant="light" icon={<IconUsersGroup size={16} />}>Ramah Keluarga</Chip>
<Chip radius="xl" variant="light" icon={<IconWallet size={16} />}>Pembayaran Non-Tunai</Chip>
</Group>
</Stack>
</Card>
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Grid gutter="lg">
<Grid.Col span={{ base: 12, md: 8 }}>
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Group justify="space-between" align="center">
<Title order={3}>Informasi Umum</Title>
<Badge variant="gradient" gradient={{ from: 'cyan', to: 'blue' }} radius="xl">Terverifikasi</Badge>
</Group>
<Divider />
<Box pb={10}>
<Table highlightOnHover withTableBorder withColumnBorders>
<Group gap="xl" align="start">
<Stack gap={2}>
<Text c="dimmed" fz="sm">Nama Fasilitas</Text>
<Text fw={600}>{nama}</Text>
</Stack>
<Stack gap={2}>
<Text c="dimmed" fz="sm">Jam Operasional</Text>
<Text fw={600}>{jam}</Text>
</Stack>
</Group>
<Divider />
<Title order={4}>Layanan Unggulan</Title>
{Array.isArray(layananUnggulan) && layananUnggulan.length > 0 ? (
<List spacing="xs" icon={<ThemeIcon variant="light" radius="xl"><IconArrowRight size={16} /></ThemeIcon>}>
{layananUnggulan.map((x: any, idx: number) => (
<ListItem key={idx}>
<Group gap="xs" wrap="wrap">
<Text fw={600}>{x?.nama || 'Layanan'}</Text>
{x?.keterangan && <Badge variant="light" radius="sm">{x.keterangan}</Badge>}
</Group>
</ListItem>
))}
</List>
) : (
<Paper withBorder radius="md" p="md">
<Group gap="sm">
<IconMoodEmpty />
<Text>Tidak ada layanan unggulan yang tercatat.</Text>
</Group>
</Paper>
)}
<Divider />
<Title order={4}>Peta Lokasi</Title>
<AspectRatio ratio={16 / 9}>
<iframe src={lokasi.mapsEmbed} style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }} loading="lazy" aria-label="Peta Lokasi" />
</AspectRatio>
</Stack>
</Card>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 4 }}>
<Stack gap="lg">
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={4}>Kontak Cepat</Title>
<Group gap="sm" wrap="wrap">
<Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">Telepon</Button>
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">WhatsApp</Button>
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">Email</Button>
</Group>
<Anchor target="_blank" underline="hover">Kunjungi situs resmi</Anchor>
</Stack>
</Card>
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={4}>Dokter & Tenaga Medis</Title>
<Table highlightOnHover withTableBorder withColumnBorders stickyHeader stickyHeaderOffset={0} aria-label="Tabel Dokter">
<TableThead>
<TableTr>
<TableTh fz={'h4'}>Nama</TableTh>
<TableTh fz={'h4'}>Spesialisasi</TableTh>
<TableTh fz={'h4'}>Jadwal</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Spesialisasi</TableTh>
<TableTh>Jadwal</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{state.findUnique.data?.dokterdantenagamedis ? (
{tenaga ? (
<TableTr>
<TableTd>{state.findUnique.data.dokterdantenagamedis.name}</TableTd>
<TableTd>{state.findUnique.data.dokterdantenagamedis.specialist}</TableTd>
<TableTd>{state.findUnique.data.dokterdantenagamedis.jadwal}</TableTd>
<TableTd><Group gap="xs"><IconUser size={16} /><Text>{tenaga?.name || '-'}</Text></Group></TableTd>
<TableTd>{tenaga?.specialist || '-'}</TableTd>
<TableTd>{tenaga?.jadwal || '-'}</TableTd>
</TableTr>
) : (
<TableTr>
<TableTd colSpan={3} style={{ textAlign: 'center' }}>Tidak ada data dokter/tenaga medis</TableTd>
<TableTd colSpan={3}>
<Group justify="center" gap="xs" c="dimmed">
<IconSearch size={18} />
<Text>Tidak ada data tenaga medis.</Text>
</Group>
</TableTd>
</TableTr>
)}
</TableTbody>
</Table>
</Box>
{/* Fasilitas Pendukung */}
<Text fz={'h4'} fw={"bold"}>
Fasilitas Pendukung
</Text>
</Stack>
</Card>
</Stack>
</Grid.Col>
</Grid>
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Grid gutter="lg">
<Grid.Col span={{ base: 12, md: 8 }}>
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={3}>Fasilitas Pendukung</Title>
<Divider />
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.fasilitaspendukung?.content }} />
{/* Dokter */}
<Text fz={'h4'} fw={"bold"}>
Layanan & Tarif
</Text>
{fasilitasPendukungHtml ? (
<Text fz="md" style={{ lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} />
) : (
<Paper withBorder radius="md" p="md">
<Group gap="sm">
<IconMoodEmpty />
<Text>Belum ada informasi fasilitas pendukung.</Text>
</Group>
</Paper>
)}
</Stack>
</Card>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 4 }}>
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={3}>Layanan & Tarif</Title>
<Divider />
<Table highlightOnHover withTableBorder withColumnBorders>
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
<TableThead>
<TableTr>
<TableTh fz={'h4'}>Layanan</TableTh>
<TableTh fz={'h4'}>Tarif</TableTh>
<TableTh>Layanan</TableTh>
<TableTh>Tarif</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>{state.findUnique?.data?.tarifdanlayanan.layanan}</TableTd>
<TableTd>{state.findUnique?.data?.tarifdanlayanan.tarif}</TableTd>
</TableTr>
{tarif ? (
<TableTr>
<TableTd>{tarif?.layanan || '-'}</TableTd>
<TableTd>{formatRupiah(tarif?.tarif)}</TableTd>
</TableTr>
) : (
<TableTr>
<TableTd colSpan={2}>
<Group justify="center" gap="xs" c="dimmed">
<IconSearch size={18} />
<Text>Tidak ada data tarif.</Text>
</Group>
</TableTd>
</TableTr>
)}
</TableTbody>
</Table>
<Text fz={'h6'} pb={10} fw={"bold"}>
* Gratis dengan BPJS Kesehatan
</Text>
{/* Prosedur Pendaftaran */}
<Text fz={'h4'} fw={"bold"}>
Prosedur Pendaftaran
</Text>
<Divider />
<List type='ordered' pb={10} >
<ListItem fz={'h4'}>Pendaftaran dibuka pukul 07:30 WITA</ListItem>
<ListItem fz={'h4'}>Bawa KTP dan Kartu BPJS (jika ada)</ListItem>
<ListItem fz={'h4'}>Ambil nomor antrian di loket pendaftaran</ListItem>
<ListItem fz={'h4'}>Pengunjung baru wajib mengisi formulir pendaftaran</ListItem>
<ListItem fz={'h4'}>Pendaftaran online tersedia melalui aplikasi S-Sehat</ListItem>
</List>
<Paper p={'lg'} bg={colors['blue-button-trans']}>
<Text fz={'h3'} c={colors['white-1']} fw={'bold'}>Kontak Darurat</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Telepon : <Text span fz={'h4'}>(0361) 123456</Text>
</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
WhatsApp : <Text span fz={'h4'}>081234567890</Text>
</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Email : <Text span fz={'h4'}>puskesmasabiansemal3@gmail.com</Text>
</Text>
</Paper>
{/* <Paper p={'lg'} w={{ base: "100%", md: "100%" }}>
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3945.272172359321!2d115.21836257533302!3d-8.569807186941553!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23e9d99b9395f%3A0xb002795fdcb33b30!2sUPTD%20Puskesmas%20Abiansemal%20III!5e0!3m2!1sid!2sid!4v1744792682341!5m2!1sid!2sid" width="600" height="450" style={{ border: 2, width: "100%", borderRadius: 10 }} loading="lazy"></iframe>
</Paper>
{gratisBpjs && (
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text>
</Group>
)}
<Group>
<Button fz={'lg'} bg={colors['blue-button']}>
Download Brosur Layanan (PDF)
</Button>
</Group> */}
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} leftSection={<IconFileDownload size={18} />}>Unduh Brosur (PDF)</Button>
</Group>
</Stack>
</Box>
</Paper>
</Stack>
</Card>
</Grid.Col>
</Grid>
</Box>
<Box px={{ base: 'md', md: 100 }} pb="xl">
<Paper radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={3}>Prosedur Pendaftaran</Title>
<Divider />
<List type="ordered" spacing="xs" icon={<ThemeIcon variant="light" radius="xl"><IconArrowRight size={16} /></ThemeIcon>}>
<ListItem>Pendaftaran dibuka pukul 07.30 WITA</ListItem>
<ListItem>Bawa KTP dan Kartu BPJS (jika ada)</ListItem>
<ListItem>Ambil nomor antrian di loket pendaftaran</ListItem>
<ListItem>Pengunjung baru mengisi formulir pendaftaran</ListItem>
<ListItem>Pendaftaran online tersedia melalui aplikasi S-Sehat</ListItem>
</List>
</Stack>
</Paper>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,50 +1,100 @@
'use client'
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Anchor, Box, Divider, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Anchor, Badge, Box, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import { IconMapPin, IconClock, IconArrowRight } from '@tabler/icons-react';
function FasilitasKesehatanPage() {
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const router = useRouter();
useShallowEffect(() => {
state.findMany.load()
}, [])
state.findMany.load();
}, []);
if(!state.findMany.data){
if (!state.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="xl" px="md">
<Stack gap="md">
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
</Stack>
</Box>
)
);
}
return (
<Box>
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
<Stack gap={'xs'}>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Fasilitas Kesehatan</Text>
{state.findMany.data.map((item) => (
<Box key={item.id}>
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>
{item.name}
</Text>
<Text fz={'h4'}>
{item.informasiumum.alamat}
</Text>
<Text fz={'h4'}>
{item.informasiumum.jamOperasional}
</Text>
<Anchor onClick={() => router.push(`/darmasaba/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/${item.id}`)} c={colors['blue-button']} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'}>
Detail {'>>'}
</Text>
</Anchor>
</Box>
))}
<Divider color={colors['blue-button']} px={'xl'} />
<Paper p="xl" radius="xl" shadow="md" h="100%" bg="white">
<Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
Fasilitas Kesehatan
</Text>
<Divider size="sm" color={colors['blue-button']} />
<Stack gap="lg">
{state.findMany.data.map((item) => (
<Card
key={item.id}
withBorder
radius="xl"
shadow="sm"
p="lg"
style={{
background: 'linear-gradient(135deg, #fdfdfd, #f7faff)',
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(-4px)';
(e.currentTarget as HTMLElement).style.boxShadow = '0 8px 20px rgba(0,0,0,0.08)';
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(0px)';
(e.currentTarget as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.05)';
}}
>
<Stack gap="sm">
<Group justify="space-between" align="center">
<Text fw={700} fz="lg" c={colors['blue-button']}>
{item.name}
</Text>
<Badge color="blue" radius="sm" variant="light" fz="xs">
Aktif
</Badge>
</Group>
<Group gap="xs">
<IconMapPin size={18} stroke={1.5} color={colors['blue-button']} />
<Text fz="sm" c="dimmed">
{item.informasiumum.alamat}
</Text>
</Group>
<Group gap="xs">
<IconClock size={18} stroke={1.5} color={colors['blue-button']} />
<Text fz="sm" c="dimmed">
{item.informasiumum.jamOperasional}
</Text>
</Group>
<Anchor
onClick={() =>
router.push(
`/darmasaba/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/${item.id}`
)
}
c={colors['blue-button']}
fz="sm"
fw={600}
style={{ display: 'inline-flex', alignItems: 'center', gap: '4px' }}
>
Lihat Detail
<IconArrowRight size={16} stroke={1.5} />
</Anchor>
</Stack>
</Card>
))}
</Stack>
</Stack>
</Paper>
</Box>

View File

@@ -2,10 +2,30 @@
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, CheckIcon, Combobox, ComboboxChevron, ComboboxOption, ComboboxOptions, ComboboxTarget, Divider, Group, InputBase, InputPlaceholder, Paper, Skeleton, Stack, Text, Textarea, TextInput, useCombobox } from '@mantine/core';
import {
ActionIcon,
Box,
Button,
Combobox,
ComboboxChevron,
ComboboxOption,
ComboboxOptions,
ComboboxTarget,
Divider,
Group,
InputBase,
InputPlaceholder,
Paper,
Skeleton,
Stack,
Text,
Textarea,
TextInput,
useCombobox
} from '@mantine/core';
import { DateInput } from '@mantine/dates';
import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar } from '@tabler/icons-react';
import { IconCalendar, IconDownload, IconPhone, IconMail, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -17,6 +37,7 @@ const layanan = [
'Konsultasi Gizi',
'Pemeriksaan Kesehatan'
];
function Page() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
@@ -30,192 +51,160 @@ function Page() {
});
const [value, setValue] = useState<string | null>('');
const [dateInputOpened, setDateInputOpened] = useState(false);
const params = useParams();
const state = useProxy(jadwalkegiatanState);
useShallowEffect(() => {
state.findUnique.load(params?.id as string);
}, []);
const options = layanan.map((item) => (
<ComboboxOption value={item} key={item} active={item === value}>
<Group gap="xs">
{item === value && <CheckIcon size={12} />}
{item === value && <IconUser size={14} stroke={2} />}
<span>{item}</span>
</Group>
</ComboboxOption>
));
const [dateInputOpened, setDateInputOpened] = useState(false);
const pickerControl = (
<ActionIcon onClick={() => setDateInputOpened(true)} variant="subtle" color="gray">
<ActionIcon onClick={() => setDateInputOpened(true)} variant="light" color="blue">
<IconCalendar size={18} />
</ActionIcon>
);
const params = useParams()
const state = useProxy(jadwalkegiatanState)
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Stack py="xl" px="lg">
<Skeleton h={500} radius="lg" />
</Stack>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Paper radius={10}>
<Box style={{ borderTopLeftRadius: 10, borderTopRightRadius: 10 }} bg={colors['blue-button']}>
<Text p={'md'} fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw={"bold"}>
<Stack gap="lg">
<Paper radius="xl" shadow="md">
<Box
style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
bg={colors['blue-button']}
>
<Text p="md" fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw="bold">
Detail & Pendaftaran Kegiatan
</Text>
</Box>
<Box p={'md'} >
<Stack gap={'xs'}>
{/* Informasi Kegiatan */}
<Text fz={'h4'} fw={"bold"}>
Informasi Kegiatan
</Text>
<Divider />
<Text fz={'h4'} fw={"bold"}>
Nama Kegiatan : <Text span fz={'h4'}>{state.findUnique.data.informasijadwalkegiatan.name}</Text>
</Text>
<Text fz={'h4'} fw={"bold"}>
Tanggal : <Text span fz={'h4'}>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text>
</Text>
<Text fz={'h4'} fw={"bold"}>
Waktu : <Text span fz={'h4'}>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text>
</Text>
<Text pb={10} fz={'h4'} fw={"bold"}>
Lokasi : <Text span fz={'h4'}>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text>
</Text>
{/* Deskripsi Kegiatan */}
<Text fz={'h4'} fw={"bold"}>
Deskripsi Kegiatan
</Text>
<Divider />
<Text pb={10} ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
{/* Layanan Yang Tersedia */}
<Text fz={'h4'} fw={"bold"}>
Layanan Yang Tersedia
</Text>
<Divider />
<Text pb={10} ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
{/* Syarat dan Ketentuan */}
<Text fz={'h4'} fw={"bold"}>
Syarat dan Ketentuan
</Text>
<Divider />
<Text pb={10} ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
{/* Dokumen yang Perlu Dibawa */}
<Text fz={'h4'} fw={"bold"}>
Dokumen yang Perlu Dibawa
</Text>
<Divider />
<Text pb={10} ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
{/* Pendaftaran */}
<Text fz={'h4'} fw={"bold"}>
Pendaftaran
</Text>
<Divider />
<Stack gap={'xs'} pb={20}>
<TextInput
styles={{ label: { fontSize: '16px', fontWeight: 'bold' }, }}
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
label="Nama Balita"
placeholder='Masukkan nama balita'
/>
<DateInput
styles={{ label: { fontSize: '16px', fontWeight: 'bold' }, }}
placeholder='dd/mm/yyyy'
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
label="Tanggal Lahir"
popoverProps={{ opened: dateInputOpened, onChange: setDateInputOpened }}
rightSection={pickerControl}
/>
<TextInput
styles={{ label: { fontSize: '16px', fontWeight: 'bold' }, }}
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
label="Nama Orang Tua / Wali"
placeholder='Masukkan nama orang tua / wali'
/>
<TextInput
styles={{ label: { fontSize: '16px', fontWeight: 'bold' }, }}
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
label="No. Telepon"
placeholder='Masukkan no. telepon'
/>
<TextInput
styles={{ label: { fontSize: '16px', fontWeight: 'bold' }, }}
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
label="Alamat"
placeholder='Masukkan alamat lengkap'
/>
{/* Layanan */}
<Text fz={'16px'} fw={"bold"}>
Layananan Yang Dibutuhkan
</Text>
<Box
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
>
<Combobox
store={combobox}
resetSelectionOnOptionHover
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val);
combobox.updateSelectedOptionIndex('active');
}}
>
<ComboboxTarget targetType="button">
<InputBase
component="button"
type="button"
pointer
rightSection={<ComboboxChevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <InputPlaceholder>Layanan</InputPlaceholder>}
</InputBase>
</ComboboxTarget>
<Combobox.Dropdown>
<ComboboxOptions>{options}</ComboboxOptions>
</Combobox.Dropdown>
</Combobox>
</Box>
<Textarea
pb={10}
styles={{ label: { fontSize: '16px', fontWeight: 'bold' }, }} w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
label="Catatan Khusus (Opsional)"
placeholder='Masukkan catatan jika ada'
/>
<Button
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
fz={'md'} bg={colors['blue-button']}>
Daftar Sekarang
</Button>
<Box p="lg">
<Stack gap="xl">
<Stack gap="sm">
<Text fz="lg" fw="bold">Informasi Kegiatan</Text>
<Divider />
<Text fz="md" fw="bold">Nama Kegiatan: <Text span>{state.findUnique.data.informasijadwalkegiatan.name}</Text></Text>
<Text fz="md" fw="bold">Tanggal: <Text span>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text></Text>
<Text fz="md" fw="bold">Waktu: <Text span>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text></Text>
<Text fz="md" fw="bold">Lokasi: <Text span>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text></Text>
</Stack>
<Paper p={'lg'} bg={colors['blue-button-trans']}>
<Text fz={'h3'} c={colors['white-1']} fw={'bold'}>Informasi Kontak</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Penanggung Jawab : <Text span fz={'h4'}>Bidan Komang Ayu</Text>
</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Telepon : <Text span fz={'h4'}>081234567890</Text>
</Text>
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
Email : <Text span fz={'h4'}>puskesmasabiansemal3@gmail.com</Text>
</Text>
<Stack gap="sm">
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
<Divider />
<Text ta="justify" fz="md" dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
<Divider />
<Text ta="justify" fz="md" dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
<Divider />
<Text ta="justify" fz="md" dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
<Divider />
<Text ta="justify" fz="md" dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text>
<Divider />
<Stack gap="md">
<TextInput label="Nama Balita" placeholder="Masukkan nama balita" size="md" />
<DateInput
label="Tanggal Lahir"
placeholder="dd/mm/yyyy"
size="md"
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
popoverProps={{ opened: dateInputOpened, onChange: setDateInputOpened }}
rightSection={pickerControl}
/>
<TextInput label="Nama Orang Tua / Wali" placeholder="Masukkan nama orang tua / wali" size="md" />
<TextInput label="Nomor Telepon" placeholder="Masukkan nomor telepon" size="md" />
<TextInput label="Alamat" placeholder="Masukkan alamat lengkap" size="md" />
<Box w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}>
<Text fz="sm" fw="bold" pb={4}>Pilih Layanan</Text>
<Combobox
store={combobox}
resetSelectionOnOptionHover
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val);
combobox.updateSelectedOptionIndex('active');
}}
>
<ComboboxTarget targetType="button">
<InputBase
component="button"
type="button"
pointer
rightSection={<ComboboxChevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <InputPlaceholder>Pilih layanan</InputPlaceholder>}
</InputBase>
</ComboboxTarget>
<Combobox.Dropdown>
<ComboboxOptions>{options}</ComboboxOptions>
</Combobox.Dropdown>
</Combobox>
</Box>
<Textarea label="Catatan Khusus (Opsional)" placeholder="Masukkan catatan jika ada" size="md" />
<Button size="md" radius="lg" bg={colors['blue-button']}>
Daftar Sekarang
</Button>
</Stack>
</Stack>
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
<Stack gap="xs">
<Text fz="lg" c={colors['white-1']} fw="bold">Informasi Kontak</Text>
<Group gap="xs">
<IconUser size={18} color="white" />
<Text fz="md" c={colors['white-1']}>Penanggung Jawab: <Text span fw="bold">Bidan Komang Ayu</Text></Text>
</Group>
<Group gap="xs">
<IconPhone size={18} color="white" />
<Text fz="md" c={colors['white-1']}>081234567890</Text>
</Group>
<Group gap="xs">
<IconMail size={18} color="white" />
<Text fz="md" c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text>
</Group>
</Stack>
</Paper>
<Group>
<Button fz={'lg'} bg={'green'}>
Download Jadwal Posyandu 2025 (PDF)
<Button size="md" radius="lg" leftSection={<IconDownload size={18} />} color="green">
Unduh Jadwal Posyandu 2025 (PDF)
</Button>
</Group>
</Stack>
@@ -227,5 +216,4 @@ function Page() {
);
}
export default Page;

View File

@@ -1,53 +1,101 @@
'use client'
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Anchor, Box, Divider, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconChevronRight, IconClockHour4, IconMapPin } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function JadwalKegiatanPage() {
const state = useProxy(jadwalkegiatanState)
const state = useProxy(jadwalkegiatanState);
const router = useRouter();
useShallowEffect(()=> {
state.findMany.load()
}, [])
useShallowEffect(() => {
state.findMany.load();
}, []);
if(!state.findMany.data){
if (!state.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="lg">
<Skeleton h={500} radius="lg" />
</Box>
)
);
}
return (
<Box>
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
<Stack gap={'xs'}>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Jadwal Kegiatan</Text>
{state.findMany.data.map((item) => (
<Box key={item.id}>
<Text fz={'h4'} fw={'bold'}>
{item.informasijadwalkegiatan.name}
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md" h="auto" mih="100vh">
<Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
Jadwal Kegiatan Warga
</Text>
{state.findMany.data.length === 0 ? (
<Box py="xl" ta="center">
<Text fz="lg" c="dimmed">
Belum ada jadwal kegiatan yang tersedia
</Text>
<Text fz={'h4'}>
{item.informasijadwalkegiatan.waktu}
</Text>
<Text fz={'h4'}>
{item.informasijadwalkegiatan.lokasi}
</Text>
<Text fz={'h6'} fw={'bold'} c={colors['blue-button']}>
{item.informasijadwalkegiatan.tanggal}
</Text>
<Anchor onClick={()=> router.push(`/darmasaba/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/${item.id}`)} c={colors['blue-button']} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} >
Detail & Pendaftaran
</Text>
</Anchor>
</Box>
))}
<Divider color={colors['blue-button']} px={'xl'} />
) : (
state.findMany.data.map((item) => (
<Card
key={item.id}
withBorder
radius="xl"
shadow="sm"
p="lg"
style={{ backdropFilter: 'blur(8px)' }}
>
<Stack gap="sm">
<Group justify="space-between">
<Text fw={700} fz="xl">
{item.informasijadwalkegiatan.name}
</Text>
<Text fw={600} fz="sm" c={colors['blue-button']}>
{item.informasijadwalkegiatan.tanggal}
</Text>
</Group>
<Group gap="xs">
<IconClockHour4 size={18} color={colors['blue-button']} />
<Text fz="sm">{item.informasijadwalkegiatan.waktu}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} color={colors['blue-button']} />
<Text fz="sm">{item.informasijadwalkegiatan.lokasi}</Text>
</Group>
<Divider my="sm" />
<Group justify="flex-end">
<Button
variant="light"
radius="lg"
size="sm"
rightSection={<IconChevronRight size={18} />}
onClick={() =>
router.push(
`/darmasaba/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/${item.id}`
)
}
styles={{
root: {
background: colors['blue-button'],
color: 'white',
boxShadow: '0 0 12px rgba(0, 123, 255, 0.4)',
transition: 'all 0.2s ease',
},
}}
>
Lihat Detail & Daftar
</Button>
</Group>
</Stack>
</Card>
))
)}
</Stack>
</Paper>
</Box>

View File

@@ -1,100 +1,169 @@
'use client'
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import {
Box,
Center,
Grid,
GridCol,
Image,
Pagination,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
TextInput,
Badge,
HoverCard,
Divider,
Group,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { IconSearch, IconInfoCircle } from '@tabler/icons-react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const state = useProxy(infoWabahPenyakit)
const [search, setSearch] = useState('')
const state = useProxy(infoWabahPenyakit);
const [search, setSearch] = useState('');
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 3, search)
}, [page, search])
load(page, 6, search);
}, [page, search]);
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="xl" px={{ base: 'md', md: 100 }}>
<Skeleton h={500} radius="lg" />
</Box>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Info Wabah / Penyakit
<Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 8 }}>
<Text
fz={{ base: '1.8rem', md: '2.8rem' }}
c={colors['blue-button']}
fw="bold"
lh={1.2}
>
Informasi Wabah & Penyakit
</Text>
<Text fz="md" c="dimmed" mt={4}>
Dapatkan informasi terbaru mengenai wabah dan penyakit yang sedang
diawasi.
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<GridCol span={{ base: 12, md: 4 }}>
<TextInput
radius={"lg"}
placeholder='Cari Info Wabah / Penyakit'
radius="xl"
placeholder="Cari nama penyakit atau wabah..."
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
w="100%"
size="md"
/>
</GridCol>
</Grid>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<SimpleGrid
cols={{
base: 1,
md: 3,
}}
>
{data.map((v, k) => {
return (
<Paper key={k} p={'xl'} bg={colors['white-trans-1']}>
<Stack gap={'xs'}>
<Box>
<Text fw={"bold"} fz={'h3'} c={colors['blue-button']}>{v.name}</Text>
<Image w={330} h={200} fit='contain' pt={5} src={v.image.link} alt={v.name} />
<Text fz={'h4'} fw={'bold'} >
{v.name}
<Box px={{ base: 'md', md: 100 }}>
{data.length === 0 ? (
<Center py="6xl">
<Stack align="center" gap="sm">
<IconInfoCircle size={50} color={colors['blue-button']} />
<Text fz="lg" fw={500} c="dimmed">
Tidak ada data yang cocok dengan pencarian Anda.
</Text>
</Stack>
</Center>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
{data.map((v, k) => (
<Paper
key={k}
radius="lg"
shadow="md"
p="lg"
withBorder
bg={colors['white-trans-1']}
style={{
transition: 'transform 200ms ease, box-shadow 200ms ease',
}}
>
<Stack gap="sm">
<Image
radius="md"
h={180}
src={v.image.link}
alt={v.name}
fit="cover"
/>
<Group justify="space-between" mt="sm">
<Text fw={700} fz="lg" c={colors['blue-button']}>
{v.name}
</Text>
<Badge color="blue" variant="light" radius="sm">
Wabah
</Badge>
</Group>
<Text fz="sm" c="dimmed">
Diposting: 12 Februari 2025 · Dinas Kesehatan
</Text>
<Divider />
<Text fz="sm" lh={1.5}>
{v.deskripsiSingkat}
</Text>
<HoverCard shadow="md" position="bottom" radius="md" width={300}>
<HoverCard.Target>
<Text
fz="sm"
fw={500}
c={colors['blue-button']}
style={{ cursor: 'pointer' }}
>
Lihat detail lengkap
</Text>
<Text fz={'h6'} pb={10}>
Diposting: 12 Februari 2025 | Dinas Kesehatan
</Text>
<Text fz={'h4'} pb={10}>
{v.deskripsiSingkat}
</Text>
<Text fz={'h4'} pb={10} dangerouslySetInnerHTML={{ __html: v.deskripsiLengkap }} />
</Box>
</Stack>
</Paper>
)
})}
</HoverCard.Target>
<HoverCard.Dropdown>
<Text
fz="sm"
lh={1.6}
dangerouslySetInnerHTML={{
__html: v.deskripsiLengkap,
}}
/>
</HoverCard.Dropdown>
</HoverCard>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
)}
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
radius="xl"
size="md"
mt="lg"
/>
</Center>
)}
</Stack>
);
}

View File

@@ -1,106 +1,150 @@
'use client'
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import { useShallowEffect } from '@mantine/hooks';
import {
Box,
Center,
Grid,
GridCol,
Image,
Pagination,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
TextInput,
Tooltip,
Badge,
} from '@mantine/core';
import { IconSearch, IconPhone } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
import { IconSearch } from '@tabler/icons-react';
import colors from '@/con/colors';
function Page() {
const state = useProxy(kontakDarurat)
const [search, setSearch] = useState('')
const state = useProxy(kontakDarurat);
const [search, setSearch] = useState('');
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 3, search)
}, [page, search])
load(page, 6, search);
}, [page, search]);
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="xl" px={{ base: 'md', md: 100 }}>
<Skeleton height={500} radius="lg" />
</Box>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Penanganan Darurat
<Grid align="center" px={{ base: 'md', md: 100 }} gutter="lg">
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{ base: '2rem', md: '2.8rem' }} c={colors['blue-button']} fw={800}>
Kontak Darurat
</Text>
<Text c="dimmed" fz="md" mt={4}>
Hubungi layanan penting dengan cepat dan mudah
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Penanganan Darurat'
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
/>
<GridCol span={{ base: 12, md: 4 }}>
<Tooltip label="Cari berdasarkan nama atau deskripsi" withArrow>
<TextInput
radius="xl"
size="md"
placeholder="Cari kontak darurat..."
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w="100%"
/>
</Tooltip>
</GridCol>
</Grid>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<SimpleGrid
pb={10}
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center py={40}>
<Image
src={v.image.link}
alt={v.name}
w={200}
h={200}
fit='contain'
/>
</Center>
<Box px={'lg'}>
<Box pb={20}>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.name}
</Text>
<Box px={10}>
<Text fz={"h4"} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Box>
</Box>
</Box>
</Stack>
</Paper>
)
})}
<Box px={{ base: 'md', md: 100 }}>
{data.length === 0 ? (
<Center mih={300}>
<Stack align="center" gap="xs">
<IconSearch size={50} color={colors['blue-button']} />
<Text fz="lg" fw={600} c={colors['blue-button']}>
Tidak ada kontak ditemukan
</Text>
<Text fz="sm" c="dimmed">
Coba kata kunci lain untuk pencarian
</Text>
</Stack>
</Center>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
{data.map((v, k) => (
<Paper
key={k}
radius="xl"
shadow="md"
withBorder
p="lg"
bg={colors['white-trans-1']}
style={{
transition: 'all 200ms ease',
cursor: 'pointer',
}}
>
<Stack align="center" gap="sm">
<Image
src={v.image.link}
alt={v.name}
w={140}
h={140}
fit="contain"
radius="md"
/>
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}>
{v.name}
</Text>
<Text fz="sm" c="dimmed" ta="center" lineClamp={3}>
<span dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Text>
<Badge
color="blue"
leftSection={<IconPhone size={14} />}
variant="light"
mt="sm"
>
Panggil Sekarang
</Badge>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
)}
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{totalPages > 1 && (
<Center mt="xl">
<Pagination
value={page}
onChange={(newPage) => load(newPage, 6, search)}
total={totalPages}
radius="xl"
size="md"
styles={{
control: {
borderRadius: '999px',
},
}}
/>
</Center>
)}
</Stack>
);
}

View File

@@ -1,108 +1,162 @@
'use client'
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import { useState } from 'react';
import { useShallowEffect } from '@mantine/hooks';
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
import { IconSearch } from '@tabler/icons-react';
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat'
import colors from '@/con/colors'
import {
Badge,
Box,
Center,
Grid,
GridCol,
Image,
Pagination,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
TextInput,
Tooltip
} 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'
function Page() {
const state = useProxy(penangananDarurat)
const [search, setSearch] = useState('')
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
const { data, page, totalPages, loading, load } = state.findMany
useShallowEffect(() => {
load(page, 3, search)
load(page, 6, search)
}, [page, search])
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="xl" px={{ base: 'md', md: 100 }}>
<Skeleton h={500} radius="lg" />
</Box>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack bg={colors.Bg} py="xl" gap="xl" pos="relative">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<Grid align="center" px={{ base: 'md', md: 100 }} mb="lg">
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Text fz={{ base: 30, md: 40 }} c={colors['blue-button']} fw={800} lh={1.2}>
Penanganan Darurat
</Text>
<Text fz="md" c="dimmed" mt={4}>
Informasi cepat dan jelas untuk situasi darurat kesehatan
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Penanganan Darurat'
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
/>
<Tooltip label="Ketik kata kunci untuk mencari penanganan darurat">
<TextInput
radius="lg"
placeholder="Cari penanganan darurat..."
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w="100%"
/>
</Tooltip>
</GridCol>
</Grid>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Box px={{ base: 'md', md: 100 }}>
{data.length === 0 ? (
<Center py={100}>
<Stack align="center" gap="xs">
<Text fz="lg" fw={600} c={colors['blue-button']}>
Tidak ada data ditemukan
</Text>
<Text fz="sm" c="dimmed">
Coba gunakan kata kunci lain
</Text>
</Stack>
</Center>
) : (
<SimpleGrid
pb={10}
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center py={40}>
<Image
src={v.image.link}
alt={v.name}
w={200}
h={200}
fit='contain'
cols={{ base: 1, sm: 2, md: 3 }}
spacing="xl"
verticalSpacing="xl"
pb={20}
>
{data.map((v, k) => (
<Paper
key={k}
radius="xl"
p="md"
shadow="sm"
withBorder
bg={colors['white-trans-1']}
style={{ transition: 'all 0.3s ease' }}
>
<Stack align="center" gap="md">
<Center>
<Image
src={v.image.link}
alt={v.name}
w={160}
h={160}
fit="contain"
radius="md"
/>
</Center>
<Stack gap={4} w="100%">
<Text
fz="lg"
fw={700}
c={colors['blue-button']}
ta="center"
lineClamp={2}
>
{v.name}
</Text>
<Box>
<Text
fz="sm"
c="dimmed"
lineClamp={4}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Center>
<Box px={'lg'}>
<Box pb={20}>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.name}
</Text>
<Box px={10}>
<Text fz={"h4"} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Box>
</Box>
</Box>
</Stack>
</Paper>
)
})}
<Badge radius="md" color="blue" variant="light" mt="sm">
Darurat
</Badge>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
)}
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{totalPages > 1 && (
<Center mt="xl">
<Pagination
value={page}
onChange={(newPage) => load(newPage, 6, search)}
total={totalPages}
size="lg"
radius="xl"
styles={{
control: {
border: `1px solid ${colors['blue-button']}`,
},
}}
/>
</Center>
)}
</Stack>
);
)
}
export default Page;
export default Page

View File

@@ -1,119 +1,166 @@
'use client'
import colors from "@/con/colors";
import { Box, Center, Flex, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, TextInput } from "@mantine/core";
import BackButton from "../../desa/layanan/_com/BackButto";
// import { useTransitionRouter } from "next-view-transitions";
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
import colors from "@/con/colors";
import { Badge, Box, Center, Flex, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, TextInput } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { useProxy } from "valtio/utils";
import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import { IconSearch } from "@tabler/icons-react";
import { useProxy } from "valtio/utils";
import BackButton from "../../desa/layanan/_com/BackButto";
export default function Page() {
const state = useProxy(posyandustate)
// const router = useTransitionRouter()
const [search, setSearch] = useState("")
const state = useProxy(posyandustate);
const [search, setSearch] = useState("");
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 6, search);
}, [page, search]);
useShallowEffect(() => {
load(page, 3, search)
}, [page, search])
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
if (loading || !data) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
<Flex mt={10} justify={"space-between"} align={"center"}>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Posyandu Darmasaba
</Text>
<TextInput
placeholder="Cari Posyandu"
radius="lg"
leftSection={<IconSearch size={20} />}
w={{ base: "25%", md: "30%" }}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Flex>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<SimpleGrid
pb={10}
cols={{
base: 1,
md: 3,
}}>
{data?.map((v, k) => {
return (
<Paper key={k} p={"xl"} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Text c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.name}
</Text>
<Center>
<Image src={v.image.link} alt="" />
</Center>
<Text fz={'h4'}>
No.Telp : {v.nomor}
</Text>
<Box>
<Text fz={'h4'}>
Jadwal Pelayanan
</Text>
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} />
</Box>
<Spoiler
maxHeight={60} // tinggi maksimum sebelum di-collapse
showLabel="Read more"
hideLabel="Read less"
>
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Spoiler>
</Stack>
</Paper>
)
})}
</SimpleGrid>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
<Text fz={'h4'} fw={"bold"}>
Pelayanan Posyandu
</Text>
<List type="ordered">
<ListItem fz={'h4'}>Penimbangan bayi dan balita</ListItem>
<ListItem fz={'h4'}>Pemantuan status gizi</ListItem>
<ListItem fz={'h4'}>Imunisasi dasar lengkap</ListItem>
<ListItem fz={'h4'}>Konseling</ListItem>
<ListItem fz={'h4'}>Pemantuan kesehatan ibu hamil</ListItem>
</List>
</Stack>
</Box>
</Stack>
<Box py="xl" px={{ base: "md", md: 100 }}>
<Skeleton h={500} radius="lg" />
</Box>
);
}
)
}
return (
<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>
<Box px={{ base: "md", md: 100 }}>
<Stack gap="xl">
<SimpleGrid
pb="lg"
cols={{ base: 1, sm: 2, md: 3 }}
spacing="lg"
>
{data?.map((v, k) => (
<Paper
key={k}
p="xl"
radius="lg"
shadow="md"
withBorder
bg={colors["white-trans-1"]}
style={{
transition: "transform 0.2s ease, box-shadow 0.2s ease",
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.transform = "translateY(-4px)";
(e.currentTarget as HTMLElement).style.boxShadow =
"0 8px 24px rgba(0,0,0,0.12)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.transform = "translateY(0)";
(e.currentTarget as HTMLElement).style.boxShadow =
"0 4px 12px rgba(0,0,0,0.08)";
}}
>
<Stack gap="sm">
<Flex justify="space-between" align="center">
<Text c={colors["blue-button"]} fw="bold" fz="lg" lineClamp={1}>
{v.name}
</Text>
<Badge color="blue" variant="light" size="sm" radius="sm">
Aktif
</Badge>
</Flex>
<Center>
<Image
src={v.image.link}
alt={`Gambar ${v.name}`}
radius="md"
w="100%"
h={180}
fit="cover"
/>
</Center>
<Flex align="center" gap="xs">
<IconPhone size={18} stroke={1.5} />
<Text fz="sm" c="dimmed">
{v.nomor || "Tidak tersedia"}
</Text>
</Flex>
<Flex align="center" gap="xs">
<IconCalendar size={18} stroke={1.5} />
<Text fz="sm" c="dimmed">
Jadwal:{" "}
<span dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} />
</Text>
</Flex>
<Spoiler
maxHeight={70}
showLabel="Lihat selengkapnya"
hideLabel="Sembunyikan"
transitionDuration={300}
>
<Text
fz="sm"
lh={1.5}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Spoiler>
</Stack>
</Paper>
))}
</SimpleGrid>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
radius="lg"
size="md"
withControls
mt="md"
/>
</Center>
)}
<Stack gap="sm">
<Flex align="center" gap="xs">
<IconInfoCircle size={22} color={colors["blue-button"]} />
<Text fz="lg" fw="bold" c={colors["blue-button"]}>
Layanan Utama Posyandu
</Text>
</Flex>
<List spacing="xs" size="sm" center>
<ListItem>Penimbangan bayi dan balita</ListItem>
<ListItem>Pemantauan status gizi</ListItem>
<ListItem>Imunisasi dasar lengkap</ListItem>
<ListItem>Konseling kesehatan</ListItem>
<ListItem>Pemantauan kesehatan ibu hamil</ListItem>
</List>
</Stack>
</Stack>
</Box>
</Stack>
);
}

View File

@@ -1,7 +1,7 @@
'use client'
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
import colors from '@/con/colors';
import { Box, Center, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Center, Group, Image, Loader, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
@@ -18,45 +18,75 @@ function Page() {
if (!state.findUnique.data) {
return (
<div>
<Skeleton h={500} />
</div>
<Center mih="60vh">
<Stack align="center" gap="sm">
<Loader color={colors["blue-button"]} size="lg" />
<Text c="dimmed" fz="sm">Memuat data program kesehatan...</Text>
</Stack>
</Center>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Paper px={{ base: 'md', md: 100 }} radius={10} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center my={20}>
<Image radius={"lg"} src={state.findUnique.data.image?.link} alt="" />
<Paper
px={{ base: 'md', md: 100 }}
py="xl"
radius="xl"
shadow="md"
bg={colors["white-trans-1"]}
>
<Stack gap="lg">
<Center>
{state.findUnique.data.image?.link ? (
<Image
radius="xl"
src={state.findUnique.data.image?.link}
alt={state.findUnique.data.name}
w="100%"
maw={800}
fit="cover"
/>
) : (
<Skeleton h={300} w="100%" radius="xl" />
)}
</Center>
<Box px={'lg'}>
<Box>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{state.findUnique.data.name}
</Text>
<Text ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}></Text>
</Box>
<Group py={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
{state.findUnique.data.createdAt ? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
}) : 'No date available'}
</Text>
</Group>
<Group gap="xs">
<IconUser size={18} />
<Text size="sm">Admin Desa</Text>
</Group>
</Group>
<Box>
<Text pb="sm" c={colors["blue-button"]} fw="bold" fz={{ base: 24, md: 32 }} lh={1.2}>
{state.findUnique.data.name}
</Text>
<Text
ta="justify"
fz="md"
lh={1.6}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}
/>
</Box>
<Group gap="xl">
<Group gap="xs">
<Tooltip label="Tanggal dibuat" withArrow>
<IconCalendar size={20} stroke={1.5} />
</Tooltip>
<Text size="sm" c="dimmed">
{state.findUnique.data.createdAt
? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
: 'Tanggal tidak tersedia'}
</Text>
</Group>
<Group gap="xs">
<Tooltip label="Dibuat oleh" withArrow>
<IconUser size={20} stroke={1.5} />
</Tooltip>
<Text size="sm" c="dimmed">Admin Desa</Text>
</Group>
</Group>
</Stack>
</Paper>
</Stack>

View File

@@ -1,7 +1,31 @@
'use client'
import colors from "@/con/colors";
import { Box, Button, Center, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from "@mantine/core";
import { IconBarbell, IconCalendar, IconOld, IconSearch, IconUser, IconUsersGroup } from "@tabler/icons-react";
import {
Box,
Button,
Center,
Grid,
GridCol,
Group,
Image,
Pagination,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
TextInput,
Tooltip,
Transition,
} from "@mantine/core";
import {
IconBarbell,
IconCalendar,
IconOld,
IconSearch,
IconUser,
IconUsersGroup,
} from "@tabler/icons-react";
import BackButton from "../../desa/layanan/_com/BackButto";
import { useProxy } from "valtio/utils";
import programKesehatan from "@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan";
@@ -9,104 +33,134 @@ import { useState } from "react";
import { useShallowEffect } from "@mantine/hooks";
import { useRouter } from "next/navigation";
const data2 = [
const manfaatProgram = [
{
id: 1,
icon: <IconBarbell size={50} color={colors['BG-trans']} />,
title: "Menjaga Kesehatan Tubuh",
desc: "Program kesehatan desa dirancang untuk memelihara kesehatan fisik masyarakat melalui aktivitas rutin, pemeriksaan kesehatan berkala, dan edukasi gaya hidup sehat.",
icon: <IconBarbell size={40} color="#fff" />,
title: "Menjaga Tubuh Bugar",
desc: "Meningkatkan kesehatan fisik masyarakat melalui olahraga rutin, pemeriksaan berkala, dan edukasi gaya hidup sehat.",
},
{
id: 2,
icon: <IconUsersGroup size={50} color={colors['BG-trans']} />,
title: "Mempererat Kebersamaan",
desc: "Kegiatan kesehatan komunal menjadi wadah interaksi sosial yang memperkuat ikatan antar warga desa, menumbuhkan rasa kepedulian dan gotong royong.",
icon: <IconUsersGroup size={40} color="#fff" />,
title: "Menguatkan Kebersamaan",
desc: "Menciptakan interaksi sosial sehat, mempererat solidaritas, dan memperkuat budaya gotong royong antar warga.",
},
{
id: 3,
icon: <IconOld size={50} color={colors['BG-trans']} />,
title: "Medukung Lansia Aktif",
desc: "Program khusus bagi lansia membantu menjaga kebugaran, mengurangi risiko penyakit degeneratif, dan menciptakan komunitas pendukung untuk kehidupan yang sehat dan bahagia.",
icon: <IconOld size={40} color="#fff" />,
title: "Dukungan untuk Lansia",
desc: "Membantu lansia tetap aktif, sehat, dan bahagia melalui kegiatan komunitas yang aman dan menyenangkan.",
},
]
];
export default function Page() {
const state = useProxy(programKesehatan)
const router = useRouter()
const [search, setSearch] = useState('')
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
const state = useProxy(programKesehatan);
const router = useRouter();
const [search, setSearch] = useState("");
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 3, search)
}, [page, search])
load(page, 3, search);
}, [page, search]);
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="xl" px={{ base: "md", md: 100 }}>
<Skeleton h={500} radius="lg" />
</Box>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<Stack bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Grid px={{ base: 'md', md: 100 }} align="center">
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Program Kesehatan Unggulan
<Grid px={{ base: "md", md: 100 }} align="center" gutter="lg">
<GridCol span={{ base: 12, md: 8 }}>
<Text
fz={{ base: "2rem", md: "2.8rem" }}
c={colors["blue-button"]}
fw="bold"
>
Program Kesehatan Desa
</Text>
<Text fz="lg" c="dimmed" mt="xs">
Temukan berbagai program kesehatan untuk mendukung kualitas hidup
masyarakat Darmasaba.
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<GridCol span={{ base: 12, md: 4 }}>
<TextInput
placeholder='Cari Program Kesehatan'
placeholder="Cari program kesehatan..."
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
radius="lg"
size="md"
aria-label="Pencarian program kesehatan"
/>
</GridCol>
</Grid>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<SimpleGrid
pb={10}
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center>
<Image src={v.image?.link} alt="" style={{ borderRadius: '14px 14px 0 0' }} />
</Center>
<Box px={'lg'}>
<Box>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.name}
</Text>
<Text ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
</Box>
<Box py={15} onClick={() => router.push(`/darmasaba/kesehatan/program-kesehatan/${v.id}`)}>
<Button fw={'bold'} fz={'h5'} c={colors["blue-button"]} bg={colors["BG-trans"]}>Detail Program</Button>
</Box>
<Group py={20}>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" pb="xl">
{data.map((v, k) => (
<Transition
key={k}
mounted
transition="fade-up"
duration={400}
timingFunction="ease"
>
{(styles) => (
<Paper
style={styles}
radius="xl"
withBorder
bg="white"
shadow="md"
className="hover-scale"
>
<Stack gap="md">
<Image
src={v.image?.link}
alt={v.name}
radius="xl"
height={180}
fit="cover"
/>
<Box px="lg" pb="lg">
<Text
fw="bold"
fz="xl"
c={colors["blue-button"]}
mb="xs"
>
{v.name}
</Text>
<Text
fz="sm"
c="dimmed"
lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
<Group justify="space-between" mt="md">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
{v.createdAt ? new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
}) : 'No date available'}
{v.createdAt
? new Date(v.createdAt).toLocaleDateString(
"id-ID",
{
day: "numeric",
month: "long",
year: "numeric",
}
)
: "Tanggal tidak tersedia"}
</Text>
</Group>
<Group gap="xs">
@@ -114,61 +168,99 @@ export default function Page() {
<Text size="sm">Admin Desa</Text>
</Group>
</Group>
<Tooltip label="Lihat detail program" withArrow>
<Button
mt="lg"
fullWidth
radius="lg"
size="md"
fw="bold"
variant="gradient"
gradient={{ from: colors["blue-button"], to: "#4dabf7" }}
onClick={() =>
router.push(
`/darmasaba/kesehatan/program-kesehatan/${v.id}`
)
}
>
Lihat Detail
</Button>
</Tooltip>
</Box>
</Stack>
</Paper>
)
})}
</SimpleGrid>
)}
</Transition>
))}
</SimpleGrid>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
onChange={(newPage) => load(newPage)}
total={totalPages}
mt="md"
mb="md"
size="lg"
radius="xl"
styles={{
control: {
borderRadius: "50%",
boxShadow: "0 0 10px rgba(0,0,0,0.1)",
},
}}
/>
</Center>
</Stack>
)}
</Box>
<Box py={10} px={{ base: "md", md: 100 }}>
<Box pb={10}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Box px={{ base: "md", md: 100 }} py="xl">
<Stack gap="sm" mb="lg">
<Text
fz={{ base: "2rem", md: "2.5rem" }}
c={colors["blue-button"]}
fw="bold"
>
Manfaat Program Kesehatan
</Text>
<Text fz={{ base: "h4", md: "h3" }} >
Program kesehatan di Desa Darmasaba memiliki peran penting dalam meningkatkan kesejahteraan masyarakat.
<Text fz="lg" c="dimmed" maw={700}>
Program kesehatan Desa Darmasaba berperan penting dalam meningkatkan
kesejahteraan dan kualitas hidup warganya.
</Text>
</Box>
<SimpleGrid
pb={30}
cols={{
base: 1,
md: 3,
}}>
{data2.map((v, k) => {
return (
<Paper key={k} px={"xl"} py={80} bg={colors['white-trans-1']} c={colors['blue-button']}>
<Stack justify='space-between' >
<Group justify='center'>
<Paper p={'xl'} radius={'100'} bg={colors['blue-button']}>
<Center >
{v.icon}
</Center>
</Paper>
</Group>
<Text ta={"center"} fw={"bold"} fz={"h3"}>
{v.title}
</Text>
<Text ta={"center"} fz={'h4'}>
{v.desc}
</Text>
</Stack>
</Paper>
)
})}
</Stack>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{manfaatProgram.map((v) => (
<Paper
key={v.id}
px="xl"
py="xl"
radius="xl"
shadow="sm"
withBorder
bg="white"
className="hover-glow"
>
<Stack align="center" gap="md">
<Paper
p="lg"
radius="50%"
shadow="md"
bg={colors["blue-button"]}
className="pulse-icon"
>
<Center>{v.icon}</Center>
</Paper>
<Text ta="center" fw="bold" fz="xl" c={colors["blue-button"]}>
{v.title}
</Text>
<Text ta="center" fz="sm" c="dimmed">
{v.desc}
</Text>
</Stack>
</Paper>
))}
</SimpleGrid>
</Box>
</Stack>
)
);
}

View File

@@ -1,10 +1,11 @@
'use client'
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
import colors from '@/con/colors';
import { BackgroundImage, Box, Grid, GridCol, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { BackgroundImage, Box, Grid, GridCol, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip, Badge, Divider, Group } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import { IconClock, IconMapPin, IconPhone, IconMail, IconBuildingHospital } from '@tabler/icons-react';
import BackButton from '../../../desa/layanan/_com/BackButto';
function Page() {
@@ -17,95 +18,117 @@ function Page() {
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Stack py="xl" px={{ base: 'md', md: 100 }}>
<Skeleton h={500} radius="lg" />
<Stack gap="sm">
<Skeleton h={20} w="40%" />
<Skeleton h={20} w="60%" />
<Skeleton h={20} w="50%" />
</Stack>
</Stack>
)
}
const data = state.findUnique.data
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Stack gap={'lg'} px={{ base: 'md', md: 100 }}>
<Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Box pb={30}>
<BackgroundImage
pb={30}
radius={16}
h={{ base: 250, md: 500 }}
src={state.findUnique.data.image.link}
style={{ position: 'relative' }}
<Stack gap="xl" px={{ base: 'md', md: 100 }}>
<Paper p="lg" radius="xl" shadow="md" bg={colors['white-trans-1']} withBorder>
<Box pb="xl">
<BackgroundImage
radius="lg"
h={{ base: 260, md: 480 }}
src={data.image.link}
style={{ position: 'relative', overflow: 'hidden' }}
>
<Box
pos="absolute"
w="100%"
h="100%"
bg={colors.trans.dark[2]}
style={{ borderRadius: 16 }}
/>
<Stack
pos="absolute"
bottom={20}
left={20}
gap={6}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Text style={{
position: 'absolute',
bottom: 35,
left: 15,
}} fw={'bold'} fz={{ base: 'md', md: 'h3' }} c={colors['white-1']}>{state.findUnique.data.name}</Text>
<Text style={{
position: 'absolute',
bottom: 10,
left: 15,
}} fw={'bold'} fz={{ base: 'md', md: 'h4' }} c={colors['white-1']}>{state.findUnique.data.alamat}</Text>
</BackgroundImage>
<Grid
py={20}>
<GridCol span={{ base: 12, md: 6 }}>
<Text fw="bold" fz={{ base: 'lg', md: 'h2' }} c={colors['white-1']}>
{data.name}
</Text>
<Group gap={6}>
<IconMapPin size={20} color="white" />
<Text fz={{ base: 'sm', md: 'md' }} c={colors['white-1']}>
{data.alamat}
</Text>
</Group>
</Stack>
</BackgroundImage>
<Grid mt="xl" gutter="xl">
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="lg">
<Box>
<Stack>
<Title order={3}>Informasi</Title>
<Box>
<Text>Alamat: {state.findUnique.data.alamat}</Text>
<Text>Telepon: {state.findUnique.data.kontak.kontakPuskesmas}</Text>
<Text>Email: {state.findUnique.data.kontak.email}</Text>
</Box>
<Title order={3}>Jam Operasional</Title>
<Box>
<Text pb={10} fz={'h4'} fw={"bold"}>
Senin - Kamis: <Text span fz={'h4'}>{state.findUnique.data?.jam.workDays} - {state.findUnique.data?.jam.weekDays}</Text>
</Text>
</Box>
<Title order={3} mb={10}>Informasi Kontak</Title>
<Stack gap={8}>
<Group gap={8}>
<IconPhone size={18} />
<Text fz="md">{data.kontak.kontakPuskesmas || '-'}</Text>
</Group>
<Group gap={8}>
<IconMail size={18} />
<Text fz="md">{data.kontak.email || '-'}</Text>
</Group>
</Stack>
</Box>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Box>
<Paper p={"xl"} bg={'#B1C5F2'}>
<SimpleGrid
cols={{
base: 1,
md: 2
}}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fw={"bold"} fz={'h3'} ta={'center'}>Poliklinik Umum</Text>
<Text ta={'center'} fz={{ base: 45, md: 65 }} fw={'bold'} c={colors['blue-button']}>26</Text>
</Paper>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text ta={'center'} fz={'h3'} fw={"bold"}>Poli Gigi</Text>
<Text ta={'center'} fz={{ base: 45, md: 65 }} fw={'bold'} c={colors['blue-button']}>26</Text>
</Paper>
</SimpleGrid>
</Paper>
</Box>
</GridCol>
<Box>
</Box>
</Grid>
</Box>
</Paper>
</Box>
<Divider />
<Box>
<Title order={3} mb={10}>Jam Operasional</Title>
<Group gap={8}>
<IconClock size={18} />
<Text fw="bold" fz="md">
{data.jam.workDays} - {data.jam.weekDays}
</Text>
<Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip>
</Group>
</Box>
</Stack>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Paper p="xl" radius="lg" bg="linear-gradient(135deg, #EAF0FB, #BFD4F5)" shadow="sm">
<Title order={3} mb="lg" ta="center" c="dark">Layanan Unggulan</Title>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poliklinik Umum</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text>
</Stack>
</Paper>
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poli Gigi</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text>
</Stack>
</Paper>
</SimpleGrid>
</Paper>
</GridCol>
</Grid>
</Box>
</Paper>
</Stack>
</Stack>
);

View File

@@ -1,14 +1,13 @@
'use client'
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
import colors from '@/con/colors';
import { Anchor, Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { Anchor, Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Badge, Group } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const state = useProxy(puskesmasState)
const [search, setSearch] = useState('')
@@ -22,76 +21,122 @@ function Page() {
} = state.findMany;
useShallowEffect(() => {
load(page, 3, search)
load(page, 6, search)
}, [page, search])
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
<Box py="xl" px={{ base: 'md', md: 100 }}>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} height={320} radius="lg" />
))}
</SimpleGrid>
</Box>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Puskesmas Darmasaba
<Grid align="center" px={{ base: 'md', md: 100 }} mb="md">
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{ base: "2rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
Daftar Puskesmas
</Text>
<Text fz="sm" c="dimmed">
Temukan informasi lengkap mengenai layanan, kontak, dan lokasi Puskesmas Darmasaba
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<GridCol span={{ base: 12, md: 4 }}>
<TextInput
radius={"lg"}
placeholder='Cari Puskesmas'
radius="xl"
placeholder="Cari nama puskesmas..."
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
leftSection={<IconSearch size={18} />}
aria-label="Cari Puskesmas"
/>
</GridCol>
</Grid>
<Box px={{ base: "md", md: 100 }}>
<SimpleGrid
cols={{
base: 1,
md: 3,
}}
>
{data.map((v, k) => {
return (
<Paper p={"xl"} bg={colors['white-trans-1']} key={k}>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"h3"}>{v.name}</Text>
<Box px={{ base: 'md', md: 100 }}>
{data.length === 0 ? (
<Center py="xl">
<Stack align="center" gap="xs">
<Text fz="lg" fw={500} c="dimmed">Tidak ada data ditemukan</Text>
<Text fz="sm" c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text>
</Stack>
</Center>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
{data.map((v, k) => (
<Paper
key={k}
p="lg"
radius="xl"
withBorder
shadow="md"
bg={colors['white-trans-1']}
style={{ transition: 'transform 200ms ease' }}
className="hover:scale-[1.02]"
>
<Stack gap="sm">
<Image
src={v.image.link}
alt={v.name}
radius="md"
height={160}
fit="cover"
/>
<Box>
<Text>Alamat: {v.alamat}</Text>
<Text>Telepon: {v.kontak.kontakPuskesmas}</Text>
<Text>Email: {v.kontak.email}</Text>
</Box>
<Anchor c={colors['blue-button']} href={`/darmasaba/kesehatan/puskesmas/${v.id}`}>Lihat Detail &gt;</Anchor>
<Group justify="space-between">
<Text fw={600} fz="lg" lineClamp={1}>{v.name}</Text>
<Badge color="blue" variant="light" radius="sm" fz="xs">Aktif</Badge>
</Group>
<Stack gap={4}>
<Group gap="xs">
<IconMapPin size={16} />
<Text fz="sm" c="dimmed" lineClamp={2}>{v.alamat}</Text>
</Group>
<Group gap="xs">
<IconPhone size={16} />
<Text fz="sm" c="dimmed">{v.kontak.kontakPuskesmas}</Text>
</Group>
<Group gap="xs">
<IconMail size={16} />
<Text fz="sm" c="dimmed">{v.kontak.email}</Text>
</Group>
</Stack>
<Anchor
href={`/darmasaba/kesehatan/puskesmas/${v.id}`}
fz="sm"
fw={500}
c={colors['blue-button']}
>
Lihat detail
</Anchor>
</Stack>
</Paper>
)
})}
</SimpleGrid>
))}
</SimpleGrid>
)}
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage, 6, search)}
total={totalPages}
size="md"
radius="xl"
mt="lg"
/>
</Center>
)}
</Stack>
);
}

View File

@@ -1,85 +1,126 @@
'use client'
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
import colors from '@/con/colors';
import { Box, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconChristmasTree, IconDroplet, IconHome, IconLeaf, IconTrash } from '@tabler/icons-react';
import { Badge, Box, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Tooltip } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { Icon, IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDroplet, IconHome, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent, IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
import React, { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const data = [
{
id: 1,
icon: <IconLeaf size={100} color={colors['blue-button']} />,
title: 'Luas Lahan Hijau',
jumlah: '± 25 hektar',
deskripsi: 'tersebar di area persawahan, kebun, dan taman desa.'
},
{
id: 2,
icon: <IconHome size={100} color={colors['blue-button']} />,
title: 'Jumlah Rumah Tangga',
jumlah: '± 1.500 rumah tangga',
deskripsi: 'yang mayoritas sudah memiliki fasilitas pengelolaan sampah mandiri.'
},
{
id: 3,
icon: <IconDroplet size={100} color={colors['blue-button']} />,
title: 'Jumlah Sungai dan Saluran Air',
jumlah: '± 3 Sungai Besar',
deskripsi: 'dan beberapa saluran irigasi tradisional (subak) yang masih aktif digunakan.'
},
{
id: 4,
icon: <IconChristmasTree size={100} color={colors['blue-button']} />,
title: 'Program Penghijauan',
jumlah: '± 1000 Pohon',
deskripsi: 'Dilaksanakan secara berkala melalui kegiatan menanam pohon di area umum dan perbukitan.'
},
{
id: 5,
icon: <IconTrash size={100} color={colors['blue-button']} />,
title: 'Pengelolaan Sampah',
jumlah: '± 5 Bank Sampah',
deskripsi: 'Didukung oleh Bank Sampah dan sistem pemilahan sampah rumah tangga.'
},
]
function Page() {
const state = useProxy(dataLingkunganDesaState.findMany)
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
const {
data,
load,
page,
totalPages,
} = state
const iconMap: Record<string, Icon> = {
ekowisata: IconLeaf,
kompetisi: IconTrophy,
wisata: IconTent,
ekonomi: IconChartLine,
sampah: IconRecycle,
truck: IconTruckFilled,
scale: IconScale,
clipboard: IconClipboardTextFilled,
trash: IconTrashFilled,
lingkunganSehat: IconHomeEco,
sumberOksigen: IconChristmasTreeFilled,
ekonomiBerkelanjutan: IconTrendingUp,
mencegahBencana: IconShieldFilled,
rumah: IconHome,
pohon: IconTree,
air: IconDroplet
};
useShallowEffect(() => {
load(page, 6, debouncedSearch)
}, [page, debouncedSearch])
if (state.loading || !data) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="28px">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Data Lingkungan Desa
<Box px={{ base: 'md', md: 100 }}>
<Group justify="space-between" align='center' mt="md">
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
Data Lingkungan Desa
</Text>
<TextInput
radius="xl"
w={'30%'}
placeholder="Cari Data Lingkungan Desa"
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Group>
<Text fz="lg" c={'black'}>
Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya. Fokus utama meliputi penghijauan, pengelolaan sampah, dan perlindungan kawasan hijau.
</Text>
<Text px={20} ta={'center'} fz={'h4'}>Desa Darmasaba memiliki lingkungan yang terus dijaga dan dikembangkan demi kesejahteraan warganya. Upaya pelestarian lingkungan difokuskan pada penghijauan, pengelolaan sampah, serta perlindungan kawasan hijau.</Text>
</Box>
<Box px={{ base: 'md', md: 100 }} >
<SimpleGrid
cols={{
base: 1,
md: 2,
}}>
{data.map((v, k) => {
return (
<Box key={k}>
<Paper p={20} bg={colors['white-trans-1']}>
<Text fw={'bold'} c={colors['blue-button']} fz={{ base: "lg", md: "xl" }} >
{v.title}
</Text>
<Box>
{v.icon}
<Text c={colors['blue-button']} fz={'h4'} fw={'bold'}>
{v.jumlah}
</Text>
</Box>
<Text fz={{ base: "lg", md: "xl" }} >
{v.deskripsi}
</Text>
</Paper>
</Box>
)
})}
<Box px={{ base: 'md', md: 100 }}>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
{data.map((item) => (
<Paper
key={item.id}
p="lg"
bg={colors['white-trans-2']}
radius="md"
shadow="xl"
style={{ transition: 'all 0.3s', '&:hover': { transform: 'translateY(-5px)', boxShadow: `0 0 20px ${colors['blue-button']}` } }}
>
<Stack align="center" gap="md">
<Tooltip label={item.name} position="top" withArrow>
<Center>
{iconMap[item.icon] ? (
React.createElement(iconMap[item.icon], {
size: 55,
color: colors['blue-button'],
style: {
transition: 'all 0.3s',
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'
}
})
) : null}
</Center>
</Tooltip>
<Text fw="bold" fz="xl" c={colors['blue-button']}>
± {item.jumlah}
</Text>
<Text ta="center" fz="md" c={"black"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Badge variant="gradient" gradient={{ from: '#1C6EA4', to: '#69C0FF' }} size="sm">
{item.name}
</Badge>
</Stack>
</Paper>
))}
</SimpleGrid>
<Center mt="xl">
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
color="blue"
radius="xl"
/>
</Center>
</Box>
</Stack>
);

View File

@@ -1,100 +1,87 @@
'use client'
import colors from '@/con/colors';
import { Box, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { Box, List, ListItem, Paper, SimpleGrid, Stack, Text, Tooltip } from '@mantine/core';
import { IconLeaf, IconPlant2, IconRecycle } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
const data = [
{
id: 1,
title: 'Tujuan Edukasi Lingkungan',
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
<ListItem>
Meningkatkan kesadaran masyarakat tentang pentingnya lingkungan bersih dan sehat
</ListItem>
<ListItem>
Mendorong partisipasi warga dalam kegiatan pengelolaan sampah, penghijauan, dan konservasi
</ListItem>
<ListItem>
Mengurangi dampak negatif terhadap lingkungan dari kegiatan manusia
</ListItem>
<ListItem>
Membentuk generasi muda yang peduli terhadap isu-isu lingkungan
</ListItem>
</List>
icon: <IconLeaf size={28} color={colors['blue-button']} />,
listDeskripsi: [
'Meningkatkan kesadaran masyarakat akan pentingnya lingkungan bersih dan sehat',
'Mendorong partisipasi warga dalam pengelolaan sampah, penghijauan, dan konservasi',
'Mengurangi dampak negatif kegiatan manusia terhadap lingkungan',
'Membentuk generasi muda peduli isu-isu lingkungan',
],
},
{
id: 2,
title: 'Materi Edukasi yang Diberikan',
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
<ListItem>
Pengelolaan Sampah (Pilah sampah organik dan anorganik)
</ListItem>
<ListItem>
Pencegahan pencemaran lingkungan (air, udara, dan tanah)
</ListItem>
<ListItem>
Pemanfaatan lahan hijau dan penghijauan desa
</ListItem>
<ListItem>
Daur ulang dan kreatifitas dari sampah
</ListItem>
<ListItem>
Bahaya pembakaran sampah sembarangan
</ListItem>
</List>
icon: <IconRecycle size={28} color={colors['blue-button']} />,
listDeskripsi: [
'Pengelolaan sampah: pilah organik & anorganik',
'Pencegahan pencemaran lingkungan (air, udara, tanah)',
'Pemanfaatan lahan hijau dan penghijauan desa',
'Daur ulang dan kreativitas dari sampah',
'Bahaya pembakaran sampah sembarangan',
],
},
{
id: 3,
title: 'Contoh Kegiatan di Desa Darmasaba',
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
<ListItem>
Pelatihan membuat kompos dari sampah rumah tangga
</ListItem>
<ListItem>
Gerakan &quot;Jumat Bersih&quot; rutin
</ListItem>
<ListItem>
Workshop membuat ecobrick
</ListItem>
<ListItem>
Lomba kebersihan antar banjar
</ListItem>
<ListItem>
Sosialisasi lingkungan di sekolah dan posyandu
</ListItem>
</List>
icon: <IconPlant2 size={28} color={colors['blue-button']} />,
listDeskripsi: [
'Pelatihan membuat kompos dari sampah rumah tangga',
'Gerakan "Jumat Bersih" rutin',
'Workshop pembuatan ecobrick',
'Lomba kebersihan antar banjar',
'Sosialisasi lingkungan di sekolah dan posyandu',
],
},
]
];
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} pb={20}>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Text ta={'center'} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
Edukasi Lingkungan
</Text>
<Text px={20} ta={'center'} fz={'h4'}>
Edukasi Lingkungan adalah bagian penting dalam membentuk perilaku masyarakat yang peduli dan bertanggung jawab terhadap kelestarian alam. Melalui program ini, masyarakat diajak untuk memahami pentingnya menjaga lingkungan demi kesehatan, kenyamanan, dan keberlanjutan hidup bersama.
<Text ta={'center'} fz="h4" c="black">
Program edukasi ini membimbing masyarakat untuk peduli dan bertanggung jawab terhadap alam,
meningkatkan kesehatan, kenyamanan, dan keberlanjutan hidup bersama.
</Text>
</Box>
<Box px={{ base: 'md', md: 100 }} >
<SimpleGrid
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Box key={k}>
<Paper h={{base: 0, md: 350}} p={20} bg={colors['white-trans-1']}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.title}</Text>
{v.listDeskripsi}
</Paper>
</Box>
)
})}
<Box px={{ base: 'md', md: 100 }}>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{data.map((item) => (
<Paper key={item.id} p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
<Stack gap="md">
<Box>
<Tooltip label={item.title} position="top" withArrow>
<Stack gap={4} align="center">
{item.icon}
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
{item.title}
</Text>
</Stack>
</Tooltip>
</Box>
<List fz="h4" spacing="sm" withPadding>
{item.listDeskripsi.map((desc, idx) => (
<ListItem key={idx}>{desc}</ListItem>
))}
</List>
</Stack>
</Paper>
))}
</SimpleGrid>
</Box>
</Stack>

View File

@@ -0,0 +1,80 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(gotongRoyongState.kegiatanDesa)
const [loading, setLoading] = useState(true)
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.findUnique.load(id);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [id])
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!state.findUnique.data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
{state.findUnique.data?.judul}
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Informasi Kegiatan Gotong Royong
</Text>
</Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={"xs"}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsiLengkap || '' }} />
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,168 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
import {
Badge,
Box,
Button,
Card,
Center,
Container,
Divider,
Grid,
GridCol,
Group,
Image,
Pagination,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
Title,
} from '@mantine/core';
import { IconArrowRight, IconCalendar } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
export default function Content({ kategori }: { kategori: string }) {
const router = useTransitionRouter();
const [page, setPage] = useState(1);
const state = useProxy(gotongRoyongState.kegiatanDesa);
const featuredState = useProxy(gotongRoyongState.kegiatanDesa.findFirst);
const featured = featuredState.data;
const paginatedNews = state.findMany.data || [];
const totalPages = state.findMany.totalPages || 1;
// Load data
useEffect(() => {
gotongRoyongState.kegiatanDesa.findFirst.load(kategori);
}, [kategori]);
useEffect(() => {
state.findMany.load(page, 3, '', kategori);
}, [page, kategori]);
return (
<Box py={20}>
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
{/* === Gotong Royong Utama === */}
{featuredState.loading ? (
<Center><Skeleton h={400} /></Center>
) : featured ? (
<Box mb={50}>
<Text fz="h2" fw={700} mb="md">Gotong Royong Utama</Text>
<Paper shadow="md" radius="md" withBorder>
<Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}>
<Image
src={featured.image?.link}
alt={featured.judul || 'Berita Utama'}
height={400}
fit="cover"
radius="md"
style={{ borderBottomRightRadius: 0, borderTopRightRadius: 0 }}
/>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} p="xl">
<Stack h="100%" justify="space-between">
<div>
<Badge color="blue" variant="light" mb="md">
{featured.kategoriKegiatan?.nama || kategori}
</Badge>
<Title order={2} mb="md">{featured.judul}</Title>
<Text color="dimmed" lineClamp={3} mb="md">{featured.deskripsiLengkap}</Text>
</div>
<Group justify="apart" mt="auto">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
{new Date(featured.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
</Group>
<Button
variant="light"
rightSection={<IconArrowRight size={16} />}
onClick={() => router.push(`/darmasaba/lingkungan/gotong-royong/${kategori}/${featured.id}`)}
>
Baca Selengkapnya
</Button>
</Group>
</Stack>
</GridCol>
</Grid>
</Paper>
</Box>
) : null}
{/* === Daftar Gotong Royong === */}
<Box mt={50}>
<Title order={2} mb="md">Daftar Gotong Royong</Title>
<Divider mb="xl" />
{state.findMany.loading ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
{Array(3).fill(0).map((_, i) => (
<Skeleton key={i} h={300} radius="md" />
))}
</SimpleGrid>
) : paginatedNews.length === 0 ? (
<Text c="dimmed" ta="center">Belum ada gotong royong di kategori &quot;{kategori}&quot;.</Text>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
{paginatedNews.map((item) => (
<Card
key={item.id}
shadow="sm"
p="lg"
radius="md"
withBorder
onClick={() => router.push(`/darmasaba/lingkungan/gotong-royong/${kategori}/${item.id}`)}
style={{ cursor: 'pointer' }}
>
<Card.Section>
<Image src={item.image?.link} height={200} alt={item.judul} fit="cover" />
</Card.Section>
<Badge color="blue" variant="light" mt="md">
{item.kategoriKegiatan?.nama || kategori}
</Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
<Text size="sm" color="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsiLengkap }} />
<Group justify="apart" mt="md" gap="xs">
<Text size="xs" color="dimmed">
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'short',
year: 'numeric',
})}
</Text>
<Badge color="gray" variant="outline">Baca Selengkapnya</Badge>
</Group>
</Card>
))}
</SimpleGrid>
)}
{/* Pagination */}
<Center mt="xl">
<Pagination
total={totalPages}
value={page}
onChange={(newPage) => setPage(newPage)}
siblings={1}
boundaries={1}
withEdges
/>
</Center>
</Box>
</Container>
</Box>
);
}

View File

@@ -0,0 +1,14 @@
// src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
import { Suspense } from "react";
import Content from "./content";
export default async function Page({ params }: { params: Promise<{ kategori: string }> }) {
const { kategori } = await params;
return (
<Suspense fallback={<div>Loading...</div>}>
<Content kategori={kategori} />
</Suspense>
);
}

View File

@@ -0,0 +1,171 @@
'use client'
import colors from '@/con/colors';
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import React, { useEffect, useState } from 'react';
import BackButton from '../../../desa/layanan/_com/BackButto';
type HeaderSearchProps = {
placeholder?: string;
searchIcon?: React.ReactNode;
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
children?: React.ReactNode;
};
function LayoutTabsGotongRoyong({
children,
placeholder = "pencarian",
searchIcon = <IconSearch size={20} />
}: HeaderSearchProps) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
// Get active tab from URL path
const activeTab = pathname.split('/').pop() || 'semua';
// Get initial search value from URL
const initialSearch = searchParams.get('search') || '';
const [searchValue, setSearchValue] = useState(initialSearch);
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
// Update active tab state when pathname changes
const [activeTabState, setActiveTabState] = useState(activeTab);
useEffect(() => {
setActiveTabState(activeTab);
}, [activeTab]);
// Clean up timeouts on unmount
useEffect(() => {
return () => {
if (searchTimeout !== null) {
clearTimeout(searchTimeout);
}
};
}, [searchTimeout]);
// Handle search input change with debounce
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setSearchValue(value);
// Clear previous timeout
if (searchTimeout !== null) {
clearTimeout(searchTimeout);
}
// Set new timeout
const newTimeout = window.setTimeout(() => {
const params = new URLSearchParams(searchParams.toString());
if (value) {
params.set('search', value);
} else {
params.delete('search');
}
// Only update URL if the search value has actually changed
if (params.toString() !== searchParams.toString()) {
router.push(`/darmasaba/lingkungan/gotong-royong/${activeTab}?${params.toString()}`);
}
}, 500); // 500ms debounce delay
setSearchTimeout(newTimeout);
};
const tabs = [
{
label: "Semua",
value: "semua",
href: "/darmasaba/lingkungan/gotong-royong/semua"
},
{
label: "Kebersihan",
value: "kebersihan",
href: "/darmasaba/lingkungan/gotong-royong/kebersihan"
},
{
label: "Infrasturktur",
value: "infrasturktur",
href: "/darmasaba/lingkungan/gotong-royong/infrasturktur"
},
{
label: "Sosial",
value: "sosial",
href: "/darmasaba/lingkungan/gotong-royong/sosial"
},
{
label: "Lingkungan",
value: "lingkungan",
href: "/darmasaba/lingkungan/gotong-royong/lingkungan"
}
];
const handleTabChange = (value: string | null) => {
if (!value) return;
const tab = tabs.find(t => t.value === value);
if (tab) {
const params = new URLSearchParams(searchParams.toString());
router.push(`/darmasaba/lingkungan/gotong-royong/${value}${params.toString() ? `?${params.toString()}` : ''}`);
}
};
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md">
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Gotong Royong Desa Darmasaba
</Text>
<Text ta="center" px="md">
Gotong royong rutin dilakukan oleh warga desa untuk meningkatkan kualitas hidup dan kesejahteraan masyarakat Desa Darmasaba
</Text>
</Stack>
</Container>
<Tabs
color={colors['blue-button']}
variant="pills"
value={activeTabState}
onChange={handleTabChange}
>
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
<Grid>
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
<TabsList>
{tabs.map((tab, index) => (
<TabsTab
key={index}
value={tab.value}
onClick={() => router.push(tab.href)}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</GridCol>
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
value={searchValue}
onChange={handleSearchChange}
/>
</GridCol>
</Grid>
</Box>
{children}
</Tabs>
</Stack>
);
}
export default LayoutTabsGotongRoyong;

View File

@@ -0,0 +1,12 @@
// app/desa/berita/BeritaLayoutClient.tsx
'use client'
import dynamic from 'next/dynamic';
const LayoutTabsGotongRoyong = dynamic(
() => import('./_lib/layoutTabs'),
{ ssr: false }
);
export default function GotongRoyongLayoutClient({ children }: { children: React.ReactNode }) {
return <LayoutTabsGotongRoyong>{children}</LayoutTabsGotongRoyong>;
}

View File

@@ -1,89 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Grid, GridCol, Group, Paper, TextInput, Text, Image, Flex, Button } from '@mantine/core';
import { IconCalendar, IconMapPin, IconSearch, IconUsersGroup } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import Link from 'next/link';
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md">
<Stack align="center" gap={0} mb="xl">
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Program Gotong Royong
</Text>
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Desa Darmasaba
</Text>
</Stack>
</Container>
{/* Tabs Menu */}
<Box px={{ base: "md", md: "xl" }} py="md" bg={colors['BG-trans']} mb="md">
<Grid align="center" justify="space-between" mb={20}>
<GridCol span={{ base: 12, md: 8 }}>
<Group gap="md" wrap="wrap">
<Paper bg={colors['blue-button']} radius="xl" py={5} px={20}>
<Text c={colors['white-1']} size="sm">
Semua
</Text>
</Paper>
{['Kebersihan', 'Infrastruktur', 'Sosial', 'Lingkungan'].map((kategori) => (
<Paper key={kategori} bg={colors['blue-button-trans']} radius="xl" py={5} px={20}>
<Text size="sm">
{kategori}
</Text>
</Paper>
))}
</Group>
</GridCol>
<GridCol span={{ base: 12, md: 4 }}>
<TextInput
radius="lg"
placeholder="Cari Program Gotong Royong"
leftSection={<IconSearch size={18} />}
w="100%"
/>
</GridCol>
</Grid>
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Stack gap={'xs'}>
<Image radius={20} src={'/api/img/gotong-royong.png'} w={'100%'} alt='' />
<Text fw={"bold"} fz={{ base: "h2", md: "h1" }}>Membangun Fasilitas Desa</Text>
<Group>
<Paper py={5} px={20} bg={colors['blue-button-trans']} radius={20}>
<Text c={colors['white-1']}>Sosial</Text>
</Paper>
</Group>
<Text fz={{ base: "h4", md: "h3" }}>
Program Pembangunan Fasilitas Desa Maju, Masyarakat Sejahtera.
</Text>
<Flex gap={5} align={'center'}>
<IconCalendar color={colors['blue-button-trans']} size={45} />
<Text fz={{ base: "h4", md: "h3" }}>1 April 2025</Text>
</Flex>
<Flex gap={5} align={'center'}>
<IconMapPin color={colors['blue-button-trans']} size={45} />
<Text fz={{ base: "h4", md: "h3" }}>Banjar Desa Darmasaba</Text>
</Flex>
<Flex gap={5} align={'center'}>
<IconUsersGroup color={colors['blue-button-trans']} size={45} />
<Text fz={{ base: "h4", md: "h3" }}>30 Partisipan</Text>
</Flex>
<Text fw={'bold'} fz={'md'}>Deskripsi : Program pembangunan Pura sebagai pusat spiritual dan budaya desa, melibatkan gotong royong masyarakat dalam pembangunan struktur utama serta ornamen tradisional.</Text>
<Group py={20} justify='center'>
<Button component={Link} href={'https://www.whatsapp.com/?lang=id'} bg={colors['blue-button']} >Daftar Sebagai Relawan</Button>
</Group>
</Stack>
</Paper>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,183 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
import { Badge, Box, Button, Card, Center, Container, Divider, Flex, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconArrowRight, IconCalendar } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function Page() {
const searchParams = useSearchParams();
const router = useTransitionRouter();
// Parameter URL
const search = searchParams.get('search') || '';
const currentPage = parseInt(searchParams.get('page') || '1');
const [page, setPage] = useState(currentPage);
// Gunakan proxy untuk state
const state = useProxy(gotongRoyongState.kegiatanDesa);
const featured = useProxy(gotongRoyongState.kegiatanDesa.findFirst); // ✅ Berita utama
const loadingGrid = state.findMany.loading;
const loadingFeatured = featured.loading;
// Load berita utama (hanya sekali)
useEffect(() => {
if (!featured.data && !loadingFeatured) {
gotongRoyongState.kegiatanDesa.findFirst.load();
}
}, [featured.data, loadingFeatured]);
// Load berita terbaru (untuk grid) saat page/search berubah
useEffect(() => {
const limit = 3; // Sesuaikan dengan tampilan grid
state.findMany.load(page, limit, search);
}, [page, search]);
// Update URL saat page berubah
useEffect(() => {
const url = new URLSearchParams();
if (search) url.set('search', search);
if (page > 1) url.set('page', page.toString());
router.replace(`?${url.toString()}`);
}, [page, search]);
const featuredData = featured.data;
const paginatedNews = state.findMany.data || [];
const totalPages = state.findMany.totalPages || 1;
return (
<Box py={20}>
<Container size="xl" px={{ base: "md", md: "xl" }}>
{/* === Gotong royong Utama (Tetap) === */}
{loadingFeatured ? (
<Center><Skeleton h={400} /></Center>
) : featuredData ? (
<Box mb={50}>
<Text fz="h2" fw={700} mb="md">Gotong royong Utama</Text>
<Paper shadow="md" radius="md" withBorder>
<Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}>
<Image
src={featuredData.image?.link || '/images/placeholder.jpg'}
alt={featuredData.judul || 'Gotong royong Utama'}
height={400}
fit="cover"
radius="md"
style={{ borderBottomRightRadius: 0, borderTopRightRadius: 0 }}
/>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} p="xl">
<Stack h="100%" justify="space-between">
<div>
<Badge color="blue" variant="light" mb="md">
{featuredData.kategoriKegiatan?.nama || 'Gotong royong'}
</Badge>
<Title order={2} mb="md">{featuredData.judul}</Title>
<Text c="dimmed" lineClamp={3} mb="md">
{featuredData.deskripsiSingkat}
</Text>
</div>
<Group justify="apart" mt="auto">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
{new Date(featuredData.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</Text>
</Group>
<Button
variant="light"
rightSection={<IconArrowRight size={16} />}
onClick={() => router.push(`/darmasaba/lingkungan/gotong-royong/${featuredData.kategoriKegiatan?.nama}/${featuredData.id}`)}
>
Baca Selengkapnya
</Button>
</Group>
</Stack>
</GridCol>
</Grid>
</Paper>
</Box>
) : null}
{/* === Gotong royong Terbaru (Berubah Saat Pagination) === */}
<Box mt={50}>
<Title order={2} mb="md">Gotong royong Terbaru</Title>
<Divider mb="xl" />
{loadingGrid ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
{Array(3).fill(0).map((_, i) => (
<Skeleton key={i} h={300} radius="md" />
))}
</SimpleGrid>
) : paginatedNews.length === 0 ? (
<Text c="dimmed" ta="center">Tidak ada gotong royong ditemukan.</Text>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
{paginatedNews.map((item) => (
<Card
key={item.id}
shadow="sm"
p="lg"
radius="md"
withBorder
>
<Card.Section>
<Image
src={item.image?.link || '/images/placeholder-small.jpg'}
height={200}
alt={item.judul}
fit="cover"
/>
</Card.Section>
<Badge color="blue" variant="light" mt="md">
{item.kategoriKegiatan?.nama || 'Gotong royong'}
</Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
<Text size="sm" c="dimmed" lineClamp={3} mt="xs">{item.deskripsiSingkat}</Text>
<Flex align="center" justify="apart" mt="md" gap="xs">
<Text size="xs" c="dimmed">
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'short',
year: 'numeric'
})}
</Text>
<Button p="xs" variant="light" rightSection={<IconArrowRight size={16} />} onClick={() => router.push(`/darmasaba/lingkungan/gotong-royong/${item.kategoriKegiatan?.nama}/${item.id}`)}>Baca Selengkapnya</Button>
</Flex>
</Card>
))}
</SimpleGrid>
)}
{/* Pagination hanya untuk berita terbaru */}
<Center mt="xl">
<Pagination
total={totalPages}
value={page}
onChange={setPage}
siblings={1}
boundaries={1}
withEdges
/>
</Center>
</Box>
</Container>
</Box>
);
}
export default Page;

View File

@@ -1,93 +1,79 @@
import colors from '@/con/colors';
import { Box, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { Box, Center, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
const data = [
{
id: 1,
title: 'Filosofi Tri Hita Karana',
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
<ListItem>
Parahyangan: Hubungan manusia dengan Tuhan
</ListItem>
<ListItem>
Pawongan: Hubungan antar manusia
</ListItem>
<ListItem>
Palemahan: Hubungan manusia dengan alam
</ListItem>
</List>
listDeskripsi: (
<List fz={'lg'} spacing="sm" ta={'justify'}>
<ListItem>Parahyangan: Hubungan manusia dengan Tuhan yang dijaga penuh kesadaran spiritual</ListItem>
<ListItem>Pawongan: Harmoni dan kerja sama antar manusia dalam masyarakat</ListItem>
<ListItem>Palemahan: Pelestarian lingkungan dan hubungan manusia dengan alam</ListItem>
</List>
),
},
{
id: 2,
title: 'Bentuk Konservasi Berdasarkan Adat',
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
<ListItem>
Pelestarian Hutan Adat seperti Alas Pala Sangeh atau Wana Kerthi
</ListItem>
<ListItem>
Subak: Sistem pengelolaan irigasi tradisional yang menjunjung kebersamaan dan keberlanjutan
</ListItem>
<ListItem>
Hari Raya Tumpek Uduh: Perayaan khusus untuk menghormati pohon dan tumbuhan
</ListItem>
<ListItem>
Perarem dan Awig-Awig: Aturan adat desa yang mengatur larangan menebang pohon sembarangan, membuang limbah ke sungai, dll.
</ListItem>
<ListItem>
Ritual penyucian alam seperti Melasti, Piodalan Segara, dan lainnya
</ListItem>
</List>
listDeskripsi: (
<List fz={'lg'} spacing="sm" ta={'justify'}>
<ListItem>Pelestarian Hutan Adat seperti Alas Pala Sangeh dan Wana Kerthi</ListItem>
<ListItem>Subak: Sistem irigasi tradisional yang menekankan kebersamaan dan keberlanjutan</ListItem>
<ListItem>Hari Raya Tumpek Uduh: Perayaan untuk menghormati pohon dan tumbuhan</ListItem>
<ListItem>Perarem & Awig-Awig: Aturan adat untuk menjaga lingkungan dari kerusakan</ListItem>
<ListItem>Ritual penyucian alam seperti Melasti dan Piodalan Segara</ListItem>
</List>
),
},
{
id: 3,
title: 'Nilai Konservasi Adat',
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
<ListItem>
Menjaga keseimbangan ekosistem
</ListItem>
<ListItem>
Melestarikan spiritualitas lokal dan kesucian alam
</ListItem>
<ListItem>
Menumbuhkan kesadaran kolektif untuk hidup selaras dengan lingkungan
</ListItem>
<ListItem>
Menjaga keberlangsungan sumber daya alam untuk generasi mendatang
</ListItem>
</List>
listDeskripsi: (
<List fz={'lg'} spacing="sm" ta={'justify'}>
<ListItem>Menjaga keseimbangan ekosistem dan lingkungan hidup</ListItem>
<ListItem>Melestarikan spiritualitas lokal dan kesucian alam</ListItem>
<ListItem>Meningkatkan kesadaran kolektif untuk hidup selaras dengan alam</ListItem>
<ListItem>Menjamin keberlanjutan sumber daya alam untuk generasi mendatang</ListItem>
</List>
),
},
]
];
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="24">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} pb={20}>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Box px={{ base: 'md', md: 100 }} pb={30}>
<Text ta="center" fz={{ base: '2xl', md: '3rem' }} c={colors['blue-button']} fw="bold">
Konservasi Adat Bali
</Text>
<Text px={20} ta={'center'} fz={'h4'}>
Konservasi Adat Bali adalah upaya pelestarian lingkungan yang berpijak pada kearifan lokal masyarakat Bali, di mana alam dan budaya dianggap sebagai satu kesatuan yang harus dijaga secara harmonis.
<Text px={20} ta="center" fz="lg" c="black">
Pelestarian lingkungan di Bali yang berpijak pada kearifan lokal, menjaga harmoni antara alam, budaya, dan manusia.
</Text>
</Box>
<Box px={{ base: 'md', md: 100 }} >
<SimpleGrid
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Box key={k}>
<Paper h={{ base: 0, md: 450 }} p={20} bg={colors['white-trans-1']}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.title}</Text>
{v.listDeskripsi}
</Paper>
</Box>
)
})}
<Box px={{ base: 'md', md: 100 }}>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{data.map((item) => (
<Paper
key={item.id}
p="lg"
bg="linear-gradient(145deg, #DFE3E8FF 0%, #EFF1F4FF 100%)"
style={{ borderRadius: 16, boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)' }}
>
<Stack gap="md" px={20}>
<Center>
<Text fz="xl" fw="bold" c="black">
{item.title}
</Text>
</Center>
{item.listDeskripsi}
</Stack>
</Paper>
))}
</SimpleGrid>
</Box>
</Stack>

View File

@@ -1,63 +1,60 @@
'use client'
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
import colors from '@/con/colors';
import { Box, Flex, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
import { IconClipboardTextFilled, IconMapPin, IconRecycle, IconScale, IconSearch, IconTrashFilled, IconTruckFilled } from '@tabler/icons-react';
import { Box, Flex, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Icon, IconChartLine, IconClipboardTextFilled, IconLeaf, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
import React from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import dynamic from 'next/dynamic';
// Dynamically import the map component to avoid SSR issues with Leaflet
const LeafletMultiMarkerMap = dynamic(
() => import('@/app/admin/(dashboard)/_com/LeafletMultiMarkerMap'),
{ ssr: false }
);
const data = [
{
id: 1,
icon: <IconRecycle size={50} color={colors['blue-button']} />,
deskripsi: '1. Pilah sampah sesuai jenisnya'
},
{
id: 2,
icon: <IconTruckFilled size={50} color={colors["blue-button"]} />,
deskripsi: '2. Bawa sampah ke Bank Sampah'
},
{
id: 3,
icon: <IconScale size={50} color={colors["blue-button"]} />,
deskripsi: '3. Timbang sampah di Bank Sampah'
},
{
id: 4,
icon: <IconClipboardTextFilled size={50} color={colors["blue-button"]} />,
deskripsi: '4. Catat hasil timbangan di buku tabungan'
},
{
id: 5,
icon: <IconTrashFilled size={50} color={colors["blue-button"]} />,
deskripsi: '5. Sampah didaur ulang oleh petugas Bank Sampah'
},
]
const bankSampah = [
{
id: 1,
icon: <IconMapPin size={50} color={colors['blue-button']} />,
deskripsi: 'Bank Sampah Sarana Gathi',
alamat: 'Jl. Ahmad Yani Utara No.453, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115'
},
{
id: 2,
icon: <IconMapPin size={50} color={colors['blue-button']} />,
deskripsi: 'Bank Sampah BALI WASTU LESTARI',
alamat: 'Jl. Ahmad Yani Utara Gg. Garuda No.1, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115'
},
{
id: 3,
icon: <IconMapPin size={50} color={colors['blue-button']} />,
deskripsi: 'Bank Sampah Jempiring Sari',
alamat: 'Jl. Gn. Lebah I No.9, Tegal Harum, Kec. Denpasar Bar., Kota Denpasar, Bali 80119'
},
{
id: 4,
icon: <IconMapPin size={50} color={colors['blue-button']} />,
deskripsi: 'Bank Sampah Sarana Gathi',
alamat: 'Jl. Ahmad Yani Utara No.453, Peguyangan, Kec. Denpasar Utara, Kota Denpasar, Bali 80115'
},
]
function Page() {
const state = useProxy(pengelolaanSampahState.pengelolaanSampah)
const state2 = useProxy(pengelolaanSampahState.keteranganSampah)
const {
data,
load
} = state.findMany
const {
data: data2,
load: load2
} = state2.findMany
useShallowEffect(() => {
load()
load2()
}, [])
const iconMap: Record<string, Icon> = {
ekowisata: IconLeaf,
kompetisi: IconTrophy,
wisata: IconTent,
ekonomi: IconChartLine,
sampah: IconRecycle,
truck: IconTruckFilled,
scale: IconScale,
clipboard: IconClipboardTextFilled,
trash: IconTrashFilled,
};
if (state.findMany.loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -84,10 +81,10 @@ function Page() {
<Box key={k} px={28}>
<Paper p={20} bg={colors['white-trans-1']}>
<Flex gap={20} align={'center'}>
<Box>
{v.icon}
<Box style={{ alignContent: 'center', alignItems: 'center' }}>
{k + 1} {iconMap[v.icon] ? React.createElement(iconMap[v.icon]) : null}
</Box>
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>{v.deskripsi}</Text>
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>{v.name}</Text>
</Flex>
</Paper>
</Box>
@@ -96,7 +93,7 @@ function Page() {
</SimpleGrid>
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Box px={{ base: 'md', md: 100 }}>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Keterangan Bank Sampah Terdekat
</Text>
@@ -107,66 +104,64 @@ function Page() {
leftSection={<IconSearch size={20} />}
placeholder='Cari Bank Sampah Terdekat'
/>
<SimpleGrid
cols={{
base: 1,
md: 2,
}}>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
{/* Left side - List of bank locations */}
<Box>
<SimpleGrid
cols={{
base: 1,
md: 1,
}}
>
{bankSampah.map((v, k) => {
return (
<Box key={k} px={20}>
<Paper p={20} bg={colors['white-trans-1']} radius={'lg'}>
<Flex gap={20} align={'center'}>
<Box>
{v.icon}
</Box>
<Box>
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>
{v.deskripsi}
</Text>
<Text fz={{ base: "md", md: "lg" }} c={'black'}>
{v.alamat}
</Text>
</Box>
</Flex>
</Paper>
</Box>
)
})}
</SimpleGrid>
<Paper p="md" bg={colors['white-trans-1']} radius="lg">
<Text fz="xl" fw="bold" mb="md">Daftar Bank Sampah</Text>
<Stack gap="md">
{data2?.map((v, k) => (
<Paper key={k} p="md" withBorder radius="md">
<Text fw="bold" fz="lg">{v.namaTempatMaps}</Text>
<Text c="dimmed" fz="sm" mb="sm">{v.alamat}</Text>
{v.lat && v.lng ? (
<a
href={`https://www.google.com/maps/dir/?api=1&destination=${v.lat},${v.lng}`}
target="_blank"
rel="noopener noreferrer"
style={{ color: colors['blue-button'], textDecoration: 'none' }}
>
<Text fz="sm">📌 Buka di Google Maps</Text>
</a>
) : (
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
)}
</Paper>
))}
</Stack>
</Paper>
</Box>
<Box style={{
position: 'relative',
width: '100%',
paddingBottom: '90.5%', // Aspek rasio 16:9 (atau gunakan '100%' untuk aspek rasio 1:1)
height: 0,
overflow: 'hidden'
}}>
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d31558.578355337635!2d115.18413781150647!3d-8.613053599999985!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23ff3a9d0f0ab%3A0xb6bb54a820adbae6!2sBank%20Sampah%20Sarana%20Gathi!5e0!3m2!1sid!2sid!4v1743994947623!5m2!1sid!2sid"
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
border: 0
}}
loading="lazy"
allowFullScreen
></iframe>
{/* Right side - Single map showing all locations */}
<Box style={{ position: 'sticky', top: '20px' }}>
<Paper p="md" bg={colors['white-trans-1']} radius="lg" h="100%">
<Text fz="xl" fw="bold" mb="md">Peta Lokasi</Text>
{data2?.some(v => v.lat && v.lng) ? (
<Box style={{ height: '600px', width: '100%', borderRadius: '8px', overflow: 'hidden' }}>
<LeafletMultiMarkerMap
center={[
data2[0]?.lat || -8.3405,
data2[0]?.lng || 115.0920
]}
markers={data2
.filter(v => v.lat && v.lng)
.map(v => ({
position: [v.lat, v.lng],
popup: v.namaTempatMaps
}))}
/>
</Box>
) : (
<Text c="dimmed" fz="sm">Tidak ada koordinat yang tersedia</Text>
)}
</Paper>
</Box>
</SimpleGrid>
</Box>
</Stack>
</Stack >
);
}
export default Page;

View File

@@ -1,71 +1,131 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconChristmasTreeFilled, IconHomeEco, IconShieldFilled, IconTrendingUp } from '@tabler/icons-react';
import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
import { IconSearch, IconLeaf, IconTrophy, IconTent, IconChartLine, IconRecycle, IconTruckFilled, IconScale, IconClipboardTextFilled, IconTrashFilled, IconHomeEco, IconChristmasTreeFilled, IconTrendingUp, IconShieldFilled } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
import { useProxy } from 'valtio/utils';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import React from 'react';
const data = [
{
id: 1,
deskripsi: 'Lingkungan Sehat',
icon: <IconHomeEco size={80} color={colors['blue-button']} />,
},
{
id: 2,
deskripsi: 'Sumber Oksigen',
icon: <IconChristmasTreeFilled size={80} color={colors['blue-button']} />,
},
{
id: 3,
deskripsi: 'Ekonomi Berkelanjutan',
icon: <IconTrendingUp size={80} color={colors['blue-button']} />,
},
{
id: 4,
deskripsi: 'Mencegah Bencana',
icon: <IconShieldFilled size={80} color={colors['blue-button']} />,
},
]
function Page() {
const state = useProxy(programPenghijauanState);
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 500);
const { data, load, page, totalPages, loading } = state.findMany;
useShallowEffect(() => {
load(page, 10, debouncedSearch);
}, [page, debouncedSearch]);
const iconMap: Record<string, any> = {
ekowisata: IconLeaf,
kompetisi: IconTrophy,
wisata: IconTent,
ekonomi: IconChartLine,
sampah: IconRecycle,
truck: IconTruckFilled,
scale: IconScale,
clipboard: IconClipboardTextFilled,
trash: IconTrashFilled,
lingkunganSehat: IconHomeEco,
sumberOksigen: IconChristmasTreeFilled,
ekonomiBerkelanjutan: IconTrendingUp,
mencegahBencana: IconShieldFilled,
};
if (loading || !data) {
return (
<Stack py={20}>
<Skeleton h={500} radius="xl" />
</Stack>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Program Penghijauan Desa
<Box px={{ base: 'md', md: 100 }}>
<Box>
<Group justify="space-between" align='center' mt="md">
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
Program Penghijauan Desa
</Text>
<TextInput
radius="xl"
w={'30%'}
placeholder="Cari program atau kegiatan"
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Group>
</Box>
<Text c="dimmed" fz={{ base: 'sm', md: 'lg' }} mt="sm">
Mari berpartisipasi menanam dan merawat pohon untuk menciptakan lingkungan hijau, sehat, dan seimbang bagi seluruh warga desa.
</Text>
<Text px={20} ta={'center'} fz={'h4'}>Program Penghijauan Desa bertujuan untuk meningkatkan kesadaran masyarakat akan pentingnya lingkungan hijau melalui penanaman pohon dan perawatan tanaman.</Text>
</Box>
<Box px={{ base: 'md', md: 100 }} pb={60}>
<Text c={colors['blue-button']} fw={'bold'} py={10} px={28} fz={{ base: "lg", md: "xl" }} ta={"justify"}>
Manfaat Program Penghijauan
</Text>
<SimpleGrid
cols={{
base: 1,
md: 4
}}>
{data.map((v, k) => {
return (
<Box key={k}>
<Paper p={20} bg={colors['white-trans-1']}>
<Stack flex={5}>
<Center>
{v.icon}
</Center>
<Box>
<Text fz={{ base: "lg", md: "xl" }} ta={'center'} c={colors['blue-button']} fw={'bold'}>{v.deskripsi}</Text>
</Box>
<Group justify='center'>
<Button bg={colors['blue-button']}>Detail</Button>
</Group>
</Stack>
</Paper>
</Box>
)
})}
<Title order={2} c={colors['blue-button']} fw="bold" py={10} px={28} fz={{ base: 'lg', md: 'xl' }}>
Manfaat Program
</Title>
<SimpleGrid cols={{ base: 1, md: 4 }} spacing="lg" mt="md">
{data.map((v) => (
<Paper
key={v.id}
p="xl"
radius="xl"
bg={colors['white-trans-1']}
withBorder
style={{
backdropFilter: 'blur(10px)',
border: `1px solid rgba(255,255,255,0.2)`,
transition: 'transform 0.3s, box-shadow 0.3s',
cursor: 'pointer',
}}
onMouseEnter={(e) => {
const el = e.currentTarget;
el.style.transform = 'translateY(-8px)';
el.style.boxShadow = '0 15px 30px rgba(28,110,164,0.5)';
}}
onMouseLeave={(e) => {
const el = e.currentTarget;
el.style.transform = 'translateY(0)';
el.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
}}
>
<Stack align="center" gap="sm">
<Center>
{iconMap[v.icon] && React.createElement(iconMap[v.icon], { size: 50, stroke: 1.5, color: colors['blue-button'] })}
</Center>
<Tooltip label={v.judul} withArrow position="bottom">
<Text fz={{ base: 'md', md: 'lg' }} ta="center" c={colors['blue-button']} fw="bold">
{v.name}
</Text>
</Tooltip>
<Text fz="sm" ta="center" c="dimmed">
{v.judul}
</Text>
<Button variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="sm" radius="xl" mt="sm">
Pelajari Lebih Lanjut
</Button>
</Stack>
</Paper>
))}
</SimpleGrid>
<Center mt="xl">
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
color="blue"
radius="xl"
/>
</Center>
</Box>
</Stack>
);

View File

@@ -1,105 +1,363 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
import { IconChalkboard, IconMicroscope, IconSchool, IconSearch } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
import React, { useMemo, useState } from 'react';
import {
ActionIcon,
Badge,
Box,
Button,
Center,
Container,
Group,
Paper,
Progress,
SimpleGrid,
Stack,
Text,
TextInput,
Tooltip,
VisuallyHidden,
} from '@mantine/core';
import {
IconChalkboard,
IconInfoCircle,
IconMicroscope,
IconSchool,
IconSearch,
IconArrowLeft,
} from '@tabler/icons-react';
import { motion } from 'framer-motion';
import type { IconProps } from '@tabler/icons-react';
type Stat = {
id: number;
icon: React.ComponentType<IconProps>;
jumlah: number;
nama: string;
helper?: string;
};
const dataSekolah = [
const dataSekolah: Stat[] = [
{
id: 1,
icon: <IconChalkboard size={55} color={colors["blue-button"]} />,
icon: IconChalkboard,
jumlah: 15,
nama: 'Lembaga Pendidikan'
nama: 'Lembaga Pendidikan',
helper: 'Jumlah institusi pendidikan resmi di wilayah ini',
},
{
id: 2,
icon: <IconSchool size={55} color={colors["blue-button"]} />,
icon: IconSchool,
jumlah: 3209,
nama: 'Siswa Terdaftar'
nama: 'Siswa Terdaftar',
helper: 'Total siswa aktif di semua jenjang',
},
{
id: 3,
icon: <IconMicroscope size={55} color={colors["blue-button"]} />,
icon: IconMicroscope,
jumlah: 285,
nama: 'Tenaga Pengajar'
nama: 'Tenaga Pengajar',
helper: 'Jumlah guru dan staf pengajar aktif',
},
]
];
export default function SekolahPage() {
const [query, setQuery] = useState('');
const [kategoriAktif, setKategoriAktif] = useState('Semua');
const kategoriList = ['Semua', 'TK/PAUD', 'SD', 'SMP', 'SMA/SMK'];
const maxJumlah = useMemo(() => Math.max(...dataSekolah.map((d) => d.jumlah)), []);
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
return dataSekolah.filter((d) => {
const teks = `${d.nama} ${d.jumlah}`.toLowerCase();
const matchQuery = q ? teks.includes(q) : true;
return matchQuery;
});
}, [query, kategoriAktif]);
const hasilCount = filtered.length;
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} pb={20}>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Cari Informasi Sekolah
</Text>
<Group justify='center' pb={20}>
<TextInput
w={{ base: "50%", md: "70%" }}
placeholder='Cari Sekolah...'
rightSection={
<Button
size="xs"
style={{ height: '80%', marginRight: '5px' }}
bg={colors["blue-button"]}
>
Cari
</Button>
}
rightSectionWidth={70}
leftSection={<IconSearch size={20} />}
/>
</Group>
<Group mb={20} gap="md" justify='center' wrap="wrap">
<Paper bg={colors['blue-button']} radius="xl" py={5} px={20}>
<Text c={colors['white-1']} size="sm">
Semua
</Text>
</Paper>
{['TK/PAUD', 'SD', 'SMP', 'SMA/SMK'].map((kategori) => (
<Paper key={kategori} bg={'gray'} radius="xl" py={5} px={20}>
<Text c={colors['white-1']} size="sm">
{kategori}
</Text>
</Paper>
))}
</Group>
<SimpleGrid
cols={{
base: 1,
md: 3
}}
>
{dataSekolah.map((v, k) => {
return (
<Box key={k}>
<Box style={{ minHeight: '100vh', background: '#f8fafc', paddingBottom: 48 }}>
<Container size="xl" py={{ base: 'md', md: 'xl' }}>
<Stack gap="lg">
<Box>
<ActionIcon
aria-label="Kembali"
onClick={() => window.history.back()}
size="lg"
radius="md"
variant="light"
style={{
color: '#1e293b',
background: 'white',
boxShadow: '0 2px 12px rgba(0,0,0,0.06)',
}}
>
<IconArrowLeft size={20} stroke={2} />
<VisuallyHidden>Tombol kembali</VisuallyHidden>
</ActionIcon>
</Box>
<Paper
radius="lg"
p={{ base: 'md', md: 'xl' }}
style={{
background: 'linear-gradient(180deg, #ffffff 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
boxShadow: '0 6px 24px rgba(0,0,0,0.06)',
}}
role="search"
aria-label="Pencarian sekolah"
>
<Stack gap="md">
<Center>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.8 }}
style={{ width: '100%', height: '100%' }}
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, ease: 'easeOut' }}
>
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Stack>
<Center>
{v.icon}
</Center>
<Text c={colors["blue-button"]} ta={'center'} fw={'bold'} fz={{ base: "h2", md: "h1" }}>{v.jumlah}</Text>
<Text c={colors["blue-button"]} ta={'center'} fw={'bold'}>{v.nama}</Text>
</Stack>
</Paper>
<Text
ta="center"
c="#0f172a"
fz={{ base: 22, md: 30 }}
fw={800}
style={{ letterSpacing: -0.3 }}
>
Cari Informasi Sekolah
</Text>
<Text ta="center" c="dimmed" fz="sm" mt={6}>
Masukkan nama, jenjang, atau alamat sekolah untuk hasil lebih spesifik.
</Text>
</motion.div>
</Box>
)
})}
</SimpleGrid>
</Box>
</Stack>
</Center>
<Group align="center" justify="center" gap="sm" style={{ width: '100%' }}>
<TextInput
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Contoh: SMP Negeri, SD 01, Kelurahan..."
leftSection={<IconSearch size={18} aria-hidden />}
aria-label="Masukkan kata kunci pencarian"
radius="xl"
size="md"
rightSection={
<Button
radius="xl"
size="sm"
aria-label="Telusuri"
onClick={() => {}}
style={{
height: 38,
minWidth: 110,
background: 'linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%)',
color: 'white',
boxShadow: '0 4px 16px rgba(59,130,246,0.3)',
}}
>
Telusuri
</Button>
}
rightSectionWidth={120}
style={{
width: '100%',
maxWidth: 920,
}}
/>
</Group>
<Group justify="center" gap="xs" wrap="wrap" style={{ marginTop: 4 }}>
{kategoriList.map((k) => {
const aktif = k === kategoriAktif;
return (
<motion.div
key={k}
initial={{ scale: 0.98, opacity: 0.9 }}
animate={{ scale: 1, opacity: 1 }}
whileHover={{ scale: 1.02 }}
>
<Button
onClick={() => setKategoriAktif(k)}
radius="xl"
size="sm"
variant={aktif ? 'filled' : 'light'}
style={{
background: aktif
? 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)'
: 'white',
color: aktif ? 'white' : '#2563eb',
boxShadow: aktif ? '0 4px 16px rgba(59,130,246,0.25)' : 'none',
border: '1px solid #e2e8f0',
}}
>
{k}
</Button>
</motion.div>
);
})}
</Group>
</Stack>
</Paper>
<Box aria-live="polite" aria-atomic>
<Text fz="sm" c="dimmed">
Menampilkan <Text component="span" c="#0f172a" fw={700}>{hasilCount}</Text> hasil.
</Text>
</Box>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{filtered.length === 0 ? (
<Paper
p="xl"
radius="md"
style={{
background: '#f9fafb',
border: '1px dashed #e2e8f0',
minHeight: 220,
}}
role="status"
aria-label="Tidak ada hasil"
>
<Center style={{ minHeight: 180, flexDirection: 'column' }}>
<Text fz="lg" fw={800} c="#2563eb">
Tidak ditemukan
</Text>
<Text c="dimmed" mt="6px">
Coba gunakan kata kunci lain atau setel ulang filter.
</Text>
<Button
mt="md"
radius="xl"
onClick={() => {
setQuery('');
setKategoriAktif('Semua');
}}
style={{
background: 'linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%)',
color: 'white',
boxShadow: '0 6px 18px rgba(59,130,246,0.25)',
}}
aria-label="Tampilkan semua"
>
Tampilkan Semua
</Button>
</Center>
</Paper>
) : (
filtered.map((v) => {
const percent = Math.round((v.jumlah / maxJumlah) * 100) || 0;
return (
<motion.div
key={v.id}
whileHover={{ scale: 1.025 }}
whileTap={{ scale: 0.995 }}
style={{ width: '100%' }}
>
<Paper
p="lg"
radius="lg"
style={{
background: 'white',
border: '1px solid #e2e8f0',
boxShadow: '0 8px 28px rgba(0,0,0,0.06)',
minHeight: 260,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
}}
role="article"
aria-label={`${v.nama} kartu statistik`}
>
<Stack gap="sm">
<Center>
<Box
style={{
width: 80,
height: 80,
borderRadius: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#eff6ff',
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.6)',
}}
aria-hidden
>
{React.createElement(v.icon, {
color: '#2563eb',
size: 34,
stroke: 1.6,
})}
</Box>
</Center>
<Group justify="apart" align="center" gap="xs">
<Stack gap={0}>
<Text fz={{ base: 18, md: 22 }} fw={800} c="#0f172a">
{v.jumlah.toLocaleString()}
</Text>
<Group gap={6} align="center">
<Text fz="sm" fw={700} c="#2563eb">
{v.nama}
</Text>
<Tooltip label={v.helper ?? ''} position="right" withArrow>
<ActionIcon aria-label={`Info ${v.nama}`} variant="transparent" size="xs">
<IconInfoCircle size={16} style={{ color: '#2563eb' }} />
</ActionIcon>
</Tooltip>
</Group>
</Stack>
<Badge
radius="md"
variant="light"
style={{
background: '#eff6ff',
color: '#2563eb',
border: '1px solid #e2e8f0',
}}
>
Statistik
</Badge>
</Group>
<Box>
<Progress
value={percent}
size="sm"
radius="xl"
aria-label={`${v.nama} progres ${percent} persen`}
/>
<Text fz="xs" c="dimmed" mt="6px">
Perbandingan dengan jumlah terbesar.
</Text>
</Box>
</Stack>
<Group justify="right" mt="8px">
<Button
radius="xl"
variant="outline"
onClick={() => {}}
aria-label={`Lihat detail ${v.nama}`}
style={{
borderColor: '#e2e8f0',
color: '#2563eb',
paddingLeft: 20,
paddingRight: 20,
}}
>
Lihat Detail
</Button>
</Group>
</Paper>
</motion.div>
);
})
)}
</SimpleGrid>
</Stack>
</Container>
</Box>
);
}
export default Page;

View File

@@ -2,9 +2,26 @@
import daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
import colors from '@/con/colors';
import { Box, Center, Image, Pagination, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput } from '@mantine/core';
import {
Box,
Center,
Image,
Pagination,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
TextInput,
Paper,
Badge,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { IconSearch, IconFileInfo, IconMail, IconBrandWhatsapp } from '@tabler/icons-react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
@@ -21,85 +38,122 @@ function Page() {
load,
} = listData.findMany
useShallowEffect(() => {
load(page, 5, search)
}, [page, search])
if (loading || !data) return <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} />
</Box>
<Skeleton h={40} />
<Skeleton h={40} />
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} />
</Box>
</Stack>
if (loading || !data) {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} radius="xl" />
</Box>
<Skeleton h={50} radius="xl" />
<Skeleton h={50} radius="xl" />
<Skeleton h={50} radius="xl" />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Center>
<Image src={"/darmasaba-icon.png"} w={{ base: 70, md: 100 }} alt='' />
<Image src="/darmasaba-icon.png" w={{ base: 70, md: 100 }} alt="Logo Desa Darmasaba" />
</Center>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Text ta="center" fz={{ base: "1.8rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold" lh={1.4}>
Daftar Informasi Publik Desa Darmasaba
</Text>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lmd'}>
<Text fz={{ base: 'h3', md: 'h2' }} fw={"bold"}>Tentang Informasi Publik</Text>
<Text fz={{ base: 'md', md: 'h3' }}>Daftar Informasi Publik Desa Darmasaba berupa kumpulan data yang dapat diakses oleh masyarakat sesuai dengan peraturan yang berlaku.</Text>
<Stack gap="lg">
<Paper p="lg" radius="xl" shadow="sm" withBorder>
<Stack gap="sm">
<Text fz={{ base: 'lg', md: 'xl' }} fw="bold" c={colors["blue-button"]}>
Tentang Informasi Publik
</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
Daftar Informasi Publik Desa Darmasaba adalah kumpulan data yang dapat diakses oleh masyarakat sesuai dengan ketentuan peraturan yang berlaku.
</Text>
</Stack>
</Paper>
<TextInput
placeholder='Cari Informasi...'
leftSection={<IconSearch size={20} />}
placeholder="Cari informasi publik..."
aria-label="Pencarian informasi publik"
leftSection={<IconSearch size={20} stroke={1.5} />}
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{ marginBottom: 16 }}
radius="xl"
size="md"
/>
<Table withRowBorders withColumnBorders withTableBorder>
<TableThead bg={colors['blue-button']}>
<TableTr c={colors['white-1']}>
<TableTh ta={'center'}>No</TableTh>
<TableTh ta={'center'}>Jenis Informasi</TableTh>
<TableTh ta={'center'}>Deskripsi</TableTh>
<TableTh ta={'center'}>Tanggal Publikasi</TableTh>
</TableTr>
</TableThead>
<TableTbody bg={colors['white-1']}>
{listData.findMany.data?.map((item, index) => (
<TableTr key={item.id}>
<TableTd ta={'center'}>{index + 1}</TableTd>
<TableTd>
<Text fz={'md'}>
{item.jenisInformasi}
</Text>
</TableTd>
<TableTd>
<Text
fz={'md'}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID')
: '-'}</TableTd>
{data.length === 0 ? (
<Center py="xl">
<Stack align="center" gap="sm">
<IconFileInfo size={48} stroke={1.5} color={colors["blue-button"]} />
<Text fz="md" c="dimmed">Tidak ada informasi publik yang ditemukan.</Text>
</Stack>
</Center>
) : (
<Table withRowBorders withColumnBorders withTableBorder highlightOnHover verticalSpacing="md">
<TableThead bg={colors['blue-button']}>
<TableTr c={colors['white-1']}>
<TableTh fz="sm" ta="center" w="5%">No</TableTh>
<TableTh fz="sm" ta="center" w="25%">Jenis Informasi</TableTh>
<TableTh fz="sm" ta="center" w="40%">Deskripsi</TableTh>
<TableTh fz="sm" ta="center" w="20%">Tanggal Publikasi</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</TableThead>
<TableTbody bg={colors['white-1']}>
{data.map((item, index) => (
<TableTr key={item.id}>
<TableTd ta="center">{(page - 1) * 5 + index + 1}</TableTd>
<TableTd>
<Badge variant="light" size="lg" color="blue">
{item.jenisInformasi}
</Badge>
</TableTd>
<TableTd>
<Text fz="sm" c="dark" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd ta="center">
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
}) : '-'}
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
)}
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage, 5, search)}
total={totalPages}
my="lg"
radius="xl"
size="md"
/>
</Center>
<Paper p="lg" radius="xl" shadow="xs" withBorder>
<Stack gap="xs">
<Text fz="lg" fw="bold" c={colors["blue-button"]}>Kontak PPID</Text>
<Text fz="sm" c="dimmed" lh={1.6}>
<IconMail size={16} style={{ marginRight: 6 }} /> Email: <Text span fw="500">ppid@desadarmasaba.id</Text>
</Text>
<Text fz="sm" c="dimmed" lh={1.6}>
<IconBrandWhatsapp size={16} style={{ marginRight: 6 }} /> WhatsApp: <Text span fw="500">081-xxx-xxx-xxx</Text>
</Text>
</Stack>
</Paper>
</Stack>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
<Text pt={20} fz={'h4'} fw={"bold"}>Kontak PPID</Text>
<Text fz={'sm'}>Email: ppid@desadarmasaba.id | WhatsApp: 081-xxx-xxx-xxx</Text>
</Box>
</Stack>
);

View File

@@ -1,100 +1,93 @@
'use client'
import stateDasarHukum from '@/app/admin/(dashboard)/_state/ppid/dasar_hukum/dasarHukum';
import colors from '@/con/colors';
import { Box, List, ListItem, Paper, Stack, Text } from '@mantine/core';
import { Box, Paper, Skeleton, Stack, Text, Transition } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
import { IconBook2 } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const state = useProxy(stateDasarHukum);
useShallowEffect(() => {
state.findById.load("1");
}, []);
if (!state.findById.data) {
return (
<Stack p="xl" gap="md">
<Skeleton h={70} radius="xl" />
</Stack>
);
}
const dataArray = Array.isArray(state.findById.data)
? state.findById.data
: [state.findById.data];
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Dasar Hukum
</Text>
<Stack align="center" gap="xs">
<IconBook2 size={42} stroke={1.5} color={colors["blue-button"]} />
<Text
ta="center"
fz={{ base: "2rem", md: "2.5rem" }}
c={colors["blue-button"]}
fw="bold"
style={{ letterSpacing: "-0.5px" }}
>
Dasar Hukum
</Text>
<Text ta="center" fz="sm" c="dimmed">
Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum
</Text>
</Stack>
<Box px={{ base: "md", md: 100 }}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Box>
<Text ta={"center"} fw={"bold"} fz={{ base: 'h4', md: 'h3' }}>DASAR HUKUM PEMBENTUKAN PPID DESA DARMASABA</Text>
</Box>
<List p={'lg'}>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
UU Nomor 14 Tahun 2008
</Text>
{" "}tentang Keterbukaan Informasi Publik
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
PP Nomor 61 Tahun 2010
</Text>
{" "}tentang Pelaksanaan UU 14 Tahun 2008 tentang Keterbukaan Informasi Publik
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Permendagri Nomor 3 Tahun 2017
</Text>
{" "}tentang Pedoman Pengelolaan Pelayanan Informasi dan Dokumentasi di Lingkungan Kemendagri
dan Pemerintah Daerah
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Peraturan Komisi Informasi Nomor 1 Tahun 2010
</Text>
{" "} tentang Standar Layanan Informasi Publik
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Peraturan Komisi Informasi Nomor 1 Tahun 2010
</Text>
{" "} tentang Standar Layanan Informasi Publik
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Peraturan Bupati Badung No. 42 Tahun 2017
</Text>
{" "}Tentang Pedoman Pengelolaan Pelayanan Informasi Publik dan Dokumentasi
di Lingkungan Pemerintah Kabupaten Badung
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Keputusan Bupati Badung Nomor 99/049/HK/2019
</Text>
{" "} Tentang Pengelola Layanan Informasi dan Dokumentasi Kabupaten Badung
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Keputusan Perbekel Darmasaba Nomor 101 Tahun 2019
</Text>
{" "}tentang Penetapan Pelaksana Teknis/Administrasi Pengelola Layanan Informasi
Dan Dokumentasi Di Desa Punggul
</Text>
</ListItem>
<ListItem fz={{ base: 'md', md: 'h4' }}>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
<Text fw={"bold"} span>
Peraturan Perbekel Darmasaba Nomor 12 Tahun 2019
</Text>
{" "}tentang Pedoman Pengelolaan Pelayanan Informasi Publik Dan Dokumentasi
Di Lingkungan Pemerintah Desa Darmasaba
</Text>
</ListItem>
</List>
</Paper>
<Stack gap="lg">
{dataArray.map((item, k) => (
<Transition
key={k}
mounted={true}
transition="fade-up"
duration={400}
timingFunction="ease"
>
{(styles) => (
<Paper
p="xl"
radius="xl"
shadow="md"
bg={colors['white-trans-1']}
style={{
...styles,
backdropFilter: "blur(10px)",
border: `1px solid ${colors["blue-button"]}20`,
}}
>
<Stack gap="md">
<Text
ta="center"
fw="bold"
fz={{ base: 'lg', md: 'xl' }}
style={{ lineHeight: 1.4 }}
>
{item.judul}
</Text>
<Text
fz={{ base: 'sm', md: 'md' }}
style={{ lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: item.content }}
/>
</Stack>
</Paper>
)}
</Transition>
))}
</Stack>
</Box>
</Stack>
);

View File

@@ -1,8 +1,21 @@
'use client'
import statePermohonanInformasi from '@/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Center, Checkbox, Group, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
import { IconDownload } from '@tabler/icons-react';
import {
ActionIcon,
Box,
Button,
Center,
Checkbox,
Group,
Paper,
SimpleGrid,
Stack,
Text,
TextInput,
Tooltip,
} from '@mantine/core';
import { IconDownload, IconSend2 } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
@@ -10,11 +23,31 @@ import JenisInformasiSelector from './jenis_infromasi/jenisInformasiSelector';
import MemperolehInformasi from './memperoleh_informasi/memperolehInfromasi';
import MemperolehSalinan from './salinan_informasi/salinanInformasi';
const data = [
{ id: 1, number: '1', title: "Langkah 1", desc: "Pemohon informasi publik mengajukan permohonan informasi kepada badan publik baik langsung maupun melalui surat elektronik" },
{ id: 2, number: '2', title: "Langkah 2", desc: "Isi formulir permohonan informasi dengan data diri (nama, alamat, telepon), jenis, format, dan cara penyampaian informasi, serta lampiran fotokopi kartu identitas." },
{ id: 3, number: '3', title: "Langkah 3", desc: "PPID akan memproses permohonan sesuai dengan ketentuan" },
{ id: 4, number: '4', title: "Langkah 4", desc: "Petugas PPID menyampaikan informasi sesuai permohonan kepada pemohon informasi." },
const steps = [
{
id: 1,
number: '1',
title: 'Ajukan Permohonan',
desc: 'Pemohon mengajukan permohonan informasi kepada badan publik secara langsung atau melalui surat elektronik.',
},
{
id: 2,
number: '2',
title: 'Isi Formulir',
desc: 'Lengkapi formulir dengan identitas (nama, alamat, telepon, email), jenis, format, serta cara memperoleh informasi, dan lampirkan fotokopi identitas.',
},
{
id: 3,
number: '3',
title: 'Proses oleh PPID',
desc: 'PPID akan memproses permohonan sesuai ketentuan yang berlaku.',
},
{
id: 4,
number: '4',
title: 'Penyampaian Informasi',
desc: 'Petugas PPID memberikan informasi sesuai permohonan kepada pemohon.',
},
];
function Page() {
@@ -24,87 +57,219 @@ function Page() {
const submitForms = () => {
const { create } = permohonanInformasiPublikState.statepermohonanInformasiPublik;
if (create.form.name && create.form.nik && create.form.notelp && create.form.alamat && create.form.email &&
create.form.jenisInformasiDimintaId && create.form.caraMemperolehInformasiId && create.form.caraMemperolehSalinanInformasiId) {
if (
create.form.name &&
create.form.nik &&
create.form.notelp &&
create.form.alamat &&
create.form.email &&
create.form.jenisInformasiDimintaId &&
create.form.caraMemperolehInformasiId &&
create.form.caraMemperolehSalinanInformasiId
) {
create.create();
router.push('/darmasaba/permohonan/berhasil');
} else {
console.log("Validasi gagal, form tidak lengkap");
// Display error message to user
console.log('Validasi gagal, form tidak lengkap');
}
};
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap={40}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Text
ta="center"
fz={{ base: '2rem', md: '2.5rem' }}
c={colors['blue-button']}
fw="bold"
>
Permohonan Informasi Publik
</Text>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text pb={30} ta={'center'} fw={"bold"} fz={{ base: 'h3', md: 'h2' }}>Tata Cara Permohonan</Text>
<SimpleGrid pb={30} cols={{ base: 1, md: 2, lg: 3, xl: 4 }}>
{data.map((v, k) => (
<Paper key={k} p={"xl"} bg={colors['blue-button']}>
<Stack justify='space-between'>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="xl">
<Paper
p="xl"
radius="lg"
withBorder
shadow="sm"
bg={colors['white-trans-1']}
>
<Text
pb={30}
ta="center"
fw="bold"
fz={{ base: 'h4', md: 'h3' }}
c={colors['blue-button']}
>
Tata Cara Permohonan
</Text>
<SimpleGrid pb={30} cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
{steps.map((v) => (
<Paper
key={v.id}
p="lg"
radius="md"
shadow="md"
bg={colors['blue-button']}
>
<Stack justify="space-between" gap="sm">
<Center>
<ActionIcon bg={colors['white-1']} radius={150} size={50}>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h3", md: "h2" }}>{v.number}</Text>
<ActionIcon
bg={colors['white-1']}
radius="xl"
size={60}
variant="light"
>
<Text
c={colors['blue-button']}
ta="center"
fw="bold"
fz="h3"
>
{v.number}
</Text>
</ActionIcon>
</Center>
<Text ta={"center"} c={colors['white-1']} fw={"bold"} fz={"h3"}>{v.title}</Text>
<Text ta={"center"} c={colors['white-1']} fz={'h4'}>{v.desc}</Text>
<Text
ta="center"
c={colors['white-1']}
fw="bold"
fz="lg"
>
{v.title}
</Text>
<Text ta="center" c={colors['white-1']} fz="sm">
{v.desc}
</Text>
</Stack>
</Paper>
))}
</SimpleGrid>
<Center pb={30}>
<Button fz={"h5"} bg={colors['blue-button']} leftSection={<IconDownload size={20} color={colors['white-1']} />}>
Unduh Dokumen
</Button>
<Tooltip label="Unduh dokumen tata cara permohonan" withArrow>
<Button
fz="sm"
size="md"
radius="md"
bg={colors['blue-button']}
leftSection={
<IconDownload size={20} color={colors['white-1']} />
}
>
Unduh Tata Cara
</Button>
</Tooltip>
</Center>
<Group justify='center'>
<Paper p={'xl'} bg={colors['white-1']}>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={{ base: 'h4', md: 'h3' }} ta={"center"}>Formulir Permohonan Informasi</Text>
<TextInput label="Nama Lengkap" placeholder="masukkan nama lengkap" onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.name = val.target.value;
}} />
<TextInput label="NIK" placeholder="masukkan NIK" onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.nik = val.target.value;
}} />
<TextInput label="No.Telp" placeholder="masukkan no telp" onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.notelp = val.target.value;
}} />
<TextInput label="Alamat" placeholder="masukkan alamat" onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.alamat = val.target.value;
}} />
<TextInput label="Email" placeholder="masukkan email" onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.email = val.target.value;
}} />
<JenisInformasiSelector
<Group justify="center">
<Paper
p="xl"
radius="lg"
withBorder
shadow="sm"
bg={colors['white-1']}
w="100%"
maw={800}
>
<Stack gap="md">
<Text
fw="bold"
fz={{ base: 'h4', md: 'h3' }}
ta="center"
c={colors['blue-button']}
>
Formulir Permohonan Informasi
</Text>
<TextInput
label="Nama Lengkap"
placeholder="Masukkan nama lengkap Anda"
withAsterisk
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.jenisInformasiDimintaId = val.id;
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.name =
val.target.value;
}}
/>
<MemperolehInformasi
<TextInput
label="Nomor Induk Kependudukan (NIK)"
placeholder="Masukkan NIK"
withAsterisk
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.caraMemperolehInformasiId = val.id;
}}
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.nik =
val.target.value;
}}
/>
<MemperolehSalinan
<TextInput
label="Nomor Telepon"
placeholder="Masukkan nomor telepon aktif"
withAsterisk
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.caraMemperolehSalinanInformasiId = val.id;
}}
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.notelp =
val.target.value;
}}
/>
<Box py={10}>
<Checkbox label="Saya menyatakan bahwa informasi yang saya berikan adalah benar dan akan menggunakan informasi yang diminta" />
<TextInput
label="Alamat Lengkap"
placeholder="Masukkan alamat sesuai identitas"
withAsterisk
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.alamat =
val.target.value;
}}
/>
<TextInput
label="Alamat Email"
placeholder="Masukkan alamat email aktif"
withAsterisk
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.email =
val.target.value;
}}
/>
<JenisInformasiSelector
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.jenisInformasiDimintaId =
val.id;
}}
/>
<MemperolehInformasi
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.caraMemperolehInformasiId =
val.id;
}}
/>
<MemperolehSalinan
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.caraMemperolehSalinanInformasiId =
val.id;
}}
/>
<Box py="sm">
<Checkbox
size="sm"
color={colors['blue-button']}
label="Saya menyatakan bahwa data yang saya berikan benar adanya dan informasi yang diminta akan digunakan sesuai ketentuan."
/>
</Box>
<Group>
<Button bg={colors['blue-button']} onClick={submitForms}>Kirim Permohonan</Button>
<Group justify="center" pt="sm">
<Button
size="md"
radius="md"
bg={colors['blue-button']}
leftSection={<IconSend2 size={20} color={colors['white-1']} />}
onClick={submitForms}
>
Kirim Permohonan
</Button>
</Group>
</Stack>
</Paper>
@@ -116,4 +281,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,136 +2,243 @@
import permohonanKeberatanInformasi from '@/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi';
import { PPIDTextEditor } from '@/app/admin/(dashboard)/ppid/_com/PPIDTextEditor';
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
import { IconFileCheck, IconForms, IconHourglassOff, IconPhoneRinging } from '@tabler/icons-react';
import {
Box,
Button,
Center,
Group,
Paper,
SimpleGrid,
Stack,
Text,
TextInput,
Tooltip,
} from '@mantine/core';
import {
IconFileCheck,
IconForms,
IconHourglassOff,
IconPhoneRinging,
} from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const data = [
{
id: 1,
icon: <IconForms size={50} color={colors['white-1']} />,
title: "Formulir Online",
desc: "Isi formulir keberatan secara online.",
},
{
id: 2,
icon: <IconFileCheck size={50} color={colors['white-1']} />,
title: "Verifikasi",
desc: "Tim PPID akan memverifikasi permohonan Anda.",
},
{
id: 3,
icon: <IconHourglassOff size={50} color={colors['white-1']} />,
title: "Proses Keberatan",
desc: "Proses penyelesaian keberatan dalam waktu 30 hari kerja.",
},
{
id: 4,
icon: <IconPhoneRinging size={50} color={colors['white-1']} />,
title: "Hasil",
desc: "Hasil keberatan akan dikirim via email atau SMS.",
},
]
{
id: 1,
icon: IconForms,
title: 'Isi Formulir',
desc: 'Lengkapi formulir keberatan secara online dengan mudah.',
},
{
id: 2,
icon: IconFileCheck,
title: 'Verifikasi Data',
desc: 'Tim PPID akan memeriksa dan memverifikasi permohonan Anda.',
},
{
id: 3,
icon: IconHourglassOff,
title: 'Proses Keberatan',
desc: 'Keberatan diproses maksimal dalam 30 hari kerja.',
},
{
id: 4,
icon: IconPhoneRinging,
title: 'Hasil Dikirim',
desc: 'Hasil keputusan dikirim melalui email atau WhatsApp.',
},
];
function Page() {
const stateKeberatan = useProxy(permohonanKeberatanInformasi)
const submit = () => {
if (stateKeberatan.create.form.name && stateKeberatan.create.form.email && stateKeberatan.create.form.notelp && stateKeberatan.create.form.alasan) {
stateKeberatan.create.create()
router.push('/darmasaba/permohonan/berhasil')
} else {
console.log("Validasi gagal, form tidak lengkap")
}
const stateKeberatan = useProxy(permohonanKeberatanInformasi);
const router = useRouter();
const submit = () => {
if (
stateKeberatan.create.form.name &&
stateKeberatan.create.form.email &&
stateKeberatan.create.form.notelp &&
stateKeberatan.create.form.alasan
) {
stateKeberatan.create.create();
router.push('/darmasaba/permohonan/berhasil');
} else {
console.log('Formulir belum lengkap');
}
const router = useRouter();
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
};
return (
<Stack bg={colors.Bg} py="xl" gap={40}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Stack align="center" px={{ base: 'md', md: 100 }}>
<Text
ta="center"
fz={{ base: '2rem', md: '2.8rem' }}
c={colors['blue-button']}
fw={800}
style={{ letterSpacing: '-0.5px' }}
>
Permohonan Keberatan Informasi Publik
</Text>
<Paper
p="xl"
radius="xl"
bg={colors['white-trans-1']}
shadow="sm"
withBorder
>
<Stack gap="xl">
<Box>
<Text fw={700} fz={{ base: 'lg', md: 'xl' }} mb={8}>
Tentang Permohonan Keberatan
</Text>
<Text ta="justify" fz={{ base: 'sm', md: 'md' }} lh={1.6}>
Jika Anda merasa permohonan informasi tidak ditanggapi dengan
baik atau ditolak, Anda berhak mengajukan keberatan melalui
formulir berikut.
</Text>
</Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Permohonan Keberatan Informasi Publik
</Text>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Box pb={30}>
<Text fw={"bold"} fz={{ base: 'h4', md: 'h3' }}>Tentang Permohonan Keberatan</Text>
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>Jika Anda merasa permohonan informasi yang diajukan tidak mendapatkan tanggapan yang memadai atau ditolak, anda berhak mengajukan
keberatan melalui formulir di bawah ini.</Text>
</Box>
<Text pb={20} ta={"center"} fw={"bold"} fz={{ base: 'h3', md: 'h2' }}>Bagaimana Mengajukan Keberatan?</Text>
<SimpleGrid
pb={30}
cols={{
base: 1,
md: 4,
}}>
{data.map((v, k) => {
return (
<Paper key={k} p={"xl"} bg={colors['blue-button']}>
<Stack justify='space-between'>
<Center>
{v.icon}
</Center>
<Text ta={"center"} c={colors['white-1']} fw={"bold"} fz={"h3"}>
{v.title}
</Text>
<Text ta={"center"} c={colors['white-1']} fz={'h4'}>
{v.desc}
</Text>
</Stack>
</Paper>
)
})}
</SimpleGrid>
<Group justify='center'>
<Paper p={'xl'} bg={colors['white-1']}>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={{ base: 'h4', md: 'h3' }} ta={"center"}>Formulir Permohonan Keberatan</Text>
<TextInput
label="Nama"
placeholder="masukkan nama lengkap"
onChange={(val) => {
stateKeberatan.create.form.name = val.target.value
}}
/>
<TextInput
label="Email"
placeholder="masukkan email"
onChange={(val) => {
stateKeberatan.create.form.email = val.target.value
}}
/>
<TextInput
label="Nomor Telepon"
placeholder="masukkan nomor telepon"
onChange={(val) => {
stateKeberatan.create.form.notelp = val.target.value
}}
/>
<Text fz={"sm"}>Alasan Permohonan Keberatan</Text>
<PPIDTextEditor
showSubmit={false}
onChange={(val) => {
stateKeberatan.create.form.alasan = val
}}
/>
<Group>
<Button onClick={submit} bg={'green'}>Kirim Permohonan</Button>
</Group>
</Stack>
</Paper>
</Group>
<Text pt={20} ta={"center"} fz={'h3'} fw={"bold"}>Kontak PPID</Text>
<Text ta={"center"} fz={"sm"}>Email: desadarmasaba@badungkab.go.id | WhatsApp: 081-xxx-xxx-xxx</Text>
</Paper>
<Stack>
<Text
ta="center"
fw={700}
fz={{ base: 'xl', md: '2xl' }}
style={{ letterSpacing: '-0.5px' }}
>
Alur Pengajuan Keberatan
</Text>
<SimpleGrid cols={{ base: 1, md: 4 }} spacing="lg">
{data.map((v) => (
<Paper
key={v.id}
p="lg"
radius="lg"
bg={colors['blue-button']}
shadow="md"
>
<Stack align="center" gap="sm">
<Center>
<v.icon size={48} color={colors['white-1']} />
</Center>
<Text
ta="center"
c={colors['white-1']}
fw={700}
fz="lg"
>
{v.title}
</Text>
<Text ta="center" c={colors['white-1']} fz="sm">
{v.desc}
</Text>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
<Group justify="center">
<Paper
p="xl"
radius="xl"
bg={colors['white-1']}
shadow="md"
withBorder
maw={600}
w="100%"
>
<Stack gap="md">
<Text
fw={700}
fz={{ base: 'lg', md: 'xl' }}
ta="center"
mb={4}
>
Formulir Keberatan
</Text>
<TextInput
label="Nama Lengkap"
placeholder="Tulis nama lengkap Anda"
radius="md"
size="md"
withAsterisk
onChange={(val) =>
(stateKeberatan.create.form.name = val.target.value)
}
/>
<TextInput
label="Alamat Email"
placeholder="contoh: nama@email.com"
radius="md"
size="md"
type="email"
withAsterisk
onChange={(val) =>
(stateKeberatan.create.form.email = val.target.value)
}
/>
<TextInput
label="Nomor Telepon"
placeholder="contoh: 0812-3456-7890"
radius="md"
size="md"
withAsterisk
onChange={(val) =>
(stateKeberatan.create.form.notelp = val.target.value)
}
/>
<Box>
<Text fw={600} fz="sm" mb={6}>
Alasan Keberatan
</Text>
<PPIDTextEditor
showSubmit={false}
onChange={(val) =>
(stateKeberatan.create.form.alasan = val)
}
/>
</Box>
<Tooltip label="Pastikan semua data sudah diisi dengan benar">
<Button
onClick={submit}
size="md"
radius="md"
fw={600}
bg={colors['blue-button']}
>
Kirim Permohonan
</Button>
</Tooltip>
</Stack>
</Box>
</Stack>
);
</Paper>
</Group>
<Stack gap={4} pt="lg" align="center">
<Text fw={700} fz="lg">
Kontak PPID
</Text>
<Text fz="sm" c="dimmed">
Email: desadarmasaba@badungkab.go.id | WhatsApp: 081-xxx-xxx-xxx
</Text>
</Stack>
</Stack>
</Paper>
</Stack>
</Stack>
);
}
export default Page;

View File

@@ -3,119 +3,132 @@ import stateProfilePPID from '@/app/admin/(dashboard)/_state/ppid/profile_ppid/p
import colors from '@/con/colors';
import { Box, Center, Divider, Flex, Image, List, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconBuildingCommunity, IconTargetArrow, IconTimeline, IconUser } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const allList = useProxy(stateProfilePPID)
useShallowEffect(() => {
allList.profile.load("edit") // Assuming "1" is your default ID, adjust as needed
allList.profile.load("edit")
}, [])
if (!allList.profile.data) return <Stack bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton style={{backgroundColor: colors.Bg}} h={40} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={80} />
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
{Array.from({ length: 10 }).map((v, k) =>
<Skeleton key={k} h={40} />
)}
</Paper>
</Box>
</Stack>
if (!allList.profile.data) return (
<Stack bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={80} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Paper p="xl" bg={colors['white-trans-1']}>
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={i} h={40} mb="sm" />
))}
</Paper>
</Box>
</Stack>
)
const dataArray = Array.isArray(allList.profile.data)
? allList.profile.data
: [allList.profile.data];
: [allList.profile.data]
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Text ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }} c={colors["blue-button"]} fw={"bold"}>
Profil Singkat PPID Desa Darmasaba
<Text ta="center" fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }} c={colors["blue-button"]} fw="bold">
PPID Desa Darmasaba Profile
</Text>
</Box>
{dataArray.map((item) => (
<Box key={item.id} px={{ base: "md", md: 100 }}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Paper p="xl" bg={colors['white-trans-1']} radius="lg" shadow="xl">
<Box px={{ base: "md", md: 100 }}>
<Flex align={"center"} gap={50}>
<Image src={"/darmasaba-icon.png"} h={{ base: 80, md: 150 }} alt='' />
<Text fz={{ base: "1.4rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw={'bold'}>PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA </Text>
<Flex align="center" gap={40} justify="center">
<Image src="/darmasaba-icon.png" h={{ base: 70, md: 120 }} alt="Village logo" />
<Text fz={{ base: "1.5rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
Public Information Officer
</Text>
</Flex>
</Box>
<Divider my={"md"} />
{/* biodata perbekel */}
<Box px={{ base: 0, md: 50 }} pb={30}>
<SimpleGrid
cols={{
base: 1,
md: 1,
lg: 1,
xl: 2,
}}
>
<Box px={{ base: 0, md: 50 }}>
<Paper bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
<Stack gap={0}>
<Center>
<Image pt={{ base: 0, md: 90 }} src={item.imageUrl ? `${item.imageUrl}?t=${Date.now()}` : "/api/img/perbekel.png"} w={{ base: 250, md: 355 }} alt='' />
</Center>
<Paper
bg={colors['blue-button']}
py={30}
className="glass3"
px={{ base: 20, md: 20 }}
<Divider my="lg" />
>
<Text ta={"center"} c={colors['white-1']} fw={"bolder"} fz={{ base: "1.5rem", md: "2.125rem", lg: "2.25rem", xl: "1.5rem" }}>
<Box px={{ base: 0, md: 50 }} pb={40}>
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
<Box px={{ base: 0, md: 50 }}>
<Paper bg={colors['white-trans-1']} radius="lg" shadow="sm">
<Stack gap="md">
<Center>
<Image
src={item.imageUrl ? `${item.imageUrl}?t=${Date.now()}` : "/api/img/perbekel.png"}
w={{ base: 220, md: 330 }}
alt="Leader photo"
radius="md"
/>
</Center>
<Paper bg={colors['blue-button']} py={25} radius="lg" className="glass3">
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.5rem", md: "2rem" }}>
{item.name}
</Text>
</Paper>
</Stack>
</Paper>
</Box>
<Box>
<Box>
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Biodata</Text>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.biodata }} />
</Box>
<Box>
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Riwayat Karir</Text>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} dangerouslySetInnerHTML={{ __html: item.riwayat }} />
</Box>
<Stack gap="xl">
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconUser size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Biography</Text>
</Flex>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.biodata }} />
</Box>
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconTimeline size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Career History</Text>
</Flex>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} dangerouslySetInnerHTML={{ __html: item.riwayat }} />
</Box>
</Stack>
</Box>
</SimpleGrid>
</Box>
<Box pb={30}>
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Pengalaman Organisasi</Text>
<List>
<Box pb={40}>
<Flex align="center" gap="sm" mb="sm">
<IconBuildingCommunity size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Organizational Experience</Text>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.pengalaman }} />
<Text fz={{ base: "1rem", md: "1.125rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.pengalaman }} />
</Box>
</List>
</Box>
<Box pb={20}>
<Text fz={{ base: "1.125rem", md: "1.375rem", lg: "1.75rem", xl: "2rem" }} fw={'bold'}>Program Kerja Unggulan</Text>
<List>
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconTargetArrow size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Flagship Programs</Text>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.unggulan }} />
<Text fz={{ base: "1rem", md: "1.125rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.unggulan }} />
</Box>
</List>
</Box>
</Paper>
</Box>
))}
</Stack>
);
)
}
export default Page;
export default Page

View File

@@ -1,121 +1,395 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
import colors from '@/con/colors';
import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { OrganizationChart } from 'primereact/organizationchart';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
// /* eslint-disable react-hooks/exhaustive-deps */
// /* eslint-disable @typescript-eslint/no-explicit-any */
// 'use client'
// import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
// import colors from '@/con/colors';
// import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
// import { OrganizationChart } from 'primereact/organizationchart';
// import { useEffect } from 'react';
// import { useProxy } from 'valtio/utils';
// import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Title ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>Struktur PPID</Title>
<StrukturOrganisasiPPID />
// function Page() {
// return (
// <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
// <Box px={{ base: 'md', md: 100 }}>
// <BackButton />
// </Box>
// <Title ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>Struktur PPID</Title>
// <StrukturOrganisasiPPID />
</Stack>
);
// </Stack>
// );
// }
// function StrukturOrganisasiPPID() {
// const stateOrganisasi = useProxy(stateStrukturPPID.pegawai)
// useEffect(() => {
// stateOrganisasi.findMany.load()
// }, [])
// if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) {
// return (
// <Stack py={10}>
// <Skeleton h={500} />
// </Stack>
// );
// }
// // Step 1: Group pegawai berdasarkan posisiId
// const posisiMap = new Map<string, any>();
// for (const pegawai of stateOrganisasi.findMany.data) {
// const posisiId = pegawai.posisi.id;
// if (!posisiMap.has(posisiId)) {
// posisiMap.set(posisiId, {
// ...pegawai.posisi,
// pegawaiList: [],
// children: []
// });
// }
// posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
// }
// // Step 2: Buat struktur pohon berdasarkan parentId
// const root: any[] = [];
// posisiMap.forEach((posisi) => {
// if (posisi.parentId) {
// const parent = posisiMap.get(posisi.parentId);
// if (parent) {
// parent.children.push(posisi);
// }
// } else {
// root.push(posisi);
// }
// });
// // Step 3: Ubah struktur ke format OrganizationChart
// function toOrgChartFormat(node: any): any {
// return {
// expanded: true,
// type: 'person',
// styleClass: 'p-person',
// data: {
// name: node.pegawaiList?.[0]?.namaLengkap || 'Tidak ada pegawai',
// status: node.nama,
// image: node.pegawaiList?.[0]?.image?.link || '/img/default.png'
// },
// children: node.children.map(toOrgChartFormat)
// };
// }
// const chartData = root.map(toOrgChartFormat);
// return (
// <Box py={10}>
// <Paper bg={colors.grey} p="md" style={{ overflowX: 'auto' }}>
// <OrganizationChart style={{ color: colors['blue-button'] }} value={chartData} nodeTemplate={nodeTemplate} />
// </Paper>
// </Box>
// );
// }
// function nodeTemplate(node: any) {
// const imageSrc = node?.data?.image || '/img/default.png';
// const name = node?.data?.name || 'Tanpa Nama';
// const status = node?.data?.status || 'Tidak ada deskripsi';
// return (
// <Stack pos={"relative"} py={"xl"} gap={"22"}>
// <Stack align="center" gap={4}>
// <Image
// src={imageSrc}
// alt={name}
// radius="xl"
// w={120}
// h={120}
// fit="cover"
// />
// <Text fw={600} ta="center">{name}</Text>
// <Text size="sm" c="dimmed" ta="center">{status}</Text>
// </Stack>
// </Stack>
// );
// }
// export default Page;
'use client'
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID'
import {
Box,
Button,
Card,
Center,
Container,
Group,
Image,
Loader,
Paper,
Stack,
Text,
Title,
Tooltip,
Transition,
} from '@mantine/core'
import { IconRefresh, IconSearch, IconUsers } from '@tabler/icons-react'
import { OrganizationChart } from 'primereact/organizationchart'
import { useEffect } from 'react'
import { useProxy } from 'valtio/utils'
import BackButton from '../../desa/layanan/_com/BackButto'
import colors from '@/con/colors'
export default function Page() {
return (
<Box
style={{
minHeight: '100vh',
background: colors['Bg'],
color: '#E6F0FF',
paddingBottom: 48,
}}
>
<Container size="xl" py="xl">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Stack align="center" gap="xl" mt="xl">
<Title
order={1}
ta="center"
c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }}
>
Struktur Organisasi PPID
</Title>
<Text ta="center" c="black" maw={800}>
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
untuk melihat detail atau klik node untuk fokus tampilan.
</Text>
</Stack>
<Box mt="lg">
<StrukturOrganisasiPPID />
</Box>
</Container>
</Box>
)
}
function StrukturOrganisasiPPID() {
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai)
const stateOrganisasi: any = useProxy(stateStrukturPPID.pegawai)
useEffect(() => {
stateOrganisasi.findMany.load()
void stateOrganisasi.findMany.load()
}, [])
if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) {
const isLoading =
!stateOrganisasi.findMany.data &&
stateOrganisasi.findMany.loading !== false
if (isLoading) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
);
<Center py={48}>
<Stack align="center" gap="sm">
<Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text>
<Text c="dimmed" size="sm">
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
</Text>
</Stack>
</Center>
)
}
// Step 1: Group pegawai berdasarkan posisiId
const posisiMap = new Map<string, any>();
if (
!stateOrganisasi.findMany.data ||
stateOrganisasi.findMany.data.length === 0
) {
return (
<Center py={40}>
<Stack align="center" gap="md">
<Paper
radius="md"
p="xl"
style={{
width: 560,
background: 'rgba(28,110,164,0.2)',
border: `1px solid rgba(255,255,255,0.1)`,
textAlign: 'center',
}}
>
<Center>
<IconUsers size={56} />
</Center>
<Title order={3} mt="md">
Data pegawai belum tersedia
</Title>
<Text c="dimmed" mt="xs">
Belum ada data pegawai yang tercatat untuk PPID. Silakan coba
muat ulang atau periksa sumber data.
</Text>
<Group justify="center" mt="lg">
<Button
leftSection={<IconRefresh size={16} />}
variant="gradient"
gradient={{ from: 'indigo', to: 'cyan' }}
onClick={() => stateOrganisasi.findMany.load()}
>
Muat Ulang
</Button>
<Button
leftSection={<IconSearch size={16} />}
variant="subtle"
onClick={() =>
stateOrganisasi.findMany.load({ query: { q: '' } })
}
>
Cari Pegawai
</Button>
</Group>
</Paper>
</Stack>
</Center>
)
}
const posisiMap = new Map<string, any>()
for (const pegawai of stateOrganisasi.findMany.data) {
const posisiId = pegawai.posisi.id;
const posisiId = pegawai.posisi.id
if (!posisiMap.has(posisiId)) {
posisiMap.set(posisiId, {
...pegawai.posisi,
pegawaiList: [],
children: []
});
children: [],
})
}
posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
posisiMap.get(posisiId)!.pegawaiList.push(pegawai)
}
// Step 2: Buat struktur pohon berdasarkan parentId
const root: any[] = [];
const root: any[] = []
posisiMap.forEach((posisi) => {
if (posisi.parentId) {
const parent = posisiMap.get(posisi.parentId);
const parent = posisiMap.get(posisi.parentId)
if (parent) {
parent.children.push(posisi);
parent.children.push(posisi)
} else {
root.push(posisi)
}
} else {
root.push(posisi);
root.push(posisi)
}
});
})
// Step 3: Ubah struktur ke format OrganizationChart
function toOrgChartFormat(node: any): any {
return {
expanded: true,
type: 'person',
styleClass: 'p-person',
data: {
name: node.pegawaiList?.[0]?.namaLengkap || 'Tidak ada pegawai',
status: node.nama,
image: node.pegawaiList?.[0]?.image?.link || '/img/default.png'
name: node.pegawaiList?.[0]?.namaLengkap || 'Belum ditugaskan',
title: node.nama || 'Tanpa jabatan',
image: node.pegawaiList?.[0]?.image?.link || '/img/default.png',
description: node.deskripsi || '',
positionId: node.id || null,
},
children: node.children.map(toOrgChartFormat)
};
children: node.children?.map(toOrgChartFormat) || [],
}
}
const chartData = root.map(toOrgChartFormat);
const chartData = root.map(toOrgChartFormat)
return (
<Box py={10}>
<Paper bg={colors.grey} p="md" style={{ overflowX: 'auto' }}>
<OrganizationChart style={{ color: colors['blue-button'] }} value={chartData} nodeTemplate={nodeTemplate} />
<Box py={16} >
<Paper
radius="md"
p="md"
style={{
background: 'rgba(28,110,164,0.2)',
border: `1px solid rgba(255,255,255,0.1)`,
overflowX: 'auto',
}}
>
<OrganizationChart
value={chartData}
nodeTemplate={nodeTemplate}
/>
</Paper>
</Box>
);
)
}
function nodeTemplate(node: any) {
const imageSrc = node?.data?.image || '/img/default.png';
const name = node?.data?.name || 'Tanpa Nama';
const status = node?.data?.status || 'Tidak ada deskripsi';
const imageSrc = node?.data?.image || '/img/default.png'
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const description = node?.data?.description || ''
return (
<Stack pos={"relative"} py={"xl"} gap={"22"}>
<Stack align="center" gap={4}>
<Image
src={imageSrc}
alt={name}
radius="xl"
w={120}
h={120}
fit="cover"
/>
<Text fw={600} ta="center">{name}</Text>
<Text size="sm" c="dimmed" ta="center">{status}</Text>
</Stack>
</Stack>
);
<Transition mounted transition="pop" duration={240}>
{(styles) => (
<Card
radius="lg"
withBorder
style={{
...styles,
width: 260,
padding: 16,
background: 'rgba(28,110,164,0.3)',
borderColor: 'rgba(255,255,255,0.15)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
<Image
src={imageSrc}
alt={name}
radius="md"
width={120}
height={120}
fit="cover"
style={{
objectFit: 'cover',
border: '2px solid rgba(255,255,255,0.2)',
marginBottom: 12,
}}
/>
<Text fw={700}>{name}</Text>
<Text size="sm" c="dimmed" mt={4}>
{title}
</Text>
<Text size="xs" c="dimmed" mt={8} lineClamp={3}>
{description || 'Belum ada deskripsi.'}
</Text>
<Tooltip label="Lihat detail" withArrow position="bottom">
<Button
variant="light"
size="xs"
mt="md"
onClick={() => {
const id = node?.data?.positionId
if (id && (window as any).scrollTo) {
;(window as any).scrollTo({ top: 0, behavior: 'smooth' })
}
}}
>
Detail
</Button>
</Tooltip>
</Card>
)}
</Transition>
)
}
export default Page;

View File

@@ -1,58 +1,100 @@
'use client'
import stateVisiMisiPPID from '@/app/admin/(dashboard)/_state/ppid/visi_misi_ppid/visimisiPPID';
import colors from '@/con/colors';
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Divider, Transition } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
import { IconSparkles } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const allList = useProxy(stateVisiMisiPPID)
const allList = useProxy(stateVisiMisiPPID);
useShallowEffect(() => {
allList.findById.load("1") // Assuming "1" is your default ID, adjust as needed
}, [])
allList.findById.load("1");
}, []);
if (!allList.findById.data) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
if (!allList.findById.data) {
return (
<Stack p="xl" gap="sm">
{Array.from({ length: 6 }).map((_, k) => (
<Skeleton key={k} h={60} radius="lg" />
))}
</Stack>
);
}
const dataArray = Array.isArray(allList.findById.data)
? allList.findById.data
: [allList.findById.data];
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack bg={colors.Bg} py="xl" gap={40}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
{dataArray.map((item) => (
<Box key={item.id} px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Box pb={30}>
<Center>
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
</Center>
<Text ta={"center"} fz={{ base: "h2", md: "2.5rem" }} fw={"bold"}>
MOTO PPID DESA DARMASABA
</Text>
<Text ta={"center"} fz={{ base: "h4", md: "h3" }} >
MEMBERIKAN INFORMASI YANG CEPAT, MUDAH, TEPAT DAN TRANSPARAN
</Text>
</Box>
<Box px={{ base: 20, md: 50 }} pb={30}>
<Text ta={"center"} fz={{ base: "h3", md: "h2" }} fw={"bold"}>
VISI PPID
</Text>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.visi }} />
</Box>
<Box px={{ base: 20, md: 50 }}>
<Text ta={"center"} fz={{ base: "h3", md: "h2" }} fw={"bold"}>
MISI PPID
</Text>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: item.misi }} />
</Box>
</Paper>
</Stack>
<Box key={item.id} px={{ base: 'md', md: 100 }}>
<Transition mounted={true} transition="fade" duration={500} timingFunction="ease">
{(styles) => (
<Paper
style={styles}
p={{ base: 'lg', md: 'xl' }}
radius="2xl"
shadow="xl"
bg={colors['white-trans-1']}
withBorder
>
<Stack gap="xl">
<Box>
<Center mb="md">
<Image src="/darmasaba-icon.png" w={{ base: 80, md: 130 }} alt="Logo Desa Darmasaba" />
</Center>
<Text
ta="center"
fz={{ base: 28, md: 36 }}
fw={800}
variant="gradient"
gradient={{ from: colors['blue-button'], to: 'cyan', deg: 45 }}
>
Moto PPID Desa Darmasaba
</Text>
<Text ta="center" fz={{ base: 16, md: 20 }} mt="xs" c="dimmed">
Memberikan informasi yang cepat, mudah, tepat, dan transparan
</Text>
</Box>
<Divider my="sm" labelPosition="center" label={<IconSparkles size={18} />} />
<Box>
<Text ta="center" fz={{ base: 24, md: 30 }} fw={700} mb="sm">
Visi PPID
</Text>
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.7}
ta="justify"
dangerouslySetInnerHTML={{ __html: item.visi }}
/>
</Box>
<Divider my="sm" />
<Box>
<Text ta="center" fz={{ base: 24, md: 30 }} fw={700} mb="sm">
Misi PPID
</Text>
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.7}
ta="justify"
dangerouslySetInnerHTML={{ __html: item.misi }}
/>
</Box>
</Stack>
</Paper>
)}
</Transition>
</Box>
))}
</Stack>

View File

@@ -1,112 +1,97 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { ActionIcon, BackgroundImage, Box, Center, Container, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconDownload } from '@tabler/icons-react';
import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
import { useProxy } from 'valtio/utils';
import { useEffect, useState } from 'react';
import { Link } from 'next-view-transitions';
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
import colors from '@/con/colors'
import { ActionIcon, BackgroundImage, Box, Center, Container, Group, Loader, SimpleGrid, Stack, Text, Title } from '@mantine/core'
import { IconDownload } from '@tabler/icons-react'
import { Link } from 'next-view-transitions'
import { useEffect, useState } from 'react'
import { useProxy } from 'valtio/utils'
import BackButton from '../../(pages)/desa/layanan/_com/BackButto'
function Page() {
const state = useProxy(apbdes);
const [loading, setLoading] = useState(false);
const state = useProxy(apbdes)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load();
setLoading(true)
await state.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
console.error(error)
} finally {
setLoading(false);
setLoading(false)
}
}
loadData();
loadData()
}, [])
const data = state.findMany.data || [];
const data = state.findMany.data || []
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"}>
APBDes
</Text>
<Text
py={10}
ta={"justify"}
>
Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab. Adapun APBDes sebagai berikut:
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: '100%', md: '60%' }}>
<Stack align="center" gap="sm">
<Title order={1} fz={{ base: '2.4rem', md: '3.2rem' }} fw="bold" ta="center">
Anggaran Pendapatan & Belanja Desa (APBDes)
</Title>
<Text fz="md" c="dimmed" ta="center">
Laporan transparansi APBDes Desa Darmasaba sebagai bentuk keterbukaan dan akuntabilitas pengelolaan anggaran desa.
</Text>
</Stack>
</Container>
<SimpleGrid
px={{ base: "md", md: 100 }}
cols={{
base: 1,
sm: 3,
}}
>
{loading ? (
<Center>
<Text fz={"2.4rem"}>Memuat Data...</Text>
</Center>
) : (
data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}>{v.jumlah}</Text>
<Group justify="center">
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]} component={Link} href={v.file?.link || ''}>
<Flex gap={"md"}>
<IconDownload size={20} />
<Text fz={"sm"} c={"white"}>Download</Text>
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
{loading ? (
<Center mih={200}>
<Stack align="center" gap="sm">
<Loader size="lg" color="blue" />
<Text fz="lg" c="dimmed">Sedang memuat data APBDes...</Text>
</Stack>
</Center>
) : data.length === 0 ? (
<Center mih={200}>
<Stack align="center" gap="xs">
<Text fz="xl" fw={600} c="dimmed">Belum ada data APBDes tersedia</Text>
<Text fz="sm" c="dimmed">Data akan ditampilkan jika sudah diunggah oleh admin desa</Text>
</Stack>
</Center>
) : (
<SimpleGrid px={{ base: 'md', md: 100 }} cols={{ base: 1, sm: 2, md: 3 }} spacing="xl">
{data.map((v: any, k: number) => (
<BackgroundImage key={k} src={v.image?.link || ''} h={360} radius="xl" pos="relative">
<Box pos="absolute" inset={0} bg="rgba(0,0,0,0.45)" style={{ borderRadius: 16 }} />
<Stack justify="space-between" h="100%" p="lg" pos="relative">
<Box>
<Text fz="lg" fw={600} c="white" ta="center">
{v.name}
</Text>
</Box>
<Text fz="2.6rem" fw="bold" c="white" ta="center">
{v.jumlah}
</Text>
<Group justify="center">
<ActionIcon
component={Link}
href={v.file?.link || '#'}
radius="xl"
size="lg"
bg={colors['blue-button']}
variant="filled"
>
<IconDownload size={20} color="white" />
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
))}
</SimpleGrid>
)}
</Stack>
);
)
}
export default Page;
export default Page

View File

@@ -1,137 +1,136 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import React, { useEffect, useState } from 'react';
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import React, { useEffect, useState } from 'react';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text, ActionIcon } from '@mantine/core';
import { IconFile } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import {
Box,
Button,
Container,
Flex,
Paper,
SimpleGrid,
Stack,
Text,
ActionIcon,
Loader,
Tooltip,
} from '@mantine/core';
import { IconFile, IconInbox } from '@tabler/icons-react';
function Lokal() {
const [selectedKategori, setSelectedKategori] = useState<string>('PENGUATAN TATA LAKSANA');
const [loading, setLoading] = useState(true);
const state = useProxy(korupsiState);
// Load data on component mount
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.desaAntikorupsi.findMany.load(1, 100); // Load first 100 items
} catch (error) {
console.error('Error loading data:', error);
await state.desaAntikorupsi.findMany.load(1, 100);
} finally {
setLoading(false);
}
};
loadData();
}, []);
// Get data from state
const data = state.desaAntikorupsi.findMany.data || [];
// Debug: Log the complete data structure
console.log('Complete data:', JSON.parse(JSON.stringify(data)));
// Get unique categories
const categories = [...new Set(
data
.filter(item => item.kategori?.name) // Only include items with a category name
.map(item => item.kategori.name)
)];
// Filter data based on selected category
const filteredData = selectedKategori === 'PENGUATAN TATA LAKSANA'
? data
: data.filter(item => item.kategori?.name === selectedKategori);
// Debug: Log filtered data
console.log('Filtered data:', JSON.parse(JSON.stringify(filteredData)));
const categories = [...new Set(data.filter(i => i.kategori?.name).map(i => i.kategori.name))];
const filteredData =
selectedKategori === 'PENGUATAN TATA LAKSANA'
? data
: data.filter(i => i.kategori?.name === selectedKategori);
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: "md", md: 100 }}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "80%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"}>
<Container w={{ base: '100%', md: '80%' }}>
<Stack align="center" gap={4}>
<Text fz={{ base: 32, md: 44 }} fw={700} ta="center" c={colors['blue-button']}>
Desa Anti Korupsi
</Text>
<Text
py={10}
ta={"justify"}
>
Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan. Adapun beberapa jenis tata penguatan :
<Text ta="center" fz="md" c="dimmed" maw={700}>
Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola
terbuka dengan melibatkan warga untuk mengawasi anggaran sehingga tepat sasaran sesuai
kebutuhan.
</Text>
</Stack>
</Container>
<Container size="lg" px={{ base: "md", md: "xl" }}>
{/* Category Filter Buttons */}
<Flex gap="md" justify="center" mb="xl" wrap="wrap">
{categories.map((kategori) => (
<Container size="lg" px={{ base: 'md', md: 'xl' }}>
<Flex gap="sm" justify="center" mb="xl" wrap="wrap">
{categories.map(kategori => (
<Button
color={selectedKategori === kategori ? colors['blue-button'] : 'gray'}
key={kategori}
variant={selectedKategori === kategori ? 'filled' : 'outline'}
onClick={() => setSelectedKategori(kategori)}
size="sm"
radius="xl"
variant={selectedKategori === kategori ? 'filled' : 'outline'}
color={selectedKategori === kategori ? colors['blue-button'] : 'gray'}
>
{kategori}
</Button>
))}
</Flex>
{/* Loading State */}
{loading ? (
<Text ta="center">Memuat data...</Text>
<Flex justify="center" align="center" h={200}>
<Loader color={colors['blue-button']} size="lg" />
</Flex>
) : filteredData.length === 0 ? (
<Flex direction="column" align="center" justify="center" gap="sm" py="xl">
<IconInbox size={48} stroke={1.5} color={colors['blue-button']} />
<Text fz="lg" fw={500} c="dimmed">
Belum ada data untuk kategori ini
</Text>
</Flex>
) : (
<SimpleGrid
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="xl"
verticalSpacing="xl"
>
{filteredData.map((item) => {
console.log('Item data:', item);
console.log('All item properties:', Object.keys(item).map(key => `${key}: ${item[key]}`).join(', '));
const handleDownload = async (e: React.MouseEvent) => {
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
{filteredData.map(item => {
const handleDownload = (e: React.MouseEvent) => {
e.stopPropagation();
if (!item?.file?.link) return;
try {
const fileUrl = item.file.link.startsWith('http')
? item.file.link
: `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${item.file.link}`;
window.open(fileUrl, '_blank');
} catch (error) {
console.error('Error opening file:', error);
}
const url = item.file.link.startsWith('http')
? item.file.link
: `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${
item.file.link
}`;
window.open(url, '_blank');
};
return (
<Paper key={item.id} p="lg" shadow="sm" radius="md" withBorder>
<Paper
key={item.id}
p="lg"
shadow="md"
radius="xl"
withBorder
style={{
background: 'linear-gradient(135deg, #ffffff, #f8fbff)',
}}
>
<Stack h="100%" justify="space-between">
<div>
<Text fz="lg" fw={600} mb="sm" c={colors["blue-button"]}>
{item.name}
</Text>
</div>
<Text fz="lg" fw={600} c={colors['blue-button']} lineClamp={2}>
{item.name}
</Text>
{item?.file && (
<ActionIcon
variant="filled"
color={colors["blue-button"]}
size="lg"
onClick={handleDownload}
style={{ marginTop: 10 }}
title="Unduh File"
>
<IconFile size={20} />
</ActionIcon>
<Tooltip label="Unduh file" withArrow>
<ActionIcon
variant="filled"
color={colors['blue-button']}
size="lg"
radius="xl"
onClick={handleDownload}
>
<IconFile size={20} />
</ActionIcon>
</Tooltip>
)}
</Stack>
</Paper>
@@ -141,9 +140,7 @@ function Lokal() {
)}
</Container>
</Stack>
)
);
}
export default Lokal;

View File

@@ -2,13 +2,12 @@
'use client'
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Stack, Container, Text, Image, ActionIcon, Box, Divider, Flex, Center, Skeleton } from '@mantine/core';
import { Stack, Container, Text, Image, ActionIcon, Box, Divider, Flex, Center, Skeleton, Paper, Tooltip } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
import { useState } from 'react';
function Page() {
const params = useParams<{ id: string }>();
@@ -23,77 +22,99 @@ function Page() {
setLoading(true);
await state.findUnique.load(id);
} catch (error) {
console.error('Error loading data:', error);
console.error('Gagal memuat data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [id])
};
loadData();
}, [id]);
if (loading) {
return (
<Center>
<Skeleton height={500} />
<Center h={"70vh"}>
<Stack align="center" gap="sm">
<Skeleton height={40} width={200} radius="xl" />
<Skeleton height={300} width={300} radius="md" />
<Skeleton height={20} width="80%" radius="xl" />
</Stack>
</Center>
);
}
if (!state.findUnique.data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
<Center h={"70vh"}>
<Paper withBorder shadow="md" p="xl" radius="lg">
<Text ta="center" fz="lg" fw="bold" c="dimmed">
Data penghargaan tidak tersedia
</Text>
<Text ta="center" fz="sm" c="dimmed" mt="sm">
Silakan kembali dan pilih penghargaan lainnya
</Text>
</Paper>
</Center>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text
ta={"center"}
fw={"bold"}
fz={"2.3rem"}
>
<Container w={{ base: "100%", md: "60%" }}>
<Stack align="center" gap="md">
<Text ta="center" fw="bold" fz={{ base: "1.8rem", md: "2.3rem" }} lh={1.3}>
{state.findUnique.data?.name}
</Text>
<Image py={20} src={state.findUnique.data?.image?.link || ''} alt='' />
<Image
src={state.findUnique.data?.image?.link || ''}
alt="Gambar penghargaan"
radius="lg"
fit="contain"
mah={400}
fallbackSrc="https://placehold.co/600x400?text=Tidak+Ada+Gambar"
/>
</Stack>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text
pb={20}
ta={"justify"}
fw={"bold"}
ta="justify"
fz="md"
lh={1.6}
dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || '' }}
/>
<Box py={20}>
<Divider color={colors['blue-button']} />
<Flex justify={"space-between"} py={20}>
<Text fz={"sm"}>{new Date(state.findUnique.data?.createdAt).toLocaleDateString()}</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors['blue-button']} size={"30"} />
<Flex direction={{ base: "column", sm: "row" }} justify="space-between" align={{ base: "start", sm: "center" }} gap="sm" py={20}>
<Text fz="sm" c="dimmed">
Diterbitkan: {new Date(state.findUnique.data?.createdAt).toLocaleDateString('id-ID')}
</Text>
<Flex gap="md">
<Tooltip label="Bagikan ke Facebook" withArrow>
<ActionIcon variant="light" radius="xl" size="lg" color="blue">
<IconBrandFacebook size={22} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors['blue-button']} size={"30"} />
</Tooltip>
<Tooltip label="Bagikan ke Instagram" withArrow>
<ActionIcon variant="light" radius="xl" size="lg" color="pink">
<IconBrandInstagram size={22} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors['blue-button']} size={"30"} />
</Tooltip>
<Tooltip label="Bagikan ke Twitter" withArrow>
<ActionIcon variant="light" radius="xl" size="lg" color="cyan">
<IconBrandTwitter size={22} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors['blue-button']} size={"30"} />
</Tooltip>
<Tooltip label="Bagikan ke WhatsApp" withArrow>
<ActionIcon variant="light" radius="xl" size="lg" color="green">
<IconBrandWhatsapp size={22} />
</ActionIcon>
</Flex>
</Box>
</Tooltip>
</Flex>
</Flex>
<Divider color={colors['blue-button']} pb={50} />
<Divider color={colors['blue-button']} />
</Box>
</Box>
</Stack>

View File

@@ -3,109 +3,131 @@
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import colors from "@/con/colors";
import { Carousel, CarouselSlide } from "@mantine/carousel";
import { Box, Button, Container, Group, Paper, Stack, Text, useMantineTheme } from "@mantine/core";
import { Box, Button, Container, Group, Paper, Stack, Text, useMantineTheme, Skeleton } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import Autoplay from "embla-carousel-autoplay";
import { IconAward, IconArrowRight } from "@tabler/icons-react";
import { useTransitionRouter } from "next-view-transitions";
import { useEffect, useRef } from "react";
import { useProxy } from "valtio/utils";
import BackButton from "../../(pages)/desa/layanan/_com/BackButto";
export default function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"}>
Penghargaan
</Text>
<Text
py={10}
ta={"justify"}
>
Desa Darmasaba telah berhasil meraih berbagai penghargaan bergengsi yang membuktikan dedikasi dan kerja keras seluruh elemen masyarakat dalam membangun desa yang maju dan berkelanjutan. Berikut ini adalah macam-macam penghargaan yang telah diraih oleh Desa Darmasaba:
</Text>
<Slider />
</Stack>
</Container>
return (
<Stack pos="relative" bg={colors.grey[1]} py="xl" gap={32}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "60%" }}>
<Stack align="center" gap="sm">
<Group gap="xs">
<IconAward size={40} color={colors["blue-button"]} />
<Text fz={{ base: "2rem", md: "3.2rem" }} fw={800} variant="gradient" gradient={{ from: "#1C6EA4", to: "#69BFF8" }}>
Penghargaan Desa
</Text>
</Group>
<Text fz="lg" c="dimmed" ta="center">
Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan.
</Text>
<Slider />
</Stack>
)
</Container>
</Stack>
);
}
function Slider() {
const height = 720;
const width = 1200;
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const autoplay = useRef(Autoplay({ delay: 2000 }));
const state = useProxy(penghargaanState);
const roter = useTransitionRouter()
const height = 500;
const width = 1200;
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const autoplay = useRef(Autoplay({ delay: 3000 }));
const state = useProxy(penghargaanState);
const router = useTransitionRouter();
useEffect(() => {
const loadData = async () => {
try {
await state.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
}
}
loadData();
}, [])
useEffect(() => {
state.findMany.load();
}, []);
const data = state.findMany.data || [];
const slides = data.map((item) => (
<CarouselSlide key={item.id}>
<Paper h={"100%"} pos={"relative"} style={{
backgroundImage: `url(${item.image?.link}) `,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}>
<Box
style={{
borderRadius: 16,
zIndex: 0,
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify="space-between" h={"100%"} gap={0} p={"lg"} pos={"relative"} >
<Box p={"lg"}>
<Text fz={"1.5rem"} ta={"center"} c={"white"}>{item.name}</Text>
</Box>
<Group justify="center" >
<Button onClick={() => roter.push(`/darmasaba/penghargaan/${item.id}`)} px={46} radius={"100"} size="md" bg={colors["blue-button"]}>
Detail
</Button>
</Group>
</Stack>
</Paper>
</CarouselSlide>
));
const data = state.findMany.data || [];
const loading = state.findMany.loading;
if (loading) {
return (
<Carousel
c={"white"}
py={50}
plugins={[autoplay.current]}
onMouseEnter={autoplay.current.stop}
onMouseLeave={autoplay.current.reset}
w={{ base: 500, md: 800, lg: 900, xl: width }}
height={height}
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
slideGap={{ base: "xl", sm: "md" }}
loop
align="start"
slidesToScroll={mobile ? 1 : 2}
>
{slides}
</Carousel>
<Group justify="center" py="xl">
<Skeleton w={300} h={200} radius="lg" />
<Skeleton w={300} h={200} radius="lg" visibleFrom="sm" />
<Skeleton w={300} h={200} radius="lg" visibleFrom="md" />
</Group>
);
}
}
if (!loading && data.length === 0) {
return (
<Stack align="center" py="xl">
<IconAward size={56} color={colors["blue-button"]} />
<Text fz="lg" fw={600} c="dimmed">
Belum ada penghargaan yang ditambahkan
</Text>
</Stack>
);
}
const slides = data.map((item) => (
<CarouselSlide key={item.id}>
<Paper
h="100%"
radius="lg"
shadow="md"
pos="relative"
style={{
backgroundImage: `url(${item.image?.link})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<Box
pos="absolute"
inset={0}
bg="linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0.3))"
style={{ borderRadius: 16 }}
/>
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
<Text fz="xl" fw={700} ta="center" c="white">
{item.name}
</Text>
<Group justify="center">
<Button
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}
size="md"
radius="xl"
rightSection={<IconArrowRight size={18} />}
variant="gradient"
gradient={{ from: "#1C6EA4", to: "#69BFF8" }}
>
Lihat Detail
</Button>
</Group>
</Stack>
</Paper>
</CarouselSlide>
));
return (
<Carousel
py="xl"
plugins={[autoplay.current]}
onMouseEnter={autoplay.current.stop}
onMouseLeave={autoplay.current.reset}
w={{ base: "100%", sm: "90%", md: "80%", lg: width }}
h={height}
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
slideGap="md"
loop
align="start"
slidesToScroll={mobile ? 1 : 2}
>
{slides}
</Carousel>
);
}

View File

@@ -1,98 +1,102 @@
'use client'
import { Box, Center, Container, Image, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Box, Center, Container, Image, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title, Tooltip } from '@mantine/core';
import { Prisma } from '@prisma/client';
import { useEffect, useState } from 'react';
import { IconMoodSad } from '@tabler/icons-react';
import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
function Page() {
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Ensure the data is an array before setting it
let data = [];
if (Array.isArray(result.data)) {
data = result.data;
} else if (Array.isArray(result)) {
// In case the API returns the array directly
data = result;
} else {
console.error('Invalid data format:', result);
}
setSdgsDesa(data);
} catch (error) {
console.error('Error fetching sdgs desa:', error);
} finally {
setLoading(false);
}
};
fetchSdgsDesa();
}, []);
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const result = await response.json();
let data = [];
if (Array.isArray(result.data)) {
data = result.data;
} else if (Array.isArray(result)) {
data = result;
} else {
console.error('Format data tidak valid:', result);
}
setSdgsDesa(data);
} catch (error) {
console.error('Gagal mengambil data sdgs desa:', error);
} finally {
setLoading(false);
}
};
fetchSdgsDesa();
}, []);
return (
<Stack pos={"relative"} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"}>
return (
<Stack pos="relative" py="xl" gap={32}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: '100%', md: '60%' }}>
<Stack align="center" gap="sm">
<Title order={1} fz={{ base: '2.4rem', md: '3.4rem' }} fw={800} ta="center" style={{ letterSpacing: '-0.02em' }}>
SDGs Desa
</Title>
<Text ta="center" c="dimmed" fz="lg">
Pembangunan berkelanjutan yang inklusif, adil, dan berdaya saing di tingkat desa
</Text>
</Stack>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text
py={10}
ta={"justify"}
>
SDGs Desa adalah upaya terpadu pemerintah dalam percepatan pencapaian tujuan pembangunan berkelanjutan di tingkat desa. Ini merupakan terjemahan dari SDGs global dalam konteks pembangunan desa di Indonesia. SDGs Desa bertujuan untuk menciptakan desa yang lebih inklusif, berkelanjutan, dan tangguh menghadapi tantangan masa depan. Adapun
<Box px={{ base: 'md', md: 100 }}>
<Text py={10} ta="justify" fz="md" lh={1.7}>
SDGs Desa adalah upaya terpadu pemerintah dalam percepatan pencapaian tujuan pembangunan berkelanjutan di tingkat desa.
Ini merupakan adaptasi dari SDGs global dalam konteks pembangunan desa di Indonesia, yang bertujuan menciptakan desa
inklusif, berkelanjutan, dan tangguh menghadapi tantangan masa depan.
</Text>
<Text
ta={"justify"}
pb={20}
>
SDGs Desa sebagaimana dijabarkan dalam Permendesa Nomor 21 tahun 2020 terdiri dari 18 tujuan yang harus dicapai pada tahun 2030. Tujuan-tujuan tersebut mencakup berbagai aspek kehidupan masyarakat desa, mulai dari pengentasan kemiskinan, peningkatan kesehatan dan pendidikan, kesetaraan gender, pertumbuhan ekonomi, infrastruktur, hingga kelestarian lingkungan. Adapun SDGs Desa terdiri dari tujuan-tujuan sebagai berikut:
<Text ta="justify" pb={20} fz="md" lh={1.7}>
Berdasarkan Permendesa Nomor 21 Tahun 2020, SDGs Desa mencakup 18 tujuan yang harus dicapai pada tahun 2030.
Tujuan-tujuan tersebut meliputi pengentasan kemiskinan, peningkatan kesehatan dan pendidikan, kesetaraan gender,
pertumbuhan ekonomi, pembangunan infrastruktur, hingga pelestarian lingkungan.
</Text>
</Box >
<Box py={20} px={{ base: "md", md: 100 }}>
</Box>
<Box py={20} px={{ base: 'md', md: 100 }}>
<Box pos="relative" style={{ minHeight: 200 }}>
<LoadingOverlay visible={loading} overlayProps={{ blur: 2 }} />
{!loading && sdgsDesa.length > 0 ? (
<SimpleGrid
cols={{ base: 1, sm: 2, md: 3, lg: 4 }}
spacing="xl"
verticalSpacing="xl"
>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3, lg: 4 }} spacing="xl" verticalSpacing="xl">
{sdgsDesa.map((item) => (
<Paper
key={item.id}
p="md"
radius="md"
shadow="sm"
p="lg"
radius="xl"
shadow="md"
withBorder
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '100%',
transition: 'transform 0.2s',
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: '0 8px 16px rgba(0,0,0,0.1)'
}
transition: 'all 0.25s ease',
}}
onMouseEnter={(e) => {
(e.currentTarget.style.transform = 'translateY(-6px) scale(1.02)');
(e.currentTarget.style.boxShadow = '0 12px 24px rgba(0,0,0,0.08)');
}}
onMouseLeave={(e) => {
(e.currentTarget.style.transform = 'translateY(0) scale(1)');
(e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.05)');
}}
>
<Box
p="md"
style={{
backgroundColor: '#f8f9fa',
borderRadius: '8px',
background: 'linear-gradient(145deg, #f8f9fa, #ffffff)',
borderRadius: '12px',
width: '100%',
display: 'flex',
justifyContent: 'center',
@@ -105,13 +109,23 @@ function Page() {
width={120}
height={120}
fit="contain"
style={{ filter: 'drop-shadow(0px 2px 6px rgba(0,0,0,0.1))' }}
/>
</Box>
<Stack gap="xs" style={{ width: '100%' }}>
<Title order={4} ta="center" c={"dimmed"} fw={600} lineClamp={2} style={{ minHeight: '3rem' }}>
{item.name}
</Title>
<Text ta="center" fw={"bold"} c={colors['blue-button']} fz={"h2"} lineClamp={3} style={{ minHeight: '4.5rem' }}>
<Stack gap="xs" align="center" style={{ width: '100%' }}>
<Tooltip label={item.name} position="top" withArrow>
<Title order={4} ta="center" c="dark" fw={600} lineClamp={2} style={{ minHeight: '3rem' }}>
{item.name}
</Title>
</Tooltip>
<Text
ta="center"
fw={700}
c={colors['blue-button']}
fz="2rem"
lineClamp={2}
style={{ minHeight: '4rem' }}
>
{item.jumlah}
</Text>
</Stack>
@@ -119,13 +133,16 @@ function Page() {
))}
</SimpleGrid>
) : !loading ? (
<Center style={{ minHeight: 200 }}>
<Text>Tidak ada data SDGs Desa yang tersedia</Text>
<Center style={{ minHeight: 200, flexDirection: 'column', gap: '0.5rem' }}>
<IconMoodSad size={42} stroke={1.5} color="var(--mantine-color-dimmed)" />
<Text c="dimmed" fz="lg" fw={500}>
Belum ada data SDGs Desa
</Text>
</Center>
) : null}
</Box>
</Box>
</Stack >
</Stack>
);
}

View File

@@ -1,207 +1,118 @@
'use client'
import colors from '@/con/colors';
import { ActionIcon, Anchor, Box, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title, useMantineTheme } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
function Footer() {
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
return (
<>
<Stack bg={colors["blue-button"]}>
<Box w={mobile ? "100%" : "100%"} p={"xl"} h={{ base: 2500, md: 1100 }} >
<Center>
<Paper w={"100%"}>
<Box component="footer" py="xl">
<Container size="lg">
<Stack gap="xl">
<Box>
<Title fz={"md"} order={2} fw={700} mb="md">Komitmen Dalam Pelayanan</Title>
<Stack gap="sm">
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>1. Transparansi:</Text>
<Text fz={"sm"}>
Kami berkomitmen untuk mengelola dana desa secara terbuka, sehingga masyarakat dapat
mengetahui penggunaan anggaran secara jelas dan bertanggung jawab.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>2. Profesionalisme:</Text>
<Text fz={"sm"}>
Setiap layanan desa akan dilakukan dengan profesional, cepat, dan tanpa diskriminasi,
demi memastikan kepuasan masyarakat.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>3. Partisipatif:</Text>
<Text fz={"sm"}>
Kami percaya bahwa partisipasi aktif masyarakat adalah kunci keberhasilan pembangunan desa.
Oleh karena itu, kami akan terus melibatkan warga dalam setiap proses pengambilan keputusan.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>4. Inovasi:</Text>
<Text fz={"sm"}>
Kami berkomitmen untuk terus berinovasi dalam memberikan solusi bagi permasalahan desa,
termasuk melalui pemanfaatan teknologi untuk mempermudah akses layanan.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>5. Berkeadilan:</Text>
<Text fz={"sm"}>
Setiap kebijakan dan program desa akan dirancang untuk memberikan manfaat yang merata
bagi seluruh lapisan masyarakat, tanpa memandang status sosial atau ekonomi.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>6. Pemberdayaan:</Text>
<Text fz={"sm"}>
Kami berkomitmen untuk memberdayakan masyarakat melalui pelatihan, pendampingan,
dan dukungan terhadap usaha-usaha lokal agar desa semakin mandiri.
</Text>
</Group>
<Group align="flex-start" gap="xs">
<Text fz={"sm"} fw={700}>7. Ramah Lingkungan:</Text>
<Text fz={"sm"}>
Seluruh kegiatan pembangunan dan pelayanan desa akan memperhatikan keberlanjutan lingkungan,
demi menjaga keseimbangan alam dan kenyamanan hidup warga.
</Text>
</Group>
</Stack>
</Box>
<Divider />
<Box>
<Title fz={"md"} order={2} fw={700} mb="md">Tujuan Akhir</Title>
<Text fz={"sm"} mb="sm">
Dengan visi, misi dan komitmen ini, kami bertekad untuk menjadikan desa sebagai tempat tinggal
yang nyaman, aman dan sejahtera bagi seluruh warganya.
</Text>
<Text fz={"sm"} mb="sm">
Kami percaya bahwa kemajuan desa dimulai dari kerjasama antara pemerintah desa dan masyarakat,
serta didukung oleh tata kelola yang baik dan berorientasi pada kepentingan bersama. Jika ada
masukan untuk lembaga desa, silahkan hubungi pada nomor pengaduan di bawah, terima kasih.
</Text>
</Box>
<Group justify='apart' align="center">
<Text ta={"center"} fz={"sm"} fw={700} size="lg" style={{ fontStyle: 'italic' }}>{"Desa Kuat, Masyarakat Sejahtera!"}</Text>
<Box
style={{
width: 80,
height: 80,
position: 'relative'
}}
>
<ActionIcon size={80} radius={"xl"} variant='transparent'>
<Image src="/api/img/chatbot-removebg-preview.png" alt="Logo Desa" width={80} height={80} />
</ActionIcon>
</Box>
</Group>
</Stack>
</Container>
</Box>
</Paper>
</Center>
<Box py={20} >
<SimpleGrid
p={20}
cols={{
base: 2,
sm: 4,
}}
style={{
color: "white"
}}
>
<Box p={mobile ? 30 : 30} style={{color: "white"}}>
<Stack justify='space-between'>
<Text fz={"md"} fw={"bold"} c={"white"}>Tentang Darmasaba</Text>
<Text fz={"xs"} c={"white"}>Desa Darmasaba adalah desa
budaya yang kaya akan tradisi dan
nilai-nilai luhur masyarakat Bali.</Text>
<Stack bg="linear-gradient(180deg, #1C6EA4, #124170)" c="white">
<Box w="100%" p="xl" h={{ base: 1800, md: 1100 }}>
<Center>
<Paper w="100%" bg="transparent" shadow="md" radius="lg" p="xl">
<Box component="footer">
<Container size="lg">
<Stack gap="xl">
<Box>
<Flex gap={"md"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color='white' size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color='white' size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color='white' size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color='white' size={"30"} />
</ActionIcon>
</Flex>
<Title fz="lg" order={2} fw={700} mb="md" c="white">Komitmen Layanan Kami</Title>
<Stack gap="sm">
{[
{ title: "Transparansi", text: "Pengelolaan dana desa dilakukan secara terbuka agar masyarakat dapat memahami dan memantau penggunaan anggaran." },
{ title: "Profesionalisme", text: "Layanan desa diberikan secara cepat, adil, dan profesional demi kepuasan masyarakat." },
{ title: "Partisipasi", text: "Masyarakat dilibatkan aktif dalam pengambilan keputusan demi pembangunan desa yang berhasil." },
{ title: "Inovasi", text: "Kami terus berinovasi, termasuk melalui teknologi, agar layanan semakin mudah diakses." },
{ title: "Keadilan", text: "Kebijakan dan program disusun untuk memberi manfaat yang merata bagi seluruh warga." },
{ title: "Pemberdayaan", text: "Masyarakat didukung melalui pelatihan, pendampingan, dan pengembangan usaha lokal." },
{ title: "Ramah Lingkungan", text: "Seluruh kegiatan pembangunan memperhatikan keberlanjutan demi menjaga alam dan kesehatan warga." }
].map((item, i) => (
<Group key={i} align="flex-start" gap="xs">
<Text fz="sm" c="#F3F2EC" fw={700}>{i + 1}. {item.title}:</Text>
<Text fz="sm" c="#F3F2EC">{item.text}</Text>
</Group>
))}
</Stack>
</Box>
<Divider color="white" opacity={0.2} />
<Box>
<Title fz="lg" order={2} fw={700} mb="md" c="white">Visi Kami</Title>
<Text fz="sm" mb="sm" c="#F3F2EC">
Dengan visi ini, kami berkomitmen menjadikan desa sebagai tempat yang aman, sejahtera, dan nyaman bagi seluruh warga.
</Text>
<Text fz="sm" mb="sm" c="#F3F2EC">
Kami percaya kemajuan dimulai dari kerja sama antara pemerintah desa dan masyarakat, didukung tata kelola yang baik demi kepentingan bersama. Saran maupun keluhan dapat disampaikan melalui kontak di bawah ini.
</Text>
</Box>
<Group justify="apart" align="center" mt="lg">
<Text c="#F3F2EC" ta="center" fz="md" fw={700} style={{ fontStyle: 'italic' }}>&quot;Desa Kuat, Warga Sejahtera!&quot;</Text>
<ActionIcon size={80} radius="xl" variant="transparent">
<Image src="/api/img/chatbot-removebg-preview.png" alt="Logo Desa" width={80} height={80} />
</ActionIcon>
</Group>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between' gap={"xs"}>
<Text fz={"md"} fw={"bold"} c={"white"}>Layanan</Text>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Administrasi Kependudukan</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Pelayanan Sosial</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Pengaduan Masyarakat</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Informasi Publik</Text>
</Anchor>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between' gap={"xs"}>
<Text fz={"md"} fw={"bold"} c={"white"}>Tautan Penting</Text>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Portal Badung</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>E-Government</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Transparansi</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} c={"white"}>Unduhan</Text>
</Anchor>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between'>
<Text fz={"md"} fw={"bold"} c={"white"}>Newsletter</Text>
<Text fz={"xs"} c={"white"}>Dapatkan informasi terbaru
tentang kegiatan dan program
desa</Text>
</Container>
</Box>
</Paper>
</Center>
<Box py={40}>
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="xl">
<Box>
<Stack gap="sm">
<Text c="white" fz="md" fw={700}>Tentang Darmasaba</Text>
<Text fz="xs" c="#F3F2EC">
Darmasaba adalah desa budaya yang kaya akan tradisi dan nilai-nilai warisan Bali.
</Text>
<Flex gap="md" mt="sm" c="#F3F2EC">
<ActionIcon variant="subtle" color="white"><IconBrandFacebook size={22} /></ActionIcon>
<ActionIcon variant="subtle" color="white"><IconBrandInstagram size={22} /></ActionIcon>
<ActionIcon variant="subtle" color="white"><IconBrandTwitter size={22} /></ActionIcon>
<ActionIcon variant="subtle" color="white"><IconBrandWhatsapp size={22} /></ActionIcon>
</Flex>
</Stack>
</Box>
<Box>
<Stack gap="xs">
<Text c="white" fz="md" fw={700}>Layanan Desa</Text>
<Anchor c="#F3F2EC" fz="xs">Administrasi Kependudukan</Anchor>
<Anchor c="#F3F2EC" fz="xs">Layanan Sosial</Anchor>
<Anchor c="#F3F2EC" fz="xs">Pengaduan Masyarakat</Anchor>
<Anchor c="#F3F2EC" fz="xs">Informasi Publik</Anchor>
</Stack>
</Box>
<Box>
<Stack gap="xs">
<Text c="white" fz="md" fw={700}>Tautan Penting</Text>
<Anchor c="#F3F2EC" fz="xs">Portal Badung</Anchor>
<Anchor c="#F3F2EC" fz="xs">E-Government</Anchor>
<Anchor c="#F3F2EC" fz="xs">Transparansi</Anchor>
<Anchor c="#F3F2EC" fz="xs">Unduhan</Anchor>
</Stack>
</Box>
<Box>
<Stack gap="sm">
<Text c="white" fz="md" fw={700}>Berlangganan Info</Text>
<Text c="#F3F2EC" fz="xs">Dapatkan kabar terbaru tentang program dan kegiatan desa langsung ke email Anda.</Text>
<Group wrap="nowrap">
<TextInput
placeholder='Alamat email anda'
rightSection={<IconAt color={colors["blue-button"]} />}
w="70%"
placeholder="Masukkan email Anda"
rightSection={<IconAt size={16} />}
/>
</Stack>
</Box>
</SimpleGrid>
</Box>
<Divider py={15} />
<Text ta={"center"} c={"white"} p={20}>
© 2024 Desa Darmasaba. Hak Cipta Dilindungi.
</Text>
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
</Group>
</Stack>
</Box>
</SimpleGrid>
</Box>
</Stack>
</>
<Divider opacity={0.2} my="md" />
<Text ta="center" fz="xs" c="white">© 2025 Desa Darmasaba. Hak cipta dilindungi.</Text>
</Box>
</Stack>
);
}

View File

@@ -1,23 +1,27 @@
import stateNav from "@/state/state-nav";
import { Container, Stack, TextInput } from "@mantine/core";
import { Container, Stack, TextInput, Tooltip } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
export function NavbarSearch() {
return <Container w={{
base: '100%',
md: '80%',
}} fluid py={"xl"}
onMouseLeave={stateNav.clear}
return (
<Container
w={{ base: "100%", md: "80%" }}
fluid
py="xl"
onMouseLeave={stateNav.clear}
>
<Stack pt={"xl"}>
<TextInput
autoFocus
styles={{
input: {
borderRadius: "xl",
color: "black",
// backgroundColor: "rgba(255, 255, 255, 0.3)"
}
}} size="lg" variant="transparent" placeholder="Cari" />
</Stack>
<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} />}
/>
</Tooltip>
</Stack>
</Container>
}
);
}

View File

@@ -2,78 +2,96 @@
import colors from "@/con/colors";
import navbarListMenu from "@/con/navbar-list-menu";
import stateNav from "@/state/state-nav";
import { ActionIcon, Box, Burger, Group, Image, Stack, Text } from "@mantine/core";
import { ActionIcon, Box, Burger, Group, Image, Paper, ScrollArea, Stack, Text, Tooltip } from "@mantine/core";
import { IconSquareArrowRight } from "@tabler/icons-react";
import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation';
import { motion } from "framer-motion";
import { useRouter } from "next/navigation";
import { useSnapshot } from "valtio";
import { MenuItem } from "../../../../types/menu-item";
import { NavbarMainMenu } from "./NavbarMainMenu";
export function Navbar() {
const { item, isSearch, mobileOpen } = useSnapshot(stateNav);
const router = useRouter()
const router = useRouter();
return (
<Box>
<Box
<Paper
radius="0"
className="glass2"
w={"100%"}
pos={"fixed"}
w="100%"
pos="fixed"
top={0}
style={{
zIndex: 100,
overflow: "scroll"
}}
style={{ zIndex: 100 }}
>
<NavbarMainMenu listNavbar={navbarListMenu} />
<Stack hiddenFrom="sm" bg={colors.grey[2]}>
<Group justify="space-between">
<ActionIcon variant="transparent" onClick={() => {
router.push("/darmasaba")
stateNav.mobileOpen = false
}}
size={80} radius={"xl"}
>
<Image src="/darmasaba-icon.png" alt="Logo Desa" width={50} height={50} />
</ActionIcon>
<Burger onClick={() => stateNav.mobileOpen = !stateNav.mobileOpen} color={colors["blue-button"]} opened={mobileOpen} />
</Group>
{mobileOpen && <motion.div
initial={{ x: 300 }}
animate={{ x: 0 }}
transition={{ duration: 0.1 }}
style={{
height: "100vh",
overflow: "scroll"
}}
>
<NavbarMobile listNavbar={navbarListMenu} />
</motion.div>}
</Stack>
</Box>
<Stack hiddenFrom="sm" bg={colors.grey[2]} px="md" py="sm">
<Group justify="space-between">
<ActionIcon
variant="transparent"
size="xl"
radius="xl"
onClick={() => {
router.push("/darmasaba");
stateNav.mobileOpen = false;
}}
>
<Tooltip label="Go to homepage" position="bottom" withArrow>
<Image src="/darmasaba-icon.png" alt="Village Logo" width={48} height={48} />
</Tooltip>
</ActionIcon>
<Tooltip label={mobileOpen ? "Close menu" : "Open menu"} position="bottom" withArrow>
<Burger
opened={mobileOpen}
color={colors["blue-button"]}
onClick={() => (stateNav.mobileOpen = !stateNav.mobileOpen)}
size="sm"
/>
</Tooltip>
</Group>
{mobileOpen && (
<motion.div
initial={{ x: 300 }}
animate={{ x: 0 }}
transition={{ duration: 0.2 }}
style={{ height: "100vh" }}
>
<NavbarMobile listNavbar={navbarListMenu} />
</motion.div>
)}
</Stack>
</Paper>
{(item || isSearch) && <Box className="glass" />}
</Box>
);
}
function NavbarMobile({ listNavbar }: { listNavbar: MenuItem[] }) {
const router = useRouter()
return <Stack p={"md"} style={{ backgroundColor: "rgba(255, 255, 255, 0.3)" }}>
{listNavbar.map((item, k) => {
return <Stack key={k}>
<Group justify="space-between" onClick={() => {
router.push(item.href)
stateNav.mobileOpen = false
}}>
<Text c="dark.9"
style={{ fontWeight: "bold" }}
>{item.name}</Text>
<IconSquareArrowRight />
</Group>
{item.children && <NavbarMobile listNavbar={item.children} />}
const router = useRouter();
return (
<ScrollArea h="100vh" offsetScrollbars>
<Stack p="lg" gap="md" style={{ backgroundColor: "rgba(255, 255, 255, 0.25)" }}>
{listNavbar.map((item, k) => (
<Stack key={k} gap={4}>
<Group
justify="space-between"
align="center"
onClick={() => {
router.push(item.href);
stateNav.mobileOpen = false;
}}
style={{ cursor: "pointer" }}
>
<Text c="dark.9" fw={600} fz="lg">
{item.name}
</Text>
<IconSquareArrowRight size={20} />
</Group>
{item.children && <NavbarMobile listNavbar={item.children} />}
</Stack>
))}
</Stack>
})}
</Stack>
</ScrollArea>
);
}

View File

@@ -2,7 +2,7 @@
import colors from "@/con/colors"
import stateNav from "@/state/state-nav"
import { ActionIcon, Button, Container, Flex, Image, Stack } from "@mantine/core"
import { ActionIcon, Button, Container, Flex, Image, Stack, Tooltip } from "@mantine/core"
import { useHover } from "@mantine/hooks"
import { IconSearch, IconUser } from "@tabler/icons-react"
import { useTransitionRouter } from 'next-view-transitions'
@@ -12,68 +12,91 @@ import { NavbarSearch } from "./NavBarSearch"
import { NavbarSubMenu } from "./NavbarSubMenu"
import { useRouter } from "next/navigation"
export function NavbarMainMenu({ listNavbar }: {
listNavbar: MenuItem[]
}) {
const { item, isSearch } = useSnapshot(stateNav)
const router = useTransitionRouter()
const next = useRouter()
return <Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
<Container pos={"relative"} w={{
base: '100%',
md: '80%',
}} fluid>
<Flex align={"center"} justify={"space-between"} wrap={{
base: "wrap",
md: "nowrap"
}}>
<ActionIcon radius={"100"} variant="transparent" onClick={() => {
router.push("/darmasaba")
stateNav.clear()
export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
const { item, isSearch } = useSnapshot(stateNav)
const router = useTransitionRouter()
const next = useRouter()
}} >
<Image radius={"100"} src={"/assets/images/darmasaba-icon.png"} alt="icon" w={24} h={24} loading="lazy" />
</ActionIcon>
{listNavbar.map((item, k) => {
return <MenuItemCom key={k} item={item} />
})}
<ActionIcon variant="transparent" c={isSearch ? 'grey' : colors["blue-button"]}
onClick={() => {
stateNav.item = null
stateNav.isSearch = !stateNav.isSearch
}}
>
{/* TODO: add icon search */}
<IconSearch size={"1.5rem"} />
</ActionIcon>
<ActionIcon onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")
}} color={colors["blue-button"]} radius={'xl'}>
<IconUser size={24} />
</ActionIcon>
</Flex>
</Container>
{item && <NavbarSubMenu item={item as MenuItem[]} />}
{isSearch && <NavbarSearch />}
return (
<Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
<Container pos="relative" w={{ base: '100%', md: '80%' }} fluid>
<Flex align="center" justify="space-between" wrap={{ base: "wrap", md: "nowrap" }}>
<Tooltip label="Go to Homepage" position="bottom" withArrow>
<ActionIcon
radius="xl"
variant="transparent"
onClick={() => {
router.push("/darmasaba")
stateNav.clear()
}}
>
<Image
radius="xl"
src="/assets/images/darmasaba-icon.png"
alt="Darmasaba Logo"
w={28}
h={28}
loading="lazy"
/>
</ActionIcon>
</Tooltip>
{listNavbar.map((item, k) => (
<MenuItemCom key={k} item={item} />
))}
<Tooltip label="Search content" position="bottom" withArrow>
<ActionIcon
variant="transparent"
c={isSearch ? 'gray' : colors["blue-button"]}
onClick={() => {
stateNav.item = null
stateNav.isSearch = !stateNav.isSearch
}}
radius="xl"
>
<IconSearch size="1.5rem" />
</ActionIcon>
</Tooltip>
<Tooltip label="My Profile" position="bottom" withArrow>
<ActionIcon
onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")
}}
color={colors["blue-button"]}
radius="xl"
variant="light"
>
<IconUser size={22} />
</ActionIcon>
</Tooltip>
</Flex>
</Container>
{item && <NavbarSubMenu item={item as MenuItem[]} />}
{isSearch && <NavbarSearch />}
</Stack>
)
}
function MenuItemCom({ item, }: { item: MenuItem }) {
const { ref, hovered } = useHover()
const router = useTransitionRouter()
function MenuItemCom({ item }: { item: MenuItem }) {
const { ref, hovered } = useHover()
const router = useTransitionRouter()
return <Button
ref={ref}
color={hovered ? "grey" : colors["blue-button"]}
onMouseEnter={() => {
stateNav.item = item.children || null
stateNav.isSearch = false
}}
variant="transparent"
onClick={() => {
router.push(item.href)
stateNav.clear()
}}
>{item.name}</Button>
}
return (
<Button
ref={ref}
color={hovered ? "gray" : colors["blue-button"]}
onMouseEnter={() => {
stateNav.item = item.children || null
stateNav.isSearch = false
}}
variant="subtle"
radius="xl"
onClick={() => {
router.push(item.href)
stateNav.clear()
}}
fw={500}
>
{item.name}
</Button>
)
}

View File

@@ -1,20 +1,22 @@
"use client";
import stateNav from "@/state/state-nav";
import { Button, Container, Stack } from "@mantine/core";
import _ from "lodash";
import { Button, Container, Stack, Text } from "@mantine/core";
import { motion } from "motion/react";
import { IconArrowRight } from "@tabler/icons-react";
import { MenuItem } from "../../../../types/menu-item";
import { useTransitionRouter } from 'next-view-transitions'
import { useTransitionRouter } from "next-view-transitions";
import colors from "@/con/colors";
export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
const router = useTransitionRouter()
const router = useTransitionRouter();
return (
<motion.div
key={_.uniqueId()}
initial={{ opacity: 0.5 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
key={Math.random().toString(36).slice(2)}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<Container
key={stateNav.item?.[0]?.id}
@@ -22,32 +24,50 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
stateNav.item = null;
stateNav.isSearch = false;
}}
w={{
base: "100%",
md: "80%",
}}
w={{ base: "100%", md: "80%" }}
fluid
py="xl"
>
<Stack gap={0} align="start" py={"xl"}>
{item &&
item.map((item, k) => {
return (
<Button
key={k}
fz={"lg"}
color="dark.9"
variant="transparent"
onClick={() => {
router.push(item.href)
stateNav.item = null
stateNav.isSearch = false
}}
>
{item.name}
</Button>
);
})}
</Stack>
{item && item.length > 0 ? (
<Stack gap="xs" align="stretch">
{item.map((link, index) => (
<Button
key={index}
variant="subtle"
justify="space-between"
size="lg"
radius="md"
color="gray.0"
onClick={() => {
router.push(link.href);
stateNav.item = null;
stateNav.isSearch = false;
}}
rightSection={<IconArrowRight size={18} />}
styles={(theme) => ({
root: {
background: "transparent",
color: colors['blue-button'],
fontWeight: 500,
transition: "all 0.2s ease",
"&:hover": {
background: theme.colors.gray[8],
boxShadow: `0 0 12px ${theme.colors.blue[6]}55`,
},
},
})}
>
{link.name}
</Button>
))}
</Stack>
) : (
<Stack align="center" py="xl">
<Text c="dimmed" size="sm">
No submenu available
</Text>
</Stack>
)}
</Container>
</motion.div>
);

View File

@@ -1,121 +1,136 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
import colors from '@/con/colors';
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconDownload } from '@tabler/icons-react';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
import colors from '@/con/colors'
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, Loader, SimpleGrid, Stack, Text } from '@mantine/core'
import { IconDownload } from '@tabler/icons-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { useProxy } from 'valtio/utils'
function Apbdes() {
const state = useProxy(apbdes);
const [loading, setLoading] = useState(false);
const state = useProxy(apbdes)
const [loading, setLoading] = useState(false)
const textHeading = {
title: "APBDes",
des: "Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab"
title: 'APBDes',
des: 'Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola desa yang bersih, terbuka, dan bertanggung jawab.'
}
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load();
setLoading(true)
await state.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
console.error('Error loading data:', error)
} finally {
setLoading(false);
setLoading(false)
}
}
loadData();
loadData()
}, [])
const data = (state.findMany.data || []).slice(0, 3);
const data = (state.findMany.data || []).slice(0, 3)
return (
<>
<Stack p={"sm"} gap={"4rem"} bg={colors.Bg}>
<Box
w={{
base: '100%',
sm: '60%',
}}
>
<Stack gap={0}>
<Text fz={"4.4rem"} fw={"bold"}>
{textHeading.title}
</Text>
<Text fz={"1.4rem"}>
{textHeading.des}
</Text>
</Stack>
</Box>
<SimpleGrid
cols={{
base: 1,
sm: 3,
}}
>
{loading ? (
<Center>
<Text fz={"2.4rem"}>Memuat Data...</Text>
</Center>
) : (
data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
<Stack p="lg" gap="4rem" bg={colors.Bg}>
<Box w={{ base: '100%', sm: '70%' }}>
<Stack gap="sm">
<Text fz={{ base: '2.4rem', sm: '4rem' }} fw="bold" lh={1.2}>
{textHeading.title}
</Text>
<Text fz={{ base: '1rem', sm: '1.3rem' }} c="dimmed">
{textHeading.des}
</Text>
</Stack>
</Box>
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="lg">
{loading ? (
<Center mih={200}>
<Loader size="lg" color="blue" />
</Center>
) : data.length === 0 ? (
<Center mih={200}>
<Stack align="center" gap="xs">
<Text fz="lg" c="dimmed">
Belum ada data APBDes yang tersedia
</Text>
<Text fz="sm" c="dimmed">
Data akan ditampilkan di sini setelah diunggah
</Text>
</Stack>
</Center>
) : (
data.map((v, k) => (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={360}
radius="xl"
pos="relative"
style={{ overflow: 'hidden' }}
>
<Box
pos="absolute"
inset={0}
bg="rgba(0,0,0,0.55)"
style={{ backdropFilter: 'blur(4px)' }}
/>
<Stack justify="space-between" h="100%" p="xl" pos="relative">
<Text
c="white"
fw={600}
fz="lg"
ta="center"
lineClamp={2}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}>{v.jumlah}</Text>
<Group justify="center">
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]} component={Link} href={v.file?.link || ''}>
<Flex gap={"md"}>
<IconDownload size={20} />
<Text fz={"sm"} c={"white"}>Download</Text>
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
<Group pb={80} justify='center'>
<Button component={Link} href="/darmasaba/apbdes" radius={"lg"} bg={colors["blue-button"]} fz={"h4"}>Lihat Semua</Button>
</Group>
</Stack>
</>
);
{v.name}
</Text>
<Text
fw="bold"
c="white"
fz="3rem"
ta="center"
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
>
{v.jumlah}
</Text>
<Group justify="center">
<ActionIcon
component={Link}
href={v.file?.link || ''}
radius="xl"
size="lg"
variant="gradient"
gradient={{ from: '#1C6EA4', to: '#1C6EA4' }}
>
<Flex align="center" gap="xs" px="md" py={6}>
<IconDownload size={18} color="white" />
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
))
)}
</SimpleGrid>
<Group pb={80} justify="center">
<Button
component={Link}
href="/darmasaba/apbdes"
radius="xl"
size="lg"
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
>
Lihat Semua Data
</Button>
</Group>
</Stack>
)
}
export default Apbdes;
export default Apbdes

View File

@@ -160,7 +160,12 @@ function Kepuasan() {
</Center>
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
<Center mt={10}>
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
<Button
radius={"lg"}
onClick={open}
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
>Ajukan Responden</Button>
</Center>
</Container>
<Box px={"xl"}>

View File

@@ -1,64 +1,81 @@
import profileLandingPageState from "@/app/admin/(dashboard)/_state/landing-page/profile";
import { Center, Image, Paper, SimpleGrid, Text } from "@mantine/core";
import { Box, Center, Image, Paper, SimpleGrid, Stack, Text, Tooltip } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { motion } from 'framer-motion';
import { useTransitionRouter } from 'next-view-transitions';
import { motion } from "framer-motion";
import { useTransitionRouter } from "next-view-transitions";
import { useProxy } from "valtio/utils";
import { Prisma } from "@prisma/client";
import { IconPhotoOff } from "@tabler/icons-react";
type ProgramInovasiItem = Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>;
function ModuleItem({ data }: { data: ProgramInovasiItem }) {
const router = useTransitionRouter();
return (
<Paper
onClick={() => {
router.push(`/${data.name}`);
}}
p={"md"}
bg={"white"}
radius={"32"}
pos={"relative"}
>
<Center h={"100%"}>
<motion.div
whileHover={{ scale: 1.05 }}
<motion.div whileHover={{ scale: 1.04 }}>
<Tooltip label={`Lihat ${data.name}`} withArrow>
<Paper
onClick={() => router.push(`/${data.name}`)}
p="xl"
radius="2xl"
bg="white"
className="cursor-pointer transition-all shadow-md hover:shadow-xl"
>
{data.image?.link ? (
<Image src={data.image.link} alt="icon"
fit="contain"
sizes="100%"
loading="lazy"
style={{
objectFit: "contain",
objectPosition: "center"
}}
/>
) : (
<Text>
-
</Text>
)}
</motion.div>
</Center>
</Paper>
<Center h={180}>
{data.image?.link ? (
<Image
src={data.image.link}
alt={data.name}
fit="contain"
radius="lg"
loading="lazy"
style={{ objectFit: "contain", objectPosition: "center" }}
/>
) : (
<Stack align="center" gap="xs">
<IconPhotoOff size={40} stroke={1.5} />
<Text size="sm" c="dimmed">
Belum ada gambar
</Text>
</Stack>
)}
</Center>
<Box mt="md">
<Text fw={600} ta="center" size="lg" c="black">
{data.name}
</Text>
</Box>
</Paper>
</Tooltip>
</motion.div>
);
}
function ModuleView() {
const listImageState = useProxy(profileLandingPageState.programInovasi)
const listImageState = useProxy(profileLandingPageState.programInovasi);
useShallowEffect(() => {
listImageState.findMany.load()
}, [])
listImageState.findMany.load();
}, []);
if (!listImageState.findMany.loading && !listImageState.findMany.data?.length) {
return (
<Center h={320}>
<Stack align="center" gap="sm">
<IconPhotoOff size={54} stroke={1.5} />
<Text size="lg" fw={600} c="white">
Belum ada program inovasi
</Text>
<Text size="sm" c="dimmed">
Tambahkan program inovasi untuk ditampilkan di sini
</Text>
</Stack>
</Center>
);
}
return (
<SimpleGrid
cols={{
base: 2,
md: 3,
}}
>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
{listImageState.findMany.data?.map((item) => (
<ModuleItem key={item.id} data={item} />
))}

View File

@@ -1,15 +1,28 @@
import colors from '@/con/colors';
import { Box, Card, Image, Stack, Text } from '@mantine/core';
import { Box, Card, Image, Stack, Text, Tooltip } from '@mantine/core';
import { IconUserCircle } from '@tabler/icons-react';
import React from 'react';
import { Prisma } from '@prisma/client';
import colors from '@/con/colors';
interface ProfileViewProps {
data: Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null;
}
function ProfileView({ data }: ProfileViewProps) {
export default function ProfileView({ data }: ProfileViewProps) {
if (!data) {
return <div>No profile data available</div>;
return (
<Card radius="2xl" className="glass3" py="xl" px="lg" withBorder>
<Stack align="center" gap="sm">
<IconUserCircle size={72} stroke={1.4} />
<Text fw={500} c="dimmed">
Profil belum tersedia
</Text>
<Text fz="sm" c="dimmed">
Data pejabat desa akan muncul di sini
</Text>
</Stack>
</Card>
);
}
return (
@@ -17,42 +30,38 @@ function ProfileView({ data }: ProfileViewProps) {
justify="end"
align="end"
pos="relative"
w={{
base: "100%",
md: "40%",
}}
w={{ base: '100%', md: '40%' }}
px="xl"
>
{data.image?.link ? (
<Image
src={data.image.link}
alt={data.name || "Profile image"}
sizes="100%"
fit="contain"
alt={data.name || 'Foto profil'}
fit="cover"
radius="lg"
/>
): (
<Text>
-
</Text>
) : (
<Stack align="center" gap="xs" w="100%" py="xl">
<IconUserCircle size={96} stroke={1.5} />
<Text c="dimmed" fz="sm">
Belum ada foto
</Text>
</Stack>
)}
<Box
pos="absolute"
bottom={0}
p={{
base: "xs",
md: "md",
}}
>
<Box pos="absolute" bottom={0} w="100%" p={{ base: 'xs', md: 'md' }}>
<Card
px="lg"
radius="32"
radius="2xl"
withBorder
className="glass3"
style={{
border: `1px solid white`,
}}
style={{ border: '1px solid rgba(255,255,255,0.15)' }}
>
<Text>{data.position}</Text>
<Text c={colors["blue-button"]} fw="bolder" fz="1rem">
<Tooltip label="Jabatan Resmi" withArrow>
<Text fz="sm" c="dimmed">
{data.position || 'Tidak ada jabatan'}
</Text>
</Tooltip>
<Text c={colors['blue-button']} fw={700} fz="xl" mt={4}>
{data.name}
</Text>
</Card>
@@ -60,5 +69,3 @@ function ProfileView({ data }: ProfileViewProps) {
</Stack>
);
}
export default ProfileView;

View File

@@ -1,35 +1,79 @@
import { ActionIcon, Flex, Image, Text } from "@mantine/core";
import { ActionIcon, Card, Flex, Image, Text, Tooltip } from "@mantine/core";
import { Prisma } from "@prisma/client";
import { useTransitionRouter } from "next-view-transitions";
import { IconBrandInstagram, IconBrandFacebook, IconBrandTwitter, IconWorld } from "@tabler/icons-react";
function SosmedView({data} : {data : Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]}) {
function SosmedView({
data,
}: {
data: Prisma.MediaSosialGetPayload<{ include: { image: true } }>[];
}) {
const router = useTransitionRouter();
const fallbackIcon = (platform?: string) => {
switch (platform?.toLowerCase()) {
case "instagram":
return <IconBrandInstagram size={22} />;
case "facebook":
return <IconBrandFacebook size={22} />;
case "twitter":
return <IconBrandTwitter size={22} />;
default:
return <IconWorld size={22} />;
}
};
return (
<Flex gap={"md"} justify={"center"} align={"center"}>
{data?.map((item, k) => {
return (
<Flex gap="lg" justify="center" align="center" wrap="wrap">
{data && data.length > 0 ? (
data.map((item, k) => (
<Tooltip
key={k}
label={item.name || "Tautan Sosial"}
withArrow
position="top"
transitionProps={{ transition: "pop", duration: 150 }}
>
<ActionIcon
variant="transparent"
key={k}
w={32}
h={32}
pos={"relative"}
onClick={() => {
router.push(item.iconUrl || "");
variant="light"
radius="xl"
size="xl"
onClick={() => item.iconUrl && router.push(item.iconUrl)}
style={{
transition: "all 0.3s ease",
boxShadow: "0 0 12px rgba(28, 110, 164, 0.6)",
}}
>
{item.image?.link ? (
<Image src={item.image.link} alt="icon" loading="lazy" />
<Image
src={item.image.link}
alt={item.name || "ikon"}
w={24}
h={24}
fit="contain"
loading="lazy"
/>
) : (
<Text>
none
</Text>
fallbackIcon(item.name)
)}
</ActionIcon>
);
})}
</Tooltip>
))
) : (
<Card
shadow="md"
radius="xl"
p="lg"
withBorder
style={{
background: "linear-gradient(135deg, #1C6EA4 0%, #000 100%)",
}}
>
<Text ta="center" c="dimmed" size="sm">
Belum ada media sosial yang terhubung
</Text>
</Card>
)}
</Flex>
);
}

View File

@@ -11,79 +11,77 @@ import {
Image,
Paper,
Stack,
Text
Text,
Center,
Tooltip,
Badge,
} from "@mantine/core";
import { IconCalendarTime, IconInfoCircle } from "@tabler/icons-react";
import { useEffect, useState } from "react";
import ModuleView from "./ModuleView";
import SosmedView from "./SosmedView";
import ProfileView from "./ProfileView";
const getDayOfWeek = () => {
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
const days = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"];
const today = new Date();
return days[today.getDay()];
}
};
const getCurrentTime = () => {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, "0");
const minutes = String(now.getMinutes()).padStart(2, "0");
return `${hours}:${minutes}`;
}
};
const isWorkingHours = (currentTime: string): boolean => {
const [openTime, closeTime] = ['08:00', '16:00'];
const [openTime, closeTime] = ["08:00", "16:00"];
const compareTimes = (time1: string, time2: string) => {
const [hour1, minute1] = time1.split(':').map(Number);
const [hour2, minute2] = time2.split(':').map(Number);
const [hour1, minute1] = time1.split(":").map(Number);
const [hour2, minute2] = time2.split(":").map(Number);
if (hour1 < hour2) return true;
if (hour1 > hour2) return false;
return minute1 <= minute2;
};
return compareTimes(currentTime, closeTime) && !compareTimes(currentTime, openTime);
}
};
const getWorkStatus = (day: string, currentTime: string): { status: string; message: string } => {
const workingDays = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat'];
const workingDays = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat"];
if (!workingDays.includes(day)) {
return {
status: 'Tutup',
message: 'Sabtu - Minggu'
}
return { status: "Tutup", message: "Libur Akhir Pekan" };
}
const isOpen = isWorkingHours(currentTime)
return isOpen ? { status: 'Buka', message: '08:00 - 16:00' } : { status: 'Tutup', message: '08:00 - 16:00' };
}
const isOpen = isWorkingHours(currentTime);
return isOpen
? { status: "Buka", message: "08:00 - 16:00" }
: { status: "Tutup", message: "08:00 - 16:00" };
};
function LandingPage() {
const [socialMedia, setSocialMedia] = useState<Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]>([]);
const [profile, setProfile] = useState<Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null>(null);
const [socialMedia, setSocialMedia] = useState<
Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]
>([]);
const [profile, setProfile] = useState<
Prisma.PejabatDesaGetPayload<{ include: { image: true } }> | null
>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchSocialMedia = async () => {
try {
const response = await fetch('/api/landingpage/mediasosial/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const response = await fetch("/api/landingpage/mediasosial/findMany");
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const result = await response.json();
// Ensure the data is an array before setting it
if (Array.isArray(result.data)) {
setSocialMedia(result.data);
} else if (Array.isArray(result)) {
// In case the API returns the array directly
setSocialMedia(result);
} else {
console.error('Unexpected API response format:', result);
setSocialMedia([]);
}
} catch (error) {
console.error('Error fetching social media:', error);
setSocialMedia([]); // Ensure we always have an array
} catch {
setSocialMedia([]);
} finally {
setIsLoading(false);
}
@@ -92,22 +90,22 @@ function LandingPage() {
const fetchProfile = async () => {
try {
const response = await fetch(`/api/landingpage/pejabatdesa/edit`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const result = await response.json();
setProfile(result.data || null); // Handle single object response
} catch (error) {
console.error('Error fetching profile:', error);
setProfile(result.data || null);
} catch {
setProfile(null);
}
};
fetchSocialMedia();
fetchProfile();
}, []);
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>
({ status: '', message: '' });
const [workStatus, setWorkStatus] = useState<{ status: string; message: string }>({
status: "",
message: "",
});
useEffect(() => {
const updateWorkStatus = () => {
@@ -115,212 +113,110 @@ function LandingPage() {
const time = getCurrentTime();
const status = getWorkStatus(day, time);
setWorkStatus(status);
}
};
updateWorkStatus();
const intervalId = setInterval(updateWorkStatus, 60 * 1000);
return () => clearInterval(intervalId);
}, []);
return (
<Stack bg={colors["Bg"]}>
<Flex
gap={"md"}
wrap={{
base: "wrap",
md: "nowrap",
}}
>
<Stack
gap={"xl"}
w={{
base: "100%",
md: "60%",
}}
py={{
base: "xs",
md: "40",
}}
px={{
base: "xs",
md: "100",
}}
>
<Card
radius={"32"}
bg={colors.grey[1]}
p={{
base: "xs",
md: "32",
}}
>
<Stack gap={42}>
<Flex
gap={"md"}
wrap={{
base: "wrap",
md: "nowrap",
}}
>
<Grid
>
<Grid.Col span={{
base: 3,
lg: 2,
md: 3,
}}>
<Box
pos={"relative"}
bg={"white"}
w={{
base: 64,
md: 72,
}}
h={{
base: 64,
md: 72,
}}
style={{
borderRadius: 24,
}}
p={"sm"}
>
<Image
src={"/darmasaba-icon.png"}
alt="icon"
sizes="100%"
/>
<Stack bg={colors.Bg} p="md" gap="xl">
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
<Stack gap="xl">
<Flex gap="md" wrap="wrap">
<Grid w="100%">
<Grid.Col span={{ base: 3, sm: 2 }}>
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
<Image src="/darmasaba-icon.png" alt="Logo Darmasaba" fit="contain" />
</Box>
</Grid.Col>
<Grid.Col span={{
base: 3,
lg: 2,
md: 3,
}}>
<Box
pos={"relative"}
w={{
base: 64,
md: 72,
}}
h={{
base: 64,
md: 72,
}}
style={{
borderRadius: 24,
}}
p={"sm"}
bg={"white"}
>
<Image
src={"/pudak-icon.png"}
alt="icon"
sizes={"100%"}
fit="contain"
/>
<Grid.Col span={{ base: 9, sm: 10 }}>
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
<Image src="/pudak-icon.png" alt="Logo Pudak" fit="contain" />
</Box>
</Grid.Col>
<Grid.Col span={{
base: 12,
lg: 12,
md: 12,
}}>
<Grid.Col span={12}>
<Paper
pos={"relative"}
bg={colors["blue-button"]}
p={10}
w={{ base: "100%", sm: "auto", md: "auto" }}
flex={{ base: "1", sm: "1", md: "1" }}
p="md"
radius="lg"
shadow="md"
style={{ position: "relative", overflow: "hidden" }}
>
<Grid
>
<GridCol span={{
base: 12,
lg: 6,
md: 6,
}}>
<Box>
<Text c={colors["white-1"]} fz={"sm"}>
Jadwal Kerja
</Text>
<Paper
w={{ base: "100%", sm: "100%", md: "auto" }}
p={5}
bg={colors["white-1"]}
>
<Flex justify={"space-between"} align={"center"}>
<Paper
w={{ base: "100%", sm: "100%", md: "auto" }}
p={5}
bg={colors["white-1"]}
<Grid gutter="md">
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="xs">
<Flex align="center" gap="xs">
<IconCalendarTime size={16} color="white" />
<Text c="white" fz="sm">Jam Operasional</Text>
</Flex>
<Paper p="sm" radius="md" bg="white">
<Tooltip label="Status saat ini berdasarkan jam operasional kantor">
<Badge
color={workStatus.status === "Buka" ? "green" : "red"}
radius="sm"
variant="filled"
>
<Box>
<Text fw="bold" fz="sm" c={workStatus.status === 'Buka' ? "black" : "red"}>
{workStatus.status}
</Text>
<Text fw="bold" fz="lg" >
{workStatus.message}
</Text>
</Box>
</Paper>
</Flex>
{workStatus.status}
</Badge>
</Tooltip>
<Text fw="bold" fz="lg">{workStatus.message}</Text>
</Paper>
</Box>
</Stack>
</GridCol>
{/* Edit yang ini */}
<GridCol span={{ base: 12, lg: 6, md: 6 }}>
<Box>
<Text c={colors["white-1"]} fz={"sm"}>
{new Intl.DateTimeFormat('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date())}
</Text>
<Paper bg={colors["white-1"]} p={10}>
<Text fz="sm" >
Status
</Text>
<Text fw="bold" fz="lg" >
{workStatus.status === 'Buka' ? 'Operasional' : 'Tutup'}
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="xs">
<Flex align="center" gap="xs">
<IconInfoCircle size={16} color="white" />
<Text c="white" fz="sm">Hari Ini</Text>
</Flex>
<Paper p="sm" radius="md" bg="white">
<Text fz="sm">Status Kantor</Text>
<Text fw="bold" fz="lg">
{workStatus.status === "Buka" ? "Sedang Beroperasi" : "Tidak Beroperasi"}
</Text>
</Paper>
</Box>
</Stack>
</GridCol>
</Grid>
</Paper>
</Grid.Col>
</Grid>
</Flex>
<ModuleView />
{isLoading ? (
<Skeleton height={32} width="100%" />
) : socialMedia.length > 0 ? (
<SosmedView data={socialMedia} />
) : (
<div>No social media links available</div>
<Center>
<Text c="dimmed">Belum ada tautan media sosial yang tersedia</Text>
</Center>
)}
<Text c={colors.trans.dark[2]} style={{
textAlign: "center"
}} >Sampaikan saran dan masukan guna kemajuan dalam pembangunan. Semua lebih mudah melalui fitur interaktif</Text>
<Text ta="center" c={colors.trans.dark[2]}>
Bagikan ide, kritik, atau saran Anda untuk mendukung pembangunan desa.
Semua lebih mudah dengan fitur interaktif yang kami sediakan.
</Text>
</Stack>
</Card>
</Stack>
{isLoading ? (
<Skeleton height={32} width="100%" />
<Skeleton height={300} width="100%" radius="lg" />
) : profile ? (
<ProfileView data={profile} />
) : (
<div>No profile available</div>
<Center w="100%">
<Text c="dimmed">Informasi profil belum tersedia</Text>
</Center>
)}
</Flex>
</Stack >
</Stack>
);
}

View File

@@ -1,34 +1,33 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import colors from "@/con/colors";
import { Stack, Box, Container, Button, Text } from "@mantine/core";
import { useTransitionRouter } from 'next-view-transitions'
import { Stack, Box, Container, Button, Text, Loader, Paper } from "@mantine/core";
import { IconAward, IconArrowRight } from "@tabler/icons-react";
import { useTransitionRouter } from 'next-view-transitions';
import { useEffect, useState } from "react";
import { useProxy } from "valtio/utils";
function Penghargaan() {
const router = useTransitionRouter()
const state = useProxy(penghargaanState)
const [loading, setLoading] = useState(false)
const router = useTransitionRouter();
const state = useProxy(penghargaanState);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.findMany.load()
} catch (error) {
console.error('Error loading data:', error)
setLoading(true);
await state.findMany.load();
} finally {
setLoading(false)
setLoading(false);
}
}
loadData()
}, [])
};
loadData();
}, []);
const data = state.findMany.data?.slice(0, 3);
const data = state.findMany.data?.slice(0, 3)
return (
<Stack pos={"relative"} h={720}>
<Stack pos="relative" h={720}>
<video
width="320"
height="240"
@@ -51,47 +50,68 @@ function Penghargaan() {
position: "absolute",
top: 0,
left: 0,
background: "rgba(0,0,0,0.6)",
background: "linear-gradient(to bottom, rgba(0,0,0,0.6), rgba(0,0,0,0.85))",
}}
>
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720}>
<Stack justify="center" align="center">
<Container w={{ base: "100%", md: "80%" }} h={720} p="xl">
<Stack justify="center" align="center" gap="xl" h="100%">
<Text
style={{
textAlign: "center",
}}
fw={"bold"}
fz={"2.4rem"}
c={"white"}
fw={900}
fz={{ base: "2rem", md: "2.8rem" }}
ta="center"
variant="gradient"
gradient={{ from: "cyan", to: "blue", deg: 60 }}
>
Penghargaan
Penghargaan & Prestasi Desa
</Text>
{loading ? (
<Text
style={{
textAlign: "center",
}}
fw={"bold"}
fz={"2.4rem"}
c={"white"}
>
Memuat Data...
</Text>
) : (
data?.map((v, k) => {
return (
<Box key={k}>
<Stack align="center" gap={0}>
<Text fz={"1.4rem"} c={"white"}>
<Stack align="center" gap="sm">
<Loader color="blue" size="lg" />
<Text c="gray.3" fz="lg">Sedang memuat data penghargaan...</Text>
</Stack>
) : data && data.length > 0 ? (
<Stack gap="md" w="100%" maw={600}>
{data.map((v, k) => (
<Paper
key={k}
withBorder
radius="xl"
p="lg"
shadow="xl"
style={{
background: "rgba(255,255,255,0.07)",
backdropFilter: "blur(12px)",
transition: "all 0.3s ease",
}}
>
<Stack align="center" gap="xs">
<IconAward size={40} color="var(--mantine-color-blue-4)" />
<Text fz="lg" fw={700} c="white" ta="center">
{v.name}
</Text>
</Stack>
</Box>
);
})
</Paper>
))}
</Stack>
) : (
<Stack align="center" gap="xs">
<IconAward size={48} color="var(--mantine-color-gray-5)" />
<Text c="gray.4" fz="lg" ta="center">
Belum ada penghargaan yang tercatat
</Text>
</Stack>
)}
<Button color={colors["blue-button"]} onClick={() => router.push("/darmasaba/penghargaan")} variant="white" radius={100}>
Selanjutnya
<Button
size="lg"
radius="xl"
variant="gradient"
gradient={{ from: "#26667F", to: "#124170", deg: 45 }}
rightSection={<IconArrowRight size={20} />}
onClick={() => router.push("/darmasaba/penghargaan")}
>
Lihat Semua Penghargaan
</Button>
</Stack>
</Container>
@@ -100,4 +120,4 @@ function Penghargaan() {
);
}
export default Penghargaan;
export default Penghargaan;

View File

@@ -1,5 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client";
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
import colors from "@/con/colors";
@@ -9,118 +8,128 @@ import {
Button,
Divider,
Group,
Loader,
SimpleGrid,
Stack,
Text
Text,
Tooltip,
} from "@mantine/core";
import { IconArrowRight, IconInfoCircle } from "@tabler/icons-react";
import _ from "lodash";
import { motion } from "motion/react";
import { useTransitionRouter } from "next-view-transitions";
import { useEffect, useState } from "react";
import { useProxy } from "valtio/utils";
const textHeading = {
title: "Potensi",
des: "Tidak hanya untuk warga desa, fitur ini juga dapat digunakan oleh pemerintah desa untuk merencanakan program pengembangan berbasis potensi lokal",
title: "Potensi Desa",
des: "Jelajahi berbagai potensi dan peluang yang dimiliki desa. Fitur ini membantu warga maupun pemerintah desa dalam merencanakan dan mengembangkan program berbasis kekuatan lokal.",
};
function Potensi() {
const router = useTransitionRouter()
const [loading, setLoading] = useState(false)
const state = useProxy(potensiDesaState.potensiDesa)
const router = useTransitionRouter();
const [loading, setLoading] = useState(false);
const state = useProxy(potensiDesaState.potensiDesa);
useEffect(()=> {
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load()
await state.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
console.error("Gagal memuat data:", error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
};
loadData();
}, []);
const data = (state.findMany.data || []).slice(0, 4);
return (
<Stack p={"sm"} gap={"4rem"}>
<Box
w={{
base: "100%",
sm: "60%",
}}
>
<Text fz={"4.4rem"}>{textHeading.title}</Text>
<Text size={"1.4rem"}>{textHeading.des}</Text>
<Stack p="sm" gap="4rem">
<Box w={{ base: "100%", sm: "60%" }}>
<Text fz="4.4rem" fw={700} c={colors["blue-button"]}>
{textHeading.title}
</Text>
<Text size="1.4rem" c="black">
{textHeading.des}
</Text>
</Box>
<SimpleGrid
cols={{
base: 1,
sm: 2,
}}
>
{_.take(data, 4).map((v, k) => (
<motion.div
key={k}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.8 }}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
>
<BackgroundImage
src={v.image?.link}
h={320}
{loading ? (
<Stack align="center" justify="center" h={300}>
<Loader size="lg" color={colors["blue-button"]} />
<Text c="gray.4">Sedang memuat potensi desa...</Text>
</Stack>
) : data.length === 0 ? (
<Stack align="center" justify="center" h={300} gap="xs">
<IconInfoCircle size={48} color={colors["blue-button"]} />
<Text fw={600} c="gray.3">
Belum ada potensi tersedia
</Text>
<Text size="sm" c="gray.5">
Silakan cek kembali nanti untuk pembaruan terbaru.
</Text>
</Stack>
) : (
<SimpleGrid cols={{ base: 1, sm: 2 }}>
{_.take(data, 4).map((v, k) => (
<motion.div
key={k}
radius={16}
pos={"relative"}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.95 }}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
style={{ cursor: "pointer" }}
>
<Box
style={{
borderRadius: 16,
zIndex: 0,
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack
justify="end"
h={"100%"}
p={"md"}
align="start"
pos={"absolute"}
style={{
zIndex: 1,
}}
>
<Text fw={"bold"} c={"gray.1"} size={"2.4rem"}>
{v.name}
</Text>
<Text
lineClamp={2}
style={{
textAlign: "justify",
}}
c={colors["white-1"]}
<BackgroundImage src={v.image?.link} h={320} radius={20} pos="relative">
<Box
pos="absolute"
w="100%"
h="100%"
bg={colors.trans.dark[2]}
style={{ borderRadius: 20, zIndex: 0 }}
/>
<Stack
justify="end"
h="100%"
p="md"
align="start"
pos="absolute"
style={{ zIndex: 1 }}
>
{v.deskripsi}
</Text>
</Stack>
</BackgroundImage>
</motion.div>
))}
</SimpleGrid>
<Tooltip label={v.name} position="top-start">
<Text fw={700} c="white" size="2.2rem" truncate>
{v.name}
</Text>
</Tooltip>
<Text lineClamp={2} c="gray.2" size="sm">
{v.deskripsi}
</Text>
</Stack>
</BackgroundImage>
</motion.div>
))}
</SimpleGrid>
)}
<Stack align="center">
<Group>
<Button onClick={()=> router.push("/darmasaba/desa/potensi")} color={colors["blue-button"]} variant="outline" radius={100} size="md">
Selengkapnya
<Button
onClick={() => router.push("/darmasaba/desa/potensi")}
color={colors["blue-button"]}
variant="gradient"
gradient={{ from: "#26667F", to: "#124170", }}
radius="xl"
size="md"
rightSection={<IconArrowRight size={18} />}
>
Lihat Semua Potensi
</Button>
</Group>
</Stack>
<Divider />
</Stack>
);

View File

@@ -2,104 +2,124 @@
'use client'
import prestasiState from "@/app/admin/(dashboard)/_state/landing-page/prestasi-desa";
import colors from "@/con/colors";
import { BackgroundImage, Box, Button, Center, Container, Group, SimpleGrid, Stack, Text } from "@mantine/core";
import { BackgroundImage, Box, Button, Center, Container, Group, Loader, SimpleGrid, Stack, Text } from "@mantine/core";
import { useProxy } from "valtio/utils";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { IconTrophy } from "@tabler/icons-react";
function Prestasi() {
const state = useProxy(prestasiState.prestasiDesa);
const [loading, setLoading] = useState(false);
const router = useRouter()
const state = useProxy(prestasiState.prestasiDesa);
const [loading, setLoading] = useState(false);
const router = useRouter();
useEffect(() => {
prestasiState.kategoriPrestasi.findMany.load()
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData();
}, [])
useEffect(() => {
prestasiState.kategoriPrestasi.findMany.load();
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load();
} finally {
setLoading(false);
}
};
loadData();
}, []);
const data = (state.findMany.data || []).slice(0, 3);
return (
<>
<Stack p={"sm"}>
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
<Text ta={"center"} fz={"3.4rem"}>Prestasi Desa</Text>
<Text fz={"1.4rem"} ta={"center"}>Kami bangga dengan apa yang telah dicapai desa kita hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama.</Text>
<Center pt={20}>
<Button radius={"lg"} fz={"h4"} color={colors["blue-button"]} component={Link} href="/darmasaba/prestasi-desa">Selengkapnya</Button>
</Center>
</Container>
<Box py={50}>
<SimpleGrid
cols={{
base: 1,
sm: 3
}}
>
{loading ? (
<Center>
<Text fz={"2.4rem"}>Memuat Data...</Text>
</Center>
) : (
data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify="space-between" h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" }}
ta={"center"}
>
{v.kategori.name}
</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
fz={{ base: "2rem", md: "2.5rem", lg: "2.7rem", xl: "3rem" }}
ta={"center"}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
<Group justify="center">
<Button onClick={() => router.push(`/darmasaba/prestasi-desa/${v.id}`)} px={46} radius={"100"} size="md" bg={colors["blue-button"]}>
Lihat Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
</Box>
const data = (state.findMany.data || []).slice(0, 3);
return (
<Stack p="sm" bg="linear-gradient(180deg, #ffffff 0%, #f8fbff 100%)">
<Container w={{ base: "100%", md: "80%" }} p="xl">
<Stack align="center" gap="sm">
<Group gap="xs">
<IconTrophy size={36} color={colors["blue-button"]} />
<Text ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
Prestasi Desa
</Text>
</Group>
<Text fz={{ base: "1rem", md: "1.3rem" }} ta="center" c="dimmed" maw={700}>
Kami bangga dengan pencapaian desa hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama.
</Text>
<Button
radius="xl"
size="lg"
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
component={Link}
href="/darmasaba/prestasi-desa"
>
Lihat Semua Prestasi
</Button>
</Stack>
</Container>
<Box py={50}>
{loading ? (
<Center mih={200}>
<Loader color={colors["blue-button"]} size="xl" />
</Center>
) : data.length === 0 ? (
<Center mih={200}>
<Stack align="center" gap="xs">
<IconTrophy size={48} color="gray" />
<Text fz="1.2rem" fw={500} c="dimmed">
Belum ada prestasi yang ditampilkan
</Text>
</Stack>
</>
)
</Center>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
{data.map((v, k) => (
<BackgroundImage
key={k}
src={v.image?.link || ""}
radius="xl"
pos="relative"
>
<Box
pos="absolute"
inset={0}
bg="linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.7) 100%)"
style={{ borderRadius: "1rem" }}
/>
<Stack justify="space-between" h="100%" pos="relative" p="lg">
<Box>
<Text
c="white"
fz={{ base: "1rem", md: "1.25rem" }}
ta="center"
fw={500}
>
{v.kategori.name}
</Text>
</Box>
<Text
fw={700}
c="white"
fz={{ base: "1.5rem", md: "2rem", lg: "2.5rem" }}
ta="center"
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
<Group justify="center">
<Button
onClick={() => router.push(`/darmasaba/prestasi-desa/${v.id}`)}
radius="xl"
size="md"
color={colors["blue-button"]}
>
Detail Prestasi
</Button>
</Group>
</Stack>
</BackgroundImage>
))}
</SimpleGrid>
)}
</Box>
</Stack>
);
}
export default Prestasi;
export default Prestasi;

View File

@@ -1,143 +1,155 @@
'use client'
import colors from "@/con/colors";
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import { Prisma } from "@prisma/client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { Box, Button, Center, Container, Image, Paper, SimpleGrid, Stack, Text, Title, useMantineTheme, Tooltip } from "@mantine/core"
import { useMediaQuery } from "@mantine/hooks"
import { Prisma } from "@prisma/client"
import Link from "next/link"
import { IconMoodSad } from "@tabler/icons-react"
export default function SDGS() {
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[] | null>(null);
const theme = useMantineTheme()
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`)
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[] | null>(null)
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Ensure the data is an array before setting it
let data = [];
if (Array.isArray(result.data)) {
data = result.data;
} else if (Array.isArray(result)) {
// In case the API returns the array directly
data = result;
} else {
console.error('Unexpected API response format:', result);
setSdgsDesa([]);
return;
}
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch("/api/landingpage/sdgsdesa/findMany")
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
const result = await response.json()
let data = []
if (Array.isArray(result.data)) data = result.data
else if (Array.isArray(result)) data = result
else {
setSdgsDesa([])
return
}
const top3Sdgs = [...data].sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah)).slice(0, 3)
setSdgsDesa(top3Sdgs)
} catch {
setSdgsDesa([])
}
}
fetchSdgsDesa()
}, [])
// Sort by jumlah in descending order and take top 3
const top3Sdgs = [...data]
.sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah))
.slice(0, 3);
return (
<Stack p="sm">
<Container w={{ base: "100%", md: "80%" }} p="xl">
<Center>
<Title
order={1}
fz={{ base: "2.4rem", md: "3.6rem" }}
fw={900}
style={{
background: "linear-gradient(90deg, #1A5F7A, #159895)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
}}
>
SDGs Desa
</Title>
</Center>
<Text fz={{ base: "1rem", md: "1.2rem" }} ta="center" c="dimmed" mt="md" maw={820} mx="auto">
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan: dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
</Text>
setSdgsDesa(top3Sdgs);
} catch (error) {
console.error('Error fetching sdgs desa:', error);
setSdgsDesa([]);
}
};
fetchSdgsDesa();
}, []);
return (
<Stack p={"sm"}>
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
<Center>
<Text fz={"3.4rem"}>SDGs Desa</Text>
</Center>
<Text fz={"1.4rem"} ta={"center"}>SDGs Desa adalah upaya menerapkan 17 Tujuan Pembangunan Berkelanjutan di tingkat desa.
Dengan fokus pada pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, dan pelestarian lingkungan, kami berkomitmen untuk menciptakan desa yang lebih baik bagi semua</Text>
<Box py={50}>
<Paper p={{ base: 'md', md: 'xl' }} bg={colors.Bg} radius="lg" shadow="sm">
{sdgsDesa && sdgsDesa.length > 0 ? (
<SimpleGrid
cols={{ base: 1, sm: 3 }}
spacing="xl"
verticalSpacing="xl"
>
{sdgsDesa.map((item) => (
<Box
key={item.id}
p="md"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
transition: 'transform 0.2s',
'&:hover': {
transform: 'translateY(-5px)'
}
}}
>
<Box
p="md"
style={{
backgroundColor: 'white',
width: mobile ? 150 : 180,
height: mobile ? 150 : 180,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '1.5rem',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
}}
>
<Image
src={item.image?.link ? item.image.link : '/placeholder-sdgs.png'}
alt={item.name}
width={mobile ? 100 : 120}
height={mobile ? 100 : 120}
style={{
objectFit: 'contain',
maxWidth: '100%',
maxHeight: '100%'
}}
/>
</Box>
<Text
ta="center"
fz={{ base: 'lg', md: 'xl' }}
fw={700}
mb="xs"
style={{ lineHeight: 1.2 }}
>
{item.name}
</Text>
<Title
order={2}
ta="center"
c={colors['blue-button']}
style={{
fontSize: mobile ? '2.5rem' : '3rem',
lineHeight: 1,
margin: '0.5rem 0',
fontWeight: 800
}}
>
{item.jumlah}
</Title>
</Box>
))}
</SimpleGrid>
) : (
<Text>Tidak ada data SDGs Desa</Text>
)}
</Paper>
<Center>
<Button component={Link} href={"/darmasaba/sdgs-desa"} radius={"lg"} fz={"1.2rem"} mt={20} bg={colors["blue-button"]}>Selengkapnya</Button>
<Box py={50}>
<Paper
p={{ base: "md", md: "xl" }}
radius="2xl"
withBorder
shadow="lg"
style={{
background: "linear-gradient(145deg, #FFFFFF, #F9FAFB)",
border: "1px solid rgba(0,0,0,0.06)",
}}
>
{sdgsDesa && sdgsDesa.length > 0 ? (
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xl" verticalSpacing="xl">
{sdgsDesa.map((item) => (
<Paper
key={item.id}
p="lg"
radius="xl"
shadow="sm"
withBorder
style={{
background: "linear-gradient(180deg, #FFFFFF, #F6F8FA)",
border: "1px solid rgba(0,0,0,0.05)",
transition: "all 0.3s ease",
}}
>
<Center mb="lg">
<Box
p="md"
style={{
background: "rgba(240, 249, 255, 0.8)",
backdropFilter: "blur(6px)",
width: mobile ? 140 : 160,
height: mobile ? 140 : 160,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "1rem",
boxShadow: "0 6px 16px rgba(0,0,0,0.06)",
}}
>
<Image
src={item.image?.link ? item.image.link : "/placeholder-sdgs.png"}
alt={item.name}
w={mobile ? 90 : 110}
h={mobile ? 90 : 110}
fit="contain"
/>
</Box>
</Center>
</Box>
</Container>
<Tooltip label="Nama tujuan SDGs Desa" position="top" withArrow>
<Text ta="center" fz={{ base: "lg", md: "xl" }} fw={700} mb="xs">
{item.name}
</Text>
</Tooltip>
<Title
order={2}
ta="center"
style={{
fontSize: mobile ? "2.4rem" : "3.2rem",
fontWeight: 900,
letterSpacing: "-0.5px",
color: "#124170",
}}
>
{item.jumlah}
</Title>
</Paper>
))}
</SimpleGrid>
) : (
<Center mih={200} style={{ flexDirection: "column" }}>
<IconMoodSad size={48} stroke={1.5} style={{ marginBottom: "1rem" }} />
<Text fz="lg" c="dimmed">
Data SDGs Desa belum tersedia
</Text>
</Center>
)}
</Paper>
</Stack>
);
}
<Center>
<Button
component={Link}
href="/darmasaba/sdgs-desa"
radius="xl"
size="lg"
mt={40}
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
style={{ boxShadow: "0 6px 14px rgba(18,65,112,0.25)" }}
>
Jelajahi Semua Tujuan SDGs Desa
</Button>
</Center>
</Box>
</Container>
</Stack>
)
}

View File

@@ -286,7 +286,7 @@ const navbarListMenu = [
{
id: "7.4",
name: "Gotong Royong",
href: "/darmasaba/lingkungan/gotong-royong"
href: "/darmasaba/lingkungan/gotong-royong/semua"
},
{
id: "7.5",