Compare commits
5 Commits
nico/25-au
...
nico/28-au
| Author | SHA1 | Date | |
|---|---|---|---|
| b6d6583e77 | |||
| a8fd715822 | |||
| f9530c32eb | |||
| f15ef5a275 | |||
| 3a726a3334 |
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal file
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal 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='© <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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -56,13 +56,17 @@ const dataLingkunganDesaState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
|
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
|
||||||
dataLingkunganDesaState.findMany.page = page;
|
dataLingkunganDesaState.findMany.page = page;
|
||||||
|
dataLingkunganDesaState.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
|
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -67,10 +68,46 @@ const kegiatanDesa = proxy({
|
|||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.lingkungan.kegiatandesa["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
total: 0,
|
||||||
kegiatanDesa.findMany.data = res.data?.data ?? [];
|
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 };
|
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 ========================================= //
|
// ========================================= KATEGORI kegiatan ========================================= //
|
||||||
@@ -269,9 +335,7 @@ const kategoriKegiatan = proxy({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
kategoriKegiatan.create.loading = true;
|
kategoriKegiatan.create.loading = true;
|
||||||
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
|
const res = await ApiFetch.api.lingkungan.kategorikegiatan["create"].post(kategoriKegiatan.create.form);
|
||||||
"create"
|
|
||||||
].post(kategoriKegiatan.create.form);
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
kategoriKegiatan.findMany.load();
|
kategoriKegiatan.findMany.load();
|
||||||
return toast.success("Data berhasil ditambahkan");
|
return toast.success("Data berhasil ditambahkan");
|
||||||
@@ -305,9 +369,7 @@ const kategoriKegiatan = proxy({
|
|||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(`/api/lingkungan/kategorikegiatan/${id}`);
|
||||||
`/api/lingkungan/kategorikegiatan/${id}`
|
|
||||||
);
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
kategoriKegiatan.findUnique.data = data.data ?? null;
|
kategoriKegiatan.findUnique.data = data.data ?? null;
|
||||||
@@ -367,15 +429,12 @@ const kategoriKegiatan = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/lingkungan/kategorikegiatan/${id}`, {
|
||||||
`/api/lingkungan/kategorikegiatan/${id}`,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,19 @@ const pengelolaanSampah = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
|
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
|
||||||
pengelolaanSampah.findMany.page = page;
|
pengelolaanSampah.findMany.page = page;
|
||||||
|
pengelolaanSampah.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
|
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -56,13 +56,17 @@ const programPenghijauanState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
|
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
|
||||||
programPenghijauanState.findMany.page = page;
|
programPenghijauanState.findMany.page = page;
|
||||||
|
programPenghijauanState.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
|
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -51,13 +52,46 @@ const jenjangPendidikan = proxy({
|
|||||||
id: string;
|
id: string;
|
||||||
nama: string;
|
nama: string;
|
||||||
}> | null,
|
}> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res =
|
totalPages: 1,
|
||||||
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
|
total: 0,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
jenjangPendidikan.findMany.data = res.data?.data ?? [];
|
// 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,53 @@ const lembagaPendidikan = proxy({
|
|||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res =
|
totalPages: 1,
|
||||||
await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan[
|
total: 0,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "", jenjangPendidikan = "") => {
|
||||||
lembagaPendidikan.findMany.data = res.data?.data ?? [];
|
lembagaPendidikan.findMany.loading = true;
|
||||||
|
lembagaPendidikan.findMany.page = page;
|
||||||
|
lembagaPendidikan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
...(search && { search }),
|
||||||
|
...(jenjangPendidikan && { jenjangPendidikanId: jenjangPendidikan })
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Fetching lembaga with query:', query);
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan["find-many"].get({ query });
|
||||||
|
|
||||||
|
console.log('API Response:', res);
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
lembagaPendidikan.findMany.data = Array.isArray(res.data.data) ? res.data.data : [];
|
||||||
|
lembagaPendidikan.findMany.total = typeof res.data.total === 'number' ? res.data.total : 0;
|
||||||
|
lembagaPendidikan.findMany.totalPages = typeof res.data.totalPages === 'number' ? res.data.totalPages : 1;
|
||||||
|
console.log('Successfully loaded lembaga data:', {
|
||||||
|
count: lembagaPendidikan.findMany.data.length,
|
||||||
|
total: lembagaPendidikan.findMany.total,
|
||||||
|
totalPages: lembagaPendidikan.findMany.totalPages
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load lembaga pendidikan:",
|
||||||
|
res.data?.message || 'No error message provided'
|
||||||
|
);
|
||||||
|
throw new Error(res.data?.message || 'Failed to load lembaga pendidikan');
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -554,16 +628,55 @@ const siswa = proxy({
|
|||||||
data: null as Array<
|
data: null as Array<
|
||||||
Prisma.SiswaGetPayload<{
|
Prisma.SiswaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
lembaga: true;
|
lembaga: {
|
||||||
|
include: {
|
||||||
|
jenjangPendidikan: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
|
totalPages: 1,
|
||||||
"find-many"
|
total: 0,
|
||||||
].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
siswa.findMany.data = res.data?.data ?? [];
|
jenjangPendidikan: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "", jenjangPendidikan = "") => {
|
||||||
|
siswa.findMany.loading = true;
|
||||||
|
siswa.findMany.page = page;
|
||||||
|
siswa.findMany.search = search;
|
||||||
|
siswa.findMany.jenjangPendidikan = jenjangPendidikan;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (jenjangPendidikan) query.jenjangPendidikanName = jenjangPendidikan;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -794,16 +907,56 @@ const pengajar = proxy({
|
|||||||
data: null as Array<
|
data: null as Array<
|
||||||
Prisma.PengajarGetPayload<{
|
Prisma.PengajarGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
lembaga: true;
|
lembaga: {
|
||||||
|
include: {
|
||||||
|
jenjangPendidikan: true
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
|
totalPages: 1,
|
||||||
"find-many"
|
total: 0,
|
||||||
].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
pengajar.findMany.data = res.data?.data ?? [];
|
jenjangPendidikan: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "", jenjangPendidikan = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
pengajar.findMany.loading = true; // Use the full path to access the property
|
||||||
|
pengajar.findMany.page = page;
|
||||||
|
pengajar.findMany.search = search;
|
||||||
|
pengajar.findMany.jenjangPendidikan = jenjangPendidikan;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (jenjangPendidikan) query.jenjangPendidikanId = jenjangPendidikan;
|
||||||
|
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 +968,9 @@ const pengajar = proxy({
|
|||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/pendidikan/infosekolahpaud/pengajar/${id}`);
|
const res = await fetch(
|
||||||
|
`/api/pendidikan/infosekolahpaud/pengajar/${id}`
|
||||||
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
pengajar.findUnique.data = data.data ?? null;
|
pengajar.findUnique.data = data.data ?? null;
|
||||||
@@ -948,7 +1103,8 @@ const pengajar = proxy({
|
|||||||
result
|
result
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
result?.message || `Gagal mengupdate pengajar (${response.status})`
|
result?.message ||
|
||||||
|
`Gagal mengupdate pengajar (${response.status})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +1,101 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconList, IconCategory } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "List Desa Anti Korupsi",
|
label: "List Desa Anti Korupsi",
|
||||||
value: "listDesaAntiKorupsi",
|
value: "listDesaAntiKorupsi",
|
||||||
href: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi"
|
href: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi",
|
||||||
|
icon: <IconList size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola daftar program desa anti korupsi",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Kategori Desa Anti Korupsi",
|
label: "Kategori Desa Anti Korupsi",
|
||||||
value: "kategoriDesaAntiKorupsi",
|
value: "kategoriDesaAntiKorupsi",
|
||||||
href: "/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi"
|
href: "/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi",
|
||||||
|
icon: <IconCategory size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola kategori desa anti korupsi",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find(t => t.value === value);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
router.push(tab.href)
|
router.push(tab.href);
|
||||||
}
|
}
|
||||||
setActiveTab(value)
|
setActiveTab(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="lg">
|
||||||
<Title order={3}>Desa Anti Korupsi</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Desa Anti Korupsi
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
<Tabs
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsPanel key={i} value={e.value}>
|
{tabs.map((tab, i) => (
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
<TabsPanel
|
||||||
<></>
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditKategoriDesaAntiKorupsi() {
|
export default function EditKategoriDesaAntiKorupsi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -18,16 +18,17 @@ function EditKategoriDesaAntiKorupsi() {
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
});
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadKategorikegiatan = async () => {
|
const loadKategori = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await stateKategori.edit.load(id);
|
const data = await stateKategori.edit.load(id);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// pastikan id-nya masuk ke state edit
|
|
||||||
stateKategori.edit.id = id;
|
stateKategori.edit.id = id;
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || '',
|
name: data.name || '',
|
||||||
@@ -36,63 +37,88 @@ function EditKategoriDesaAntiKorupsi() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading kategori desa anti korupsi:", error);
|
console.error("Error loading kategori desa anti korupsi:", error);
|
||||||
toast.error("Gagal memuat data kategori desa anti korupsi");
|
toast.error("Gagal memuat data kategori desa anti korupsi");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadKategorikegiatan();
|
loadKategori();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
if (!formData.name.trim()) {
|
||||||
if (!formData.name.trim()) {
|
return toast.error('Nama kategori tidak boleh kosong');
|
||||||
toast.error('Nama kategori desa anti korupsi tidak boleh kosong');
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
stateKategori.edit.form = {
|
stateKategori.edit.form = {
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safety check tambahan: pastikan ID tidak kosong
|
|
||||||
if (!stateKategori.edit.id) {
|
if (!stateKategori.edit.id) {
|
||||||
stateKategori.edit.id = id; // fallback
|
stateKategori.edit.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await stateKategori.edit.update();
|
await stateKategori.edit.update();
|
||||||
|
toast.success('Kategori berhasil diperbarui');
|
||||||
if (success) {
|
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi");
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating kategori desa anti korupsi:", error);
|
console.error("Error updating kategori desa anti korupsi:", error);
|
||||||
// toast akan ditampilkan dari fungsi update
|
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui kategori');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Kategori Desa Anti Korupsi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Kategori Desa Anti Korupsi</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Kategori"
|
||||||
|
placeholder="Masukkan nama kategori"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Desa Anti Korupsi</Text>}
|
required
|
||||||
placeholder='Masukkan nama kategori desa anti korupsi'
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
loading={isLoading}
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditKategoriDesaAntiKorupsi;
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import korupsiState from '../../../../_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '../../../../_state/landing-page/desa-anti-korupsi';
|
||||||
|
|
||||||
function CreateKategoriDesaAntiKorupsi() {
|
export default function CreateKategoriDesaAntiKorupsi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi)
|
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateKategori.findMany.load();
|
stateKategori.findMany.load();
|
||||||
@@ -20,42 +20,64 @@ function CreateKategoriDesaAntiKorupsi() {
|
|||||||
stateKategori.create.form = {
|
stateKategori.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!stateKategori.create.form.name) {
|
||||||
|
return alert('Nama kategori harus diisi');
|
||||||
|
}
|
||||||
|
|
||||||
await stateKategori.create.create();
|
await stateKategori.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi")
|
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi");
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Box mb={10}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Kategori Desa Anti Korupsi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Kategori Desa Anti Korupsi</Title>
|
bg={colors['white-1']}
|
||||||
<TextInput
|
p="lg"
|
||||||
value={stateKategori.create.form.name}
|
radius="md"
|
||||||
onChange={(val) => {
|
shadow="sm"
|
||||||
stateKategori.create.form.name = val.target.value;
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Kategori"
|
||||||
|
placeholder="Masukkan nama kategori"
|
||||||
|
value={stateKategori.create.form.name || ''}
|
||||||
|
onChange={(e) => (stateKategori.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Desa Anti Korupsi</Text>}
|
>
|
||||||
placeholder='Masukkan nama kategori desa anti korupsi'
|
Simpan
|
||||||
/>
|
</Button>
|
||||||
<Group>
|
</Group>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
</Stack>
|
||||||
</Group>
|
</Paper>
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKategoriDesaAntiKorupsi;
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
||||||
|
|
||||||
@@ -56,74 +55,84 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || []
|
||||||
|
|
||||||
// Handle loading state
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<JudulList
|
|
||||||
title='List Kategori Kegiatan'
|
|
||||||
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
|
|
||||||
/>
|
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table striped withTableBorder withRowBorders>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama</TableTh>
|
|
||||||
<TableTh>Edit</TableTh>
|
|
||||||
<TableTh>Delete</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Kategori Kegiatan'
|
<Title order={4}>Daftar Kategori Kegiatan</Title>
|
||||||
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
|
<Tooltip label="Tambah Kategori" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowY: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withTableBorder withRowBorders>
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Kategori</TableTh>
|
<TableTh>Nama Kategori</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh>Edit</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color="green" onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}>
|
<Text fw={500}>{item.name}</Text>
|
||||||
<IconEdit size={20} />
|
</TableTd>
|
||||||
</Button>
|
<TableTd>
|
||||||
</TableTd>
|
<Tooltip label="Edit" withArrow>
|
||||||
<TableTd>
|
<Button
|
||||||
<Button color="red" onClick={() => {
|
variant="light"
|
||||||
setSelectedId(item.id)
|
color="blue"
|
||||||
setModalHapus(true)
|
size="sm"
|
||||||
}}>
|
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}
|
||||||
<IconX size={20} />
|
>
|
||||||
</Button>
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Hapus" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={2}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data kategori yang ditemukan</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -133,11 +142,13 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
|
|
||||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
|
||||||
interface FormDesaAntiKorupsi {
|
interface FormDesaAntiKorupsi {
|
||||||
@@ -22,18 +20,20 @@ interface FormDesaAntiKorupsi {
|
|||||||
fileId: string;
|
fileId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditDesaAntiKorupsi() {
|
export default function EditDesaAntiKorupsi() {
|
||||||
const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi)
|
const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi);
|
||||||
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const params = useParams()
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const router = useRouter()
|
const params = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormDesaAntiKorupsi>({
|
const [formData, setFormData] = useState<FormDesaAntiKorupsi>({
|
||||||
name: '',
|
name: '',
|
||||||
deskripsi: '',
|
deskripsi: '',
|
||||||
kategoriId: '',
|
kategoriId: '',
|
||||||
fileId: '',
|
fileId: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDesaAntiKorupsi = async () => {
|
const loadDesaAntiKorupsi = async () => {
|
||||||
@@ -43,7 +43,6 @@ function EditDesaAntiKorupsi() {
|
|||||||
try {
|
try {
|
||||||
const data = await desaAntiKorupsiState.edit.load(id);
|
const data = await desaAntiKorupsiState.edit.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
// ⬇️ FIX PENTING: tambahkan ini
|
|
||||||
desaAntiKorupsiState.edit.id = id;
|
desaAntiKorupsiState.edit.id = id;
|
||||||
|
|
||||||
desaAntiKorupsiState.edit.form = {
|
desaAntiKorupsiState.edit.form = {
|
||||||
@@ -61,169 +60,198 @@ function EditDesaAntiKorupsi() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data?.file?.link) {
|
if (data?.file?.link) {
|
||||||
setPreviewFile(data.file.link)
|
setPreviewFile(data.file.link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program penghijauan:", error);
|
console.error('Error loading data:', error);
|
||||||
toast.error("Gagal memuat data program penghijauan");
|
toast.error('Gagal memuat data Desa Anti Korupsi');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loadDesaAntiKorupsi();
|
loadDesaAntiKorupsi();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!formData.name) {
|
||||||
|
return toast.warn('Masukkan judul dokumen');
|
||||||
|
}
|
||||||
|
if (!formData.kategoriId) {
|
||||||
|
return toast.warn('Pilih kategori dokumen');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
// Update global state with form data
|
// Update global state with form data
|
||||||
desaAntiKorupsiState.edit.form = {
|
desaAntiKorupsiState.edit.form = {
|
||||||
...desaAntiKorupsiState.edit.form,
|
...desaAntiKorupsiState.edit.form,
|
||||||
name: formData.name,
|
...formData,
|
||||||
deskripsi: formData.deskripsi,
|
|
||||||
kategoriId: formData.kategoriId || '',
|
kategoriId: formData.kategoriId || '',
|
||||||
fileId: formData.fileId // Keep existing imageId if not changed
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Jika ada file baru, upload
|
// Upload new file if exists
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name
|
||||||
|
});
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
throw new Error('Gagal mengunggah dokumen');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
desaAntiKorupsiState.edit.form.fileId = uploaded.id;
|
desaAntiKorupsiState.edit.form.fileId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await desaAntiKorupsiState.edit.update();
|
await desaAntiKorupsiState.edit.update();
|
||||||
toast.success("desa anti korupsi berhasil diperbarui!");
|
toast.success('Data berhasil diperbarui');
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi");
|
router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating desa anti korupsi:", error);
|
console.error('Error updating data:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui desa anti korupsi");
|
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
</Tooltip>
|
||||||
<Stack>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Text fz={"xl"} fw={"bold"}>Edit List Desa Anti Korupsi</Text>
|
Edit Desa Anti Korupsi
|
||||||
{desaAntiKorupsiState.findUnique.data ? (
|
</Title>
|
||||||
<Paper key={desaAntiKorupsiState.findUnique.data.id}>
|
</Group>
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<TextInput
|
<Paper
|
||||||
value={formData.name}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={(val) => {
|
bg={colors['white-1']}
|
||||||
setFormData({
|
p="lg"
|
||||||
...formData,
|
radius="md"
|
||||||
name: val.target.value
|
shadow="sm"
|
||||||
})
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Judul Dokumen"
|
||||||
|
placeholder="Masukkan judul dokumen"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Kategori"
|
||||||
|
placeholder="Pilih kategori"
|
||||||
|
value={formData.kategoriId}
|
||||||
|
onChange={(val) => setFormData({ ...formData, kategoriId: val || '' })}
|
||||||
|
data={
|
||||||
|
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
||||||
|
value: v.id,
|
||||||
|
label: v.name,
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
required
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Dokumen
|
||||||
|
</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0];
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewFile(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid, gunakan format dokumen')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{
|
||||||
|
'application/*': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'],
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={180} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconFile size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="lg" inline>
|
||||||
|
Seret dokumen ke sini atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewFile && (
|
||||||
|
<Box mt="md">
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Pratinjau Dokumen
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '500px',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
>
|
||||||
placeholder='Masukkan judul'
|
<iframe
|
||||||
/>
|
src={previewFile}
|
||||||
<Box>
|
width="100%"
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
height="100%"
|
||||||
<EditEditor
|
style={{ border: 'none' }}
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
deskripsi: val
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Select
|
</Box>
|
||||||
value={formData.kategoriId}
|
)}
|
||||||
onChange={(val) => {
|
</Box>
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
kategoriId: val ?? ""
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
|
||||||
placeholder="Pilih kategori"
|
|
||||||
data={
|
|
||||||
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
|
||||||
value: v.id,
|
|
||||||
label: v.name,
|
|
||||||
})) || []
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>File Document</Text>
|
|
||||||
<Box>
|
|
||||||
<Dropzone
|
|
||||||
onDrop={(files) => {
|
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
|
||||||
if (selectedFile) {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setPreviewFile(URL.createObjectURL(selectedFile)); // Buat preview
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{
|
|
||||||
'application/*': ['.pdf', '.doc', '.docx'],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconFile size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
<Group justify="right" mt="xl">
|
||||||
<Text size="xl" inline>
|
<Button
|
||||||
Drag file ke sini atau klik untuk pilih file
|
onClick={handleSubmit}
|
||||||
</Text>
|
radius="md"
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
size="md"
|
||||||
Maksimal 5MB dan harus format document
|
loading={isLoading}
|
||||||
</Text>
|
style={{
|
||||||
</div>
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
</Group>
|
color: '#fff',
|
||||||
</Dropzone>
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
<Box>
|
}}
|
||||||
<Text fw={"bold"} fz={"lg"}>Dokumen</Text>
|
>
|
||||||
{previewFile ? (
|
Simpan
|
||||||
<iframe
|
</Button>
|
||||||
src={previewFile}
|
</Group>
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditDesaAntiKorupsi;
|
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function DetailKegiatanDesa() {
|
export default function DetailKegiatanDesa() {
|
||||||
const detailState = useProxy(korupsiState.desaAntikorupsi)
|
const detailState = useProxy(korupsiState.desaAntikorupsi)
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
@@ -34,89 +34,122 @@ function DetailKegiatanDesa() {
|
|||||||
if (!detailState.findUnique.data) {
|
if (!detailState.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = detailState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Button
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
variant="subtle"
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
onClick={() => router.back()}
|
||||||
</Button>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
</Box>
|
mb={15}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
>
|
||||||
<Stack>
|
Kembali
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail List Desa Anti Korupsi</Text>
|
</Button>
|
||||||
{detailState.findUnique.data ? (
|
|
||||||
<Paper key={detailState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Box>
|
bg={colors['white-1']}
|
||||||
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
p="lg"
|
||||||
<Text fz={"lg"}>{detailState.findUnique.data?.name}</Text>
|
radius="md"
|
||||||
</Box>
|
shadow="sm"
|
||||||
<Box>
|
>
|
||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
<Stack gap="md">
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: detailState.findUnique.data?.deskripsi }} />
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
</Box>
|
Detail Desa Anti Korupsi
|
||||||
<Box>
|
</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Kategori</Text>
|
|
||||||
<Text fz={"lg"}>{detailState.findUnique.data?.kategori?.name}</Text>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
</Box>
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Dokumen</Text>
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
{detailState.findUnique.data?.file?.link ? (
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
<iframe
|
</Box>
|
||||||
src={detailState.findUnique.data.file.link}
|
|
||||||
width="100%"
|
<Box>
|
||||||
height="500px"
|
<Text fz="lg" fw="bold">Kategori</Text>
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
<Text fz="md" c="dimmed">{data.kategori?.name || '-'}</Text>
|
||||||
/>
|
</Box>
|
||||||
) : (
|
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
<Box>
|
||||||
)}
|
<Text fz="lg" fw="bold" mb="xs">Deskripsi</Text>
|
||||||
</Box>
|
<Box
|
||||||
<Flex gap={"xs"} mt={10}>
|
fz="md"
|
||||||
<Button
|
c="dimmed"
|
||||||
onClick={() => {
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
if (detailState.findUnique.data) {
|
style={{ lineHeight: 1.6 }}
|
||||||
setSelectedId(detailState.findUnique.data.id);
|
/>
|
||||||
setModalHapus(true);
|
</Box>
|
||||||
}
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold" mb="xs">Dokumen</Text>
|
||||||
|
{data.file?.link ? (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '500px',
|
||||||
|
width: '100%'
|
||||||
}}
|
}}
|
||||||
disabled={detailState.delete.loading || !detailState.findUnique.data}
|
|
||||||
color={"red"}
|
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<iframe
|
||||||
</Button>
|
src={data.file.link}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 'none' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada dokumen tersedia</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm" mt="md">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (detailState.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${detailState.findUnique.data.id}/edit`);
|
setModalHapus(true);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!detailState.findUnique.data}
|
variant="light"
|
||||||
color={"green"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
disabled={detailState.delete.loading}
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${data.id}/edit`)}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
) : null}
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus desa anti korupsi ini?'
|
text="Apakah Anda yakin ingin menghapus data Desa Anti Korupsi ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailKegiatanDesa;
|
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -13,12 +24,12 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function CreateDesaAntiKorupsi() {
|
export default function CreateDesaAntiKorupsi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateKorupsi = useProxy(korupsiState.desaAntikorupsi)
|
const stateKorupsi = useProxy(korupsiState.desaAntikorupsi);
|
||||||
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateKorupsi.findMany.load();
|
stateKorupsi.findMany.load();
|
||||||
@@ -27,140 +38,181 @@ function CreateDesaAntiKorupsi() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateKorupsi.create.form = {
|
stateKorupsi.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
kategoriId: "",
|
kategoriId: '',
|
||||||
fileId: "",
|
fileId: '',
|
||||||
};
|
};
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setPreviewFile(null);
|
setPreviewFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file pdf terlebih dahulu");
|
return toast.warn('Pilih file dokumen terlebih dahulu');
|
||||||
|
}
|
||||||
|
if (!stateKorupsi.create.form.name) {
|
||||||
|
return toast.warn('Masukkan judul dokumen');
|
||||||
|
}
|
||||||
|
if (!stateKorupsi.create.form.kategoriId) {
|
||||||
|
return toast.warn('Pilih kategori dokumen');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
setIsLoading(true);
|
||||||
file,
|
try {
|
||||||
name: file.name,
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
})
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
throw new Error('Gagal mengunggah dokumen');
|
||||||
|
}
|
||||||
|
|
||||||
|
stateKorupsi.create.form.fileId = uploaded.id;
|
||||||
|
await stateKorupsi.create.create();
|
||||||
|
|
||||||
|
toast.success('Data berhasil disimpan');
|
||||||
|
resetForm();
|
||||||
|
router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat menyimpan data');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
stateKorupsi.create.form.fileId = uploaded.id;
|
|
||||||
|
|
||||||
await stateKorupsi.create.create();
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi")
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Dokumen Desa Anti Korupsi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Kegiatan Desa</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>File Document</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Dokumen
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewFile(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewFile(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{
|
|
||||||
'application/*': ['.pdf', '.doc', '.docx'],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconFile size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag file ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format document
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Dokumen</Text>
|
|
||||||
{previewFile ? (
|
|
||||||
<iframe
|
|
||||||
src={previewFile}
|
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<TextInput
|
|
||||||
value={stateKorupsi.create.form.name}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateKorupsi.create.form.name = val.target.value;
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
|
||||||
placeholder='Masukkan judul'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateKorupsi.create.form.deskripsi}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateKorupsi.create.form.deskripsi = val;
|
|
||||||
}}
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid, gunakan format dokumen')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{
|
||||||
|
'application/*': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'],
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={180} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconFile size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="lg" inline>
|
||||||
|
Seret dokumen ke sini atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewFile && (
|
||||||
|
<Box mt="md" style={{ textAlign: 'center' }}>
|
||||||
|
<iframe
|
||||||
|
src={previewFile}
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Judul Dokumen"
|
||||||
|
placeholder="Masukkan judul dokumen"
|
||||||
|
value={stateKorupsi.create.form.name || ''}
|
||||||
|
onChange={(e) => (stateKorupsi.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateKorupsi.create.form.deskripsi || ''}
|
||||||
|
onChange={(val) => (stateKorupsi.create.form.deskripsi = val)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={stateKorupsi.create.form.kategoriId}
|
label="Kategori"
|
||||||
onChange={(val) => {
|
|
||||||
stateKorupsi.create.form.kategoriId = val ?? "";
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
|
||||||
placeholder="Pilih kategori"
|
placeholder="Pilih kategori"
|
||||||
|
value={stateKorupsi.create.form.kategoriId || ''}
|
||||||
|
onChange={(val) => (stateKorupsi.create.form.kategoriId = val || '')}
|
||||||
data={
|
data={
|
||||||
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
||||||
value: v.id,
|
value: v.id,
|
||||||
label: v.name,
|
label: v.name,
|
||||||
})) || []
|
})) || []
|
||||||
}
|
}
|
||||||
|
required
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group>
|
<Group justify="right" mt="xl">
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
loading={isLoading}
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateDesaAntiKorupsi;
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
||||||
|
|
||||||
function DesaAntiKorupsi() {
|
function DesaAntiKorupsi() {
|
||||||
@@ -16,7 +15,7 @@ function DesaAntiKorupsi() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='List Desa Anti Korupsi'
|
title='List Desa Anti Korupsi'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama program atau kategori...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,8 +26,8 @@ function DesaAntiKorupsi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListDesaAntiKorupsi({ search }: { search: string }) {
|
function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||||
const listState = useProxy(korupsiState.desaAntikorupsi)
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const listState = useProxy(korupsiState.desaAntikorupsi);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -42,99 +41,96 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
|||||||
load(page, 10, search);
|
load(page, 10, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
// Handle loading state
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<JudulList
|
|
||||||
title='List Desa Anti Korupsi'
|
|
||||||
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
|
|
||||||
/>
|
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table striped withTableBorder withRowBorders>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama</TableTh>
|
|
||||||
<TableTh>Deskripsi</TableTh>
|
|
||||||
<TableTh>Kategori</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
<Group justify="space-between" mb="md">
|
||||||
<JudulList
|
<Title order={4}>Daftar Program Desa Anti Korupsi</Title>
|
||||||
title='List Desa Anti Korupsi'
|
<Tooltip label="Tambah Program Desa Anti Korupsi" withArrow>
|
||||||
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
|
<Button
|
||||||
/>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
color="blue"
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
variant="light"
|
||||||
<TableThead>
|
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create')}
|
||||||
<TableTr>
|
>
|
||||||
<TableTh>Nama Desa Anti Korupsi</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Deskripsi Desa Anti Korupsi</TableTh>
|
</Button>
|
||||||
<TableTh>Kategori Desa Anti Korupsi</TableTh>
|
</Tooltip>
|
||||||
<TableTh>Detail</TableTh>
|
</Group>
|
||||||
</TableTr>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
</TableThead>
|
<Table highlightOnHover>
|
||||||
<TableTbody>
|
<TableThead>
|
||||||
{filteredData.map((item) => (
|
<TableTr>
|
||||||
|
<TableTh>Nama Program</TableTh>
|
||||||
|
<TableTh>Kategori</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Box w={350}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
<Text lineClamp={1} fw={500}>{item.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="sm" c="dimmed">
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
{item.kategori?.name || '-'}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.kategori?.name}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data program yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DesaAntiKorupsi;
|
export default DesaAntiKorupsi;
|
||||||
|
|||||||
@@ -1,67 +1,110 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconBulb, IconUsers, IconBrandFacebook } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "Program Inovasi",
|
label: "Program Inovasi",
|
||||||
value: "program-inovasi",
|
value: "program-inovasi",
|
||||||
href: "/admin/landing-page/profile/program-inovasi"
|
href: "/admin/landing-page/profile/program-inovasi",
|
||||||
|
icon: <IconBulb size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Lihat dan kelola program inovasi desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pejabat Desa",
|
label: "Pejabat Desa",
|
||||||
value: "pejabat-desa",
|
value: "pejabat-desa",
|
||||||
href: "/admin/landing-page/profile/pejabat-desa"
|
href: "/admin/landing-page/profile/pejabat-desa",
|
||||||
|
icon: <IconUsers size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola data pejabat desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Media Sosial",
|
label: "Media Sosial",
|
||||||
value: "media-sosial",
|
value: "media-sosial",
|
||||||
href: "/admin/landing-page/profile/media-sosial"
|
href: "/admin/landing-page/profile/media-sosial",
|
||||||
|
icon: <IconBrandFacebook size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Atur tautan media sosial desa",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find(t => t.value === value);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
router.push(tab.href)
|
router.push(tab.href);
|
||||||
}
|
}
|
||||||
setActiveTab(value)
|
setActiveTab(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="lg">
|
||||||
<Title order={3}>Profile</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Profil Desa
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
<Tabs
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsPanel key={i} value={e.value}>
|
{tabs.map((tab, i) => (
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
<TabsPanel
|
||||||
<></>
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -12,17 +23,17 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditMediaSosial() {
|
function EditMediaSosial() {
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateMediaSosial.update.form.name || "",
|
name: stateMediaSosial.update.form.name || '',
|
||||||
iconUrl: stateMediaSosial.update.form.iconUrl || "",
|
iconUrl: stateMediaSosial.update.form.iconUrl || '',
|
||||||
imageId: stateMediaSosial.update.form.imageId || ""
|
imageId: stateMediaSosial.update.form.imageId || '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -34,136 +45,147 @@ function EditMediaSosial() {
|
|||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || "",
|
name: data.name || '',
|
||||||
iconUrl: data.iconUrl || "",
|
iconUrl: data.iconUrl || '',
|
||||||
imageId: data.imageId || "",
|
imageId: data.imageId || '',
|
||||||
});
|
});
|
||||||
// Tampilkan preview gambar
|
if (data.image?.link) setPreviewImage(data.image.link);
|
||||||
if (data.image?.link) {
|
|
||||||
setPreviewImage(data.image.link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program inovasi:", error);
|
console.error('Error loading media sosial:', error);
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error ? error.message : "Gagal mengambil data program inovasi"
|
error instanceof Error ? error.message : 'Gagal mengambil data media sosial'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loadMediaSosial();
|
loadMediaSosial();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateMediaSosial.update.form = {
|
stateMediaSosial.update.form = { ...stateMediaSosial.update.form, ...formData };
|
||||||
...stateMediaSosial.update.form,
|
|
||||||
name: formData.name,
|
|
||||||
iconUrl: formData.iconUrl,
|
|
||||||
imageId: formData.imageId ?? "",
|
|
||||||
}
|
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) return toast.error('Gagal upload gambar');
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
stateMediaSosial.update.form.imageId = uploaded.id;
|
stateMediaSosial.update.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await stateMediaSosial.update.update();
|
await stateMediaSosial.update.update();
|
||||||
toast.success("Media Sosial berhasil diperbarui!");
|
toast.success('Media sosial berhasil diperbarui!');
|
||||||
router.push("/admin/landing-page/profile/media-sosial");
|
router.push('/admin/landing-page/profile/media-sosial');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating media sosial:", error);
|
console.error('Error updating media sosial:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui media sosial");
|
toast.error('Terjadi kesalahan saat memperbarui media sosial');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Media Sosial
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Media Sosial</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Media Sosial
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</Dropzone>
|
)}
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Media Sosial / Kontak"
|
||||||
|
placeholder="Masukkan nama media sosial atau kontak"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Media Sosial / Nama Kontak</Text>}
|
required
|
||||||
placeholder='Masukkan nama media sosial'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Link Media Sosial / Nomor Telepon"
|
||||||
|
placeholder="Masukkan link media sosial atau nomor telepon"
|
||||||
value={formData.iconUrl}
|
value={formData.iconUrl}
|
||||||
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Icon URL / No Telephone</Text>}
|
required
|
||||||
placeholder='Masukkan icon url'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,103 +2,132 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function DetailMediaSosial() {
|
function DetailMediaSosial() {
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateMediaSosial.findUnique.load(params?.id as string)
|
stateMediaSosial.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateMediaSosial.delete.byId(selectedId)
|
stateMediaSosial.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/landing-page/profile/media-sosial")
|
router.push("/admin/landing-page/profile/media-sosial");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!stateMediaSosial.findUnique.data) {
|
if (!stateMediaSosial.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateMediaSosial.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Button
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
variant="subtle"
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
onClick={() => router.back()}
|
||||||
</Button>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
</Box>
|
mb={15}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
>
|
||||||
<Stack>
|
Kembali
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Media Sosial</Text>
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
|
w={{ base: "100%", md: "60%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Media Sosial
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Media Sosial / Nama Kontak</Text>
|
<Text fz="lg" fw="bold">Nama Media Sosial / Kontak</Text>
|
||||||
<Text fz={"lg"}>{stateMediaSosial.findUnique.data?.name}</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Icon URL / No Telephone</Text>
|
<Text fz="lg" fw="bold">Icon / Nomor Telepon</Text>
|
||||||
<Text fz={"lg"}>{stateMediaSosial.findUnique.data?.iconUrl}</Text>
|
<Text fz="md" c="dimmed">{data.iconUrl || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
<Box w={100} h={100}>
|
{data.image?.link ? (
|
||||||
<Image src={stateMediaSosial.findUnique.data?.image?.link} alt="gambar" />
|
<Image
|
||||||
</Box>
|
src={data.image.link}
|
||||||
|
alt={data.name || 'Gambar Media Sosial'}
|
||||||
|
w={120}
|
||||||
|
h={120}
|
||||||
|
radius="md"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Media Sosial" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (stateMediaSosial.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(stateMediaSosial.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!stateMediaSosial.findUnique.data}
|
variant="light"
|
||||||
color="red">
|
radius="md"
|
||||||
<IconX size={20} />
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Media Sosial" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (stateMediaSosial.findUnique.data) {
|
onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${data.id}/edit`)}
|
||||||
router.push(`/admin/landing-page/profile/media-sosial/${stateMediaSosial.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!stateMediaSosial.findUnique.data}
|
>
|
||||||
color="green">
|
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus media sosial ini?"
|
text="Apakah Anda yakin ingin menghapus media sosial ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -11,9 +22,9 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import profileLandingPageState from '../../../../_state/landing-page/profile';
|
import profileLandingPageState from '../../../../_state/landing-page/profile';
|
||||||
|
|
||||||
function CreateMediaSosial() {
|
export default function CreateMediaSosial() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
@@ -23,27 +34,28 @@ function CreateMediaSosial() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateMediaSosial.create.form = {
|
stateMediaSosial.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
imageId: "",
|
imageId: '',
|
||||||
iconUrl: "",
|
iconUrl: '',
|
||||||
};
|
};
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn('Silakan pilih file gambar terlebih dahulu');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
return toast.error('Gagal mengunggah gambar, silakan coba lagi');
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMediaSosial.create.form.imageId = uploaded.id;
|
stateMediaSosial.create.form.imageId = uploaded.id;
|
||||||
@@ -51,98 +63,108 @@ function CreateMediaSosial() {
|
|||||||
await stateMediaSosial.create.create();
|
await stateMediaSosial.create.create();
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/landing-page/profile/media-sosial")
|
router.push('/admin/landing-page/profile/media-sosial');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Media Sosial
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Media Sosial</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Media Sosial
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
</Group>
|
||||||
|
<Text ta="center" mt="sm" size="sm" color="dimmed">
|
||||||
|
Seret gambar atau klik untuk memilih file (maks 5MB)
|
||||||
|
</Text>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</Dropzone>
|
)}
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Media Sosial / Kontak"
|
||||||
|
placeholder="Masukkan nama media sosial atau kontak"
|
||||||
value={stateMediaSosial.create.form.name || ''}
|
value={stateMediaSosial.create.form.name || ''}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateMediaSosial.create.form.name = e.target.value)}
|
||||||
stateMediaSosial.create.form.name = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Media Sosial / Nama Kontak</Text>}
|
|
||||||
placeholder='Masukkan nama media sosial / nama kontak'
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Link Media Sosial / Nomor Telepon"
|
||||||
|
placeholder="Masukkan link media sosial atau nomor telepon"
|
||||||
value={stateMediaSosial.create.form.iconUrl || ''}
|
value={stateMediaSosial.create.form.iconUrl || ''}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateMediaSosial.create.form.iconUrl = e.target.value)}
|
||||||
stateMediaSosial.create.form.iconUrl = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Link Media Sosial / No Telephone</Text>}
|
|
||||||
placeholder='Masukkan link media sosial / no telephone'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateMediaSosial;
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Group, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import profileLandingPageState from '../../../_state/landing-page/profile';
|
import profileLandingPageState from '../../../_state/landing-page/profile';
|
||||||
|
|
||||||
function MediaSosial() {
|
function MediaSosial() {
|
||||||
@@ -16,7 +15,7 @@ function MediaSosial() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Media Sosial'
|
title='Media Sosial'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama media sosial atau kontak...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -44,80 +43,77 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || []
|
||||||
|
|
||||||
// Handle loading state
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<JudulList
|
|
||||||
title='List Media Sosial'
|
|
||||||
href='/admin/landing-page/profile/media-sosial/create'
|
|
||||||
/>
|
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table striped withTableBorder withRowBorders>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
|
|
||||||
<TableTh>Image</TableTh>
|
|
||||||
<TableTh>Icon URL / No Telephone</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Media Sosial'
|
<Title order={4}>Daftar Media Sosial</Title>
|
||||||
href='/admin/landing-page/profile/media-sosial/create'
|
<Tooltip label="Tambah Media Sosial" withArrow>
|
||||||
/>
|
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/landing-page/profile/media-sosial/create')}>
|
||||||
<Box style={{ overflowY: "auto" }}>
|
Tambah Baru
|
||||||
<Table striped withTableBorder withRowBorders>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
|
<TableTh>Nama Media Sosial / Kontak</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Gambar</TableTh>
|
||||||
<TableTh>Icon URL / No Telephone</TableTh>
|
<TableTh>Icon / No. Telepon</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={50} h={50}>
|
<Text fw={500}>{item.name}</Text>
|
||||||
<Image src={item.image?.link} alt={item.name} />
|
</TableTd>
|
||||||
</Box>
|
<TableTd>
|
||||||
</TableTd>
|
<Box w={50} h={50} style={{ borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<TableTd>
|
{item.image?.link ? (
|
||||||
<Box w={250}>
|
<Image src={item.image.link} alt={item.name} fit="cover" />
|
||||||
<a style={{color: "black"}} href={item.iconUrl} target="_blank" rel="noopener noreferrer">
|
) : (
|
||||||
<Text truncate fz={'sm'}>{item.iconUrl}</Text>
|
<Box bg={colors['blue-button']} w="100%" h="100%" />
|
||||||
</a>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}>
|
<Text truncate fz="sm" color="dimmed">
|
||||||
<IconDeviceImac size={20} />
|
{item.iconUrl || item.noTelp || '-'}
|
||||||
</Button>
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data media sosial yang cocok</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -127,11 +123,13 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Alert, Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Alert, Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -144,124 +144,134 @@ function EditPejabatDesa() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={handleBack}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Pejabat Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Box>
|
<Paper
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p="md" radius={10}>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Stack gap="xs">
|
bg={colors['white-1']}
|
||||||
<Title order={3}>Edit Profile Pejabat Desa</Title>
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Profile Pejabat Desa</Title>
|
||||||
|
|
||||||
{/* Nama Field */}
|
{/* Nama Field */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw="bold">Nama Perbekel</Text>}
|
label={<Text fw="bold">Nama Perbekel</Text>}
|
||||||
placeholder="Masukkan nama perbekel"
|
placeholder="Masukkan nama perbekel"
|
||||||
value={allState.edit.form.name}
|
value={allState.edit.form.name}
|
||||||
onChange={(e) => handleFieldChange('name', e.currentTarget.value)}
|
onChange={(e) => handleFieldChange('name', e.currentTarget.value)}
|
||||||
error={!allState.edit.form.name && "Nama wajib diisi"}
|
error={!allState.edit.form.name && "Nama wajib diisi"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Posisi Field */}
|
{/* Posisi Field */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw="bold">Posisi</Text>}
|
label={<Text fw="bold">Posisi</Text>}
|
||||||
placeholder="Masukkan posisi"
|
placeholder="Masukkan posisi"
|
||||||
value={allState.edit.form.position}
|
value={allState.edit.form.position}
|
||||||
onChange={(e) => handleFieldChange('position', e.currentTarget.value)}
|
onChange={(e) => handleFieldChange('position', e.currentTarget.value)}
|
||||||
error={!allState.edit.form.position && "Posisi wajib diisi"}
|
error={!allState.edit.form.position && "Posisi wajib diisi"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* File Upload */}
|
{/* File Upload */}
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Dropzone
|
||||||
<Box>
|
onDrop={(files) => handleFileChange(files[0])}
|
||||||
<Dropzone
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
onDrop={(files) => handleFileChange(files[0])}
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
accept={{ 'image/*': [] }}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
>
|
||||||
accept={{ 'image/*': [] }}
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
>
|
<Dropzone.Accept>
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
<Dropzone.Accept>
|
</Dropzone.Accept>
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
<Dropzone.Reject>
|
||||||
</Dropzone.Accept>
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
<Dropzone.Reject>
|
</Dropzone.Reject>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<Dropzone.Idle>
|
||||||
</Dropzone.Reject>
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
<Dropzone.Idle>
|
</Dropzone.Idle>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="xl" inline>
|
<Text size="xl" inline>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
Maksimal 5MB dan harus format gambar
|
Maksimal 5MB dan harus format gambar
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
{/* Tampilkan preview kalau ada */}
|
||||||
{previewImage && (
|
{previewImage && (
|
||||||
<Box mt="sm">
|
<Box mt="sm">
|
||||||
<Image
|
<Image
|
||||||
src={previewImage}
|
src={previewImage}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '200px',
|
maxHeight: '200px',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Preview Gambar */}
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray.2">
|
|
||||||
<Stack align="center" gap="xs">
|
|
||||||
<IconImageInPicture size={48} color="gray" />
|
|
||||||
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Preview Gambar */}
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
||||||
bg={colors['blue-button']}
|
{previewImage ? (
|
||||||
onClick={handleSubmit}
|
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
||||||
loading={isSubmitting || allState.edit.loading}
|
) : (
|
||||||
disabled={!allState.edit.form.name}
|
<Center w={200} h={200} bg="gray.2">
|
||||||
>
|
<Stack align="center" gap="xs">
|
||||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
<IconImageInPicture size={48} color="gray" />
|
||||||
</Button>
|
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Button
|
{/* Submit Button */}
|
||||||
variant="outline"
|
<Group>
|
||||||
onClick={handleBack}
|
<Button
|
||||||
disabled={isSubmitting || allState.edit.loading}
|
bg={colors['blue-button']}
|
||||||
>
|
onClick={handleSubmit}
|
||||||
Batal
|
loading={isSubmitting || allState.edit.loading}
|
||||||
</Button>
|
disabled={!allState.edit.form.name}
|
||||||
</Group>
|
>
|
||||||
</Stack>
|
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||||
</Paper>
|
</Button>
|
||||||
</Box>
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={isSubmitting || allState.edit.loading}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const allList = useProxy(profileLandingPageState.pejabatDesa)
|
const allList = useProxy(profileLandingPageState.pejabatDesa);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
allList.findUnique.load("edit") // Assuming "1" is your default ID, adjust as needed
|
allList.findUnique.load("edit");
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!allList.findUnique.data) {
|
if (!allList.findUnique.data) {
|
||||||
return <Stack>
|
return (
|
||||||
<Skeleton radius={10} h={800} />
|
<Stack align="center" justify="center" py="xl">
|
||||||
</Stack>
|
<Skeleton radius="md" height={800} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataArray = Array.isArray(allList.findUnique.data)
|
const dataArray = Array.isArray(allList.findUnique.data)
|
||||||
@@ -26,79 +28,82 @@ function Page() {
|
|||||||
: [allList.findUnique.data];
|
: [allList.findUnique.data];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="md">
|
||||||
<Grid>
|
<Grid align="center">
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={3}>Preview Pejabat Desa</Title>
|
<Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push(`/admin/landing-page/profile/pejabat-desa/${allList.findUnique.data?.id}`)}>
|
<Tooltip label="Edit Profil Pejabat" withArrow>
|
||||||
<IconEdit size={16} />
|
<Button
|
||||||
</Button>
|
c="blue"
|
||||||
|
variant="light"
|
||||||
|
leftSection={<IconEdit size={18} stroke={2} />}
|
||||||
|
radius="md"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/profile/pejabat-desa/${allList.findUnique.data?.id}`)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
{dataArray.map((item) => (
|
{dataArray.map((item) => (
|
||||||
<Box key={item.id} >
|
<Paper key={item.id} p="xl" bg={colors['BG-trans']} radius="md" shadow="xs">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Box px={{ base: "sm", md: 100 }}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Grid>
|
||||||
<Grid>
|
<GridCol span={12}>
|
||||||
<GridCol span={{ base: 12, md: 12 }}>
|
<Center>
|
||||||
<Center>
|
<Image src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo Desa" />
|
||||||
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
</Center>
|
||||||
</Center>
|
</GridCol>
|
||||||
</GridCol>
|
<GridCol span={12}>
|
||||||
<GridCol span={{ base: 12, md: 12 }}>
|
<Text ta="center" fz={{ base: "1.2rem", md: "1.8rem" }} fw="bold" c={colors['blue-button']}>
|
||||||
<Text ta={"center"} fz={{ base: "1.2rem", md: "1.8rem" }} fw={'bold'}>PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA </Text>
|
Profil Pimpinan Badan Publik Desa Darmasaba
|
||||||
</GridCol>
|
</Text>
|
||||||
</Grid>
|
</GridCol>
|
||||||
</Box>
|
</Grid>
|
||||||
<Divider my={"md"} color={colors['blue-button']} />
|
</Box>
|
||||||
{/* biodata perbekel */}
|
<Divider my="md" color={colors['blue-button']} />
|
||||||
<Box px={{ base: 0, md: 50 }} pb={30}>
|
<Box px={{ base: 0, md: 50 }} pb="xl">
|
||||||
<Box pb={20} px={{ base: 0, md: 50 }}>
|
<Paper bg={colors['BG-trans']} radius="md" shadow="xs" p="lg">
|
||||||
<Paper bg={colors['BG-trans']} w={{ base: "100%", md: "100%" }}>
|
<Stack gap={0}>
|
||||||
<Stack gap={0}>
|
<Center>
|
||||||
<Center>
|
<Image
|
||||||
<Image
|
pt={{ base: 0, md: 60 }}
|
||||||
pt={{ base: 0, md: 90 }}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
src={item.image?.link || "/perbekel.png"}
|
w={{ base: 250, md: 350 }}
|
||||||
w={{ base: 250, md: 350 }}
|
alt="Foto Profil Pejabat"
|
||||||
alt='Foto Profil PPID'
|
radius="md"
|
||||||
onError={(e) => {
|
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||||
e.currentTarget.src = "/perbekel.png";
|
/>
|
||||||
}}
|
</Center>
|
||||||
/>
|
<Paper
|
||||||
</Center>
|
bg={colors['blue-button']}
|
||||||
<Paper
|
py="md"
|
||||||
bg={colors['blue-button']}
|
px="sm"
|
||||||
py={20}
|
radius="md"
|
||||||
className="glass3"
|
className="glass3"
|
||||||
px={{ base: 10, md: 10 }}
|
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
||||||
|
>
|
||||||
>
|
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.2rem", md: "1.6rem" }}>
|
||||||
<Text ta={"center"} c={colors['white-1']} fw={"bolder"} fz={{ base: "1.2rem", md: "1.6rem" }}>
|
{item.name}
|
||||||
{item.name}
|
</Text>
|
||||||
</Text>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Stack>
|
||||||
<Box pt={10}>
|
</Paper>
|
||||||
<Box>
|
<Box mt="lg">
|
||||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Position</Text>
|
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Jabatan</Text>
|
||||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"}>{item.position}</Text>
|
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']}>
|
||||||
</Box>
|
{item.position}
|
||||||
</Box>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Box>
|
||||||
</Box>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
||||||
export default Page;
|
|
||||||
|
|||||||
@@ -3,7 +3,18 @@
|
|||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -86,92 +97,113 @@ function EditProgramInovasi() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Program Inovasi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Program Inovasi</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Program Inovasi
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</Dropzone>
|
)}
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Program Inovasi"
|
||||||
|
placeholder="Masukkan nama program inovasi"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Produk</Text>}
|
required
|
||||||
placeholder='Masukkan nama produk'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Deskripsi"
|
||||||
|
placeholder="Masukkan deskripsi program inovasi"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>}
|
required
|
||||||
placeholder='Masukkan deskripsi'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Link Program Inovasi"
|
||||||
|
placeholder="Masukkan link program inovasi (opsional)"
|
||||||
value={formData.link}
|
value={formData.link}
|
||||||
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Link</Text>}
|
|
||||||
placeholder='Masukkan link'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -31,91 +31,116 @@ function DetailProgramInovasi() {
|
|||||||
|
|
||||||
if (!stateProgramInovasi.findUnique.data) {
|
if (!stateProgramInovasi.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={12}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={520} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateProgramInovasi.findUnique.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'md', md: 'xl' }} py="lg">
|
||||||
<Box mb={10}>
|
<Button variant="subtle" onClick={() => router.back()} leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
Kembali
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
</Button>
|
||||||
</Button>
|
|
||||||
</Box>
|
<Paper
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
w={{ base: "100%", md: "60%" }}
|
||||||
<Stack>
|
bg={colors['white-1']}
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Program Inovasi</Text>
|
p="lg"
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
radius="md"
|
||||||
<Stack gap={"xs"}>
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Program Inovasi
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Program Inovasi</Text>
|
<Text fz="lg" fw="bold">Nama Program Inovasi</Text>
|
||||||
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.name}</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text
|
<Text fz="md" c="dimmed" style={{ whiteSpace: 'pre-wrap' }}>{data.description || '-'}</Text>
|
||||||
fz={"lg"}
|
|
||||||
|
|
||||||
>{stateProgramInovasi.findUnique.data?.description}</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Link</Text>
|
<Text fz="lg" fw="bold">Link</Text>
|
||||||
<a
|
{data.link ? (
|
||||||
href={stateProgramInovasi.findUnique.data?.link || "#"}
|
<a
|
||||||
target="_blank"
|
href={data.link}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
style={{
|
rel="noopener noreferrer"
|
||||||
wordWrap: 'break-word',
|
style={{
|
||||||
whiteSpace: 'pre-wrap',
|
color: colors['blue-button'],
|
||||||
overflowWrap: 'break-word',
|
textDecoration: 'underline',
|
||||||
width: '100%'
|
wordBreak: 'break-word',
|
||||||
}}
|
|
||||||
>
|
|
||||||
{stateProgramInovasi.findUnique.data?.link || "Tidak ada link"}
|
|
||||||
</a>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={stateProgramInovasi.findUnique.data?.image?.link} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (stateProgramInovasi.findUnique.data) {
|
|
||||||
setSelectedId(stateProgramInovasi.findUnique.data.id);
|
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!stateProgramInovasi.findUnique.data}
|
>
|
||||||
color="red">
|
{data.link}
|
||||||
<IconX size={20} />
|
</a>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt="Gambar Program"
|
||||||
|
radius="md"
|
||||||
|
style={{ maxWidth: '100%', maxHeight: 300, objectFit: 'contain' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Program Inovasi" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Program Inovasi" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (stateProgramInovasi.findUnique.data) {
|
onClick={() => router.push(`/admin/landing-page/profile/program-inovasi/${data.id}/edit`)}
|
||||||
router.push(`/admin/landing-page/profile/program-inovasi/${stateProgramInovasi.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!stateProgramInovasi.findUnique.data}
|
>
|
||||||
color="green">
|
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus program inovasi ini?"
|
text="Apakah Anda yakin ingin menghapus program inovasi ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -13,7 +25,7 @@ import profileLandingPageState from '../../../../_state/landing-page/profile';
|
|||||||
|
|
||||||
function CreateProgramInovasi() {
|
function CreateProgramInovasi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
|
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
@@ -31,20 +43,21 @@ function CreateProgramInovasi() {
|
|||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn("Silakan pilih file gambar terlebih dahulu");
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
return toast.error("Gagal mengunggah gambar, silakan coba lagi");
|
||||||
}
|
}
|
||||||
|
|
||||||
stateProgramInovasi.create.form.imageId = uploaded.id;
|
stateProgramInovasi.create.form.imageId = uploaded.id;
|
||||||
@@ -55,99 +68,116 @@ function CreateProgramInovasi() {
|
|||||||
router.push("/admin/landing-page/profile/program-inovasi")
|
router.push("/admin/landing-page/profile/program-inovasi")
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Program Inovasi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Program Inovasi</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Program Inovasi
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</Dropzone>
|
)}
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<TextInput
|
|
||||||
value={stateProgramInovasi.create.form.name || ''}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateProgramInovasi.create.form.name = val.target.value;
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Program Inovasi</Text>}
|
|
||||||
placeholder='Masukkan nama program inovasi'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
value={stateProgramInovasi.create.form.description || ''}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateProgramInovasi.create.form.description = val.target.value;
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>}
|
|
||||||
placeholder='Masukkan deskripsi'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={stateProgramInovasi.create.form.link || ''}
|
label="Nama Program Inovasi"
|
||||||
onChange={(val) => {
|
placeholder="Masukkan nama program inovasi"
|
||||||
stateProgramInovasi.create.form.link = val.target.value;
|
value={stateProgramInovasi.create.form.name}
|
||||||
}}
|
onChange={(e) => (stateProgramInovasi.create.form.name = e.target.value)}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Link</Text>}
|
required
|
||||||
placeholder='Masukkan link'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateProgramInovasi.create.form.description || ''}
|
||||||
|
onChange={(htmlContent: string) => {
|
||||||
|
stateProgramInovasi.create.form.description = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Link Program Inovasi"
|
||||||
|
placeholder="Masukkan link program inovasi (opsional)"
|
||||||
|
value={stateProgramInovasi.create.form.link || ''}
|
||||||
|
onChange={(e) => (stateProgramInovasi.create.form.link = e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import profileLandingPageState from '../../../_state/landing-page/profile';
|
import profileLandingPageState from '../../../_state/landing-page/profile';
|
||||||
|
|
||||||
function ProgramInovasi() {
|
function ProgramInovasi() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px="md" py="lg">
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Program Inovasi'
|
title="Program Inovasi"
|
||||||
placeholder='pencarian'
|
placeholder="Cari program inovasi..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,107 +27,118 @@ function ProgramInovasi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListProgramInovasi({ search }: { search: string }) {
|
function ListProgramInovasi({ search }: { search: string }) {
|
||||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
|
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stateProgramInovasi.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = stateProgramInovasi.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={20}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<JudulList
|
|
||||||
title='List Program Inovasi'
|
|
||||||
href='/admin/landing-page/profile/program-inovasi/create'
|
|
||||||
/>
|
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table striped withTableBorder withRowBorders>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama Program</TableTh>
|
|
||||||
<TableTh>Deskripsi</TableTh>
|
|
||||||
<TableTh>Link</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={15}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
|
||||||
<JudulList
|
<Box mb="md" display="flex"
|
||||||
title='List Program Inovasi'
|
style={{ justifyContent: 'space-between', alignItems: 'center' }}
|
||||||
href='/admin/landing-page/profile/program-inovasi/create'
|
>
|
||||||
/>
|
<Title order={4}>Daftar Program Inovasi</Title>
|
||||||
<Box style={{ overflowY: "auto" }}>
|
<Tooltip label="Tambah Program Inovasi" withArrow>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Button
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
onClick={() => router.push('/admin/landing-page/profile/program-inovasi/create')}
|
||||||
|
>
|
||||||
|
Tambah Program
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover striped verticalSpacing="sm">
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Program</TableTh>
|
<TableTh>Nama Program</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Link</TableTh>
|
<TableTh>Link</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length === 0 ? (
|
||||||
<TableTr key={item.id}>
|
<TableTr>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTd colSpan={4}>
|
||||||
<TableTd w={200}>{item.description}</TableTd>
|
<Center py={20}>
|
||||||
<TableTd>
|
<Text color="dimmed">Belum ada data program inovasi</Text>
|
||||||
<Box w={250}>
|
</Center>
|
||||||
<a style={{ color: "black" }} href={item.link} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Text truncate fz={'sm'}>{item.link}</Text>
|
|
||||||
</a>
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push(`/admin/landing-page/profile/program-inovasi/${item.id}`)}>
|
|
||||||
<IconDeviceImac size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
) : (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text fw={500}>{item.name}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ maxWidth: 250 }}>
|
||||||
|
<Text fz="sm" lineClamp={2}>
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ maxWidth: 250 }}>
|
||||||
|
<Tooltip label="Buka tautan program" position="top" withArrow>
|
||||||
|
<a
|
||||||
|
href={item.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ color: colors['blue-button'], textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
<Text truncate fz="sm">{item.link}</Text>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/landing-page/profile/program-inovasi/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="md">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
|
||||||
<Pagination
|
|
||||||
value={page}
|
|
||||||
onChange={(newPage) => {
|
|
||||||
load(newPage, 10);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}}
|
|
||||||
total={totalPages}
|
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,18 +42,10 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [page])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (data || []).filter(item => {
|
const filteredData = data || []
|
||||||
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 iconMap: Record<string, React.FC<any>> = {
|
const iconMap: Record<string, React.FC<any>> = {
|
||||||
ekowisata: IconLeaf,
|
ekowisata: IconLeaf,
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
|||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</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%' }}>
|
<TableTd style={{ width: '10%' }}>
|
||||||
{iconMap[item.icon] && (
|
{iconMap[item.icon] && (
|
||||||
<Box title={item.icon}>
|
<Box title={item.icon}>
|
||||||
|
|||||||
@@ -7,18 +7,23 @@ import {
|
|||||||
AppShellHeader,
|
AppShellHeader,
|
||||||
AppShellMain,
|
AppShellMain,
|
||||||
AppShellNavbar,
|
AppShellNavbar,
|
||||||
Box,
|
|
||||||
Burger,
|
Burger,
|
||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
Image,
|
||||||
NavLink,
|
NavLink,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Text
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
rem
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { IconChevronLeft, IconChevronRight, IconDoorExit } from "@tabler/icons-react";
|
import {
|
||||||
import _ from 'lodash';
|
IconChevronLeft,
|
||||||
|
IconChevronRight,
|
||||||
|
IconDoorExit,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import _ from "lodash";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
||||||
import { navBar } from "./_com/list_PageAdmin";
|
import { navBar } from "./_com/list_PageAdmin";
|
||||||
@@ -26,69 +31,97 @@ import { navBar } from "./_com/list_PageAdmin";
|
|||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const [opened, { toggle }] = useDisclosure();
|
const [opened, { toggle }] = useDisclosure();
|
||||||
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
|
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
// Normalisasi semua segmen jadi lowercase
|
const segments = useSelectedLayoutSegments().map((s) => _.lowerCase(s));
|
||||||
const segments = useSelectedLayoutSegments().map(s => _.lowerCase(s));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
header={{ height: 60 }}
|
header={{ height: 64 }}
|
||||||
navbar={{
|
navbar={{
|
||||||
width: 300,
|
width: 300,
|
||||||
breakpoint: 'sm',
|
breakpoint: "sm",
|
||||||
collapsed: {
|
collapsed: {
|
||||||
mobile: !opened,
|
mobile: !opened,
|
||||||
desktop: !desktopOpened,
|
desktop: !desktopOpened,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
padding={'md'}
|
padding="md"
|
||||||
>
|
>
|
||||||
<AppShellHeader bg={colors["white-1"]}>
|
<AppShellHeader
|
||||||
<Group px={10} align="center">
|
style={{
|
||||||
<Flex align="center" gap={'xs'}>
|
background: "linear-gradient(90deg, #ffffff, #f9fbff)",
|
||||||
|
borderBottom: `1px solid ${colors["blue-button"]}20`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group px="md" h="100%" justify="space-between">
|
||||||
|
<Flex align="center" gap="sm">
|
||||||
<Image
|
<Image
|
||||||
py={5}
|
src="/assets/images/darmasaba-icon.png"
|
||||||
src={'/assets/images/darmasaba-icon.png'}
|
alt="Logo Darmasaba"
|
||||||
alt=""
|
width={46}
|
||||||
width={50}
|
height={46}
|
||||||
height={50}
|
radius="md"
|
||||||
/>
|
/>
|
||||||
<Text fw={'bold'} c={colors["blue-button"]} fz={'lg'}>
|
<Text
|
||||||
Dashboard Admin
|
fw={700}
|
||||||
|
c={colors["blue-button"]}
|
||||||
|
fz="lg"
|
||||||
|
style={{ letterSpacing: rem(0.3) }}
|
||||||
|
>
|
||||||
|
Admin Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
{!desktopOpened && (
|
|
||||||
<ActionIcon variant="light" onClick={toggleDesktop}>
|
<Group gap="xs">
|
||||||
<IconChevronRight />
|
{!desktopOpened && (
|
||||||
</ActionIcon>
|
<Tooltip label="Buka Navigasi" position="bottom" withArrow>
|
||||||
)}
|
<ActionIcon
|
||||||
<Burger
|
variant="light"
|
||||||
opened={opened}
|
radius="xl"
|
||||||
onClick={toggle}
|
size="lg"
|
||||||
hiddenFrom="sm"
|
onClick={toggleDesktop}
|
||||||
size={'sm'}
|
color={colors["blue-button"]}
|
||||||
/>
|
>
|
||||||
<Box>
|
<IconChevronRight />
|
||||||
<ActionIcon onClick={() => {
|
</ActionIcon>
|
||||||
router.push("/darmasaba")
|
</Tooltip>
|
||||||
}} color={colors["blue-button"]} radius={'xl'}>
|
)}
|
||||||
<IconDoorExit size={24} />
|
|
||||||
</ActionIcon>
|
<Burger
|
||||||
</Box>
|
opened={opened}
|
||||||
<ActionIcon
|
onClick={toggle}
|
||||||
w={50}
|
hiddenFrom="sm"
|
||||||
h={50}
|
size="sm"
|
||||||
variant="transparent"
|
color={colors["blue-button"]}
|
||||||
component={Link}
|
/>
|
||||||
href="/admin"
|
|
||||||
>
|
<Tooltip label="Kembali ke Website Desa" position="bottom" withArrow>
|
||||||
</ActionIcon>
|
<ActionIcon
|
||||||
|
onClick={() => {
|
||||||
|
router.push("/darmasaba");
|
||||||
|
}}
|
||||||
|
color={colors["blue-button"]}
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||||
|
>
|
||||||
|
<IconDoorExit size={22} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</AppShellHeader>
|
</AppShellHeader>
|
||||||
|
|
||||||
<AppShellNavbar c={colors["blue-button"]} component={ScrollArea}>
|
<AppShellNavbar
|
||||||
<AppShell.Section>
|
component={ScrollArea}
|
||||||
|
style={{
|
||||||
|
background: "#ffffff",
|
||||||
|
borderRight: `1px solid ${colors["blue-button"]}20`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppShell.Section p="sm">
|
||||||
{navBar.map((v, k) => {
|
{navBar.map((v, k) => {
|
||||||
const isParentActive = segments.includes(_.lowerCase(v.name));
|
const isParentActive = segments.includes(_.lowerCase(v.name));
|
||||||
|
|
||||||
@@ -96,26 +129,42 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<NavLink
|
<NavLink
|
||||||
key={k}
|
key={k}
|
||||||
defaultOpened={isParentActive}
|
defaultOpened={isParentActive}
|
||||||
c={isParentActive ? colors["blue-button"] : "grey"}
|
c={isParentActive ? colors["blue-button"] : "gray"}
|
||||||
label={
|
label={
|
||||||
<Text style={{ fontWeight: isParentActive ? "bold" : "normal" }}>
|
<Text fw={isParentActive ? 600 : 400} fz="sm">
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
|
style={{
|
||||||
|
borderRadius: rem(10),
|
||||||
|
marginBottom: rem(4),
|
||||||
|
transition: "background 150ms ease",
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
active={isParentActive}
|
||||||
>
|
>
|
||||||
{v.children.map((child, key) => {
|
{v.children.map((child, key) => {
|
||||||
const isChildActive = segments.includes(_.lowerCase(child.name));
|
const isChildActive = segments.includes(
|
||||||
|
_.lowerCase(child.name)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={key}
|
key={key}
|
||||||
href={child.path}
|
href={child.path}
|
||||||
c={isChildActive ? colors["blue-button"] : "grey"}
|
c={isChildActive ? colors["blue-button"] : "gray"}
|
||||||
label={
|
label={
|
||||||
<Text style={{ fontWeight: isChildActive ? "bold" : "normal" }}>
|
<Text fw={isChildActive ? 600 : 400} fz="sm">
|
||||||
{child.name}
|
{child.name}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
|
style={{
|
||||||
|
borderRadius: rem(8),
|
||||||
|
marginBottom: rem(2),
|
||||||
|
transition: "background 150ms ease",
|
||||||
|
}}
|
||||||
|
active={isChildActive}
|
||||||
|
component={Link}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -124,16 +173,35 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
})}
|
})}
|
||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
|
|
||||||
<AppShell.Section py={20}>
|
<AppShell.Section py="md">
|
||||||
<Group justify="end">
|
<Group justify="end" pr="sm">
|
||||||
<ActionIcon variant="light" onClick={toggleDesktop}>
|
<Tooltip
|
||||||
<IconChevronLeft />
|
label={desktopOpened ? "Tutup Navigasi" : "Buka Navigasi"}
|
||||||
</ActionIcon>
|
position="top"
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
onClick={toggleDesktop}
|
||||||
|
color={colors["blue-button"]}
|
||||||
|
>
|
||||||
|
<IconChevronLeft />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
</AppShellNavbar>
|
</AppShellNavbar>
|
||||||
|
|
||||||
<AppShellMain bg={colors.Bg}>{children}</AppShellMain>
|
<AppShellMain
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(180deg, #fdfdfd, #f6f9fc)",
|
||||||
|
minHeight: "100vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AppShellMain>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
@@ -6,17 +7,28 @@ export default async function dataLingkunganDesaFindMany(context: Context) {
|
|||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
const skip = (page - 1) * limit;
|
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 {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.dataLingkunganDesa.findMany({
|
prisma.dataLingkunganDesa.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.dataLingkunganDesa.count({
|
prisma.dataLingkunganDesa.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
// /api/berita/findManyPaginated.ts
|
// /api/berita/findManyPaginated.ts
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
@@ -5,12 +6,38 @@ import { Context } from "elysia";
|
|||||||
async function kegiatanDesaFindMany(context: Context) {
|
async function kegiatanDesaFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
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;
|
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 {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.kegiatanDesa.findMany({
|
prisma.kegiatanDesa.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
include: {
|
include: {
|
||||||
kategoriKegiatan: true,
|
kategoriKegiatan: true,
|
||||||
image: true,
|
image: true,
|
||||||
@@ -20,7 +47,7 @@ async function kegiatanDesaFindMany(context: Context) {
|
|||||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||||
}),
|
}),
|
||||||
prisma.kegiatanDesa.count({
|
prisma.kegiatanDesa.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import KegiatanDesaDelete from "./del";
|
|||||||
import KegiatanDesaFindMany from "./findMany";
|
import KegiatanDesaFindMany from "./findMany";
|
||||||
import KegiatanDesaFindUnique from "./findUnique";
|
import KegiatanDesaFindUnique from "./findUnique";
|
||||||
import KegiatanDesaUpdate from "./updt";
|
import KegiatanDesaUpdate from "./updt";
|
||||||
|
import KegiatanDesaFindFirst from "./findFirst";
|
||||||
|
|
||||||
const KegiatanDesa = new Elysia({
|
const KegiatanDesa = new Elysia({
|
||||||
prefix: "/kegiatandesa",
|
prefix: "/kegiatandesa",
|
||||||
@@ -16,6 +17,9 @@ const KegiatanDesa = new Elysia({
|
|||||||
// ✅ Find by ID
|
// ✅ Find by ID
|
||||||
.get("/:id", KegiatanDesaFindUnique)
|
.get("/:id", KegiatanDesaFindUnique)
|
||||||
|
|
||||||
|
// ✅ Find First
|
||||||
|
.get("/find-first", KegiatanDesaFindFirst)
|
||||||
|
|
||||||
// ✅ Create
|
// ✅ Create
|
||||||
.post("/create", KegiatanDesaCreate, {
|
.post("/create", KegiatanDesaCreate, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
@@ -5,18 +6,28 @@ import { Context } from "elysia";
|
|||||||
export default async function pengelolaanSampahFindMany(context: Context) {
|
export default async function pengelolaanSampahFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ name: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.pengelolaanSampah.findMany({
|
prisma.pengelolaanSampah.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.pengelolaanSampah.count({
|
prisma.pengelolaanSampah.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
@@ -6,17 +7,26 @@ export default async function programPenghijauanFindMany(context: Context) {
|
|||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
const skip = (page - 1) * limit;
|
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 {
|
try {
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.programPenghijauan.findMany({
|
prisma.programPenghijauan.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.programPenghijauan.count({
|
prisma.programPenghijauan.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,56 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function jenjangPendidikanFindMany() {
|
export default async function jenjangPendidikanFindMany(context: Context) {
|
||||||
const data = await prisma.jenjangPendidikan.findMany();
|
const page = Number(context.query.page) || 1;
|
||||||
return {
|
const limit = Number(context.query.limit) || 10;
|
||||||
success: true,
|
const search = (context.query.search as string) || "";
|
||||||
data: data.map((item: any) => {
|
const skip = (page - 1) * limit;
|
||||||
return {
|
|
||||||
id: item.id,
|
// Buat where clause
|
||||||
nama: item.nama,
|
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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,64 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
// /api/berita/findManyPaginated.ts
|
// /api/berita/findManyPaginated.ts
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
async function lembagaPendidikanFindMany(context: Context) {
|
async function lembagaPendidikanFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
|
||||||
const limit = Number(context.query.limit) || 10;
|
|
||||||
const skip = (page - 1) * limit;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
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 jenjangPendidikanName = (context.query.jenjangPendidikanId as string) || "";
|
||||||
|
|
||||||
|
console.log('Lembaga API Query Params:', { page, limit, search, jenjangPendidikanName });
|
||||||
|
|
||||||
|
// Buat where clause
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Filter berdasarkan jenjang pendidikan (jika ada)
|
||||||
|
if (jenjangPendidikanName) {
|
||||||
|
// Cari jenjang pendidikan berdasarkan nama
|
||||||
|
const jenjangPendidikan = await prisma.jenjangPendidikan.findFirst({
|
||||||
|
where: {
|
||||||
|
nama: {
|
||||||
|
equals: jenjangPendidikanName,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
orderBy: { nama: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (jenjangPendidikan) {
|
||||||
|
where.jenjangId = jenjangPendidikan.id;
|
||||||
|
} else {
|
||||||
|
// Jika tidak ditemukan, return data kosong
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Jenjang pendidikan tidak ditemukan",
|
||||||
|
data: [],
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ nama: { contains: search, mode: "insensitive" } },
|
||||||
|
{ siswa: { nama: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ pengajar: { nama: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ jenjangPendidikan: { nama: { contains: search, mode: "insensitive" } } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.lembaga.findMany({
|
prisma.lembaga.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
include: {
|
include: {
|
||||||
jenjangPendidikan: true,
|
jenjangPendidikan: true,
|
||||||
siswa: true,
|
siswa: true,
|
||||||
@@ -18,18 +66,22 @@ async function lembagaPendidikanFindMany(context: Context) {
|
|||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
orderBy: { jenjangPendidikan: { nama: 'asc' } },
|
||||||
}),
|
}),
|
||||||
prisma.kegiatanDesa.count({
|
prisma.lembaga.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
console.log('Fetched data count:', data.length);
|
||||||
|
console.log('Total count:', total);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Success fetch lembaga pendidikan with pagination",
|
message: "Success fetch lembaga pendidikan with pagination",
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
limit,
|
||||||
totalPages: Math.ceil(total / limit),
|
totalPages: Math.ceil(total / limit),
|
||||||
total,
|
total,
|
||||||
};
|
};
|
||||||
@@ -37,7 +89,7 @@ async function lembagaPendidikanFindMany(context: Context) {
|
|||||||
console.error("Find many paginated error:", e);
|
console.error("Find many paginated error:", e);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Failed fetch lembaga pendidikan with pagination",
|
message: `Failed fetch lembaga pendidikan: ${e instanceof Error ? e.message : 'Unknown error'}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,97 @@
|
|||||||
// /api/berita/findManyPaginated.ts
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
async function pengajarFindMany(context: Context) {
|
async function pengajarFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
|
||||||
const limit = Number(context.query.limit) || 10;
|
|
||||||
const skip = (page - 1) * limit;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
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 jenjangPendidikanName = (context.query.jenjangPendidikanId as string) || "";
|
||||||
|
|
||||||
|
console.log('Pengajar API Query Params:', { page, limit, search, jenjangPendidikanId: jenjangPendidikanName });
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Filter berdasarkan jenjang pendidikan (jika ada)
|
||||||
|
if (jenjangPendidikanName) {
|
||||||
|
// Cari jenjang pendidikan berdasarkan nama
|
||||||
|
const jenjangPendidikan = await prisma.jenjangPendidikan.findFirst({
|
||||||
|
where: {
|
||||||
|
nama: {
|
||||||
|
equals: jenjangPendidikanName,
|
||||||
|
mode: 'insensitive'
|
||||||
|
},
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
orderBy: { nama: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (jenjangPendidikan) {
|
||||||
|
where.lembaga = {
|
||||||
|
...where.lembaga,
|
||||||
|
jenjangId: jenjangPendidikan.id
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Jika tidak ditemukan, return data kosong
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Jenjang pendidikan tidak ditemukan",
|
||||||
|
data: [],
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add search condition if search term exists
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ nama: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ lembaga: { nama: { contains: search, mode: 'insensitive' } } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.pengajar.findMany({
|
prisma.pengajar.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
include: {
|
include: {
|
||||||
lembaga: true,
|
lembaga: {
|
||||||
|
include: {
|
||||||
|
jenjangPendidikan: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
orderBy: { lembaga: { jenjangPendidikan: { nama: 'asc' } } },
|
||||||
}),
|
}),
|
||||||
prisma.pengajar.count({
|
prisma.pengajar.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
console.log('Fetched pengajar data count:', data.length);
|
||||||
|
console.log('Total pengajar count:', total);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Success fetch pengajar with pagination",
|
message: "Success fetch pengajar with pagination",
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
limit,
|
||||||
totalPages: Math.ceil(total / limit),
|
totalPages: Math.ceil(total / limit),
|
||||||
total,
|
total,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error("Find many paginated error:", e);
|
console.error("Error in pengajarFindMany:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Failed fetch pengajar with pagination",
|
message: `Failed fetch pengajar: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export default pengajarFindMany;
|
||||||
export default pengajarFindMany;
|
|
||||||
@@ -1,41 +1,96 @@
|
|||||||
// /api/berita/findManyPaginated.ts
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
async function siswaFindMany(context: Context) {
|
async function siswaFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
|
||||||
const limit = Number(context.query.limit) || 10;
|
|
||||||
const skip = (page - 1) * limit;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
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 jenjangPendidikanName = (context.query.jenjangPendidikanName as string) || "";
|
||||||
|
|
||||||
|
console.log('Siswa API Query Params:', { page, limit, search, jenjangPendidikanId: jenjangPendidikanName });
|
||||||
|
|
||||||
|
// Buat where clause
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Filter berdasarkan jenjang pendidikan (jika ada)
|
||||||
|
if (jenjangPendidikanName) {
|
||||||
|
// Cari jenjang pendidikan berdasarkan nama
|
||||||
|
const jenjangPendidikan = await prisma.jenjangPendidikan.findFirst({
|
||||||
|
where: {
|
||||||
|
nama: {
|
||||||
|
equals: jenjangPendidikanName,
|
||||||
|
mode: 'insensitive'
|
||||||
|
},
|
||||||
|
isActive: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (jenjangPendidikan) {
|
||||||
|
where.lembaga = {
|
||||||
|
...where.lembaga,
|
||||||
|
jenjangId: jenjangPendidikan.id
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Jika tidak ditemukan, return data kosong
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Jenjang pendidikan tidak ditemukan",
|
||||||
|
data: [],
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ nama: { contains: search, mode: "insensitive" } },
|
||||||
|
{ lembaga: { nama: { contains: search, mode: 'insensitive' } } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.siswa.findMany({
|
prisma.siswa.findMany({
|
||||||
where: { isActive: true },
|
where,
|
||||||
include: {
|
include: {
|
||||||
lembaga: true,
|
lembaga: {
|
||||||
|
include: {
|
||||||
|
jenjangPendidikan: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
orderBy: { lembaga: { jenjangPendidikan: { nama: 'asc' } } },
|
||||||
}),
|
}),
|
||||||
prisma.siswa.count({
|
prisma.siswa.count({
|
||||||
where: { isActive: true }
|
where,
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
console.log('Fetched siswa data count:', data.length);
|
||||||
|
console.log('Total siswa count:', total);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Success fetch siswa with pagination",
|
message: "Success fetch siswa with pagination",
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
limit,
|
||||||
totalPages: Math.ceil(total / limit),
|
totalPages: Math.ceil(total / limit),
|
||||||
total,
|
total,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error("Find many paginated error:", e);
|
console.error("Error in siswaFindMany:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Failed fetch siswa with pagination",
|
message: `Failed fetch siswa: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,53 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||||
import { Badge, Box, Button, Card, Center, Container, Divider, Flex, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
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 { IconArrowRight, IconCalendar } from '@tabler/icons-react';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function Semua() {
|
function Semua() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useTransitionRouter();
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
// Parameter URL
|
// Ambil parameter langsung dari URL
|
||||||
const search = searchParams.get('search') || '';
|
const search = searchParams.get('search') || '';
|
||||||
const currentPage = parseInt(searchParams.get('page') || '1');
|
const page = parseInt(searchParams.get('page') || '1');
|
||||||
const [page, setPage] = useState(currentPage);
|
|
||||||
|
|
||||||
// Gunakan proxy untuk state
|
// Gunakan proxy untuk state global
|
||||||
const state = useProxy(stateDashboardBerita.berita);
|
const state = useProxy(stateDashboardBerita.berita);
|
||||||
const featured = useProxy(stateDashboardBerita.berita.findFirst); // ✅ Berita utama
|
const featured = useProxy(stateDashboardBerita.berita.findFirst);
|
||||||
const loadingGrid = state.findMany.loading;
|
const loadingGrid = state.findMany.loading;
|
||||||
const loadingFeatured = featured.loading;
|
const loadingFeatured = featured.loading;
|
||||||
|
|
||||||
// Load berita utama (hanya sekali)
|
// Load berita utama sekali saja
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!featured.data && !loadingFeatured) {
|
if (!featured.data && !loadingFeatured) {
|
||||||
stateDashboardBerita.berita.findFirst.load();
|
stateDashboardBerita.berita.findFirst.load();
|
||||||
}
|
}
|
||||||
}, [featured.data, loadingFeatured]);
|
}, [featured.data, loadingFeatured]);
|
||||||
|
|
||||||
// Load berita terbaru (untuk grid) saat page/search berubah
|
// Load berita terbaru tiap page / search berubah
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const limit = 3; // Sesuaikan dengan tampilan grid
|
const limit = 3;
|
||||||
state.findMany.load(page, limit, search);
|
state.findMany.load(page, limit, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
// Update URL saat page berubah
|
// Handler pagination → langsung update URL
|
||||||
useEffect(() => {
|
const handlePageChange = (newPage: number) => {
|
||||||
const url = new URLSearchParams();
|
const url = new URLSearchParams(searchParams.toString());
|
||||||
if (search) url.set('search', search);
|
if (search) url.set('search', search);
|
||||||
if (page > 1) url.set('page', page.toString());
|
if (newPage > 1) url.set('page', newPage.toString());
|
||||||
|
else url.delete('page'); // biar page=1 ga muncul di URL
|
||||||
|
|
||||||
router.replace(`?${url.toString()}`);
|
router.replace(`?${url.toString()}`);
|
||||||
}, [search, page, router]);
|
};
|
||||||
|
|
||||||
const featuredData = featured.data;
|
const featuredData = featured.data;
|
||||||
const paginatedNews = state.findMany.data || [];
|
const paginatedNews = state.findMany.data || [];
|
||||||
@@ -51,7 +56,7 @@ function Semua() {
|
|||||||
return (
|
return (
|
||||||
<Box py={20}>
|
<Box py={20}>
|
||||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||||
{/* === Berita Utama (Tetap) === */}
|
{/* === Berita Utama === */}
|
||||||
{loadingFeatured ? (
|
{loadingFeatured ? (
|
||||||
<Center><Skeleton h={400} /></Center>
|
<Center><Skeleton h={400} /></Center>
|
||||||
) : featuredData ? (
|
) : featuredData ? (
|
||||||
@@ -94,7 +99,9 @@ function Semua() {
|
|||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
rightSection={<IconArrowRight size={16} />}
|
rightSection={<IconArrowRight size={16} />}
|
||||||
onClick={() => router.push(`/darmasaba/desa/berita/${featuredData.kategoriBerita?.name}/${featuredData.id}`)}
|
onClick={() =>
|
||||||
|
router.push(`/darmasaba/desa/berita/${featuredData.kategoriBerita?.name}/${featuredData.id}`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Baca Selengkapnya
|
Baca Selengkapnya
|
||||||
</Button>
|
</Button>
|
||||||
@@ -106,7 +113,7 @@ function Semua() {
|
|||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* === Berita Terbaru (Berubah Saat Pagination) === */}
|
{/* === Berita Terbaru === */}
|
||||||
<Box mt={50}>
|
<Box mt={50}>
|
||||||
<Title order={2} mb="md">Berita Terbaru</Title>
|
<Title order={2} mb="md">Berita Terbaru</Title>
|
||||||
<Divider mb="xl" />
|
<Divider mb="xl" />
|
||||||
@@ -122,13 +129,7 @@ function Semua() {
|
|||||||
) : (
|
) : (
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
||||||
{paginatedNews.map((item) => (
|
{paginatedNews.map((item) => (
|
||||||
<Card
|
<Card key={item.id} shadow="sm" p="lg" radius="md" withBorder>
|
||||||
key={item.id}
|
|
||||||
shadow="sm"
|
|
||||||
p="lg"
|
|
||||||
radius="md"
|
|
||||||
withBorder
|
|
||||||
>
|
|
||||||
<Card.Section>
|
<Card.Section>
|
||||||
<Image
|
<Image
|
||||||
src={item.image?.link || '/images/placeholder-small.jpg'}
|
src={item.image?.link || '/images/placeholder-small.jpg'}
|
||||||
@@ -143,7 +144,6 @@ function Semua() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||||
|
|
||||||
<Text size="sm" color="dimmed" lineClamp={3} mt="xs">{item.deskripsi}</Text>
|
<Text size="sm" color="dimmed" lineClamp={3} mt="xs">{item.deskripsi}</Text>
|
||||||
|
|
||||||
<Flex align="center" justify="apart" mt="md" gap="xs">
|
<Flex align="center" justify="apart" mt="md" gap="xs">
|
||||||
@@ -154,20 +154,28 @@ function Semua() {
|
|||||||
year: 'numeric'
|
year: 'numeric'
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Button
|
||||||
<Button p="xs" variant="light" rightSection={<IconArrowRight size={16} />} onClick={() => router.push(`/darmasaba/desa/berita/${item.kategoriBerita?.name}/${item.id}`)}>Baca Selengkapnya</Button>
|
p="xs"
|
||||||
|
variant="light"
|
||||||
|
rightSection={<IconArrowRight size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/darmasaba/desa/berita/${item.kategoriBerita?.name}/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Baca Selengkapnya
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pagination hanya untuk berita terbaru */}
|
{/* Pagination */}
|
||||||
<Center mt="xl">
|
<Center mt="xl">
|
||||||
<Pagination
|
<Pagination
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
value={page}
|
value={page}
|
||||||
onChange={setPage}
|
onChange={handlePageChange}
|
||||||
siblings={1}
|
siblings={1}
|
||||||
boundaries={1}
|
boundaries={1}
|
||||||
withEdges
|
withEdges
|
||||||
@@ -179,4 +187,4 @@ function Semua() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Semua;
|
export default Semua;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import { useParams } from 'next/navigation';
|
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../_com/BackButto';
|
import BackButton from '../_com/BackButto';
|
||||||
@@ -34,46 +34,44 @@ function Page() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.suratKeterangan.findUnique.load(id);
|
await state.suratKeterangan.findUnique.load(id);
|
||||||
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
|
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
|
||||||
setData(result);
|
setData(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Terjadi kesalahan saat memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="100vh" bg={colors.Bg}>
|
||||||
<Skeleton height={500} />
|
<Skeleton height={500} radius="md" animate />
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="100vh" bg={colors.Bg}>
|
||||||
<Text>Data tidak ditemukan</Text>
|
<Text fz="xl" c="dimmed">Maaf, data layanan tidak ditemukan</Text>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container w={{ base: "100%", md: "50%" }}>
|
<Container w={{ base: "100%", md: "60%" }}>
|
||||||
<Text
|
<Text
|
||||||
fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }}
|
fz={{ base: "2rem", md: "2.5rem", lg: "3rem" }}
|
||||||
ta="center"
|
ta="center"
|
||||||
fw="bold"
|
fw="bold"
|
||||||
>
|
>
|
||||||
@@ -81,19 +79,32 @@ function Page() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={0}>
|
<Stack gap="md">
|
||||||
<Text
|
<Text
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
fz={{ base: "h4", md: "h2" }}
|
fz={{ base: "md", md: "lg" }}
|
||||||
pb={20}
|
c="dark.7"
|
||||||
|
ta="justify"
|
||||||
/>
|
/>
|
||||||
{data.image2?.link && (
|
{data.image2?.link && (
|
||||||
<Center>
|
<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>
|
</Center>
|
||||||
)}
|
)}
|
||||||
<Group justify='center' mt="md">
|
<Group justify="center" mt="md">
|
||||||
<Button radius="lg" fz="h4" bg={colors['blue-button']}>
|
<Button
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
||||||
|
>
|
||||||
Ajukan Permohonan
|
Ajukan Permohonan
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,62 +1,95 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
"use client";
|
||||||
|
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
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 { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function PelayananPendudukNonPermanent() {
|
function PelayananPendudukNonPermanent() {
|
||||||
const state = useProxy(stateLayananDesa)
|
const state = useProxy(stateLayananDesa);
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
await state.pelayananPendudukNonPermanen.findById.load('1')
|
await state.pelayananPendudukNonPermanen.findById.load('1');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
const data = state.pelayananPendudukNonPermanen.findById.data;
|
||||||
|
|
||||||
const data = state.pelayananPendudukNonPermanen.findById.data
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py="lg">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton h={500} />
|
<Stack gap="lg">
|
||||||
|
<Skeleton height={40} radius="md" />
|
||||||
|
<Skeleton height={200} radius="md" />
|
||||||
|
<Skeleton height={30} radius="md" />
|
||||||
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Stack gap="xl">
|
||||||
<Box py={15}>
|
<Box>
|
||||||
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{data?.name}</Text>
|
<Text fz={{ base: "xl", md: "2xl" }} fw={700} lh={1.3} c="dark">
|
||||||
|
{data?.name || "Judul belum tersedia"}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"} dangerouslySetInnerHTML={{__html: data?.deskripsi || ''}} />
|
|
||||||
<Divider color={colors["blue-button"]} />
|
<Box>
|
||||||
<Flex justify={"space-between"} py={20}>
|
{data?.deskripsi ? (
|
||||||
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
|
<Text
|
||||||
<Box>
|
fz={{ base: "sm", md: "md" }}
|
||||||
<Flex gap={"lg"}>
|
lh={1.7}
|
||||||
<ActionIcon variant='transparent'>
|
ta="justify"
|
||||||
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
|
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>
|
||||||
<ActionIcon variant='transparent'>
|
</Tooltip>
|
||||||
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
|
<Tooltip label="Bagikan ke Instagram" withArrow>
|
||||||
|
<ActionIcon size="lg" radius="xl" variant="subtle" color="pink">
|
||||||
|
<IconBrandInstagram size={24} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon variant='transparent'>
|
</Tooltip>
|
||||||
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
|
<Tooltip label="Bagikan ke Twitter" withArrow>
|
||||||
|
<ActionIcon size="lg" radius="xl" variant="subtle" color="blue">
|
||||||
|
<IconBrandTwitter size={24} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon variant='transparent'>
|
</Tooltip>
|
||||||
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
|
<Tooltip label="Bagikan ke WhatsApp" withArrow>
|
||||||
|
<ActionIcon size="lg" radius="xl" variant="subtle" color="green">
|
||||||
|
<IconBrandWhatsapp size={24} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider color={colors["blue-button"]} pb={50} />
|
|
||||||
</Box>
|
<Divider color={colors["blue-button"]} size="sm" />
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
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 { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ function PelayananPerizinanBerusaha() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.pelayananPerizinanBerusaha.findById.load('1')
|
await state.pelayananPerizinanBerusaha.findById.load('1')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -28,76 +29,87 @@ function PelayananPerizinanBerusaha() {
|
|||||||
|
|
||||||
const data = state.pelayananPerizinanBerusaha.findById.data;
|
const data = state.pelayananPerizinanBerusaha.findById.data;
|
||||||
|
|
||||||
if (!data) {
|
if (!data && !loading) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center mih={300}>
|
||||||
<Text>Data tidak tersedia</Text>
|
<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>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'md', md: 'xl' }} py="lg">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Center>
|
<Center mih={300}>
|
||||||
<Skeleton h={250} />
|
<Loader size="lg" color="blue" />
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Stack gap="lg">
|
||||||
<Box py={15}>
|
<Box>
|
||||||
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
|
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
|
||||||
</Box>
|
Perizinan Berusaha Berbasis Risiko melalui OSS
|
||||||
<Text
|
</Title>
|
||||||
py={10}
|
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||||
ta={"justify"}
|
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
|
||||||
fz={{ base: "sm", md: 'h3' }}
|
</Text>
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '' }}
|
</Box>
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<Group justify="center" mt="xl">
|
<Text fz={{ base: 'sm', md: 'md' }} ta="justify" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '' }} />
|
||||||
<Button variant="default" onClick={prevStep}>Back</Button>
|
|
||||||
<Button onClick={nextStep}>Next step</Button>
|
<Box>
|
||||||
</Group>
|
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>Alur pendaftaran NIB:</Text>
|
||||||
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>
|
<Stepper active={active} onStepClick={setActive} orientation="vertical" color="blue" radius="md"
|
||||||
Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
|
styles={{
|
||||||
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
|
step: { padding: '14px 0' },
|
||||||
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.
|
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>
|
</Text>
|
||||||
</Box>
|
</Stack>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,96 +1,124 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
"use client";
|
||||||
|
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
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 { useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function PelayananSuratKeterangan({ search }: { search: string }) {
|
function PelayananSuratKeterangan({ search }: { search: string }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const state = useProxy(stateLayananDesa)
|
const state = useProxy(stateLayananDesa);
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
if (!state.suratKeterangan.findMany.data) return [];
|
if (!state.suratKeterangan.findMany.data) return [];
|
||||||
return state.suratKeterangan.findMany.data.filter(item => {
|
return state.suratKeterangan.findMany.data.filter((item) => {
|
||||||
const keyword = search.toLowerCase();
|
const keyword = search.toLowerCase();
|
||||||
return (
|
return item.name?.toLowerCase().includes(keyword);
|
||||||
item.name?.toLowerCase().includes(keyword)
|
});
|
||||||
);
|
|
||||||
})
|
|
||||||
}, [state.suratKeterangan.findMany.data, search]);
|
}, [state.suratKeterangan.findMany.data, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.suratKeterangan.findMany.load()
|
await state.suratKeterangan.findMany.load();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pb={10}>
|
<Box pb="xl">
|
||||||
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
|
<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
|
<SimpleGrid
|
||||||
py={20}
|
py="lg"
|
||||||
cols={{
|
cols={{ base: 1, sm: 2, md: 3 }}
|
||||||
base: 1,
|
spacing="lg"
|
||||||
sm: 3
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Center>
|
Array.from({ length: 3 }).map((_, i) => (
|
||||||
<Skeleton h={250} />
|
<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>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
filteredData.map((v, k) => {
|
filteredData.map((v, k) => (
|
||||||
return (
|
<BackgroundImage
|
||||||
<BackgroundImage
|
key={k}
|
||||||
key={k}
|
src={v.image?.link || ''}
|
||||||
src={v.image?.link || ''}
|
h={250}
|
||||||
h={250}
|
radius="lg"
|
||||||
radius={16}
|
pos="relative"
|
||||||
pos={"relative"}
|
style={{
|
||||||
>
|
boxShadow: "0 4px 20px rgba(0,0,0,0.1)",
|
||||||
<Box
|
overflow: "hidden",
|
||||||
style={{
|
}}
|
||||||
borderRadius: 16,
|
>
|
||||||
zIndex: 0
|
<Box
|
||||||
}}
|
pos="absolute"
|
||||||
pos={"absolute"}
|
w="100%"
|
||||||
w={"100%"}
|
h="100%"
|
||||||
h={"100%"}
|
bg="rgba(0,0,0,0.45)"
|
||||||
bg={colors.trans.dark[2]}
|
style={{ borderRadius: 16 }}
|
||||||
/>
|
/>
|
||||||
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
|
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
||||||
<Box p={"lg"}>
|
<Text
|
||||||
<Text
|
c="white"
|
||||||
c={"white"}
|
fw={600}
|
||||||
size={"1.5rem"}
|
fz="lg"
|
||||||
style={{
|
ta="center"
|
||||||
textAlign: "center",
|
lineClamp={2}
|
||||||
}}>{v.name}</Text>
|
>
|
||||||
</Box>
|
{v.name}
|
||||||
<Group justify="center">
|
</Text>
|
||||||
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
|
<Group justify="center">
|
||||||
onClick={() => router.push(`/darmasaba/desa/layanan/${v.id}`)}>
|
<Button
|
||||||
Detail
|
size="md"
|
||||||
</Button>
|
radius="xl"
|
||||||
</Group>
|
bg={colors["blue-button"]}
|
||||||
</Stack>
|
px="lg"
|
||||||
</BackgroundImage>
|
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>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
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 { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -11,22 +12,22 @@ interface ServiceItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PelayananTelunjukSaktiDesa() {
|
function PelayananTelunjukSaktiDesa() {
|
||||||
const state = useProxy(stateLayananDesa)
|
const state = useProxy(stateLayananDesa);
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
await state.pelayananTelunjukSaktiDesa.findMany.load()
|
await state.pelayananTelunjukSaktiDesa.findMany.load();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{
|
const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{
|
||||||
name: string;
|
name: string;
|
||||||
@@ -37,28 +38,63 @@ function PelayananTelunjukSaktiDesa() {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
deletedAt: Date | null;
|
deletedAt: Date | null;
|
||||||
}>
|
}>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<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 ? (
|
{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) => {
|
<Grid gutter="lg">
|
||||||
return (
|
{data.map((v, k) => (
|
||||||
<Box key={k}>
|
<Grid.Col span={{ base: 12, sm: 6, lg: 4 }} key={k}>
|
||||||
<Box py={10}>
|
<Card
|
||||||
<Flex gap={"3"} align={"center"}>
|
shadow="md"
|
||||||
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{v.name}
|
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>
|
||||||
<Text span fz={{ base: "h4", md: "h3" }}>
|
<Flex gap="xs" align="center">
|
||||||
<a href={v.link} target="_blank" rel="noopener noreferrer" style={{ color: '#228be6' }}>{v.deskripsi}</a>
|
<IconExternalLink size={18} stroke={1.5} />
|
||||||
</Text>
|
<Text
|
||||||
</Flex>
|
component="a"
|
||||||
</Box>
|
href={v.link}
|
||||||
</Box>
|
target="_blank"
|
||||||
)
|
rel="noopener noreferrer"
|
||||||
})
|
fz="sm"
|
||||||
|
c="blue"
|
||||||
|
td="underline"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
{v.deskripsi}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</Grid.Col>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import PelayananPendudukNonPermanent from "./_com/pelayananPendudukNonPermanent"
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
@@ -40,14 +40,16 @@ export default function Page() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
{/* Bagian Pelayanan Surat Keterangan */}
|
<Stack gap={"xl"}>
|
||||||
<PelayananSuratKeterangan search={search} />
|
{/* Bagian Pelayanan Surat Keterangan */}
|
||||||
{/* Bagian Pelayanan Perizinan Berusaha */}
|
<PelayananSuratKeterangan search={search} />
|
||||||
<PelayananPerizinanBerusaha/>
|
{/* Bagian Pelayanan Perizinan Berusaha */}
|
||||||
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
|
<PelayananPerizinanBerusaha />
|
||||||
<PelayananTelunjukSaktiDesa/>
|
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
|
||||||
{/* Bagian Pelayanan Penduduk Non Permanent */}
|
<PelayananTelunjukSaktiDesa />
|
||||||
<PelayananPendudukNonPermanent/>
|
{/* Bagian Pelayanan Penduduk Non Permanent */}
|
||||||
|
<PelayananPendudukNonPermanent />
|
||||||
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
||||||
import colors from '@/con/colors';
|
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 { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../layanan/_com/BackButto';
|
import BackButton from '../../layanan/_com/BackButto';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||||
const state = useProxy(potensiDesaState.potensiDesa)
|
const state = useProxy(potensiDesaState.potensiDesa);
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -22,54 +22,66 @@ function Page() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.findUnique.load(id);
|
await state.findUnique.load(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [id])
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="80vh">
|
||||||
<Skeleton height={500} />
|
<Stack align="center" gap="md">
|
||||||
|
<Loader size="lg" color="blue" />
|
||||||
|
<Text c="dimmed" fz="sm">Sedang memuat informasi...</Text>
|
||||||
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="80vh">
|
||||||
<Text>Data tidak ditemukan</Text>
|
<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>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" 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>
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
<BackButton />
|
||||||
{state.findUnique.data?.deskripsi || ''}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</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>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
|
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
||||||
import colors from '@/con/colors';
|
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 { useTransitionRouter } from 'next-view-transitions';
|
||||||
import BackButton from '../layanan/_com/BackButto';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
import BackButton from '../layanan/_com/BackButto';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const state = useProxy(potensiDesaState)
|
const state = useProxy(potensiDesaState)
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
state.kategoriPotensi.findMany.load()
|
state.kategoriPotensi.findMany.load()
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
await state.potensiDesa.findMany.load()
|
await state.potensiDesa.findMany.load()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -30,104 +30,121 @@ function Page() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const data = state.potensiDesa.findMany.data
|
const data = state.potensiDesa.findMany.data
|
||||||
// const kategoriData = state.kategoriPotensi.findMany.data
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Box>
|
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
|
||||||
<Stack gap={0}>
|
<Stack gap="sm" maw={600}>
|
||||||
<Flex justify={"space-between"} align={"center"} >
|
<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>
|
<Box>
|
||||||
<Text fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta="center" fz="2rem" fw={800} c="white">
|
||||||
Potensi Desa
|
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"justify"} >
|
<Text ta="center" fz="sm" c="white" fw={500}>
|
||||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba.
|
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>
|
</Text>
|
||||||
</Box>
|
</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>
|
</Flex>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
<SimpleGrid
|
<SimpleGrid py={48} cols={{ base: 1, sm: 2, lg: 3 }} spacing="2xl">
|
||||||
py={20}
|
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
sm: 3
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Center>
|
Array.from({ length: 6 }).map((_, i) => (
|
||||||
<Skeleton h={250} />
|
<Skeleton key={i} h={360} radius="xl" />
|
||||||
</Center>
|
))
|
||||||
) : (
|
) : data && data.length > 0 ? (
|
||||||
data?.map((v, k) => {
|
data.map((v, k) => (
|
||||||
return (
|
|
||||||
<BackgroundImage
|
<BackgroundImage
|
||||||
key={k}
|
key={k}
|
||||||
src={v.image?.link || ''}
|
src={v.image?.link || ''}
|
||||||
h={350}
|
h={360}
|
||||||
radius={16}
|
radius="xl"
|
||||||
pos={"relative"}
|
style={{ overflow: 'hidden', position: 'relative' }}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
pos="absolute"
|
||||||
borderRadius: 16,
|
inset={0}
|
||||||
zIndex: 0
|
bg="linear-gradient(180deg, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.7) 100%)"
|
||||||
}}
|
|
||||||
pos={"absolute"}
|
|
||||||
w={"100%"}
|
|
||||||
h={"100%"}
|
|
||||||
bg={colors.trans.dark[2]}
|
|
||||||
/>
|
/>
|
||||||
<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>
|
<Group>
|
||||||
<Paper radius={"lg"} py={7} px={10}>
|
<Paper radius="lg" py={6} px={12} shadow="md" withBorder bg="rgba(255,255,255,0.85)">
|
||||||
<Text>{v.kategori?.nama}</Text>
|
<Text fz="sm" fw={600}>{v.kategori?.nama}</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Group>
|
</Group>
|
||||||
<Box p={"lg"}>
|
<Box>
|
||||||
<Text
|
<Text
|
||||||
fw={"bold"}
|
fw={800}
|
||||||
c={"white"}
|
c="white"
|
||||||
size={"1.8rem"}
|
fz="xl"
|
||||||
style={{
|
ta="center"
|
||||||
textAlign: "center",
|
lineClamp={2}
|
||||||
}}>{v.name}</Text>
|
lh={1.3}
|
||||||
|
>
|
||||||
|
{v.name}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Group justify="center">
|
<Group justify="center">
|
||||||
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
|
<Tooltip label="Lihat detail potensi" withArrow>
|
||||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}>
|
<Button
|
||||||
Detail
|
radius="xl"
|
||||||
</Button>
|
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>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</BackgroundImage>
|
</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>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
|
||||||
import { Box, Stack, Paper, Image, Text, Center, Skeleton } from '@mantine/core';
|
import colors from '@/con/colors'
|
||||||
import React, { useEffect } from 'react';
|
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useEffect } from 'react'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import { useProxy } from 'valtio/utils'
|
||||||
|
|
||||||
function LambangDesa() {
|
function LambangDesa() {
|
||||||
const state = useProxy(stateProfileDesa.lambangDesa)
|
const state = useProxy(stateProfileDesa.lambangDesa)
|
||||||
@@ -17,26 +17,60 @@ function LambangDesa() {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="lg">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={420} radius="xl" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pb={70}>
|
<Box pb={90}>
|
||||||
<Stack align='center' gap={0}>
|
<Stack align="center" gap="lg">
|
||||||
<Box pb={30}>
|
<Box pb="lg">
|
||||||
<Center>
|
<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>
|
</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>
|
</Box>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
<Paper
|
||||||
<Text fz={{ base: "md", md: "h3"}} ta={"justify"} dangerouslySetInnerHTML={{__html: data.deskripsi}} />
|
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>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LambangDesa;
|
export default LambangDesa
|
||||||
|
|||||||
@@ -1,59 +1,95 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
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, Card, Center, Group, Image, Loader, Paper, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { Box, Card, Center, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconPhoto } from '@tabler/icons-react';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
function MaskotDesa() {
|
function MaskotDesa() {
|
||||||
const state = useProxy(stateProfileDesa.maskotDesa)
|
const state = useProxy(stateProfileDesa.maskotDesa);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
state.findUnique.load("edit")
|
state.findUnique.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const { data, loading } = state.findUnique
|
const { data, loading } = state.findUnique;
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Center mih={500}>
|
||||||
<Skeleton h={500} />
|
<Stack align="center" gap="sm">
|
||||||
</Box>
|
<Loader size="lg" color="blue" />
|
||||||
)
|
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pb={70}>
|
<Box pb={80}>
|
||||||
<Stack align='center' gap={0}>
|
<Stack align="center" gap="xl">
|
||||||
<Box pb={30}>
|
<Stack align="center" gap={10}>
|
||||||
<Center>
|
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} />
|
||||||
<Image src={"/pudak-icon.png"} alt="" w={{ base: 200, md: 300 }} />
|
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
|
||||||
</Center>
|
</Stack>
|
||||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Maskot Desa</Text>
|
|
||||||
</Box>
|
<Paper
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
p={{ base: 'md', md: 'xl' }}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.deskripsi }} />
|
radius="lg"
|
||||||
<Group wrap="wrap" gap="md">
|
shadow="sm"
|
||||||
{data.images.map((img, index) => (
|
withBorder
|
||||||
<Card key={index} p="xs" w={220}>
|
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
|
||||||
<Image
|
>
|
||||||
src={img.image.link}
|
<Text
|
||||||
alt={img.label}
|
fz={{ base: 'sm', md: 'lg' }}
|
||||||
w={200}
|
lh={1.7}
|
||||||
h={200}
|
ta="justify"
|
||||||
fit="cover"
|
c="dark"
|
||||||
radius="md"
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
style={{ border: '1px solid #ccc', objectFit: 'cover' }}
|
/>
|
||||||
/>
|
|
||||||
<Text ta="center" mt="xs" fw="bold">{img.label}</Text>
|
<Group justify="center" gap="lg" mt="lg">
|
||||||
</Card>
|
{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>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default MaskotDesa;
|
export default MaskotDesa;
|
||||||
|
|||||||
@@ -1,75 +1,133 @@
|
|||||||
import colors from '@/con/colors';
|
'use client'
|
||||||
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
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 = [
|
const dataText = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "SANTUN",
|
title: "Santun",
|
||||||
description: "Memberikan pelayanan yang baik, penuh rasa empati, sopan, dan beretika"
|
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: "ADAPTIF",
|
title: "Adaptif",
|
||||||
description: "Cepat menyesuaikan diri menghadapi perubahan dan bertindak proaktif"
|
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: "INOVATIF",
|
title: "Inovatif",
|
||||||
description: "Selalu berusaha menciptakan pembaruan atau kreasi baru"
|
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
title: "PROFESIONAL",
|
title: "Profesional",
|
||||||
description: "Memiliki pengetahuan, terampil dan bertanggung jawab"
|
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
title: "GESIT",
|
title: "Gesit",
|
||||||
description: "Inisiatif dan cekatan dalam bekerja"
|
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
const letters = ["S", "I", "G", "A", "P"];
|
||||||
|
|
||||||
function MotoDesa() {
|
function MotoDesa() {
|
||||||
return (
|
return (
|
||||||
<Box pb={70}>
|
<Box pb={80} px={{ base: "md", md: "xl" }}>
|
||||||
<Stack align='center' gap={0} >
|
<Stack align="center" gap="lg">
|
||||||
<Box pb={30}>
|
<Box>
|
||||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h1", md: "2.5rem" }}>Moto Desa Darmasaba</Text>
|
<Text
|
||||||
</Box>
|
ta="center"
|
||||||
<Flex gap={50} pb={50} pt={20} justify={"space-evenly"} align={"center"} >
|
fw={800}
|
||||||
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
|
fz={{ base: "2rem", md: "2.8rem" }}
|
||||||
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>S</Text>
|
style={{
|
||||||
</ActionIcon>
|
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
|
||||||
<ActionIcon bg={colors['blue-button']} radius={150} p={{ base: "lg", md: "xl" }}>
|
WebkitBackgroundClip: "text",
|
||||||
<Text c={colors['white-1']} ta={"center"} fw={"bold"} fz={{ base: "h2", md: "h1" }}>I</Text>
|
WebkitTextFillColor: "transparent",
|
||||||
</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,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{dataText.map((v, k) => {
|
Moto Desa Darmasaba
|
||||||
return (
|
</Text>
|
||||||
<Box key={k}>
|
</Box>
|
||||||
<Text fw={"bold"} fz={{ base: "lg", md: "h3" }}>{v.title}</Text>
|
|
||||||
<Text fz={{ base: "sm", md: "md" }}>{v.description}</Text>
|
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
|
||||||
</Box>
|
{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>
|
</SimpleGrid>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Text mb={40} c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h4", md: "h3" }}>"Berkomitmen memberikan pelayanan terbaik dengan prinsip SIGAP untuk masyarakat Desa Darmasaba"</Text>
|
|
||||||
|
<Text
|
||||||
|
ta="center"
|
||||||
|
fw={700}
|
||||||
|
fz={{ base: "md", md: "xl" }}
|
||||||
|
c="blue.8"
|
||||||
|
mt="md"
|
||||||
|
style={{
|
||||||
|
maxWidth: 720,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
"Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
|
||||||
|
<Text span fw={800} c="cyan.6">
|
||||||
|
SIGAP
|
||||||
|
</Text>{" "}
|
||||||
|
untuk masyarakat Desa Darmasaba."
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
import colors from '@/con/colors';
|
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 { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconUser, IconBriefcase, IconUsers, IconTargetArrow } from '@tabler/icons-react';
|
||||||
|
|
||||||
function ProfilPerbekel() {
|
function ProfilPerbekel() {
|
||||||
const state = useProxy(stateProfileDesa.profilPerbekel)
|
const state = useProxy(stateProfileDesa.profilPerbekel)
|
||||||
@@ -17,77 +18,155 @@ function ProfilPerbekel() {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={20} px="md">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pb={70}>
|
<Box pb={80} px="md">
|
||||||
<Stack align='center' gap={0}>
|
<Stack align="center" gap={0} mb={40}>
|
||||||
<Box pb={30}>
|
<Text
|
||||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Profil Perbekel</Text>
|
c={colors['blue-button']}
|
||||||
</Box>
|
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>
|
</Stack>
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
|
||||||
base: 1,
|
|
||||||
md: 2,
|
|
||||||
}}
|
|
||||||
pb={50}
|
|
||||||
>
|
|
||||||
<Box>
|
<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}>
|
<Stack gap={0}>
|
||||||
<Image
|
<Image
|
||||||
pt={{ base: 0, md: 120 }}
|
pt={{ base: 0, md: 100 }}
|
||||||
px={"lg"}
|
px="lg"
|
||||||
src={data.image?.link || "/perbekel.png"}
|
src={data.image?.link || "/perbekel.png"}
|
||||||
sizes='100%'
|
alt="Foto Perbekel"
|
||||||
alt=''
|
radius="lg"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
e.currentTarget.src = "/perbekel.png";
|
e.currentTarget.src = "/perbekel.png";
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Paper
|
<Paper
|
||||||
bg={colors['blue-button']}
|
bg={colors['blue-button']}
|
||||||
px={"lg"}
|
px="lg"
|
||||||
|
radius="0 0 var(--mantine-radius-xl) var(--mantine-radius-xl)"
|
||||||
className="glass3"
|
className="glass3"
|
||||||
py={{ base: 20, md: 50 }}
|
py={{ base: 20, md: 50 }}
|
||||||
|
|
||||||
>
|
>
|
||||||
<Text c={colors['white-1']} fz={{ base: "md", md: "h3" }}>Perbekel Desa Darmasaba</Text>
|
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
|
||||||
<Text c={colors['white-1']} fw={"bolder"} fz={{ base: "md", md: "h2" }}>
|
Perbekel Desa Darmasaba
|
||||||
I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P.
|
</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>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Biodata</Text>
|
p="xl"
|
||||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.biodata }} />
|
bg={colors['white-trans-1']}
|
||||||
</Box>
|
w="100%"
|
||||||
<Box>
|
radius="xl"
|
||||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman</Text>
|
shadow="md"
|
||||||
<Text fz={{ base: "1rem", md: "1.5rem" }} dangerouslySetInnerHTML={{ __html: data.pengalaman }} />
|
withBorder
|
||||||
</Box>
|
>
|
||||||
|
<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>
|
</Paper>
|
||||||
</SimpleGrid >
|
</SimpleGrid>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman Organisasi</Text>
|
p="xl"
|
||||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }} />
|
bg={colors['white-trans-1']}
|
||||||
</Box>
|
w="100%"
|
||||||
<Box pb={20}>
|
radius="xl"
|
||||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Program Kerja Unggulan</Text>
|
shadow="md"
|
||||||
<Box px={20}>
|
withBorder
|
||||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.programUnggulan }} />
|
>
|
||||||
|
<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>
|
|
||||||
|
<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>
|
</Paper>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,39 +7,69 @@ import { useEffect } from 'react';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function SejarahDesa() {
|
function SejarahDesa() {
|
||||||
const state = useProxy(stateProfileDesa.sejarahDesa)
|
const state = useProxy(stateProfileDesa.sejarahDesa);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
state.findUnique.load("edit")
|
state.findUnique.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const { data, loading } = state.findUnique
|
const { data, loading } = state.findUnique;
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="lg">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box py="xl">
|
||||||
<Box pb={70}>
|
<Stack align="center" gap="xl">
|
||||||
<Stack align='center' gap={0}>
|
<Stack align="center" gap="sm">
|
||||||
<Box pb={30}>
|
<Center>
|
||||||
<Center>
|
<Image
|
||||||
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
|
src="/darmasaba-icon.png"
|
||||||
</Center>
|
alt="Ikon Desa Darmasaba"
|
||||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Sejarah Desa</Text>
|
w={{ base: 180, md: 260 }}
|
||||||
</Box>
|
radius="md"
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: data.deskripsi }} />
|
/>
|
||||||
</Paper>
|
</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>
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,102 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'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 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 { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconUser } from '@tabler/icons-react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function SemuaPerbekel() {
|
function SemuaPerbekel() {
|
||||||
const state = useProxy(stateProfileDesa.mantanPerbekel)
|
const state = useProxy(stateProfileDesa.mantanPerbekel);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findMany.load()
|
state.findMany.load();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const {data, loading} = state.findMany
|
const { data, loading } = state.findMany;
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="xl" />
|
||||||
</Box>
|
</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 (
|
return (
|
||||||
<Box pb={50}>
|
<Box pb={80}>
|
||||||
<Stack align='center'>
|
<Stack align="center" gap="lg">
|
||||||
<Box pb={30}>
|
<Box>
|
||||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={{ base: "h1", md: "2.5rem" }}>Perbekel Dari Masa Ke Masa</Text>
|
<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>
|
</Box>
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
|
||||||
base: 1,
|
{data.map((v: any, k: number) => (
|
||||||
md: 3,
|
<Paper
|
||||||
}}
|
key={k}
|
||||||
>
|
radius="2xl"
|
||||||
{data.map((v, k) => {
|
shadow="md"
|
||||||
return (
|
withBorder
|
||||||
<Box key={k}>
|
p="lg"
|
||||||
<Paper h={620} mb={50} radius={26} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
bg="white"
|
||||||
<Box>
|
style={{
|
||||||
<Center>
|
transition: "all 250ms ease",
|
||||||
<Image src={v.image?.link} alt='' />
|
}}
|
||||||
</Center>
|
className="hover:shadow-xl hover:scale-[1.02]"
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Stack gap="md" align="center">
|
||||||
<Stack gap={"sm"} py={10}>
|
<Box w="100%" h={300} style={{ overflow: "hidden", borderRadius: "1rem" }}>
|
||||||
<Text ta={"center"} fw={"bold"} fz={{ base: "h3", md: "h3" }}>{v.nama}</Text>
|
<Image
|
||||||
<Text ta={"center"} fz={{ base: "h5", md: "h4" }}>{v.daerah}</Text>
|
src={v.image?.link}
|
||||||
<Text ta={"center"} fz={{ base: "h5", md: "h4" }}>{v.periode}</Text>
|
alt={v.nama || "Foto Perbekel"}
|
||||||
</Stack>
|
fit="cover"
|
||||||
</Box>
|
h="100%"
|
||||||
</Paper>
|
w="100%"
|
||||||
</Box>
|
/>
|
||||||
)
|
</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>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -6,43 +6,90 @@ import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function VisimisiDesa() {
|
function VisiMisiDesa() {
|
||||||
const state = useProxy(stateProfileDesa.visiMisiDesa)
|
const state = useProxy(stateProfileDesa.visiMisiDesa);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
state.findUnique.load("edit")
|
state.findUnique.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const { data, loading } = state.findUnique
|
const { data, loading } = state.findUnique;
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box py="xl">
|
||||||
<Box pb={30}>
|
<Skeleton h={500} radius="lg" />
|
||||||
<Stack align='center' gap={0}>
|
</Box>
|
||||||
<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 >
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|||||||
@@ -5,112 +5,130 @@ import { IconArrowRight } from '@tabler/icons-react';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
px={{ base: 20, md: 100 }}
|
px={{ base: 20, md: 100 }}
|
||||||
cols={{
|
cols={{ base: 1, md: 2 }}
|
||||||
base: 1,
|
spacing="xl"
|
||||||
md: 2
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Content 1 */}
|
<Box pt={{ base: 0, md: 35 }}>
|
||||||
<Box pt={{base: 0, md: 35}}>
|
<Text c={colors['blue-button']} fz={{ base: 'h4', md: 'h3' }}>
|
||||||
<Text c={colors["blue-button"]} fz={{ base: "h4", md: "h3" }} >
|
Community Safety & Crime Prevention
|
||||||
Info Keamanan dan Pencegahan Kriminalitas
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
|
||||||
Kontak Darurat
|
Emergency Contacts
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
|
<Button
|
||||||
Lihat Detail
|
rightSection={<IconArrowRight />}
|
||||||
|
mt={20}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
c={colors['white-1']}
|
||||||
|
>
|
||||||
|
View Details
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Content 2 */}
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
<Paper p="lg" radius="xl" shadow="md">
|
||||||
<Paper p={"lg"}>
|
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
|
||||||
<Text c={colors["blue-button"]} fw={'bold'} fz={{ base: "h4", md: "h3" }} >
|
How to Keep Your Neighborhood Safe
|
||||||
Tips menjaga keamanan lingkungan
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h5", md: "h4" }} c={colors["blue-button"]} >
|
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
|
||||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
Practical safety habits everyone can apply daily to reduce risks.
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
|
<Button
|
||||||
Lihat Detail
|
rightSection={<IconArrowRight />}
|
||||||
|
mt={20}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
c={colors['white-1']}
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Paper p={"lg"}>
|
<Paper p="lg" radius="xl" shadow="md">
|
||||||
<Text c={colors["blue-button"]} fw={'bold'} fz={{ base: "h4", md: "h3" }} >
|
<Text c={colors['blue-button']} fw="bold" fz={{ base: 'h4', md: 'h3' }}>
|
||||||
Mengenali tanda-tanda kegiatan kriminal
|
Recognizing Criminal Activities
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h5", md: "h4" }} c={colors["blue-button"]} >
|
<Text fz={{ base: 'h5', md: 'h4' }} c={colors['blue-button']} mt="sm">
|
||||||
the printing and typesetting industry. the printing and typesetting industry.
|
Key warning signs and behavior patterns you should stay aware of.
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button rightSection={<IconArrowRight />} mt={20} radius={10} bg={colors["blue-button"]} c={colors["white-1"]}>
|
<Button
|
||||||
Lihat Detail
|
rightSection={<IconArrowRight />}
|
||||||
|
mt={20}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
c={colors['white-1']}
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
{/* Content 3 */}
|
<Paper p="xl" radius="xl" shadow="lg">
|
||||||
<Paper p={'xl'}>
|
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} c={colors["blue-button"]} fw={"bold"}>
|
Ongoing Security Programs
|
||||||
Program Keamanan Rutin
|
|
||||||
</Text>
|
</Text>
|
||||||
<Stack pt={30} gap={'lg'}>
|
<Stack pt={30} gap="lg">
|
||||||
<Box>
|
{['Night Patrol', 'Neighborhood Watch', 'Emergency Preparedness'].map((program, i) => (
|
||||||
<Paper p={"md"} bg={colors['blue-button']} w={{ base: "100%", md: "100%" }}>
|
<Paper key={i} p="md" bg={colors['blue-button']} radius="md" shadow="sm">
|
||||||
<Flex align={'center'} justify={'space-between'}>
|
<Flex align="center" justify="space-between">
|
||||||
<Text fz={'h3'} c={colors['white-1']}>Ronda Malam</Text>
|
<Text fz="h3" c={colors['white-1']}>
|
||||||
<IconArrowRight size={30} color={colors['white-1']} />
|
{program}
|
||||||
|
</Text>
|
||||||
|
<IconArrowRight size={28} color={colors['white-1']} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Paper>
|
</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}>
|
<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>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
{/* Content 4 */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<Paper p={'xl'} >
|
<Paper p="xl" radius="xl" shadow="lg">
|
||||||
<Box style={{ maxWidth: "560px", width: "100%", aspectRatio: "16/9" }}>
|
<Box style={{ maxWidth: 560, width: '100%', aspectRatio: '16/9' }}>
|
||||||
<iframe width="100%"
|
<iframe
|
||||||
|
width="100%"
|
||||||
height="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>
|
</Box>
|
||||||
<Text py={10} fz={{ base: "h3", md: "h2" }} fw={'bold'} c={colors['blue-button']}>
|
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
|
||||||
Patroli Malam Darmasaba
|
Darmasaba Night Patrol
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={'h4'} >
|
<Text fz="h4" c={colors['blue-button']}>
|
||||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
A closer look at how the community works together to maintain safety.
|
||||||
</Text>
|
</Text>
|
||||||
<Box pt={10}>
|
<Box pt={10}>
|
||||||
<Button bg={colors['blue-button']} rightSection={<IconArrowRight size={20} color={colors['white-1']} />}>
|
<Button
|
||||||
Lihat Video Lainnya
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
|
||||||
|
>
|
||||||
|
Watch More Videos
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,158 +2,155 @@
|
|||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(artikelKesehatanState)
|
const state = useProxy(artikelKesehatanState);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="xl" px="md">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={420} radius="lg" />
|
||||||
|
<Skeleton h={20} mt="md" w="60%" />
|
||||||
|
<Skeleton h={20} w="40%" />
|
||||||
|
<Skeleton h={200} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Paper radius={10}>
|
<Stack gap="lg">
|
||||||
<Box style={{ borderTopLeftRadius: 10, borderTopRightRadius: 10 }} bg={colors['blue-button']}>
|
<Paper radius="xl" shadow="md" withBorder>
|
||||||
<Text p={'md'} fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw={"bold"}>
|
<Box style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} bg={colors['blue-button']}>
|
||||||
Detail Lengkap Fasilitas Kesehatan
|
<Text p="md" fz={{ base: 'h3', md: 'h2' }} c={colors['white-1']} fw="bold">
|
||||||
|
{state.findUnique.data.title || 'Detail Artikel Kesehatan'}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</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']}>
|
<Box p="lg">
|
||||||
<Text fz={'h3'} c={colors['white-1']} fw={'bold'}>Informasi Lebih Lanjut</Text>
|
<Stack gap="lg">
|
||||||
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
|
<Group gap="xs">
|
||||||
Hotline DBD : <Text span fz={'h4'}>(0361) 123456</Text>
|
<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>
|
||||||
<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>
|
</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>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -16,36 +17,71 @@ function ArtikelKesehatanPage() {
|
|||||||
|
|
||||||
if(!state.findMany.data){
|
if(!state.findMany.data){
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" ta="center">
|
||||||
<Skeleton h={500}/>
|
<Loader size="lg" color={colors['blue-button']} />
|
||||||
|
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
|
||||||
</Box>
|
</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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
|
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
|
||||||
<Stack gap={'xs'}>
|
<Stack gap="xl">
|
||||||
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
|
<Title order={2} ta="center" c={colors['blue-button']}>Artikel Kesehatan</Title>
|
||||||
{state.findMany.data.map((item) => (
|
<Divider color={colors['blue-button']} />
|
||||||
<Box key={item.id}>
|
<Stack gap="lg">
|
||||||
<Image pt={5} src={'/api/img/dbd.png'} alt="" />
|
{state.findMany.data.map((item) => (
|
||||||
<Text fz={'h4'} fw={'bold'} >
|
<Card
|
||||||
{item.title}
|
key={item.id}
|
||||||
</Text>
|
withBorder
|
||||||
<Text fz={'h6'} pb={10}>
|
radius="lg"
|
||||||
Diposting: {new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} | Dinas Kesehatan
|
shadow="sm"
|
||||||
</Text>
|
p="lg"
|
||||||
<Text fz={'h4'} pb={10} lineClamp={2}>
|
style={{ transition: 'transform 200ms ease' }}
|
||||||
{item.content}
|
onMouseEnter={(e) => (e.currentTarget.style.transform = 'translateY(-4px)')}
|
||||||
</Text>
|
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
|
||||||
<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'} >
|
<Card.Section>
|
||||||
Baca Selengkapnya {'>>'}
|
<Image src={'/api/img/dbd.png'} alt={item.title} height={200} fit="cover" />
|
||||||
</Text>
|
</Card.Section>
|
||||||
</Anchor>
|
<Stack gap="xs" mt="md">
|
||||||
</Box>
|
<Text fw="bold" fz="xl" c="dark">{item.title}</Text>
|
||||||
))}
|
<Group gap="xs">
|
||||||
<Divider color={colors['blue-button']} px={'xl'} />
|
<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>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,160 +1,353 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
import colors from '@/con/colors';
|
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 { 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 { useParams } from 'next/navigation';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
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() {
|
function Page() {
|
||||||
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
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 (
|
return (
|
||||||
<Stack py={10}>
|
<Stack bg={colors.Bg} mih="100vh" p="lg" gap="lg">
|
||||||
<Skeleton h={500} />
|
<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>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<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>
|
||||||
<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 */}
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text fz={'h4'} fw={"bold"}>
|
<Card radius="xl" p="lg" withBorder style={{ backdropFilter: 'blur(6px)' }}>
|
||||||
Dokter & Tenaga Medis
|
<Stack gap="sm">
|
||||||
</Text>
|
<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 />
|
<Divider />
|
||||||
<Box pb={10}>
|
<Group gap="xl" align="start">
|
||||||
<Table highlightOnHover withTableBorder withColumnBorders>
|
<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>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh fz={'h4'}>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh fz={'h4'}>Spesialisasi</TableTh>
|
<TableTh>Spesialisasi</TableTh>
|
||||||
<TableTh fz={'h4'}>Jadwal</TableTh>
|
<TableTh>Jadwal</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{state.findUnique.data?.dokterdantenagamedis ? (
|
{tenaga ? (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd>{state.findUnique.data.dokterdantenagamedis.name}</TableTd>
|
<TableTd><Group gap="xs"><IconUser size={16} /><Text>{tenaga?.name || '-'}</Text></Group></TableTd>
|
||||||
<TableTd>{state.findUnique.data.dokterdantenagamedis.specialist}</TableTd>
|
<TableTd>{tenaga?.specialist || '-'}</TableTd>
|
||||||
<TableTd>{state.findUnique.data.dokterdantenagamedis.jadwal}</TableTd>
|
<TableTd>{tenaga?.jadwal || '-'}</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
) : (
|
) : (
|
||||||
<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>
|
</TableTr>
|
||||||
)}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Stack>
|
||||||
{/* Fasilitas Pendukung */}
|
</Card>
|
||||||
<Text fz={'h4'} fw={"bold"}>
|
</Stack>
|
||||||
Fasilitas Pendukung
|
</Grid.Col>
|
||||||
</Text>
|
</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 />
|
<Divider />
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.fasilitaspendukung?.content }} />
|
{fasilitasPendukungHtml ? (
|
||||||
{/* Dokter */}
|
<Text fz="md" style={{ lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} />
|
||||||
<Text fz={'h4'} fw={"bold"}>
|
) : (
|
||||||
Layanan & Tarif
|
<Paper withBorder radius="md" p="md">
|
||||||
</Text>
|
<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 />
|
<Divider />
|
||||||
<Table highlightOnHover withTableBorder withColumnBorders>
|
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh fz={'h4'}>Layanan</TableTh>
|
<TableTh>Layanan</TableTh>
|
||||||
<TableTh fz={'h4'}>Tarif</TableTh>
|
<TableTh>Tarif</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{tarif ? (
|
||||||
<TableTd>{state.findUnique?.data?.tarifdanlayanan.layanan}</TableTd>
|
<TableTr>
|
||||||
<TableTd>{state.findUnique?.data?.tarifdanlayanan.tarif}</TableTd>
|
<TableTd>{tarif?.layanan || '-'}</TableTd>
|
||||||
</TableTr>
|
<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>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Text fz={'h6'} pb={10} fw={"bold"}>
|
{gratisBpjs && (
|
||||||
* Gratis dengan BPJS Kesehatan
|
<Group gap="xs">
|
||||||
</Text>
|
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
|
||||||
{/* Prosedur Pendaftaran */}
|
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text>
|
||||||
<Text fz={'h4'} fw={"bold"}>
|
</Group>
|
||||||
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>
|
|
||||||
<Group>
|
<Group>
|
||||||
<Button fz={'lg'} bg={colors['blue-button']}>
|
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} leftSection={<IconFileDownload size={18} />}>Unduh Brosur (PDF)</Button>
|
||||||
Download Brosur Layanan (PDF)
|
</Group>
|
||||||
</Button>
|
|
||||||
</Group> */}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Card>
|
||||||
</Paper>
|
</Grid.Col>
|
||||||
</Stack>
|
</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>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,50 +1,100 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconMapPin, IconClock, IconArrowRight } from '@tabler/icons-react';
|
||||||
|
|
||||||
function FasilitasKesehatanPage() {
|
function FasilitasKesehatanPage() {
|
||||||
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findMany.load()
|
state.findMany.load();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if(!state.findMany.data){
|
if (!state.findMany.data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" px="md">
|
||||||
<Skeleton h={500} />
|
<Stack gap="md">
|
||||||
|
<Skeleton height={80} radius="lg" />
|
||||||
|
<Skeleton height={80} radius="lg" />
|
||||||
|
<Skeleton height={80} radius="lg" />
|
||||||
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
|
<Paper p="xl" radius="xl" shadow="md" h="100%" bg="white">
|
||||||
<Stack gap={'xs'}>
|
<Stack gap="lg">
|
||||||
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Fasilitas Kesehatan</Text>
|
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
||||||
{state.findMany.data.map((item) => (
|
Fasilitas Kesehatan
|
||||||
<Box key={item.id}>
|
</Text>
|
||||||
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>
|
<Divider size="sm" color={colors['blue-button']} />
|
||||||
{item.name}
|
<Stack gap="lg">
|
||||||
</Text>
|
{state.findMany.data.map((item) => (
|
||||||
<Text fz={'h4'}>
|
<Card
|
||||||
{item.informasiumum.alamat}
|
key={item.id}
|
||||||
</Text>
|
withBorder
|
||||||
<Text fz={'h4'}>
|
radius="xl"
|
||||||
{item.informasiumum.jamOperasional}
|
shadow="sm"
|
||||||
</Text>
|
p="lg"
|
||||||
<Anchor onClick={() => router.push(`/darmasaba/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/${item.id}`)} c={colors['blue-button']} variant='transparent'>
|
style={{
|
||||||
<Text c={colors['blue-button']} fz={'h4'}>
|
background: 'linear-gradient(135deg, #fdfdfd, #f7faff)',
|
||||||
Detail {'>>'}
|
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
||||||
</Text>
|
}}
|
||||||
</Anchor>
|
onMouseEnter={(e) => {
|
||||||
</Box>
|
(e.currentTarget as HTMLElement).style.transform = 'translateY(-4px)';
|
||||||
))}
|
(e.currentTarget as HTMLElement).style.boxShadow = '0 8px 20px rgba(0,0,0,0.08)';
|
||||||
<Divider color={colors['blue-button']} px={'xl'} />
|
}}
|
||||||
|
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>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -2,10 +2,30 @@
|
|||||||
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
import colors from '@/con/colors';
|
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 { DateInput } from '@mantine/dates';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
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 { useParams } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -17,6 +37,7 @@ const layanan = [
|
|||||||
'Konsultasi Gizi',
|
'Konsultasi Gizi',
|
||||||
'Pemeriksaan Kesehatan'
|
'Pemeriksaan Kesehatan'
|
||||||
];
|
];
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const combobox = useCombobox({
|
const combobox = useCombobox({
|
||||||
onDropdownClose: () => combobox.resetSelectedOption(),
|
onDropdownClose: () => combobox.resetSelectedOption(),
|
||||||
@@ -30,192 +51,160 @@ function Page() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [value, setValue] = useState<string | null>('');
|
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) => (
|
const options = layanan.map((item) => (
|
||||||
<ComboboxOption value={item} key={item} active={item === value}>
|
<ComboboxOption value={item} key={item} active={item === value}>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
{item === value && <CheckIcon size={12} />}
|
{item === value && <IconUser size={14} stroke={2} />}
|
||||||
<span>{item}</span>
|
<span>{item}</span>
|
||||||
</Group>
|
</Group>
|
||||||
</ComboboxOption>
|
</ComboboxOption>
|
||||||
));
|
));
|
||||||
const [dateInputOpened, setDateInputOpened] = useState(false);
|
|
||||||
|
|
||||||
const pickerControl = (
|
const pickerControl = (
|
||||||
<ActionIcon onClick={() => setDateInputOpened(true)} variant="subtle" color="gray">
|
<ActionIcon onClick={() => setDateInputOpened(true)} variant="light" color="blue">
|
||||||
<IconCalendar size={18} />
|
<IconCalendar size={18} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const params = useParams()
|
|
||||||
const state = useProxy(jadwalkegiatanState)
|
|
||||||
useShallowEffect(() => {
|
|
||||||
state.findUnique.load(params?.id as string)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="xl" px="lg">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="lg">
|
||||||
<Paper radius={10}>
|
<Paper radius="xl" shadow="md">
|
||||||
<Box style={{ borderTopLeftRadius: 10, borderTopRightRadius: 10 }} bg={colors['blue-button']}>
|
<Box
|
||||||
<Text p={'md'} fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw={"bold"}>
|
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
|
Detail & Pendaftaran Kegiatan
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box p={'md'} >
|
<Box p="lg">
|
||||||
<Stack gap={'xs'}>
|
<Stack gap="xl">
|
||||||
{/* Informasi Kegiatan */}
|
<Stack gap="sm">
|
||||||
<Text fz={'h4'} fw={"bold"}>
|
<Text fz="lg" fw="bold">Informasi Kegiatan</Text>
|
||||||
Informasi Kegiatan
|
<Divider />
|
||||||
</Text>
|
<Text fz="md" fw="bold">Nama Kegiatan: <Text span>{state.findUnique.data.informasijadwalkegiatan.name}</Text></Text>
|
||||||
<Divider />
|
<Text fz="md" fw="bold">Tanggal: <Text span>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text></Text>
|
||||||
<Text fz={'h4'} fw={"bold"}>
|
<Text fz="md" fw="bold">Waktu: <Text span>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text></Text>
|
||||||
Nama Kegiatan : <Text span fz={'h4'}>{state.findUnique.data.informasijadwalkegiatan.name}</Text>
|
<Text fz="md" fw="bold">Lokasi: <Text span>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text></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>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<Paper p={'lg'} bg={colors['blue-button-trans']}>
|
|
||||||
<Text fz={'h3'} c={colors['white-1']} fw={'bold'}>Informasi Kontak</Text>
|
<Stack gap="sm">
|
||||||
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
|
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
|
||||||
Penanggung Jawab : <Text span fz={'h4'}>Bidan Komang Ayu</Text>
|
<Divider />
|
||||||
</Text>
|
<Text ta="justify" fz="md" dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
||||||
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
|
</Stack>
|
||||||
Telepon : <Text span fz={'h4'}>081234567890</Text>
|
|
||||||
</Text>
|
<Stack gap="sm">
|
||||||
<Text fz={'h4'} c={colors['white-1']} fw={"bold"}>
|
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
|
||||||
Email : <Text span fz={'h4'}>puskesmasabiansemal3@gmail.com</Text>
|
<Divider />
|
||||||
</Text>
|
<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>
|
</Paper>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<Button fz={'lg'} bg={'green'}>
|
<Button size="md" radius="lg" leftSection={<IconDownload size={18} />} color="green">
|
||||||
Download Jadwal Posyandu 2025 (PDF)
|
Unduh Jadwal Posyandu 2025 (PDF)
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -227,5 +216,4 @@ function Page() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,53 +1,101 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconChevronRight, IconClockHour4, IconMapPin } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function JadwalKegiatanPage() {
|
function JadwalKegiatanPage() {
|
||||||
const state = useProxy(jadwalkegiatanState)
|
const state = useProxy(jadwalkegiatanState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(()=> {
|
useShallowEffect(() => {
|
||||||
state.findMany.load()
|
state.findMany.load();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if(!state.findMany.data){
|
if (!state.findMany.data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="lg">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
|
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md" h="auto" mih="100vh">
|
||||||
<Stack gap={'xs'}>
|
<Stack gap="lg">
|
||||||
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Jadwal Kegiatan</Text>
|
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
||||||
{state.findMany.data.map((item) => (
|
Jadwal Kegiatan Warga
|
||||||
<Box key={item.id}>
|
</Text>
|
||||||
<Text fz={'h4'} fw={'bold'}>
|
|
||||||
{item.informasijadwalkegiatan.name}
|
{state.findMany.data.length === 0 ? (
|
||||||
|
<Box py="xl" ta="center">
|
||||||
|
<Text fz="lg" c="dimmed">
|
||||||
|
Belum ada jadwal kegiatan yang tersedia
|
||||||
</Text>
|
</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>
|
</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>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,100 +1,169 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch, IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(infoWabahPenyakit)
|
const state = useProxy(infoWabahPenyakit);
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = state.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, search)
|
load(page, 6, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
Info Wabah / Penyakit
|
<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>
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius="xl"
|
||||||
placeholder='Cari Info Wabah / Penyakit'
|
placeholder="Cari nama penyakit atau wabah..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
w={{ base: "50%", md: "100%" }}
|
w="100%"
|
||||||
|
size="md"
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<SimpleGrid
|
{data.length === 0 ? (
|
||||||
cols={{
|
<Center py="6xl">
|
||||||
base: 1,
|
<Stack align="center" gap="sm">
|
||||||
md: 3,
|
<IconInfoCircle size={50} color={colors['blue-button']} />
|
||||||
}}
|
<Text fz="lg" fw={500} c="dimmed">
|
||||||
>
|
Tidak ada data yang cocok dengan pencarian Anda.
|
||||||
{data.map((v, k) => {
|
</Text>
|
||||||
return (
|
</Stack>
|
||||||
<Paper key={k} p={'xl'} bg={colors['white-trans-1']}>
|
</Center>
|
||||||
<Stack gap={'xs'}>
|
) : (
|
||||||
<Box>
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
|
||||||
<Text fw={"bold"} fz={'h3'} c={colors['blue-button']}>{v.name}</Text>
|
{data.map((v, k) => (
|
||||||
<Image w={330} h={200} fit='contain' pt={5} src={v.image.link} alt={v.name} />
|
<Paper
|
||||||
<Text fz={'h4'} fw={'bold'} >
|
key={k}
|
||||||
{v.name}
|
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>
|
||||||
<Text fz={'h6'} pb={10}>
|
</HoverCard.Target>
|
||||||
Diposting: 12 Februari 2025 | Dinas Kesehatan
|
<HoverCard.Dropdown>
|
||||||
</Text>
|
<Text
|
||||||
<Text fz={'h4'} pb={10}>
|
fz="sm"
|
||||||
{v.deskripsiSingkat}
|
lh={1.6}
|
||||||
</Text>
|
dangerouslySetInnerHTML={{
|
||||||
<Text fz={'h4'} pb={10} dangerouslySetInnerHTML={{ __html: v.deskripsiLengkap }} />
|
__html: v.deskripsiLengkap,
|
||||||
</Box>
|
}}
|
||||||
</Stack>
|
/>
|
||||||
</Paper>
|
</HoverCard.Dropdown>
|
||||||
)
|
</HoverCard>
|
||||||
})}
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
|
||||||
<Pagination
|
{totalPages > 1 && (
|
||||||
value={page}
|
<Center>
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
<Pagination
|
||||||
total={totalPages}
|
value={page}
|
||||||
mt="md"
|
onChange={(newPage) => load(newPage)}
|
||||||
mb="md"
|
total={totalPages}
|
||||||
/>
|
radius="xl"
|
||||||
</Center>
|
size="md"
|
||||||
|
mt="lg"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +1,150 @@
|
|||||||
'use client'
|
'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 { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
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 kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(kontakDarurat)
|
const state = useProxy(kontakDarurat);
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = state.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, search)
|
load(page, 6, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<Grid align="center" px={{ base: 'md', md: 100 }} gutter="lg">
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
Penanganan Darurat
|
<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>
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<TextInput
|
<Tooltip label="Cari berdasarkan nama atau deskripsi" withArrow>
|
||||||
radius={"lg"}
|
<TextInput
|
||||||
placeholder='Cari Penanganan Darurat'
|
radius="xl"
|
||||||
value={search}
|
size="md"
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
placeholder="Cari kontak darurat..."
|
||||||
leftSection={<IconSearch size={20} />}
|
value={search}
|
||||||
w={{ base: "50%", md: "100%" }}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
leftSection={<IconSearch size={20} />}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<SimpleGrid
|
{data.length === 0 ? (
|
||||||
pb={10}
|
<Center mih={300}>
|
||||||
cols={{
|
<Stack align="center" gap="xs">
|
||||||
base: 1,
|
<IconSearch size={50} color={colors['blue-button']} />
|
||||||
md: 3,
|
<Text fz="lg" fw={600} c={colors['blue-button']}>
|
||||||
}}>
|
Tidak ada kontak ditemukan
|
||||||
{data.map((v, k) => {
|
</Text>
|
||||||
return (
|
<Text fz="sm" c="dimmed">
|
||||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
Coba kata kunci lain untuk pencarian
|
||||||
<Stack gap={'xs'}>
|
</Text>
|
||||||
<Center py={40}>
|
</Stack>
|
||||||
<Image
|
</Center>
|
||||||
src={v.image.link}
|
) : (
|
||||||
alt={v.name}
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
|
||||||
w={200}
|
{data.map((v, k) => (
|
||||||
h={200}
|
<Paper
|
||||||
fit='contain'
|
key={k}
|
||||||
/>
|
radius="xl"
|
||||||
</Center>
|
shadow="md"
|
||||||
<Box px={'lg'}>
|
withBorder
|
||||||
<Box pb={20}>
|
p="lg"
|
||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
bg={colors['white-trans-1']}
|
||||||
{v.name}
|
style={{
|
||||||
</Text>
|
transition: 'all 200ms ease',
|
||||||
<Box px={10}>
|
cursor: 'pointer',
|
||||||
<Text fz={"h4"} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
}}
|
||||||
</Box>
|
>
|
||||||
</Box>
|
<Stack align="center" gap="sm">
|
||||||
</Box>
|
<Image
|
||||||
</Stack>
|
src={v.image.link}
|
||||||
</Paper>
|
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>
|
</SimpleGrid>
|
||||||
</Stack>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
|
||||||
<Pagination
|
{totalPages > 1 && (
|
||||||
value={page}
|
<Center mt="xl">
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
<Pagination
|
||||||
total={totalPages}
|
value={page}
|
||||||
mt="md"
|
onChange={(newPage) => load(newPage, 6, search)}
|
||||||
mb="md"
|
total={totalPages}
|
||||||
/>
|
radius="xl"
|
||||||
</Center>
|
size="md"
|
||||||
|
styles={{
|
||||||
|
control: {
|
||||||
|
borderRadius: '999px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +1,162 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat'
|
||||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import colors from '@/con/colors'
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import {
|
||||||
import { useProxy } from 'valtio/utils';
|
Badge,
|
||||||
import { useState } from 'react';
|
Box,
|
||||||
|
Center,
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
Grid,
|
||||||
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
GridCol,
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
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() {
|
function Page() {
|
||||||
const state = useProxy(penangananDarurat)
|
const state = useProxy(penangananDarurat)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = state.findMany
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = state.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, search)
|
load(page, 6, search)
|
||||||
}, [page, search])
|
}, [page, search])
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</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 }}>
|
<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
|
Penanganan Darurat
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed" mt={4}>
|
||||||
|
Informasi cepat dan jelas untuk situasi darurat kesehatan
|
||||||
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
<TextInput
|
<Tooltip label="Ketik kata kunci untuk mencari penanganan darurat">
|
||||||
radius={"lg"}
|
<TextInput
|
||||||
placeholder='Cari Penanganan Darurat'
|
radius="lg"
|
||||||
value={search}
|
placeholder="Cari penanganan darurat..."
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
value={search}
|
||||||
leftSection={<IconSearch size={20} />}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
w={{ base: "50%", md: "100%" }}
|
leftSection={<IconSearch size={20} />}
|
||||||
/>
|
w="100%"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</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
|
<SimpleGrid
|
||||||
pb={10}
|
cols={{ base: 1, sm: 2, md: 3 }}
|
||||||
cols={{
|
spacing="xl"
|
||||||
base: 1,
|
verticalSpacing="xl"
|
||||||
md: 3,
|
pb={20}
|
||||||
}}>
|
>
|
||||||
{data.map((v, k) => {
|
{data.map((v, k) => (
|
||||||
return (
|
<Paper
|
||||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
key={k}
|
||||||
<Stack gap={'xs'}>
|
radius="xl"
|
||||||
<Center py={40}>
|
p="md"
|
||||||
<Image
|
shadow="sm"
|
||||||
src={v.image.link}
|
withBorder
|
||||||
alt={v.name}
|
bg={colors['white-trans-1']}
|
||||||
w={200}
|
style={{ transition: 'all 0.3s ease' }}
|
||||||
h={200}
|
>
|
||||||
fit='contain'
|
<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>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
<Badge radius="md" color="blue" variant="light" mt="sm">
|
||||||
)
|
Darurat
|
||||||
})}
|
</Badge>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
|
||||||
<Pagination
|
{totalPages > 1 && (
|
||||||
value={page}
|
<Center mt="xl">
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
<Pagination
|
||||||
total={totalPages}
|
value={page}
|
||||||
mt="md"
|
onChange={(newPage) => load(newPage, 6, search)}
|
||||||
mb="md"
|
total={totalPages}
|
||||||
/>
|
size="lg"
|
||||||
</Center>
|
radius="xl"
|
||||||
|
styles={{
|
||||||
|
control: {
|
||||||
|
border: `1px solid ${colors['blue-button']}`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page
|
||||||
|
|||||||
@@ -1,119 +1,166 @@
|
|||||||
'use client'
|
'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 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 { useShallowEffect } from "@mantine/hooks";
|
||||||
import { useProxy } from "valtio/utils";
|
import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
|
||||||
import { useState } from "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() {
|
export default function Page() {
|
||||||
const state = useProxy(posyandustate)
|
const state = useProxy(posyandustate);
|
||||||
// const router = useTransitionRouter()
|
const [search, setSearch] = useState("");
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = state.findMany;
|
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 6, search);
|
||||||
|
}, [page, search]);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
if (loading || !data) {
|
||||||
load(page, 3, search)
|
|
||||||
}, [page, search])
|
|
||||||
|
|
||||||
if (loading || !data) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Box py="xl" px={{ base: "md", md: 100 }}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Skeleton h={500} radius="lg" />
|
||||||
<BackButton />
|
</Box>
|
||||||
<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>
|
|
||||||
|
|
||||||
)
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
@@ -18,45 +18,75 @@ function Page() {
|
|||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Center mih="60vh">
|
||||||
<Skeleton h={500} />
|
<Stack align="center" gap="sm">
|
||||||
</div>
|
<Loader color={colors["blue-button"]} size="lg" />
|
||||||
|
<Text c="dimmed" fz="sm">Memuat data program kesehatan...</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Paper px={{ base: 'md', md: 100 }} radius={10} bg={colors["white-trans-1"]}>
|
<Paper
|
||||||
<Stack gap={'xs'}>
|
px={{ base: 'md', md: 100 }}
|
||||||
<Center my={20}>
|
py="xl"
|
||||||
<Image radius={"lg"} src={state.findUnique.data.image?.link} alt="" />
|
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>
|
</Center>
|
||||||
<Box px={'lg'}>
|
<Box>
|
||||||
<Box>
|
<Text pb="sm" c={colors["blue-button"]} fw="bold" fz={{ base: 24, md: 32 }} lh={1.2}>
|
||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
{state.findUnique.data.name}
|
||||||
{state.findUnique.data.name}
|
</Text>
|
||||||
</Text>
|
<Text
|
||||||
<Text ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}></Text>
|
ta="justify"
|
||||||
</Box>
|
fz="md"
|
||||||
<Group py={20}>
|
lh={1.6}
|
||||||
<Group gap="xs">
|
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}
|
||||||
<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>
|
</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>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,7 +1,31 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { Box, Button, Center, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from "@mantine/core";
|
import {
|
||||||
import { IconBarbell, IconCalendar, IconOld, IconSearch, IconUser, IconUsersGroup } from "@tabler/icons-react";
|
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 BackButton from "../../desa/layanan/_com/BackButto";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
import programKesehatan from "@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan";
|
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 { useShallowEffect } from "@mantine/hooks";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
const data2 = [
|
const manfaatProgram = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
icon: <IconBarbell size={50} color={colors['BG-trans']} />,
|
icon: <IconBarbell size={40} color="#fff" />,
|
||||||
title: "Menjaga Kesehatan Tubuh",
|
title: "Menjaga Tubuh Bugar",
|
||||||
desc: "Program kesehatan desa dirancang untuk memelihara kesehatan fisik masyarakat melalui aktivitas rutin, pemeriksaan kesehatan berkala, dan edukasi gaya hidup sehat.",
|
desc: "Meningkatkan kesehatan fisik masyarakat melalui olahraga rutin, pemeriksaan berkala, dan edukasi gaya hidup sehat.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
icon: <IconUsersGroup size={50} color={colors['BG-trans']} />,
|
icon: <IconUsersGroup size={40} color="#fff" />,
|
||||||
title: "Mempererat Kebersamaan",
|
title: "Menguatkan Kebersamaan",
|
||||||
desc: "Kegiatan kesehatan komunal menjadi wadah interaksi sosial yang memperkuat ikatan antar warga desa, menumbuhkan rasa kepedulian dan gotong royong.",
|
desc: "Menciptakan interaksi sosial sehat, mempererat solidaritas, dan memperkuat budaya gotong royong antar warga.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
icon: <IconOld size={50} color={colors['BG-trans']} />,
|
icon: <IconOld size={40} color="#fff" />,
|
||||||
title: "Medukung Lansia Aktif",
|
title: "Dukungan untuk Lansia",
|
||||||
desc: "Program khusus bagi lansia membantu menjaga kebugaran, mengurangi risiko penyakit degeneratif, dan menciptakan komunitas pendukung untuk kehidupan yang sehat dan bahagia.",
|
desc: "Membantu lansia tetap aktif, sehat, dan bahagia melalui kegiatan komunitas yang aman dan menyenangkan.",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const state = useProxy(programKesehatan)
|
const state = useProxy(programKesehatan);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState("");
|
||||||
const {
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = state.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, search)
|
load(page, 3, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" px={{ base: "md", md: 100 }}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Grid px={{ base: 'md', md: 100 }} align="center">
|
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<Grid px={{ base: "md", md: 100 }} align="center" gutter="lg">
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
Program Kesehatan Unggulan
|
<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>
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder='Cari Program Kesehatan'
|
placeholder="Cari program kesehatan..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
w={{ base: "50%", md: "100%" }}
|
radius="lg"
|
||||||
|
size="md"
|
||||||
|
aria-label="Pencarian program kesehatan"
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'}>
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" pb="xl">
|
||||||
<SimpleGrid
|
{data.map((v, k) => (
|
||||||
pb={10}
|
<Transition
|
||||||
cols={{
|
key={k}
|
||||||
base: 1,
|
mounted
|
||||||
md: 3,
|
transition="fade-up"
|
||||||
}}>
|
duration={400}
|
||||||
{data.map((v, k) => {
|
timingFunction="ease"
|
||||||
return (
|
>
|
||||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
{(styles) => (
|
||||||
<Stack gap={'xs'}>
|
<Paper
|
||||||
<Center>
|
style={styles}
|
||||||
<Image src={v.image?.link} alt="" style={{ borderRadius: '14px 14px 0 0' }} />
|
radius="xl"
|
||||||
</Center>
|
withBorder
|
||||||
<Box px={'lg'}>
|
bg="white"
|
||||||
<Box>
|
shadow="md"
|
||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
className="hover-scale"
|
||||||
{v.name}
|
>
|
||||||
</Text>
|
<Stack gap="md">
|
||||||
<Text ta={'justify'} fz={'h4'} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
|
<Image
|
||||||
</Box>
|
src={v.image?.link}
|
||||||
<Box py={15} onClick={() => router.push(`/darmasaba/kesehatan/program-kesehatan/${v.id}`)}>
|
alt={v.name}
|
||||||
<Button fw={'bold'} fz={'h5'} c={colors["blue-button"]} bg={colors["BG-trans"]}>Detail Program</Button>
|
radius="xl"
|
||||||
</Box>
|
height={180}
|
||||||
<Group py={20}>
|
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">
|
<Group gap="xs">
|
||||||
<IconCalendar size={18} />
|
<IconCalendar size={18} />
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
{v.createdAt ? new Date(v.createdAt).toLocaleDateString('id-ID', {
|
{v.createdAt
|
||||||
day: 'numeric',
|
? new Date(v.createdAt).toLocaleDateString(
|
||||||
month: 'long',
|
"id-ID",
|
||||||
year: 'numeric',
|
{
|
||||||
}) : 'No date available'}
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: "Tanggal tidak tersedia"}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
@@ -114,61 +168,99 @@ export default function Page() {
|
|||||||
<Text size="sm">Admin Desa</Text>
|
<Text size="sm">Admin Desa</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</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>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)}
|
||||||
})}
|
</Transition>
|
||||||
</SimpleGrid>
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => load(newPage)}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
styles={{
|
||||||
|
control: {
|
||||||
|
borderRadius: "50%",
|
||||||
|
boxShadow: "0 0 10px rgba(0,0,0,0.1)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Stack>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box py={10} px={{ base: "md", md: 100 }}>
|
|
||||||
<Box pb={10}>
|
<Box px={{ base: "md", md: 100 }} py="xl">
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Stack gap="sm" mb="lg">
|
||||||
|
<Text
|
||||||
|
fz={{ base: "2rem", md: "2.5rem" }}
|
||||||
|
c={colors["blue-button"]}
|
||||||
|
fw="bold"
|
||||||
|
>
|
||||||
Manfaat Program Kesehatan
|
Manfaat Program Kesehatan
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "h4", md: "h3" }} >
|
<Text fz="lg" c="dimmed" maw={700}>
|
||||||
Program kesehatan di Desa Darmasaba memiliki peran penting dalam meningkatkan kesejahteraan masyarakat.
|
Program kesehatan Desa Darmasaba berperan penting dalam meningkatkan
|
||||||
|
kesejahteraan dan kualitas hidup warganya.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Stack>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
pb={30}
|
{manfaatProgram.map((v) => (
|
||||||
cols={{
|
<Paper
|
||||||
base: 1,
|
key={v.id}
|
||||||
md: 3,
|
px="xl"
|
||||||
}}>
|
py="xl"
|
||||||
{data2.map((v, k) => {
|
radius="xl"
|
||||||
return (
|
shadow="sm"
|
||||||
<Paper key={k} px={"xl"} py={80} bg={colors['white-trans-1']} c={colors['blue-button']}>
|
withBorder
|
||||||
<Stack justify='space-between' >
|
bg="white"
|
||||||
<Group justify='center'>
|
className="hover-glow"
|
||||||
<Paper p={'xl'} radius={'100'} bg={colors['blue-button']}>
|
>
|
||||||
<Center >
|
<Stack align="center" gap="md">
|
||||||
{v.icon}
|
<Paper
|
||||||
</Center>
|
p="lg"
|
||||||
</Paper>
|
radius="50%"
|
||||||
</Group>
|
shadow="md"
|
||||||
<Text ta={"center"} fw={"bold"} fz={"h3"}>
|
bg={colors["blue-button"]}
|
||||||
{v.title}
|
className="pulse-icon"
|
||||||
</Text>
|
>
|
||||||
<Text ta={"center"} fz={'h4'}>
|
<Center>{v.icon}</Center>
|
||||||
{v.desc}
|
</Paper>
|
||||||
</Text>
|
<Text ta="center" fw="bold" fz="xl" c={colors["blue-button"]}>
|
||||||
</Stack>
|
{v.title}
|
||||||
</Paper>
|
</Text>
|
||||||
)
|
<Text ta="center" fz="sm" c="dimmed">
|
||||||
})}
|
{v.desc}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconClock, IconMapPin, IconPhone, IconMail, IconBuildingHospital } from '@tabler/icons-react';
|
||||||
import BackButton from '../../../desa/layanan/_com/BackButto';
|
import BackButton from '../../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
@@ -17,95 +18,117 @@ function Page() {
|
|||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
<Skeleton h={500} />
|
<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>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={'lg'} px={{ base: 'md', md: 100 }}>
|
|
||||||
<Box>
|
<Stack gap="xl" px={{ base: 'md', md: 100 }}>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
<Paper p="lg" radius="xl" shadow="md" bg={colors['white-trans-1']} withBorder>
|
||||||
<Box pb={30}>
|
<Box pb="xl">
|
||||||
<BackgroundImage
|
<BackgroundImage
|
||||||
pb={30}
|
radius="lg"
|
||||||
radius={16}
|
h={{ base: 260, md: 480 }}
|
||||||
h={{ base: 250, md: 500 }}
|
src={data.image.link}
|
||||||
src={state.findUnique.data.image.link}
|
style={{ position: 'relative', overflow: 'hidden' }}
|
||||||
style={{ position: 'relative' }}
|
>
|
||||||
|
<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
|
<Text fw="bold" fz={{ base: 'lg', md: 'h2' }} c={colors['white-1']}>
|
||||||
style={{
|
{data.name}
|
||||||
borderRadius: 16,
|
</Text>
|
||||||
zIndex: 0
|
<Group gap={6}>
|
||||||
}}
|
<IconMapPin size={20} color="white" />
|
||||||
pos={"absolute"}
|
<Text fz={{ base: 'sm', md: 'md' }} c={colors['white-1']}>
|
||||||
w={"100%"}
|
{data.alamat}
|
||||||
h={"100%"}
|
</Text>
|
||||||
bg={colors.trans.dark[2]}
|
</Group>
|
||||||
/>
|
</Stack>
|
||||||
<Text style={{
|
</BackgroundImage>
|
||||||
position: 'absolute',
|
|
||||||
bottom: 35,
|
<Grid mt="xl" gutter="xl">
|
||||||
left: 15,
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
}} fw={'bold'} fz={{ base: 'md', md: 'h3' }} c={colors['white-1']}>{state.findUnique.data.name}</Text>
|
<Stack gap="lg">
|
||||||
<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 }}>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Stack>
|
<Title order={3} mb={10}>Informasi Kontak</Title>
|
||||||
<Title order={3}>Informasi</Title>
|
<Stack gap={8}>
|
||||||
<Box>
|
<Group gap={8}>
|
||||||
<Text>Alamat: {state.findUnique.data.alamat}</Text>
|
<IconPhone size={18} />
|
||||||
<Text>Telepon: {state.findUnique.data.kontak.kontakPuskesmas}</Text>
|
<Text fz="md">{data.kontak.kontakPuskesmas || '-'}</Text>
|
||||||
<Text>Email: {state.findUnique.data.kontak.email}</Text>
|
</Group>
|
||||||
</Box>
|
<Group gap={8}>
|
||||||
<Title order={3}>Jam Operasional</Title>
|
<IconMail size={18} />
|
||||||
<Box>
|
<Text fz="md">{data.kontak.email || '-'}</Text>
|
||||||
<Text pb={10} fz={'h4'} fw={"bold"}>
|
</Group>
|
||||||
Senin - Kamis: <Text span fz={'h4'}>{state.findUnique.data?.jam.workDays} - {state.findUnique.data?.jam.weekDays}</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</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>
|
<Divider />
|
||||||
</Box>
|
|
||||||
|
<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>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(puskesmasState)
|
const state = useProxy(puskesmasState)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
@@ -22,76 +21,122 @@ function Page() {
|
|||||||
} = state.findMany;
|
} = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 3, search)
|
load(page, 6, search)
|
||||||
}, [page, search])
|
}, [page, search])
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
||||||
<Skeleton h={500} />
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<Skeleton key={i} height={320} radius="lg" />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<Grid align="center" px={{ base: 'md', md: 100 }} mb="md">
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
Puskesmas Darmasaba
|
<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>
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius="xl"
|
||||||
placeholder='Cari Puskesmas'
|
placeholder="Cari nama puskesmas..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={18} />}
|
||||||
w={{ base: "50%", md: "100%" }}
|
aria-label="Cari Puskesmas"
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<SimpleGrid
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
cols={{
|
{data.length === 0 ? (
|
||||||
base: 1,
|
<Center py="xl">
|
||||||
md: 3,
|
<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>
|
||||||
{data.map((v, k) => {
|
</Stack>
|
||||||
return (
|
</Center>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']} key={k}>
|
) : (
|
||||||
<Stack gap={"xs"}>
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
|
||||||
<Text fw={"bold"} fz={"h3"}>{v.name}</Text>
|
{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
|
<Image
|
||||||
src={v.image.link}
|
src={v.image.link}
|
||||||
alt={v.name}
|
alt={v.name}
|
||||||
|
radius="md"
|
||||||
|
height={160}
|
||||||
|
fit="cover"
|
||||||
/>
|
/>
|
||||||
<Box>
|
<Group justify="space-between">
|
||||||
<Text>Alamat: {v.alamat}</Text>
|
<Text fw={600} fz="lg" lineClamp={1}>{v.name}</Text>
|
||||||
<Text>Telepon: {v.kontak.kontakPuskesmas}</Text>
|
<Badge color="blue" variant="light" radius="sm" fz="xs">Aktif</Badge>
|
||||||
<Text>Email: {v.kontak.email}</Text>
|
</Group>
|
||||||
</Box>
|
<Stack gap={4}>
|
||||||
<Anchor c={colors['blue-button']} href={`/darmasaba/kesehatan/puskesmas/${v.id}`}>Lihat Detail ></Anchor>
|
<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>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
))}
|
||||||
})}
|
</SimpleGrid>
|
||||||
</SimpleGrid>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
|
||||||
<Pagination
|
{totalPages > 1 && (
|
||||||
value={page}
|
<Center>
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
<Pagination
|
||||||
total={totalPages}
|
value={page}
|
||||||
mt="md"
|
onChange={(newPage) => load(newPage, 6, search)}
|
||||||
mb="md"
|
total={totalPages}
|
||||||
/>
|
size="md"
|
||||||
</Center>
|
radius="xl"
|
||||||
|
mt="lg"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +1,126 @@
|
|||||||
|
'use client'
|
||||||
|
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Badge, Box, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Tooltip } from '@mantine/core';
|
||||||
import { IconChristmasTree, IconDroplet, IconHome, IconLeaf, IconTrash } from '@tabler/icons-react';
|
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';
|
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() {
|
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 (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</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"}>
|
<Group justify="space-between" align='center' mt="md">
|
||||||
Data Lingkungan Desa
|
<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>
|
||||||
<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>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
cols={{
|
{data.map((item) => (
|
||||||
base: 1,
|
<Paper
|
||||||
md: 2,
|
key={item.id}
|
||||||
}}>
|
p="lg"
|
||||||
{data.map((v, k) => {
|
bg={colors['white-trans-2']}
|
||||||
return (
|
radius="md"
|
||||||
<Box key={k}>
|
shadow="xl"
|
||||||
<Paper p={20} bg={colors['white-trans-1']}>
|
style={{ transition: 'all 0.3s', '&:hover': { transform: 'translateY(-5px)', boxShadow: `0 0 20px ${colors['blue-button']}` } }}
|
||||||
<Text fw={'bold'} c={colors['blue-button']} fz={{ base: "lg", md: "xl" }} >
|
>
|
||||||
{v.title}
|
<Stack align="center" gap="md">
|
||||||
</Text>
|
<Tooltip label={item.name} position="top" withArrow>
|
||||||
<Box>
|
<Center>
|
||||||
{v.icon}
|
{iconMap[item.icon] ? (
|
||||||
<Text c={colors['blue-button']} fz={'h4'} fw={'bold'}>
|
React.createElement(iconMap[item.icon], {
|
||||||
{v.jumlah}
|
size: 55,
|
||||||
</Text>
|
color: colors['blue-button'],
|
||||||
</Box>
|
style: {
|
||||||
<Text fz={{ base: "lg", md: "xl" }} >
|
transition: 'all 0.3s',
|
||||||
{v.deskripsi}
|
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'
|
||||||
</Text>
|
}
|
||||||
|
})
|
||||||
</Paper>
|
) : null}
|
||||||
</Box>
|
</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>
|
</SimpleGrid>
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
radius="xl"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,100 +1,87 @@
|
|||||||
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'Tujuan Edukasi Lingkungan',
|
title: 'Tujuan Edukasi Lingkungan',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
icon: <IconLeaf size={28} color={colors['blue-button']} />,
|
||||||
<ListItem>
|
listDeskripsi: [
|
||||||
Meningkatkan kesadaran masyarakat tentang pentingnya lingkungan bersih dan sehat
|
'Meningkatkan kesadaran masyarakat akan pentingnya lingkungan bersih dan sehat',
|
||||||
</ListItem>
|
'Mendorong partisipasi warga dalam pengelolaan sampah, penghijauan, dan konservasi',
|
||||||
<ListItem>
|
'Mengurangi dampak negatif kegiatan manusia terhadap lingkungan',
|
||||||
Mendorong partisipasi warga dalam kegiatan pengelolaan sampah, penghijauan, dan konservasi
|
'Membentuk generasi muda peduli isu-isu lingkungan',
|
||||||
</ListItem>
|
],
|
||||||
<ListItem>
|
|
||||||
Mengurangi dampak negatif terhadap lingkungan dari kegiatan manusia
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Membentuk generasi muda yang peduli terhadap isu-isu lingkungan
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'Materi Edukasi yang Diberikan',
|
title: 'Materi Edukasi yang Diberikan',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
icon: <IconRecycle size={28} color={colors['blue-button']} />,
|
||||||
<ListItem>
|
listDeskripsi: [
|
||||||
Pengelolaan Sampah (Pilah sampah organik dan anorganik)
|
'Pengelolaan sampah: pilah organik & anorganik',
|
||||||
</ListItem>
|
'Pencegahan pencemaran lingkungan (air, udara, tanah)',
|
||||||
<ListItem>
|
'Pemanfaatan lahan hijau dan penghijauan desa',
|
||||||
Pencegahan pencemaran lingkungan (air, udara, dan tanah)
|
'Daur ulang dan kreativitas dari sampah',
|
||||||
</ListItem>
|
'Bahaya pembakaran sampah sembarangan',
|
||||||
<ListItem>
|
],
|
||||||
Pemanfaatan lahan hijau dan penghijauan desa
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Daur ulang dan kreatifitas dari sampah
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Bahaya pembakaran sampah sembarangan
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Contoh Kegiatan di Desa Darmasaba',
|
title: 'Contoh Kegiatan di Desa Darmasaba',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
icon: <IconPlant2 size={28} color={colors['blue-button']} />,
|
||||||
<ListItem>
|
listDeskripsi: [
|
||||||
Pelatihan membuat kompos dari sampah rumah tangga
|
'Pelatihan membuat kompos dari sampah rumah tangga',
|
||||||
</ListItem>
|
'Gerakan "Jumat Bersih" rutin',
|
||||||
<ListItem>
|
'Workshop pembuatan ecobrick',
|
||||||
Gerakan "Jumat Bersih" rutin
|
'Lomba kebersihan antar banjar',
|
||||||
</ListItem>
|
'Sosialisasi lingkungan di sekolah dan posyandu',
|
||||||
<ListItem>
|
],
|
||||||
Workshop membuat ecobrick
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Lomba kebersihan antar banjar
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Sosialisasi lingkungan di sekolah dan posyandu
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
<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
|
Edukasi Lingkungan
|
||||||
</Text>
|
</Text>
|
||||||
<Text px={20} ta={'center'} fz={'h4'}>
|
<Text ta={'center'} fz="h4" c="black">
|
||||||
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.
|
Program edukasi ini membimbing masyarakat untuk peduli dan bertanggung jawab terhadap alam,
|
||||||
|
meningkatkan kesehatan, kenyamanan, dan keberlanjutan hidup bersama.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
|
||||||
<SimpleGrid
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
cols={{
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
base: 1,
|
{data.map((item) => (
|
||||||
md: 3,
|
<Paper key={item.id} p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
|
||||||
}}>
|
<Stack gap="md">
|
||||||
{data.map((v, k) => {
|
<Box>
|
||||||
return (
|
<Tooltip label={item.title} position="top" withArrow>
|
||||||
<Box key={k}>
|
<Stack gap={4} align="center">
|
||||||
<Paper h={{base: 0, md: 350}} p={20} bg={colors['white-trans-1']}>
|
{item.icon}
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.title}</Text>
|
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||||
{v.listDeskripsi}
|
{item.title}
|
||||||
</Paper>
|
</Text>
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
</Tooltip>
|
||||||
})}
|
</Box>
|
||||||
|
<List fz="h4" spacing="sm" withPadding>
|
||||||
|
{item.listDeskripsi.map((desc, idx) => (
|
||||||
|
<ListItem key={idx}>{desc}</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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 "{kategori}".</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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>;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -1,93 +1,79 @@
|
|||||||
import colors from '@/con/colors';
|
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';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'Filosofi Tri Hita Karana',
|
title: 'Filosofi Tri Hita Karana',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
listDeskripsi: (
|
||||||
<ListItem>
|
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||||
Parahyangan: Hubungan manusia dengan Tuhan
|
<ListItem>Parahyangan: Hubungan manusia dengan Tuhan yang dijaga penuh kesadaran spiritual</ListItem>
|
||||||
</ListItem>
|
<ListItem>Pawongan: Harmoni dan kerja sama antar manusia dalam masyarakat</ListItem>
|
||||||
<ListItem>
|
<ListItem>Palemahan: Pelestarian lingkungan dan hubungan manusia dengan alam</ListItem>
|
||||||
Pawongan: Hubungan antar manusia
|
</List>
|
||||||
</ListItem>
|
),
|
||||||
<ListItem>
|
|
||||||
Palemahan: Hubungan manusia dengan alam
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'Bentuk Konservasi Berdasarkan Adat',
|
title: 'Bentuk Konservasi Berdasarkan Adat',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
listDeskripsi: (
|
||||||
<ListItem>
|
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||||
Pelestarian Hutan Adat seperti Alas Pala Sangeh atau Wana Kerthi
|
<ListItem>Pelestarian Hutan Adat seperti Alas Pala Sangeh dan Wana Kerthi</ListItem>
|
||||||
</ListItem>
|
<ListItem>Subak: Sistem irigasi tradisional yang menekankan kebersamaan dan keberlanjutan</ListItem>
|
||||||
<ListItem>
|
<ListItem>Hari Raya Tumpek Uduh: Perayaan untuk menghormati pohon dan tumbuhan</ListItem>
|
||||||
Subak: Sistem pengelolaan irigasi tradisional yang menjunjung kebersamaan dan keberlanjutan
|
<ListItem>Perarem & Awig-Awig: Aturan adat untuk menjaga lingkungan dari kerusakan</ListItem>
|
||||||
</ListItem>
|
<ListItem>Ritual penyucian alam seperti Melasti dan Piodalan Segara</ListItem>
|
||||||
<ListItem>
|
</List>
|
||||||
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>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Nilai Konservasi Adat',
|
title: 'Nilai Konservasi Adat',
|
||||||
listDeskripsi: <List fz={'h4'} pr={20} ta={'justify'}>
|
listDeskripsi: (
|
||||||
<ListItem>
|
<List fz={'lg'} spacing="sm" ta={'justify'}>
|
||||||
Menjaga keseimbangan ekosistem
|
<ListItem>Menjaga keseimbangan ekosistem dan lingkungan hidup</ListItem>
|
||||||
</ListItem>
|
<ListItem>Melestarikan spiritualitas lokal dan kesucian alam</ListItem>
|
||||||
<ListItem>
|
<ListItem>Meningkatkan kesadaran kolektif untuk hidup selaras dengan alam</ListItem>
|
||||||
Melestarikan spiritualitas lokal dan kesucian alam
|
<ListItem>Menjamin keberlanjutan sumber daya alam untuk generasi mendatang</ListItem>
|
||||||
</ListItem>
|
</List>
|
||||||
<ListItem>
|
),
|
||||||
Menumbuhkan kesadaran kolektif untuk hidup selaras dengan lingkungan
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Menjaga keberlangsungan sumber daya alam untuk generasi mendatang
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
<Box px={{ base: 'md', md: 100 }} pb={30}>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta="center" fz={{ base: '2xl', md: '3rem' }} c={colors['blue-button']} fw="bold">
|
||||||
Konservasi Adat Bali
|
Konservasi Adat Bali
|
||||||
</Text>
|
</Text>
|
||||||
<Text px={20} ta={'center'} fz={'h4'}>
|
<Text px={20} ta="center" fz="lg" c="black">
|
||||||
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.
|
Pelestarian lingkungan di Bali yang berpijak pada kearifan lokal, menjaga harmoni antara alam, budaya, dan manusia.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
cols={{
|
{data.map((item) => (
|
||||||
base: 1,
|
<Paper
|
||||||
md: 3,
|
key={item.id}
|
||||||
}}>
|
p="lg"
|
||||||
{data.map((v, k) => {
|
bg="linear-gradient(145deg, #DFE3E8FF 0%, #EFF1F4FF 100%)"
|
||||||
return (
|
style={{ borderRadius: 16, boxShadow: '0 0 20px rgba(28, 110, 164, 0.5)' }}
|
||||||
<Box key={k}>
|
>
|
||||||
<Paper h={{ base: 0, md: 450 }} p={20} bg={colors['white-trans-1']}>
|
<Stack gap="md" px={20}>
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.title}</Text>
|
<Center>
|
||||||
{v.listDeskripsi}
|
<Text fz="xl" fw="bold" c="black">
|
||||||
</Paper>
|
{item.title}
|
||||||
</Box>
|
</Text>
|
||||||
)
|
</Center>
|
||||||
})}
|
{item.listDeskripsi}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,63 +1,60 @@
|
|||||||
|
'use client'
|
||||||
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Flex, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Flex, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { IconClipboardTextFilled, IconMapPin, IconRecycle, IconScale, IconSearch, IconTrashFilled, IconTruckFilled } from '@tabler/icons-react';
|
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 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() {
|
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 (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
@@ -84,10 +81,10 @@ function Page() {
|
|||||||
<Box key={k} px={28}>
|
<Box key={k} px={28}>
|
||||||
<Paper p={20} bg={colors['white-trans-1']}>
|
<Paper p={20} bg={colors['white-trans-1']}>
|
||||||
<Flex gap={20} align={'center'}>
|
<Flex gap={20} align={'center'}>
|
||||||
<Box>
|
<Box style={{ alignContent: 'center', alignItems: 'center' }}>
|
||||||
{v.icon}
|
{k + 1} {iconMap[v.icon] ? React.createElement(iconMap[v.icon]) : null}
|
||||||
</Box>
|
</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>
|
</Flex>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -96,7 +93,7 @@ function Page() {
|
|||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</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"}>
|
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||||
Keterangan Bank Sampah Terdekat
|
Keterangan Bank Sampah Terdekat
|
||||||
</Text>
|
</Text>
|
||||||
@@ -107,66 +104,64 @@ function Page() {
|
|||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
placeholder='Cari Bank Sampah Terdekat'
|
placeholder='Cari Bank Sampah Terdekat'
|
||||||
/>
|
/>
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
base: 1,
|
{/* Left side - List of bank locations */}
|
||||||
md: 2,
|
|
||||||
}}>
|
|
||||||
<Box>
|
<Box>
|
||||||
<SimpleGrid
|
<Paper p="md" bg={colors['white-trans-1']} radius="lg">
|
||||||
cols={{
|
<Text fz="xl" fw="bold" mb="md">Daftar Bank Sampah</Text>
|
||||||
base: 1,
|
<Stack gap="md">
|
||||||
md: 1,
|
{data2?.map((v, k) => (
|
||||||
}}
|
<Paper key={k} p="md" withBorder radius="md">
|
||||||
>
|
<Text fw="bold" fz="lg">{v.namaTempatMaps}</Text>
|
||||||
{bankSampah.map((v, k) => {
|
<Text c="dimmed" fz="sm" mb="sm">{v.alamat}</Text>
|
||||||
return (
|
{v.lat && v.lng ? (
|
||||||
<Box key={k} px={20}>
|
<a
|
||||||
<Paper p={20} bg={colors['white-trans-1']} radius={'lg'}>
|
href={`https://www.google.com/maps/dir/?api=1&destination=${v.lat},${v.lng}`}
|
||||||
<Flex gap={20} align={'center'}>
|
target="_blank"
|
||||||
<Box>
|
rel="noopener noreferrer"
|
||||||
{v.icon}
|
style={{ color: colors['blue-button'], textDecoration: 'none' }}
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Text fz="sm">📌 Buka di Google Maps</Text>
|
||||||
<Text fw={'bold'} fz={{ base: "lg", md: "xl" }} c={'black'}>
|
</a>
|
||||||
{v.deskripsi}
|
) : (
|
||||||
</Text>
|
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
|
||||||
<Text fz={{ base: "md", md: "lg" }} c={'black'}>
|
)}
|
||||||
{v.alamat}
|
</Paper>
|
||||||
</Text>
|
))}
|
||||||
</Box>
|
</Stack>
|
||||||
</Flex>
|
</Paper>
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box style={{
|
|
||||||
position: 'relative',
|
{/* Right side - Single map showing all locations */}
|
||||||
width: '100%',
|
<Box style={{ position: 'sticky', top: '20px' }}>
|
||||||
paddingBottom: '90.5%', // Aspek rasio 16:9 (atau gunakan '100%' untuk aspek rasio 1:1)
|
<Paper p="md" bg={colors['white-trans-1']} radius="lg" h="100%">
|
||||||
height: 0,
|
<Text fz="xl" fw="bold" mb="md">Peta Lokasi</Text>
|
||||||
overflow: 'hidden'
|
{data2?.some(v => v.lat && v.lng) ? (
|
||||||
}}>
|
<Box style={{ height: '600px', width: '100%', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<iframe
|
<LeafletMultiMarkerMap
|
||||||
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"
|
center={[
|
||||||
style={{
|
data2[0]?.lat || -8.3405,
|
||||||
position: 'absolute',
|
data2[0]?.lng || 115.0920
|
||||||
top: 0,
|
]}
|
||||||
left: 0,
|
markers={data2
|
||||||
width: '100%',
|
.filter(v => v.lat && v.lng)
|
||||||
height: '100%',
|
.map(v => ({
|
||||||
border: 0
|
position: [v.lat, v.lng],
|
||||||
}}
|
popup: v.namaTempatMaps
|
||||||
loading="lazy"
|
}))}
|
||||||
allowFullScreen
|
/>
|
||||||
></iframe>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed" fz="sm">Tidak ada koordinat yang tersedia</Text>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +1,131 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconChristmasTreeFilled, IconHomeEco, IconShieldFilled, IconTrendingUp } from '@tabler/icons-react';
|
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 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() {
|
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 (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</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"}>
|
<Box>
|
||||||
Program Penghijauan Desa
|
<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>
|
||||||
<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>
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={60}>
|
<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"}>
|
<Title order={2} c={colors['blue-button']} fw="bold" py={10} px={28} fz={{ base: 'lg', md: 'xl' }}>
|
||||||
Manfaat Program Penghijauan
|
Manfaat Program
|
||||||
</Text>
|
</Title>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 4 }} spacing="lg" mt="md">
|
||||||
cols={{
|
{data.map((v) => (
|
||||||
base: 1,
|
<Paper
|
||||||
md: 4
|
key={v.id}
|
||||||
}}>
|
p="xl"
|
||||||
{data.map((v, k) => {
|
radius="xl"
|
||||||
return (
|
bg={colors['white-trans-1']}
|
||||||
<Box key={k}>
|
withBorder
|
||||||
<Paper p={20} bg={colors['white-trans-1']}>
|
style={{
|
||||||
<Stack flex={5}>
|
backdropFilter: 'blur(10px)',
|
||||||
<Center>
|
border: `1px solid rgba(255,255,255,0.2)`,
|
||||||
{v.icon}
|
transition: 'transform 0.3s, box-shadow 0.3s',
|
||||||
</Center>
|
cursor: 'pointer',
|
||||||
<Box>
|
}}
|
||||||
<Text fz={{ base: "lg", md: "xl" }} ta={'center'} c={colors['blue-button']} fw={'bold'}>{v.deskripsi}</Text>
|
onMouseEnter={(e) => {
|
||||||
</Box>
|
const el = e.currentTarget;
|
||||||
<Group justify='center'>
|
el.style.transform = 'translateY(-8px)';
|
||||||
<Button bg={colors['blue-button']}>Detail</Button>
|
el.style.boxShadow = '0 15px 30px rgba(28,110,164,0.5)';
|
||||||
</Group>
|
}}
|
||||||
</Stack>
|
onMouseLeave={(e) => {
|
||||||
</Paper>
|
const el = e.currentTarget;
|
||||||
</Box>
|
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>
|
</SimpleGrid>
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
radius="xl"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,46 +1,25 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Image, Modal, Paper, Select, SimpleGrid, Stack, Stepper, StepperStep, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Image, Modal, Paper, Select, SimpleGrid, Stack, Stepper, StepperStep, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useState } from 'react';
|
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { IconArrowRight, IconCoin, IconInfoCircle, IconSchool, IconUsers } from '@tabler/icons-react';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
const dataBeasiswa = [
|
const dataBeasiswa = [
|
||||||
{
|
{ id: 1, nama: 'Penerima Beasiswa', jumlah: '250+', icon: IconUsers },
|
||||||
id: 1,
|
{ id: 2, nama: 'Peluang Kelulusan', jumlah: '90%', icon: IconSchool },
|
||||||
nama: 'Penerima Beasiswa',
|
{ id: 3, nama: 'Dana Tersalurkan', jumlah: '1.5M', icon: IconCoin },
|
||||||
jumlah: '250+'
|
];
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
nama: 'Peluang Kelulusan',
|
|
||||||
jumlah: '90%'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
nama: 'Dana Tersalurkan',
|
|
||||||
jumlah: '1.5M'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const dataProgram = [
|
const dataProgram = [
|
||||||
{
|
{ id: 1, judul: "Pelatihan SoftSkill", deskripsi: "Pengembangan diri untuk mempersiapkan karir masa depan." },
|
||||||
id: 1,
|
{ id: 2, judul: "Peningkatan Akses Pendidikan", deskripsi: "Memberi kesempatan bagi masyarakat kurang mampu untuk tetap sekolah." },
|
||||||
judul: "Pelatihan SoftSkill",
|
{ id: 3, judul: "Pendampingan Intensif", deskripsi: "Bimbingan dari mentor berpengalaman untuk mendukung akademik." },
|
||||||
deskripsi: "Program pengembangan diri untuk mempersiapkan karir masa depan",
|
];
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
judul: "Peningkatan Akses Pendidikan ",
|
|
||||||
deskripsi: "Program yang menjangkau masyarakat kurang mampu secara finansial, mengurangi angka putus sekolah",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
judul: "Pendampingan Intensif",
|
|
||||||
deskripsi: "Program dengan mentor berpengalaman yang membimbing dalam perjalanan akademik",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar)
|
const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar)
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
@@ -60,267 +39,173 @@ function Page() {
|
|||||||
statusPernikahan: "",
|
statusPernikahan: "",
|
||||||
ukuranBaju: "",
|
ukuranBaju: "",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await beasiswaDesa.create.create();
|
await beasiswaDesa.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
close();
|
close();
|
||||||
}
|
};
|
||||||
|
|
||||||
const [active, setActive] = useState(1);
|
const [active, setActive] = useState(1);
|
||||||
const nextStep = () => setActive((current) => (current < 5 ? current + 1 : current));
|
const nextStep = () => setActive((current) => (current < 5 ? current + 1 : current));
|
||||||
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
|
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
{/* Page 1 */}
|
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Title fz={55} fw={'bold'} c={colors['blue-button']}>
|
<Title fz={55} fw={900} c={colors['blue-button']}>
|
||||||
Wujudkan Mimpi Pendidikanmu di Desa Darmasaba
|
Wujudkan Mimpi Pendidikanmu di Desa Darmasaba
|
||||||
</Title>
|
</Title>
|
||||||
<Text fz={'xl'} >
|
<Text fz="lg" mt="md" c="dimmed">
|
||||||
Program beasiswa komprehensif untuk mendukung pendidikan berkualitas bagi putra-putri Desa Darmasaba.
|
Program beasiswa untuk mendukung pendidikan berkualitas bagi generasi muda Desa Darmasaba.
|
||||||
</Text>
|
</Text>
|
||||||
<SimpleGrid
|
<Group mt="xl">
|
||||||
mt={10}
|
<Button size="lg" radius="xl" bg={colors['blue-button']} rightSection={<IconArrowRight size={20} />} onClick={open}>
|
||||||
cols={{
|
Daftar Sekarang
|
||||||
base: 1,
|
</Button>
|
||||||
md: 2
|
<Button size="lg" radius="xl" variant="light" color={colors['blue-button']} rightSection={<IconInfoCircle size={20} />}>
|
||||||
}}
|
Pelajari Lebih Lanjut
|
||||||
>
|
</Button>
|
||||||
<Button bg={colors['blue-button']} fz={'lg'} onClick={open}>Daftar Sekarang</Button>
|
</Group>
|
||||||
<Button bg={colors['blue-button-trans']} fz={'lg'}>Pelajari Lebih Lanjut</Button>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Image alt='' src={'/api/img/beasiswa-siswa.png'} />
|
<Image alt="Beasiswa Desa" src="/api/img/beasiswa-siswa.png" radius="lg" />
|
||||||
</Box>
|
</Box>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
<SimpleGrid mt={30}
|
|
||||||
cols={{
|
<SimpleGrid mt={50} cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
base: 1,
|
|
||||||
md: 3
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{dataBeasiswa.map((v, k) => {
|
{dataBeasiswa.map((v, k) => {
|
||||||
|
const IconComp = v.icon;
|
||||||
return (
|
return (
|
||||||
<Box key={k}>
|
<Paper key={k} p="xl" radius="xl" shadow="md" bg={colors['white-trans-1']} withBorder>
|
||||||
<Paper p={'xl'} bg={colors['white-trans-1']}>
|
<Stack align="center" gap="sm">
|
||||||
<Title ta={'center'} fz={55} fw={'bold'} c={colors['blue-button']}>
|
<IconComp size={45} color={colors['blue-button']} />
|
||||||
{v.jumlah}
|
<Title fz={42} fw={900} c={colors['blue-button']}>{v.jumlah}</Title>
|
||||||
</Title>
|
<Text fz="sm" ta="center">{v.nama}</Text>
|
||||||
<Text ta={'center'}>
|
</Stack>
|
||||||
{v.nama}
|
</Paper>
|
||||||
</Text>
|
);
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
{/* ---- */}
|
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
||||||
<Title pb={20} ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
<Title pb={20} ta="center" order={1} fw={900} c={colors['blue-button']}>
|
||||||
Keunggulan Program
|
Keunggulan Program
|
||||||
</Title>
|
</Title>
|
||||||
<Paper p={'xl'} bg={colors['white-trans-1']}>
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
<SimpleGrid
|
{dataProgram.map((v, k) => (
|
||||||
cols={{
|
<Paper key={k} p="xl" radius="xl" shadow="sm" bg={colors['white-trans-1']}>
|
||||||
base: 1,
|
<Title order={3} fw={700} c={colors['blue-button']} mb="xs">{v.judul}</Title>
|
||||||
md: 3
|
<Text fz="sm" c="dimmed">{v.deskripsi}</Text>
|
||||||
}}
|
</Paper>
|
||||||
>
|
))}
|
||||||
{dataProgram.map((v, k) => {
|
</SimpleGrid>
|
||||||
return (
|
|
||||||
<Box key={k}>
|
<Title py={40} ta="center" order={1} fw={900} c={colors['blue-button']}>
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
{v.judul}
|
|
||||||
</Title>
|
|
||||||
<Text>
|
|
||||||
{v.deskripsi}
|
|
||||||
</Text>
|
|
||||||
{/* <Divider orientation="vertical" size="md" h="auto" /> */}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Paper>
|
|
||||||
<Title py={20} ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
Timeline Pendaftaran
|
Timeline Pendaftaran
|
||||||
</Title>
|
</Title>
|
||||||
<Center>
|
<Center>
|
||||||
<Stepper mt={20} active={active} onStepClick={setActive} orientation="vertical" allowNextStepsSelect={false}>
|
<Stepper mt={20} active={active} onStepClick={setActive} orientation="vertical" allowNextStepsSelect={false}>
|
||||||
<StepperStep label="Pembukaan Pendaftaran 1 Maret 2025" description="" />
|
<StepperStep label="1 Maret 2025" description="Pembukaan Pendaftaran" />
|
||||||
<StepperStep label="Seleksi Administrasi 15 Maret 2025" description="" />
|
<StepperStep label="15 Maret 2025" description="Seleksi Administrasi" />
|
||||||
<StepperStep label="Tes Potensi Akademik 1 April 2025" description="" />
|
<StepperStep label="1 April 2025" description="Tes Potensi Akademik" />
|
||||||
<StepperStep label="Wawancara 15 April 2025" description="" />
|
<StepperStep label="15 April 2025" description="Wawancara" />
|
||||||
<StepperStep label="Pengumuman 1 Mei 2025" description="" />
|
<StepperStep label="1 Mei 2025" description="Pengumuman Hasil" />
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
<Group justify="center" mt="xl">
|
<Group justify="center" mt="xl">
|
||||||
<Button variant="default" onClick={prevStep}>Back</Button>
|
<Button variant="default" radius="xl" onClick={prevStep}>Kembali</Button>
|
||||||
<Button onClick={nextStep}>Next step</Button>
|
<Button radius="xl" bg={colors['blue-button']} onClick={nextStep}>Lanjut</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
radius={0}
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
transitionProps={{ transition: 'fade', duration: 200 }}
|
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||||
|
title={
|
||||||
|
<Text fz="xl" fw={800} c={colors['blue-button']}>
|
||||||
|
Formulir Beasiswa
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Paper p={"md"} withBorder>
|
<Paper p="lg" radius="xl" withBorder shadow="sm">
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="sm">
|
||||||
<Title order={3}>Ajukan Beasiswa</Title>
|
<TextInput
|
||||||
<TextInput
|
label="Nama Lengkap"
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
placeholder="Masukkan nama lengkap"
|
||||||
placeholder="masukkan nama"
|
onChange={(val) => { beasiswaDesa.create.form.namaLengkap = val.target.value }} />
|
||||||
onChange={(val) => {
|
<TextInput
|
||||||
beasiswaDesa.create.form.namaLengkap = val.target.value
|
type="number"
|
||||||
}}
|
label="NIK"
|
||||||
/>
|
placeholder="Masukkan NIK"
|
||||||
<TextInput
|
onChange={(val) => { beasiswaDesa.create.form.nik = val.target.value }} />
|
||||||
type='number'
|
<TextInput
|
||||||
label={<Text fz={"sm"} fw={"bold"}>NIK</Text>}
|
label="Tempat Lahir"
|
||||||
placeholder="masukkan nik"
|
placeholder="Masukkan tempat lahir"
|
||||||
onChange={(val) => {
|
onChange={(val) => { beasiswaDesa.create.form.tempatLahir = val.target.value }} />
|
||||||
beasiswaDesa.create.form.nik = val.target.value
|
<TextInput
|
||||||
}}
|
type="date"
|
||||||
/>
|
label="Tanggal Lahir"
|
||||||
<TextInput
|
placeholder="Pilih tanggal lahir"
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tempat Lahir</Text>}
|
onChange={(val) => { beasiswaDesa.create.form.tanggalLahir = val.target.value }} />
|
||||||
placeholder="masukkan tempat lahir"
|
<Select
|
||||||
onChange={(val) => {
|
label="Jenis Kelamin"
|
||||||
beasiswaDesa.create.form.tempatLahir = val.target.value
|
placeholder="Pilih jenis kelamin"
|
||||||
}}
|
data={[{ value: "LAKI_LAKI", label: "Laki-laki" }, { value: "PEREMPUAN", label: "Perempuan" }]}
|
||||||
/>
|
onChange={(val) => { if (val) beasiswaDesa.create.form.jenisKelamin = val }} />
|
||||||
<TextInput
|
<TextInput
|
||||||
type='date'
|
label="Kewarganegaraan"
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tanggal Lahir</Text>}
|
placeholder="Masukkan kewarganegaraan"
|
||||||
placeholder="masukkan tanggal lahir"
|
onChange={(val) => { beasiswaDesa.create.form.kewarganegaraan = val.target.value }} />
|
||||||
onChange={(val) => {
|
<Select
|
||||||
beasiswaDesa.create.form.tanggalLahir = val.target.value
|
label="Agama"
|
||||||
}}
|
placeholder="Pilih agama"
|
||||||
/>
|
data={[{ value: "ISLAM", label: "Islam" }, { value: "KRISTEN_PROTESTAN", label: "Kristen Protestan" }, { value: "KRISTEN_KATOLIK", label: "Kristen Katolik" }, { value: "HINDU", label: "Hindu" }, { value: "BUDDHA", label: "Buddha" }, { value: "KONGHUCU", label: "Konghucu" }, { value: "LAINNYA", label: "Lainnya" }]}
|
||||||
<Select
|
onChange={(val) => { if (val) beasiswaDesa.create.form.agama = val }} />
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
<TextInput
|
||||||
placeholder="Pilih jenis kelamin"
|
label="Alamat KTP"
|
||||||
data={[
|
placeholder="Masukkan alamat sesuai KTP"
|
||||||
{ value: "LAKI_LAKI", label: "Laki-laki" },
|
onChange={(val) => { beasiswaDesa.create.form.alamatKTP = val.target.value }} />
|
||||||
{ value: "PEREMPUAN", label: "Perempuan" },
|
<TextInput
|
||||||
]}
|
label="Alamat Domisili"
|
||||||
onChange={(val) => {
|
placeholder="Masukkan alamat domisili"
|
||||||
if (val) beasiswaDesa.create.form.jenisKelamin = val as "LAKI_LAKI" | "PEREMPUAN";
|
onChange={(val) => { beasiswaDesa.create.form.alamatDomisili = val.target.value }} />
|
||||||
}}
|
<TextInput
|
||||||
/>
|
type="number"
|
||||||
<TextInput
|
label="Nomor HP"
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Kewarganegaraan</Text>}
|
placeholder="Masukkan nomor HP"
|
||||||
placeholder="masukkan kewarganegaraan"
|
onChange={(val) => { beasiswaDesa.create.form.noHp = val.target.value }} />
|
||||||
onChange={(val) => {
|
<TextInput
|
||||||
beasiswaDesa.create.form.kewarganegaraan = val.target.value
|
type="email"
|
||||||
}}
|
label="Email"
|
||||||
/>
|
placeholder="Masukkan alamat email"
|
||||||
<Select
|
onChange={(val) => { beasiswaDesa.create.form.email = val.target.value }} />
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Agama</Text>}
|
<Select
|
||||||
placeholder="Pilih agama"
|
label="Status Pernikahan"
|
||||||
data={[
|
placeholder="Pilih status pernikahan"
|
||||||
{ value: "ISLAM", label: "Islam" },
|
data={[{ value: "BELUM_MENIKAH", label: "Belum Menikah" }, { value: "MENIKAH", label: "Menikah" }, { value: "JANDA_DUDA", label: "Janda/Duda" }]}
|
||||||
{ value: "KRISTEN_PROTESTAN", label: "Kristen Protestan" },
|
onChange={(val) => { if (val) beasiswaDesa.create.form.statusPernikahan = val }} />
|
||||||
{ value: "KRISTEN_KATOLIK", label: "Kristen Katolik" },
|
<Select
|
||||||
{ value: "HINDU", label: "Hindu" },
|
label="Ukuran Baju"
|
||||||
{ value: "BUDDHA", label: "Buddha" },
|
placeholder="Pilih ukuran baju"
|
||||||
{ value: "KONGHUCU", label: "Konghucu" },
|
data={[{ value: "S", label: "S" }, { value: "M", label: "M" }, { value: "L", label: "L" }, { value: "XL", label: "XL" }, { value: "XXL", label: "XXL" }, { value: "LAINNYA", label: "Lainnya" }]}
|
||||||
{ value: "LAINNYA", label: "Lainnya" },
|
onChange={(val) => { if (val) beasiswaDesa.create.form.ukuranBaju = val }} />
|
||||||
]}
|
<Group justify="flex-end" mt="md">
|
||||||
onChange={(val) => {
|
<Button variant="default" radius="xl" onClick={close}>Batal</Button>
|
||||||
if (val) beasiswaDesa.create.form.agama = val as
|
<Button radius="xl" bg={colors['blue-button']} onClick={handleSubmit}>Kirim</Button>
|
||||||
"ISLAM"
|
</Group>
|
||||||
| "KRISTEN_PROTESTAN"
|
|
||||||
| "KRISTEN_KATOLIK"
|
|
||||||
| "HINDU"
|
|
||||||
| "BUDDHA"
|
|
||||||
| "KONGHUCU"
|
|
||||||
| "LAINNYA";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Alamat KTP</Text>}
|
|
||||||
placeholder="masukkan alamat ktp"
|
|
||||||
onChange={(val) => {
|
|
||||||
beasiswaDesa.create.form.alamatKTP = val.target.value
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Alamat Domisili</Text>}
|
|
||||||
placeholder="masukkan alamat domisili"
|
|
||||||
onChange={(val) => {
|
|
||||||
beasiswaDesa.create.form.alamatDomisili = val.target.value
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
type='number'
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>No Hp</Text>}
|
|
||||||
placeholder="masukkan no hp"
|
|
||||||
onChange={(val) => {
|
|
||||||
beasiswaDesa.create.form.noHp = val.target.value
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
type='email'
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Email</Text>}
|
|
||||||
placeholder="masukkan email"
|
|
||||||
onChange={(val) => {
|
|
||||||
beasiswaDesa.create.form.email = val.target.value
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Status Pernikahan</Text>}
|
|
||||||
placeholder="Pilih status pernikahan"
|
|
||||||
data={[
|
|
||||||
{ value: "BELUM_MENIKAH", label: "Belum Menikah" },
|
|
||||||
{ value: "MENIKAH", label: "Menikah" },
|
|
||||||
{ value: "JANDA_DUDA", label: "Janda/Duda" },
|
|
||||||
]}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) beasiswaDesa.create.form.statusPernikahan = val as
|
|
||||||
"BELUM_MENIKAH"
|
|
||||||
| "MENIKAH"
|
|
||||||
| "JANDA_DUDA";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Ukuran Baju</Text>}
|
|
||||||
placeholder="Pilih ukuran baju"
|
|
||||||
data={[
|
|
||||||
{ value: "S", label: "S" },
|
|
||||||
{ value: "M", label: "M" },
|
|
||||||
{ value: "L", label: "L" },
|
|
||||||
{ value: "XL", label: "XL" },
|
|
||||||
{ value: "XXL", label: "XXL" },
|
|
||||||
{ value: "LAINNYA", label: "Lainnya" },
|
|
||||||
]}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) beasiswaDesa.create.form.ukuranBaju = val as
|
|
||||||
"S"
|
|
||||||
| "M"
|
|
||||||
| "L"
|
|
||||||
| "XL"
|
|
||||||
| "XXL"
|
|
||||||
| "LAINNYA";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -1,67 +1,97 @@
|
|||||||
|
'use client'
|
||||||
|
import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Box, Title, Text, SimpleGrid, Paper, List, ListItem } from '@mantine/core';
|
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip, Divider, Badge } from '@mantine/core';
|
||||||
import React from 'react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconMapPin, IconCalendarTime, IconBook2 } from '@tabler/icons-react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const stateTujuanProgram = useProxy(stateBimbinganBelajarDesa.stateTujuanProgram);
|
||||||
|
const stateLokasiDanJadwal = useProxy(stateBimbinganBelajarDesa.lokasiDanJadwalState);
|
||||||
|
const stateFasilitas = useProxy(stateBimbinganBelajarDesa.fasilitasYangDisediakanState);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateTujuanProgram.findById.load('edit');
|
||||||
|
stateLokasiDanJadwal.findById.load('edit');
|
||||||
|
stateFasilitas.findById.load('edit');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!stateTujuanProgram.findById.data || !stateLokasiDanJadwal.findById.data || !stateFasilitas.findById.data)
|
||||||
|
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={50}>
|
||||||
|
<Skeleton h={60} radius="xl" />
|
||||||
|
<Skeleton h={200} mt="lg" radius="md" />
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
<Box px={{ base: 'md', md: 120 }} pb={80}>
|
||||||
<Box>
|
<Box mb="lg">
|
||||||
<Title ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
<Title ta="center" order={1} fw="bold" c={colors['blue-button']} fz={{ base: 28, md: 38 }}>
|
||||||
Bimbingan Belajar Desa
|
Program Bimbingan Belajar Desa
|
||||||
</Title>
|
</Title>
|
||||||
<Text pb={20} ta={'justify'} fz={'xl'} px={{ base: 'md', md: 100 }}>
|
<Divider size="sm" my="md" mx="auto" w="60%" color={colors['blue-button']} />
|
||||||
Bimbingan Belajar Desa merupakan program unggulan untuk membantu siswa-siswi di Desa Darmasaba dalam memahami pelajaran sekolah, meningkatkan prestasi akademik, serta membangun semangat belajar yang tinggi sejak dini.
|
<Text ta="center" fz="lg" c="dimmed" px={{ base: 'sm', md: 120 }}>
|
||||||
|
Program unggulan untuk mendukung siswa Desa Darmasaba memahami pelajaran sekolah, meningkatkan prestasi akademik, dan menumbuhkan semangat belajar sejak dini.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="xl">
|
||||||
cols={{
|
<Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}>
|
||||||
base: 1,
|
<Stack gap="sm">
|
||||||
md: 3
|
<Box>
|
||||||
}}
|
<Badge variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="lg" radius="sm" mb="sm">
|
||||||
>
|
Tujuan Program
|
||||||
<Box>
|
</Badge>
|
||||||
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
<Tooltip label="Gambaran manfaat utama program" position="top-start" withArrow>
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
<Box>
|
||||||
Tujuan Program
|
<IconBook2 size={36} stroke={1.5} color={colors['blue-button']} />
|
||||||
</Title>
|
</Box>
|
||||||
<List>
|
</Tooltip>
|
||||||
<ListItem fz={'h4'}>Memberikan pendampingan belajar secara gratis bagi siswa SD hingga SMP</ListItem>
|
</Box>
|
||||||
<ListItem fz={'h4'}>Membantu siswa dalam menghadapi ujian dan menyelesaikan tugas sekolah</ListItem>
|
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: stateTujuanProgram.findById.data?.deskripsi }} />
|
||||||
<ListItem fz={'h4'}>Menumbuhkan kepercayaan diri dan kemandirian dalam belajar</ListItem>
|
</Stack>
|
||||||
<ListItem fz={'h4'}>Meningkatkan kesetaraan pendidikan untuk seluruh anak desa</ListItem>
|
</Paper>
|
||||||
</List>
|
<Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}>
|
||||||
</Paper>
|
<Stack gap="sm">
|
||||||
</Box>
|
<Box>
|
||||||
<Box>
|
<Badge variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="lg" radius="sm" mb="sm">
|
||||||
<Paper h={{base: 0, md: 324}} p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
Lokasi & Jadwal
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
</Badge>
|
||||||
Lokasi dan Jadwal
|
<Tooltip label="Tempat dan waktu pelaksanaan" position="top-start" withArrow>
|
||||||
</Title>
|
<Box>
|
||||||
<List>
|
<IconMapPin size={36} stroke={1.5} color={colors['blue-button']} />
|
||||||
<ListItem fz={'h4'}>Lokasi: Balai Banjar / Balai Desa Darmasaba / Perpustakaan Desa</ListItem>
|
</Box>
|
||||||
<ListItem fz={'h4'}>Jadwal: Setiap hari Senin, Rabu, dan Jumat pukul 16.00–18.00 WITA</ListItem>
|
</Tooltip>
|
||||||
<ListItem fz={'h4'}>Peserta: Terbuka untuk semua siswa SD–SMP di wilayah desa</ListItem>
|
</Box>
|
||||||
</List>
|
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: stateLokasiDanJadwal.findById.data?.deskripsi }} />
|
||||||
</Paper>
|
</Stack>
|
||||||
</Box>
|
</Paper>
|
||||||
<Box>
|
<Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}>
|
||||||
<Paper h={{base: 0, md: 324}} p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
<Stack gap="sm">
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
<Box>
|
||||||
Fasilitas yang Disediakan
|
<Badge variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="lg" radius="sm" mb="sm">
|
||||||
</Title>
|
Fasilitas
|
||||||
<List>
|
</Badge>
|
||||||
<ListItem fz={'h4'}>Buku-buku pelajaran dan alat tulis</ListItem>
|
<Tooltip label="Sarana yang disediakan untuk peserta" position="top-start" withArrow>
|
||||||
<ListItem fz={'h4'}>Ruang belajar nyaman dan kondusif</ListItem>
|
<Box>
|
||||||
<ListItem fz={'h4'}>Modul latihan dan pendampingan tugas</ListItem>
|
<IconCalendarTime size={36} stroke={1.5} color={colors['blue-button']} />
|
||||||
<ListItem fz={'h4'}>Minuman ringan dan dukungan motivasi belajar</ListItem>
|
</Box>
|
||||||
</List>
|
</Tooltip>
|
||||||
</Paper>
|
</Box>
|
||||||
</Box>
|
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: stateFasilitas.findById.data?.deskripsi }} />
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,71 +1,102 @@
|
|||||||
import colors from '@/con/colors';
|
'use client'
|
||||||
import { Stack, Box, Title, Paper } from '@mantine/core';
|
import dataPendidikan from '@/app/admin/(dashboard)/_state/pendidikan/data-pendidikan';
|
||||||
import React from 'react';
|
import { Box, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconSchool } from '@tabler/icons-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Bar, BarChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { BarChart } from '@mantine/charts';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
kategori: 'Jumlah penduduk usia 15-64 th yang tidak bisa baca tulis',
|
|
||||||
jumlah: 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kategori: 'Jumlah penduduk tidak tamat SD/sederajat',
|
|
||||||
jumlah: 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kategori: 'Jumlah penduduk tidak tamat SLTP/Sederajat',
|
|
||||||
jumlah: 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kategori: 'Jumlah penduduk tidak tamat SLTA/Sederajat',
|
|
||||||
jumlah: 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kategori: 'Jumlah penduduk tamat Sarjana/S1',
|
|
||||||
jumlah: 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kategori: 'Jumlah penduduk tamat Pascsarjana',
|
|
||||||
jumlah: 30
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
type DPMrafik = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
jumlah: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stateDPM = useProxy(dataPendidikan);
|
||||||
|
const [chartData, setChartData] = useState<DPMrafik[]>([]);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
stateDPM.findMany.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stateDPM.findMany.data) {
|
||||||
|
setChartData(
|
||||||
|
stateDPM.findMany.data.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
jumlah: Number(item.jumlah),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [stateDPM.findMany.data]);
|
||||||
|
|
||||||
|
if (!stateDPM.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack px="md" py="xl">
|
||||||
|
<Skeleton h={400} radius="lg" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack bg="var(--mantine-color-gray-0)" py="xl" gap="lg" pos="relative">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
|
||||||
<Box pb={20}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Title ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
<Stack gap="xs" align="center" pb="lg">
|
||||||
Data Pendidikan
|
<IconSchool size={48} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={1} fw={700} ta="center" c={colors['blue-button']}>
|
||||||
|
Statistik Data Pendidikan
|
||||||
</Title>
|
</Title>
|
||||||
</Box>
|
<Text c="dimmed" size="sm" ta="center">
|
||||||
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
Visualisasi jumlah pendidikan berdasarkan kategori yang tersedia
|
||||||
<BarChart
|
</Text>
|
||||||
p={'100'}
|
</Stack>
|
||||||
h={600}
|
|
||||||
data={data}
|
{!mounted || chartData.length === 0 ? (
|
||||||
dataKey="kategori"
|
<Paper radius="lg" p="xl" withBorder shadow="sm" bg="var(--mantine-color-white)">
|
||||||
series={[
|
<Stack align="center" gap="sm" justify="center" h={350}>
|
||||||
{ name: 'jumlah', color: colors['blue-button'] },
|
<IconSchool size={40} stroke={1.5} color="var(--mantine-color-gray-5)" />
|
||||||
]}
|
<Title order={4} fw={600}>
|
||||||
tickLine="y"
|
Belum Ada Data
|
||||||
xAxisProps={{
|
</Title>
|
||||||
angle: -45, // Rotate labels by -45 degrees
|
<Text c="dimmed" size="sm">
|
||||||
textAnchor: 'end', // Anchor text to the end for better alignment
|
Data pendidikan belum tersedia. Silakan tambahkan data untuk melihat grafik.
|
||||||
height: 100, // Increase height for rotated labels
|
</Text>
|
||||||
interval: 0, // Show all labels
|
</Stack>
|
||||||
style: {
|
</Paper>
|
||||||
fontSize: '12px', // Adjust font size if needed
|
) : (
|
||||||
overflow: 'visible',
|
<Paper radius="lg" p="xl" withBorder shadow="sm" bg="var(--mantine-color-white)">
|
||||||
whiteSpace: 'nowrap'
|
<Title order={4} fw={600} mb="md">
|
||||||
}
|
Grafik Pendidikan
|
||||||
}}
|
</Title>
|
||||||
/>
|
<ResponsiveContainer width="100%" height={350}>
|
||||||
</Paper>
|
<BarChart data={chartData}>
|
||||||
|
<XAxis dataKey="name" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'var(--mantine-color-gray-0)',
|
||||||
|
border: '1px solid var(--mantine-color-gray-3)',
|
||||||
|
}}
|
||||||
|
cursor={{ fill: 'var(--mantine-color-gray-1)' }}
|
||||||
|
/>
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah Pendidikan" radius={[8, 8, 0, 0]} />
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client';
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import { Box, Button, Center, Container, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip, ActionIcon } from '@mantine/core';
|
||||||
|
import { IconChalkboard, IconMicroscope, IconProps, IconRefresh, IconSchool, IconInfoCircle } from '@tabler/icons-react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
|
import React from 'react';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface Stat {
|
||||||
|
jenjangPendidikan: any;
|
||||||
|
icon: React.ComponentType<IconProps>;
|
||||||
|
jumlah: number;
|
||||||
|
nama: string;
|
||||||
|
helper?: string;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function KategoriPage({ jenjangPendidikan }: { jenjangPendidikan: string }) {
|
||||||
|
const router = useTransitionRouter();
|
||||||
|
const [stats, setStats] = useState<Stat[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
// Decode the URL parameter
|
||||||
|
const decodedJenjangPendidikan = decodeURIComponent(jenjangPendidikan);
|
||||||
|
const jenjangFilter = decodedJenjangPendidikan.toLowerCase() === 'semua'
|
||||||
|
? undefined
|
||||||
|
: decodedJenjangPendidikan;
|
||||||
|
|
||||||
|
const loadData = useCallback(async () => {
|
||||||
|
if (!decodedJenjangPendidikan) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Load all data in parallel with the jenjang filter
|
||||||
|
await Promise.all([
|
||||||
|
infoSekolahPaud.lembagaPendidikan.findMany.load(1, 100, '', jenjangFilter),
|
||||||
|
infoSekolahPaud.siswa.findMany.load(1, 100, '', jenjangFilter),
|
||||||
|
infoSekolahPaud.pengajar.findMany.load(1, 100, '', jenjangFilter),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Get filtered totals based on jenjang
|
||||||
|
const totalLembaga = infoSekolahPaud.lembagaPendidikan.findMany.total || 0;
|
||||||
|
const totalSiswa = infoSekolahPaud.siswa.findMany.total || 0;
|
||||||
|
const totalPengajar = infoSekolahPaud.pengajar.findMany.total || 0;
|
||||||
|
|
||||||
|
setStats([
|
||||||
|
{
|
||||||
|
|
||||||
|
icon: IconChalkboard,
|
||||||
|
jumlah: totalLembaga,
|
||||||
|
nama: 'Lembaga Pendidikan',
|
||||||
|
helper: 'Jumlah institusi pendidikan resmi di wilayah ini',
|
||||||
|
loading: false,
|
||||||
|
jenjangPendidikan: decodedJenjangPendidikan,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconSchool,
|
||||||
|
jumlah: totalSiswa,
|
||||||
|
nama: 'Siswa Terdaftar',
|
||||||
|
helper: 'Total siswa aktif di semua jenjang',
|
||||||
|
loading: false,
|
||||||
|
jenjangPendidikan: decodedJenjangPendidikan,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconMicroscope,
|
||||||
|
jumlah: totalPengajar,
|
||||||
|
nama: 'Tenaga Pengajar',
|
||||||
|
helper: 'Jumlah guru dan staf pengajar aktif',
|
||||||
|
loading: false,
|
||||||
|
jenjangPendidikan: decodedJenjangPendidikan,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
// Set error state or show toast notification
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [decodedJenjangPendidikan, jenjangFilter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, [loadData, decodedJenjangPendidikan]);
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasilCount = stats.reduce((sum, stat) => sum + stat.jumlah, 0);
|
||||||
|
const filtered = stats;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Box style={{ minHeight: '100vh', background: '#f8fafc', padding: '48px 0' }}>
|
||||||
|
<Container size="xl">
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
|
{[1, 2, 3].map((i) => (
|
||||||
|
<Skeleton key={i} height={260} radius="lg" />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box style={{ minHeight: '100vh', background: '#f8fafc', paddingBottom: 48 }}>
|
||||||
|
<Container size="xl" py={{ base: 'md', md: 'xl' }}>
|
||||||
|
<Box>
|
||||||
|
<Group justify="space-between" mb="md">
|
||||||
|
<Box aria-live="polite" aria-atomic>
|
||||||
|
<Text fz="sm" c="dimmed">
|
||||||
|
Menampilkan <Text component="span" c="#0f172a" fw={700}>{hasilCount}</Text> hasil.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconRefresh size={16} />}
|
||||||
|
variant="outline"
|
||||||
|
size="xs"
|
||||||
|
onClick={handleRefresh}
|
||||||
|
loading={stats.some(stat => stat.loading)}
|
||||||
|
>
|
||||||
|
Segarkan Data
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<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={handleRefresh}
|
||||||
|
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) => (
|
||||||
|
<motion.div
|
||||||
|
key={v.nama}
|
||||||
|
whileHover={{ scale: 1.025 }}
|
||||||
|
whileTap={{ scale: 0.995 }}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Skeleton visible={v.loading}>
|
||||||
|
<Paper
|
||||||
|
p="lg"
|
||||||
|
radius="lg"
|
||||||
|
style={{
|
||||||
|
background: 'white',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
boxShadow: '0 8px 28px rgba(0,0,0,0.06)',
|
||||||
|
minHeight: 260,
|
||||||
|
}}
|
||||||
|
role="article"
|
||||||
|
aria-label={`${v.nama} kartu statistik`}
|
||||||
|
>
|
||||||
|
<Stack gap="sm" mb="md">
|
||||||
|
<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="center" align="center" gap="xs">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text ta={"center"} fz={{ base: 18, md: 22 }} fw={800} c="#0f172a">
|
||||||
|
{v.jumlah.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
<Group gap={6} align="center">
|
||||||
|
<Text ta={"center"} 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>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Group justify="center" mt="8px">
|
||||||
|
<Button
|
||||||
|
radius="xl"
|
||||||
|
variant="outline"
|
||||||
|
aria-label={`Lihat detail ${v.nama}`}
|
||||||
|
style={{
|
||||||
|
borderColor: '#e2e8f0',
|
||||||
|
color: '#2563eb',
|
||||||
|
paddingLeft: 20,
|
||||||
|
paddingRight: 20,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (v.nama === "Lembaga Pendidikan") router.push(`/darmasaba/pendidikan/info-sekolah-paud/${jenjangPendidikan}/lembaga`);
|
||||||
|
if (v.nama === "Siswa Terdaftar") router.push(`/darmasaba/pendidikan/info-sekolah-paud/${jenjangPendidikan}/siswa`);
|
||||||
|
if (v.nama === "Tenaga Pengajar") router.push(`/darmasaba/pendidikan/info-sekolah-paud/${jenjangPendidikan}/pengajar`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Lihat Detail
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
</Skeleton>
|
||||||
|
</motion.div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconSchool, IconLayersSubtract } from '@tabler/icons-react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { use } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ jenjangPendidikan: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page({ params }: PageProps) {
|
||||||
|
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
|
||||||
|
const { jenjangPendidikan } = use(params);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
// Decode the URL parameter and pass it to load
|
||||||
|
const decodedJenjang = decodeURIComponent(jenjangPendidikan).toLowerCase()
|
||||||
|
load(page, 10, '', decodedJenjang === 'semua' ? '' : decodedJenjang)
|
||||||
|
}, [page, jenjangPendidikan])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="lg" gap="md">
|
||||||
|
<Skeleton h={40} radius="xl" />
|
||||||
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="lg">
|
||||||
|
<Paper
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="xl"
|
||||||
|
shadow="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center" mb="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconSchool size={28} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fz="xl">Daftar Lembaga Pendidikan</Title>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Stack align="center" justify="center" py="xl" gap="sm">
|
||||||
|
<IconLayersSubtract size={48} stroke={1.5} color={"gray"} />
|
||||||
|
<Text fz="lg" c="dimmed">Belum ada data lembaga pendidikan</Text>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
horizontalSpacing="md"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="60%">Nama Lembaga</TableTh>
|
||||||
|
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd fw={500}>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -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<{ jenjangPendidikan: string }> }) {
|
||||||
|
const { jenjangPendidikan } = await params;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Content jenjangPendidikan={jenjangPendidikan} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconSchool, IconLayersSubtract } from '@tabler/icons-react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { use } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ jenjangPendidikan: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page({ params }: PageProps) {
|
||||||
|
const stateList = useProxy(infoSekolahPaud.pengajar)
|
||||||
|
const { jenjangPendidikan } = use(params);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
// Decode the URL parameter and pass it to load
|
||||||
|
const decodedJenjang = decodeURIComponent(jenjangPendidikan).toLowerCase()
|
||||||
|
load(page, 10, '', decodedJenjang === 'semua' ? '' : decodedJenjang)
|
||||||
|
}, [page, jenjangPendidikan])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="lg" gap="md">
|
||||||
|
<Skeleton h={40} radius="xl" />
|
||||||
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="lg">
|
||||||
|
<Paper
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="xl"
|
||||||
|
shadow="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center" mb="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconSchool size={28} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fz="xl">Daftar Pengajar</Title>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Stack align="center" justify="center" py="xl" gap="sm">
|
||||||
|
<IconLayersSubtract size={48} stroke={1.5} color={"gray"} />
|
||||||
|
<Text fz="lg" c="dimmed">Belum ada data pengajar</Text>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
horizontalSpacing="md"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="30%">Nama Pengajar</TableTh>
|
||||||
|
<TableTh w="60%">Nama Lembaga</TableTh>
|
||||||
|
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd fw={500}>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconSchool, IconLayersSubtract } from '@tabler/icons-react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { use } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ jenjangPendidikan: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page({ params }: PageProps) {
|
||||||
|
const stateList = useProxy(infoSekolahPaud.siswa)
|
||||||
|
const { jenjangPendidikan } = use(params);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
// Decode the URL parameter and pass it to load
|
||||||
|
const decodedJenjang = decodeURIComponent(jenjangPendidikan).toLowerCase()
|
||||||
|
load(page, 10, '', decodedJenjang === 'semua' ? '' : decodedJenjang)
|
||||||
|
}, [page, jenjangPendidikan])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="lg" gap="md">
|
||||||
|
<Skeleton h={40} radius="xl" />
|
||||||
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="lg">
|
||||||
|
<Paper
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="xl"
|
||||||
|
shadow="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center" mb="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconSchool size={28} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fz="xl">Daftar Siswa</Title>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Stack align="center" justify="center" py="xl" gap="sm">
|
||||||
|
<IconLayersSubtract size={48} stroke={1.5} color={"gray"} />
|
||||||
|
<Text fz="lg" c="dimmed">Belum ada data siswa</Text>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
horizontalSpacing="md"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="30%">Nama Siswa</TableTh>
|
||||||
|
<TableTh w="60%">Nama Lembaga</TableTh>
|
||||||
|
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd fw={500}>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
VisuallyHidden,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowLeft, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
type LayoutSekolahProps = {
|
||||||
|
title?: string;
|
||||||
|
jenjangPendidikanList?: string[];
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LayoutSekolah({
|
||||||
|
title = 'Cari Informasi Sekolah',
|
||||||
|
jenjangPendidikanList = ['Semua', 'TK', 'SD', 'SMP', 'SMA'],
|
||||||
|
children,
|
||||||
|
}: LayoutSekolahProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const initialQuery = searchParams.get('search') || '';
|
||||||
|
const initialJenjangPendidikan = searchParams.get('jenjangPendidikan') || 'Semua';
|
||||||
|
|
||||||
|
const [query, setQuery] = useState(initialQuery);
|
||||||
|
const [jenjangPendidikanAktif, setJenjangPendidikanAktif] = useState(initialJenjangPendidikan);
|
||||||
|
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// Cleanup timeout
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (searchTimeout) clearTimeout(searchTimeout);
|
||||||
|
};
|
||||||
|
}, [searchTimeout]);
|
||||||
|
|
||||||
|
// Handle Search with debounce
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
|
||||||
|
setQuery(val);
|
||||||
|
|
||||||
|
if (searchTimeout) clearTimeout(searchTimeout);
|
||||||
|
const t = window.setTimeout(() => {
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
if (val) params.set('search', val);
|
||||||
|
else params.delete('search');
|
||||||
|
params.set('jenjangPendidikan', jenjangPendidikanAktif);
|
||||||
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
|
}, 500);
|
||||||
|
setSearchTimeout(t);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle jenjang pendidikan click
|
||||||
|
const handleJenjangPendidikanChange = (k: string) => {
|
||||||
|
// arahkan langsung ke route jenjang pendidikan
|
||||||
|
if (k.toLowerCase() === 'semua') {
|
||||||
|
setJenjangPendidikanAktif(k);
|
||||||
|
router.push(`/darmasaba/pendidikan/info-sekolah-paud/semua`);
|
||||||
|
} else {
|
||||||
|
setJenjangPendidikanAktif(k);
|
||||||
|
router.push(`/darmasaba/pendidikan/info-sekolah-paud/${encodeURIComponent(k.toLowerCase())}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box style={{ minHeight: '100vh', background: colors.Bg, paddingBottom: 48 }}>
|
||||||
|
<Container size="xl" py={{ base: 'md', md: 'xl' }}>
|
||||||
|
<Stack gap="lg">
|
||||||
|
{/* Back Button */}
|
||||||
|
<ActionIcon onClick={() => window.history.back()} variant="light" radius="md" size="lg">
|
||||||
|
<IconArrowLeft size={20} />
|
||||||
|
<VisuallyHidden>Kembali</VisuallyHidden>
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
|
{/* Search & Filter */}
|
||||||
|
<Paper radius="lg" p="xl" withBorder>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text ta="center" fw={800} fz={28}>{title}</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={query}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
placeholder="Cari sekolah..."
|
||||||
|
leftSection={<IconSearch size={18} />}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="center" gap="xs" wrap="wrap">
|
||||||
|
{jenjangPendidikanList.map((k) => {
|
||||||
|
const aktif = k === jenjangPendidikanAktif;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={k}
|
||||||
|
onClick={() => handleJenjangPendidikanChange(k)}
|
||||||
|
radius="xl"
|
||||||
|
size="sm"
|
||||||
|
variant={aktif ? 'filled' : 'light'}
|
||||||
|
>
|
||||||
|
{k}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Slot konten */}
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const LayoutSekolah = dynamic(
|
||||||
|
() => import('./_lib/layoutTabs'),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
function Layout({children} : {children: React.ReactNode}) {
|
||||||
|
return (
|
||||||
|
<LayoutSekolah>
|
||||||
|
{children}
|
||||||
|
</LayoutSekolah>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
'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 { motion } from 'framer-motion';
|
|
||||||
|
|
||||||
|
|
||||||
const dataSekolah = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
icon: <IconChalkboard size={55} color={colors["blue-button"]} />,
|
|
||||||
jumlah: 15,
|
|
||||||
nama: 'Lembaga Pendidikan'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
icon: <IconSchool size={55} color={colors["blue-button"]} />,
|
|
||||||
jumlah: 3209,
|
|
||||||
nama: 'Siswa Terdaftar'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
icon: <IconMicroscope size={55} color={colors["blue-button"]} />,
|
|
||||||
jumlah: 285,
|
|
||||||
nama: 'Tenaga Pengajar'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
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}>
|
|
||||||
<motion.div
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.8 }}
|
|
||||||
style={{ width: '100%', height: '100%' }}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</motion.div>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Page;
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconSchool, IconLayersSubtract } from '@tabler/icons-react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10)
|
||||||
|
}, [page])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="lg" gap="md">
|
||||||
|
<Skeleton h={40} radius="xl" />
|
||||||
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="lg">
|
||||||
|
<Paper
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="xl"
|
||||||
|
shadow="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center" mb="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconSchool size={28} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fz="xl">Daftar Lembaga Pendidikan</Title>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Stack align="center" justify="center" py="xl" gap="sm">
|
||||||
|
<IconLayersSubtract size={48} stroke={1.5} color={"gray"} />
|
||||||
|
<Text fz="lg" c="dimmed">Belum ada data lembaga pendidikan</Text>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
horizontalSpacing="md"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="60%">Nama Lembaga</TableTh>
|
||||||
|
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd fw={500}>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Container,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import type { IconProps } from '@tabler/icons-react';
|
||||||
|
import {
|
||||||
|
IconChalkboard,
|
||||||
|
IconInfoCircle,
|
||||||
|
IconMicroscope,
|
||||||
|
IconRefresh,
|
||||||
|
IconSchool,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
type Stat = {
|
||||||
|
icon: React.ComponentType<IconProps>;
|
||||||
|
jumlah: number;
|
||||||
|
nama: string;
|
||||||
|
helper?: string;
|
||||||
|
loading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SekolahPage() {
|
||||||
|
const [stats, setStats] = useState<Stat[]>([
|
||||||
|
{
|
||||||
|
icon: IconChalkboard,
|
||||||
|
jumlah: 0,
|
||||||
|
nama: 'Lembaga Pendidikan',
|
||||||
|
helper: 'Jumlah institusi pendidikan resmi di wilayah ini',
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconSchool,
|
||||||
|
jumlah: 0,
|
||||||
|
nama: 'Siswa Terdaftar',
|
||||||
|
helper: 'Total siswa aktif di semua jenjang',
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconMicroscope,
|
||||||
|
jumlah: 0,
|
||||||
|
nama: 'Tenaga Pengajar',
|
||||||
|
helper: 'Jumlah guru dan staf pengajar aktif',
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const router = useTransitionRouter()
|
||||||
|
const stateLembaga = useProxy(infoSekolahPaud.lembagaPendidikan);
|
||||||
|
const stateSiswa = useProxy(infoSekolahPaud.siswa);
|
||||||
|
const statePengajar = useProxy(infoSekolahPaud.pengajar);
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
// Load lembaga data
|
||||||
|
await stateLembaga.findMany.load(1, 1, '');
|
||||||
|
const totalLembaga = stateLembaga.findMany.total || 0;
|
||||||
|
|
||||||
|
// Load siswa data
|
||||||
|
await stateSiswa.findMany.load(1, 1, '');
|
||||||
|
const totalSiswa = stateSiswa.findMany.total || 0;
|
||||||
|
|
||||||
|
// Load pengajar data
|
||||||
|
await statePengajar.findMany.load(1, 1, '');
|
||||||
|
const totalPengajar = statePengajar.findMany.total || 0;
|
||||||
|
|
||||||
|
setStats([
|
||||||
|
{
|
||||||
|
icon: IconChalkboard,
|
||||||
|
jumlah: totalLembaga,
|
||||||
|
nama: 'Lembaga Pendidikan',
|
||||||
|
helper: 'Jumlah institusi pendidikan resmi di wilayah ini',
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconSchool,
|
||||||
|
jumlah: totalSiswa,
|
||||||
|
nama: 'Siswa Terdaftar',
|
||||||
|
helper: 'Total siswa aktif di semua jenjang',
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconMicroscope,
|
||||||
|
jumlah: totalPengajar,
|
||||||
|
nama: 'Tenaga Pengajar',
|
||||||
|
helper: 'Jumlah guru dan staf pengajar aktif',
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
// Set error state or show toast notification
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
setStats(prev => prev.map(stat => ({ ...stat, loading: true })));
|
||||||
|
loadData();
|
||||||
|
};
|
||||||
|
const [query] = useState('');
|
||||||
|
|
||||||
|
const filtered = stats.filter((d) => {
|
||||||
|
const q = query.trim().toLowerCase();
|
||||||
|
if (!q) return true;
|
||||||
|
const teks = `${d.nama} ${d.jumlah}`.toLowerCase();
|
||||||
|
return teks.includes(q);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper radius="md" style={{ minHeight: '100vh', background: '#f8fafc', paddingBottom: 48 }}>
|
||||||
|
<Container size="xl" py={{ base: 'md', md: 'xl' }}>
|
||||||
|
<Box>
|
||||||
|
<Group justify="start" mb="md">
|
||||||
|
<Button
|
||||||
|
leftSection={<IconRefresh size={16} />}
|
||||||
|
variant="outline"
|
||||||
|
size="xs"
|
||||||
|
onClick={handleRefresh}
|
||||||
|
loading={stats.some(stat => stat.loading)}
|
||||||
|
>
|
||||||
|
Segarkan Data
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<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={handleRefresh}
|
||||||
|
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) => (
|
||||||
|
<motion.div
|
||||||
|
key={v.nama}
|
||||||
|
whileHover={{ scale: 1.025 }}
|
||||||
|
whileTap={{ scale: 0.995 }}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Skeleton visible={v.loading}>
|
||||||
|
<Paper
|
||||||
|
p="lg"
|
||||||
|
radius="lg"
|
||||||
|
style={{
|
||||||
|
background: 'white',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
boxShadow: '0 8px 28px rgba(0,0,0,0.06)',
|
||||||
|
minHeight: 260,
|
||||||
|
}}
|
||||||
|
role="article"
|
||||||
|
aria-label={`${v.nama} kartu statistik`}
|
||||||
|
>
|
||||||
|
<Stack gap="sm" mb="md">
|
||||||
|
<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="center" align="center" gap="xs">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text ta={"center"} fz={{ base: 18, md: 22 }} fw={800} c="#0f172a">
|
||||||
|
{v.jumlah.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
<Group gap={6} align="center">
|
||||||
|
<Text ta={"center"} 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>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Group justify="center" mt="8px">
|
||||||
|
<Button
|
||||||
|
radius="xl"
|
||||||
|
variant="outline"
|
||||||
|
aria-label={`Lihat detail ${v.nama}`}
|
||||||
|
style={{
|
||||||
|
borderColor: '#e2e8f0',
|
||||||
|
color: '#2563eb',
|
||||||
|
paddingLeft: 20,
|
||||||
|
paddingRight: 20,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (v.nama === "Lembaga Pendidikan") router.push(`/darmasaba/pendidikan/info-sekolah-paud/semua/lembaga`);
|
||||||
|
if (v.nama === "Siswa Terdaftar") router.push(`/darmasaba/pendidikan/info-sekolah-paud/semua/siswa`);
|
||||||
|
if (v.nama === "Tenaga Pengajar") router.push(`/darmasaba/pendidikan/info-sekolah-paud/semua/pengajar`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Lihat Detail
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
</Skeleton>
|
||||||
|
</motion.div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconSchool, IconLayersSubtract } from '@tabler/icons-react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
const stateList = useProxy(infoSekolahPaud.pengajar)
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10)
|
||||||
|
}, [page])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="lg" gap="md">
|
||||||
|
<Skeleton h={40} radius="xl" />
|
||||||
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="lg">
|
||||||
|
<Paper
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="xl"
|
||||||
|
shadow="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center" mb="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconSchool size={28} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fz="xl">Daftar Pengajar</Title>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Stack align="center" justify="center" py="xl" gap="sm">
|
||||||
|
<IconLayersSubtract size={48} stroke={1.5} color={"gray"} />
|
||||||
|
<Text fz="lg" c="dimmed">Belum ada data pengajar</Text>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
horizontalSpacing="md"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="30%">Nama Pengajar</TableTh>
|
||||||
|
<TableTh w="30%">Nama Lembaga</TableTh>
|
||||||
|
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
'use client'
|
||||||
|
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconSchool, IconLayersSubtract } from '@tabler/icons-react';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
const stateList = useProxy(infoSekolahPaud.siswa)
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10)
|
||||||
|
}, [page])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="lg" gap="md">
|
||||||
|
<Skeleton h={40} radius="xl" />
|
||||||
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="lg">
|
||||||
|
<Paper
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="xl"
|
||||||
|
shadow="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center" mb="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconSchool size={28} stroke={1.5} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fz="xl">Daftar Siswa</Title>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<Stack align="center" justify="center" py="xl" gap="sm">
|
||||||
|
<IconLayersSubtract size={48} stroke={1.5} color={"gray"} />
|
||||||
|
<Text fz="lg" c="dimmed">Belum ada data siswa</Text>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withRowBorders
|
||||||
|
horizontalSpacing="md"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
fz="sm"
|
||||||
|
>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="30%">Nama Siswa</TableTh>
|
||||||
|
<TableTh w="30%">Nama Lembaga</TableTh>
|
||||||
|
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.nama}</TableTd>
|
||||||
|
<TableTd>{item.lembaga.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="lg">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
size="md"
|
||||||
|
radius="xl"
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1,92 +1,107 @@
|
|||||||
|
'use client'
|
||||||
|
import pendidikanNonFormalState from '@/app/admin/(dashboard)/_state/pendidikan/pendidikan-non-formal';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Box, Title, Text, SimpleGrid, Paper, List, ListItem } from '@mantine/core';
|
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import React from 'react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconMapPin, IconTarget, IconBook2 } from '@tabler/icons-react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const stateTujuanPendidikanNonFormal = useProxy(pendidikanNonFormalState.stateTujuanPendidikanNonFormal);
|
||||||
|
const stateTempatKegiatan = useProxy(pendidikanNonFormalState.stateTempatKegiatan);
|
||||||
|
const stateJenisProgram = useProxy(pendidikanNonFormalState.stateJenisProgram);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateTujuanPendidikanNonFormal.findById.load('edit');
|
||||||
|
stateTempatKegiatan.findById.load('edit');
|
||||||
|
stateJenisProgram.findById.load('edit');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!stateTujuanPendidikanNonFormal.findById.data || !stateTempatKegiatan.findById.data || !stateJenisProgram.findById.data)
|
||||||
|
return (
|
||||||
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg" mih="100vh" justify="flex-start">
|
||||||
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<BackButton />
|
||||||
|
</Box>
|
||||||
|
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||||
|
<Skeleton h={50} radius="xl" />
|
||||||
|
<Skeleton h={150} mt="lg" radius="md" />
|
||||||
|
<Skeleton h={150} mt="lg" radius="md" />
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg" mih="100vh">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||||
<Box>
|
<Box>
|
||||||
<Title ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
<Title ta="center" order={1} fw="bold" c={colors['blue-button']} mb="sm">
|
||||||
Pendidikan Non Formal
|
Pendidikan Non Formal
|
||||||
</Title>
|
</Title>
|
||||||
<Text pb={20} ta={'justify'} fz={'xl'} px={{ base: 'md', md: 100 }}>
|
<Text ta="center" fz="lg" lh={1.6} c="black" maw={800} mx="auto">
|
||||||
Pendidikan Non Formal adalah bentuk pendidikan di luar sekolah yang diselenggarakan secara terstruktur dan bertujuan memberikan keterampilan, pengetahuan, serta pengembangan karakter masyarakat dari berbagai usia dan latar belakang.
|
Bentuk pendidikan di luar sekolah yang terstruktur, bertujuan memberikan keterampilan, pengetahuan, dan pengembangan karakter masyarakat dari berbagai usia serta latar belakang.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
cols={{
|
cols={{ base: 1, md: 2 }}
|
||||||
base: 1,
|
spacing="lg"
|
||||||
md: 2
|
mt={40}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box>
|
<Paper
|
||||||
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
p="xl"
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
radius="lg"
|
||||||
Tujuan Program
|
bg={colors['white-trans-1']}
|
||||||
</Title>
|
shadow="md"
|
||||||
<List>
|
withBorder
|
||||||
<ListItem fz={'h4'}>Memberikan kesempatan belajar yang fleksibel bagi warga desa</ListItem>
|
>
|
||||||
<ListItem fz={'h4'}>Meningkatkan keterampilan hidup dan kemandirian ekonomi</ListItem>
|
|
||||||
<ListItem fz={'h4'}>Mendorong partisipasi masyarakat dalam pembangunan desa</ListItem>
|
|
||||||
<ListItem fz={'h4'}>Mengurangi angka putus sekolah dan meningkatkan kualitas SDM</ListItem>
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper h={{ base: 0, md: 210 }} p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
Tempat Kegiatan
|
|
||||||
</Title>
|
|
||||||
<List>
|
|
||||||
<ListItem fz={'h4'}>Balai Desa Darmasaba</ListItem>
|
|
||||||
<ListItem fz={'h4'}>TPK, Perpustakaan Desa, atau Posyandu</ListItem>
|
|
||||||
<ListItem fz={'h4'}>Bisa juga dilakukan secara mobile atau door to door</ListItem>
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</SimpleGrid>
|
|
||||||
<Box py={20}>
|
|
||||||
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
Jenis Program yang Diselenggarakan
|
|
||||||
</Title>
|
|
||||||
<Text fz={'h4'}>Program Pendidikan Non Formal yang diselenggarakan di Desa Darmasaba meliputi:</Text>
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<Box>
|
<Tooltip label="Fokus utama program" withArrow>
|
||||||
<Text fz={'h4'}> 1) Keaksaraan Fungsional</Text>
|
<Title order={2} fw="bold" c={colors['blue-button']} mb="xs" flex="center">
|
||||||
<List>
|
<IconTarget size={28} style={{ marginRight: 8 }} />
|
||||||
<ListItem fz={'h4'}>Untuk warga yang belum bisa membaca dan menulis</ListItem>
|
Tujuan Program
|
||||||
</List>
|
</Title>
|
||||||
</Box>
|
</Tooltip>
|
||||||
<Box>
|
<Text fz="md" lh={1.7} c="dark" dangerouslySetInnerHTML={{ __html: stateTujuanPendidikanNonFormal.findById.data?.deskripsi }} />
|
||||||
<Text fz={'h4'}> 2) Pendidikan Kesetaraan (Paket A, B, C)</Text>
|
</Stack>
|
||||||
<List>
|
</Paper>
|
||||||
<ListItem fz={'h4'}>Setara SD, SMP, dan SMA bagi yang tidak menyelesaikan pendidikan formal</ListItem>
|
<Paper
|
||||||
</List>
|
p="xl"
|
||||||
</Box>
|
radius="lg"
|
||||||
<Box>
|
bg={colors['white-trans-1']}
|
||||||
<Text fz={'h4'}> 3) Pelatihan Keterampilan</Text>
|
shadow="md"
|
||||||
<List>
|
withBorder
|
||||||
<ListItem fz={'h4'}>Menjahit, memasak, sablon, pertanian, peternakan, hingga teknologi digital</ListItem>
|
>
|
||||||
</List>
|
<Stack>
|
||||||
</Box>
|
<Tooltip label="Lokasi pelaksanaan kegiatan" withArrow>
|
||||||
<Box>
|
<Title order={2} fw="bold" c={colors['blue-button']} mb="xs" flex="center">
|
||||||
<Text fz={'h4'}> 4) Kursus & Pelatihan Soft Skill</Text>
|
<IconMapPin size={28} style={{ marginRight: 8 }} />
|
||||||
<List>
|
Tempat Kegiatan
|
||||||
<ListItem fz={'h4'}>Public speaking, pengelolaan keuangan, kepemimpinan pemuda</ListItem>
|
</Title>
|
||||||
</List>
|
</Tooltip>
|
||||||
</Box>
|
<Text fz="md" lh={1.7} c="dark" dangerouslySetInnerHTML={{ __html: stateTempatKegiatan.findById.data?.deskripsi }} />
|
||||||
<Box>
|
</Stack>
|
||||||
<Text fz={'h4'}> 5) Pendidikan Keluarga & Parenting</Text>
|
</Paper>
|
||||||
<List>
|
</SimpleGrid>
|
||||||
<ListItem fz={'h4'}>Untuk membekali orang tua dalam mendampingi tumbuh kembang anak</ListItem>
|
<Box py={40}>
|
||||||
</List>
|
<Paper
|
||||||
</Box>
|
p="xl"
|
||||||
|
radius="lg"
|
||||||
|
bg={colors['white-trans-1']}
|
||||||
|
shadow="md"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Tooltip label="Ragam jenis program yang tersedia" withArrow>
|
||||||
|
<Title order={2} fw="bold" c={colors['blue-button']} mb="xs" flex="center">
|
||||||
|
<IconBook2 size={28} style={{ marginRight: 8 }} />
|
||||||
|
Jenis Program yang Diselenggarakan
|
||||||
|
</Title>
|
||||||
|
</Tooltip>
|
||||||
|
<Text fz="md" lh={1.7} c="dark" dangerouslySetInnerHTML={{ __html: stateJenisProgram.findById.data?.deskripsi }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,55 +1,96 @@
|
|||||||
|
'use client'
|
||||||
|
import stateProgramPendidikanAnak from '@/app/admin/(dashboard)/_state/pendidikan/program-pendidikan-anak';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Box, Title, Text, SimpleGrid, Paper, List, ListItem } from '@mantine/core';
|
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip, Divider, Group } from '@mantine/core';
|
||||||
import React from 'react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { IconBook2, IconTargetArrow } from '@tabler/icons-react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const stateUnggulan = useProxy(stateProgramPendidikanAnak.programUnggulanState);
|
||||||
|
const stateTujuan = useProxy(stateProgramPendidikanAnak.stateTujuanProgram);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateUnggulan.findById.load('edit');
|
||||||
|
stateTujuan.findById.load('edit');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!stateUnggulan.findById.data || !stateTujuan.findById.data)
|
||||||
|
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={50}>
|
||||||
|
<Skeleton h={50} radius="xl" />
|
||||||
|
<Skeleton h={150} mt="lg" radius="md" />
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||||
<Box>
|
<Box mb="xl">
|
||||||
<Title ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
<Title ta="center" order={1} fw="bold" c={colors['blue-button']} mb="sm">
|
||||||
Program Pendidikan Anak
|
Program Pendidikan Anak
|
||||||
</Title>
|
</Title>
|
||||||
<Text pb={20} ta={'justify'} fz={'xl'} px={{ base: 'md', md: 100 }}>
|
<Text ta="center" fz="lg" c="black" mb="lg" maw={800} mx="auto">
|
||||||
Desa Darmasaba berkomitmen untuk menciptakan generasi muda yang cerdas, berkarakter, dan berdaya saing melalui berbagai program pendidikan yang inklusif dan berkelanjutan. Pendidikan anak menjadi pondasi utama dalam mewujudkan masa depan desa yang lebih baik.
|
Desa Darmasaba berkomitmen mencetak generasi muda yang cerdas, berkarakter, dan siap bersaing melalui program pendidikan yang inklusif dan berkelanjutan.
|
||||||
</Text>
|
</Text>
|
||||||
|
<Divider size="sm" color={colors['blue-button']} mx="auto" maw={120} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
cols={{
|
cols={{ base: 1, md: 2 }}
|
||||||
base: 1,
|
spacing="xl"
|
||||||
md: 2
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box>
|
<Paper
|
||||||
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
p="xl"
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
radius="xl"
|
||||||
Tujuan Program
|
withBorder
|
||||||
</Title>
|
bg="white"
|
||||||
<List>
|
shadow="md"
|
||||||
<ListItem fz={'h4'}>Meningkatkan akses pendidikan yang merata dan berkualitas</ListItem>
|
style={{ transition: 'transform 0.2s ease', cursor: 'default' }}
|
||||||
<ListItem fz={'h4'}>Menumbuhkan semangat belajar sejak dini</ListItem>
|
>
|
||||||
<ListItem fz={'h4'}>Membentuk karakter anak yang berakhlak dan berwawasan lingkungan</ListItem>
|
<Stack gap="sm">
|
||||||
<ListItem fz={'h4'}>Mendukung tumbuh kembang anak melalui pendekatan pendidikan yang holistik</ListItem>
|
<Group gap="sm">
|
||||||
</List>
|
<IconTargetArrow size={28} color={colors['blue-button']} />
|
||||||
</Paper>
|
<Title order={2} fw="bold" c={colors['blue-button']}>
|
||||||
</Box>
|
Tujuan Program
|
||||||
<Box>
|
</Title>
|
||||||
<Paper h={{base: 0, md: 239}} p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
</Group>
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
<Tooltip label="Detail tujuan program pendidikan anak" position="top-start" withArrow>
|
||||||
Program Unggulan
|
<Text fz="lg" lh={1.6} c="dark" dangerouslySetInnerHTML={{ __html: stateTujuan.findById.data?.deskripsi }} />
|
||||||
</Title>
|
</Tooltip>
|
||||||
<List>
|
</Stack>
|
||||||
<ListItem fz={'h4'}>Bimbingan Belajar Gratis: Untuk siswa kurang mampu</ListItem>
|
</Paper>
|
||||||
<ListItem fz={'h4'}>Gerakan Literasi Desa: Meningkatkan minat baca sejak dini</ListItem>
|
|
||||||
<ListItem fz={'h4'}>Pelatihan Digital untuk Anak dan Remaja</ListItem>
|
<Paper
|
||||||
<ListItem fz={'h4'}>Beasiswa Anak Berprestasi & Kurang Mampu</ListItem>
|
p="xl"
|
||||||
</List>
|
radius="xl"
|
||||||
</Paper>
|
withBorder
|
||||||
</Box>
|
bg="white"
|
||||||
|
shadow="md"
|
||||||
|
style={{ transition: 'transform 0.2s ease', cursor: 'default' }}
|
||||||
|
>
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconBook2 size={28} color={colors['blue-button']} />
|
||||||
|
<Title order={2} fw="bold" c={colors['blue-button']}>
|
||||||
|
Program Unggulan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
<Tooltip label="Detail program unggulan yang sedang berjalan" position="top-start" withArrow>
|
||||||
|
<Text fz="lg" lh={1.6} c="dark" dangerouslySetInnerHTML={{ __html: stateUnggulan.findById.data?.deskripsi }} />
|
||||||
|
</Tooltip>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -2,9 +2,26 @@
|
|||||||
|
|
||||||
import daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
import daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch, IconFileInfo, IconMail, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
@@ -21,85 +38,122 @@ function Page() {
|
|||||||
load,
|
load,
|
||||||
} = listData.findMany
|
} = listData.findMany
|
||||||
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 5, search)
|
load(page, 5, search)
|
||||||
}, [page, search])
|
}, [page, search])
|
||||||
|
|
||||||
if (loading || !data) return <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
if (loading || !data) {
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
return (
|
||||||
<Skeleton h={40} />
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||||
</Box>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Skeleton h={40} />
|
<Skeleton h={40} radius="xl" />
|
||||||
<Skeleton h={40} />
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Skeleton h={50} radius="xl" />
|
||||||
<Skeleton h={40} />
|
<Skeleton h={50} radius="xl" />
|
||||||
</Box>
|
<Skeleton h={50} radius="xl" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
<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>
|
</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
|
Daftar Informasi Publik Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lmd'}>
|
<Stack gap="lg">
|
||||||
<Text fz={{ base: 'h3', md: 'h2' }} fw={"bold"}>Tentang Informasi Publik</Text>
|
<Paper p="lg" radius="xl" shadow="sm" withBorder>
|
||||||
<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="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
|
<TextInput
|
||||||
placeholder='Cari Informasi...'
|
placeholder="Cari informasi publik..."
|
||||||
leftSection={<IconSearch size={20} />}
|
aria-label="Pencarian informasi publik"
|
||||||
|
leftSection={<IconSearch size={20} stroke={1.5} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
style={{ marginBottom: 16 }}
|
radius="xl"
|
||||||
|
size="md"
|
||||||
/>
|
/>
|
||||||
<Table withRowBorders withColumnBorders withTableBorder>
|
|
||||||
<TableThead bg={colors['blue-button']}>
|
{data.length === 0 ? (
|
||||||
<TableTr c={colors['white-1']}>
|
<Center py="xl">
|
||||||
<TableTh ta={'center'}>No</TableTh>
|
<Stack align="center" gap="sm">
|
||||||
<TableTh ta={'center'}>Jenis Informasi</TableTh>
|
<IconFileInfo size={48} stroke={1.5} color={colors["blue-button"]} />
|
||||||
<TableTh ta={'center'}>Deskripsi</TableTh>
|
<Text fz="md" c="dimmed">Tidak ada informasi publik yang ditemukan.</Text>
|
||||||
<TableTh ta={'center'}>Tanggal Publikasi</TableTh>
|
</Stack>
|
||||||
</TableTr>
|
</Center>
|
||||||
</TableThead>
|
) : (
|
||||||
<TableTbody bg={colors['white-1']}>
|
<Table withRowBorders withColumnBorders withTableBorder highlightOnHover verticalSpacing="md">
|
||||||
{listData.findMany.data?.map((item, index) => (
|
<TableThead bg={colors['blue-button']}>
|
||||||
<TableTr key={item.id}>
|
<TableTr c={colors['white-1']}>
|
||||||
<TableTd ta={'center'}>{index + 1}</TableTd>
|
<TableTh fz="sm" ta="center" w="5%">No</TableTh>
|
||||||
<TableTd>
|
<TableTh fz="sm" ta="center" w="25%">Jenis Informasi</TableTh>
|
||||||
<Text fz={'md'}>
|
<TableTh fz="sm" ta="center" w="40%">Deskripsi</TableTh>
|
||||||
{item.jenisInformasi}
|
<TableTh fz="sm" ta="center" w="20%">Tanggal Publikasi</TableTh>
|
||||||
</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>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody bg={colors['white-1']}>
|
||||||
</Table>
|
{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>
|
</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>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,100 +1,93 @@
|
|||||||
|
'use client'
|
||||||
|
import stateDasarHukum from '@/app/admin/(dashboard)/_state/ppid/dasar_hukum/dasarHukum';
|
||||||
import colors from '@/con/colors';
|
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';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
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 (
|
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 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Stack align="center" gap="xs">
|
||||||
Dasar Hukum
|
<IconBook2 size={42} stroke={1.5} color={colors["blue-button"]} />
|
||||||
</Text>
|
<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 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
<Stack gap="lg">
|
||||||
<Box>
|
{dataArray.map((item, k) => (
|
||||||
<Text ta={"center"} fw={"bold"} fz={{ base: 'h4', md: 'h3' }}>DASAR HUKUM PEMBENTUKAN PPID DESA DARMASABA</Text>
|
<Transition
|
||||||
</Box>
|
key={k}
|
||||||
<List p={'lg'}>
|
mounted={true}
|
||||||
<ListItem fz={{ base: 'md', md: 'h4' }}>
|
transition="fade-up"
|
||||||
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
|
duration={400}
|
||||||
<Text fw={"bold"} span>
|
timingFunction="ease"
|
||||||
UU Nomor 14 Tahun 2008
|
>
|
||||||
</Text>
|
{(styles) => (
|
||||||
{" "}tentang Keterbukaan Informasi Publik
|
<Paper
|
||||||
</Text>
|
p="xl"
|
||||||
</ListItem>
|
radius="xl"
|
||||||
<ListItem fz={{ base: 'md', md: 'h4' }}>
|
shadow="md"
|
||||||
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
|
bg={colors['white-trans-1']}
|
||||||
<Text fw={"bold"} span>
|
style={{
|
||||||
PP Nomor 61 Tahun 2010
|
...styles,
|
||||||
</Text>
|
backdropFilter: "blur(10px)",
|
||||||
{" "}tentang Pelaksanaan UU 14 Tahun 2008 tentang Keterbukaan Informasi Publik
|
border: `1px solid ${colors["blue-button"]}20`,
|
||||||
</Text>
|
}}
|
||||||
</ListItem>
|
>
|
||||||
<ListItem fz={{ base: 'md', md: 'h4' }}>
|
<Stack gap="md">
|
||||||
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
|
<Text
|
||||||
<Text fw={"bold"} span>
|
ta="center"
|
||||||
Permendagri Nomor 3 Tahun 2017
|
fw="bold"
|
||||||
</Text>
|
fz={{ base: 'lg', md: 'xl' }}
|
||||||
{" "}tentang Pedoman Pengelolaan Pelayanan Informasi dan Dokumentasi di Lingkungan Kemendagri
|
style={{ lineHeight: 1.4 }}
|
||||||
dan Pemerintah Daerah
|
>
|
||||||
</Text>
|
{item.judul}
|
||||||
</ListItem>
|
</Text>
|
||||||
<ListItem fz={{ base: 'md', md: 'h4' }}>
|
<Text
|
||||||
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
<Text fw={"bold"} span>
|
style={{ lineHeight: 1.7 }}
|
||||||
Peraturan Komisi Informasi Nomor 1 Tahun 2010
|
dangerouslySetInnerHTML={{ __html: item.content }}
|
||||||
</Text>
|
/>
|
||||||
{" "} tentang Standar Layanan Informasi Publik
|
</Stack>
|
||||||
</Text>
|
</Paper>
|
||||||
</ListItem>
|
)}
|
||||||
<ListItem fz={{ base: 'md', md: 'h4' }}>
|
</Transition>
|
||||||
<Text ta={"justify"} fz={{ base: 'md', md: 'h4' }}>
|
))}
|
||||||
<Text fw={"bold"} span>
|
</Stack>
|
||||||
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>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user