Compare commits
8 Commits
nico/7-aug
...
nico/12-au
| Author | SHA1 | Date | |
|---|---|---|---|
| a6832cad40 | |||
| a1d55e2b0a | |||
| c1583c21b1 | |||
| 2fe8b8ce1a | |||
| 5cbf7810bc | |||
| b3bf6b0327 | |||
| a65529cb23 | |||
| afc7bced44 |
10
prisma/data/ppid/ikm/jenis-kelamin/jenis-kelamin.json
Normal file
10
prisma/data/ppid/ikm/jenis-kelamin/jenis-kelamin.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"id": "cme8bt5o5000007lb9xp11unb",
|
||||
"name": "Laki-laki"
|
||||
},
|
||||
{
|
||||
"id": "cme8btctl000107lbh2hocgg8",
|
||||
"name": "Perempuan"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "cme8buup6000207lb54q9b0az",
|
||||
"name": "Sangat Baik"
|
||||
},
|
||||
{
|
||||
"id": "cme8bv15o000307lbft9b0vzy",
|
||||
"name": "Baik"
|
||||
},
|
||||
{
|
||||
"id": "cme8bvjvu000507lbgfsveog6",
|
||||
"name": "Kurang Baik"
|
||||
},
|
||||
{
|
||||
"id": "cme8bvvm6000607lbh6rn2ubm",
|
||||
"name": "Sangat Kurang Baik"
|
||||
}
|
||||
]
|
||||
14
prisma/data/ppid/ikm/umur-responden/umur-responden.json
Normal file
14
prisma/data/ppid/ikm/umur-responden/umur-responden.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"id": "cme8bwgwu000707lbawc6fz3a",
|
||||
"name": "Muda"
|
||||
},
|
||||
{
|
||||
"id": "cme8hnx09000b07jl3ipifb1k",
|
||||
"name": "Dewasa"
|
||||
},
|
||||
{
|
||||
"id": "cme8ho7dv000c07jlc7lr4b4w",
|
||||
"name": "Lansia"
|
||||
}
|
||||
]
|
||||
@@ -50,23 +50,22 @@ model AppMenuChild {
|
||||
// ========================================= FILE STORAGE ========================================= //
|
||||
|
||||
model FileStorage {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
realName String
|
||||
path String
|
||||
mimeType String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
link String
|
||||
category String // "image" / "document" / "other"
|
||||
Berita Berita[]
|
||||
PotensiDesa PotensiDesa[]
|
||||
Posyandu Posyandu[]
|
||||
StrukturPPID StrukturPPID[]
|
||||
GalleryFoto GalleryFoto[]
|
||||
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
realName String
|
||||
path String
|
||||
mimeType String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
link String
|
||||
category String // "image" / "document" / "other"
|
||||
Berita Berita[]
|
||||
PotensiDesa PotensiDesa[]
|
||||
Posyandu Posyandu[]
|
||||
StrukturPPID StrukturPPID[]
|
||||
GalleryFoto GalleryFoto[]
|
||||
Pelapor Pelapor[]
|
||||
Penghargaan Penghargaan[]
|
||||
ProfileDesaImage ProfileDesaImage[]
|
||||
@@ -98,10 +97,8 @@ model FileStorage {
|
||||
APBDesImage APBDes[] @relation("APBDesImage")
|
||||
APBDesFile APBDes[] @relation("APBDesFile")
|
||||
PrestasiDesa PrestasiDesa[]
|
||||
|
||||
DataPerpustakaan DataPerpustakaan[]
|
||||
|
||||
PegawaiPPID PegawaiPPID[]
|
||||
DataPerpustakaan DataPerpustakaan[]
|
||||
PegawaiPPID PegawaiPPID[]
|
||||
}
|
||||
|
||||
//========================================= MENU LANDING PAGE ========================================= //
|
||||
@@ -221,6 +218,53 @@ model KategoriPrestasiDesa {
|
||||
PrestasiDesa PrestasiDesa[]
|
||||
}
|
||||
|
||||
//========================================= INDEKS KEPUASAAN MASYARAKAT ========================================= //
|
||||
model Responden {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
tanggal DateTime // misal: 2025-05-01
|
||||
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
|
||||
jenisKelaminId String
|
||||
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])
|
||||
ratingId String
|
||||
kelompokUmur UmurResponden @relation(fields: [kelompokUmurId], references: [id])
|
||||
kelompokUmurId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model JenisKelaminResponden {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
Responden Responden[]
|
||||
}
|
||||
|
||||
model PilihanRatingResponden {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
Responden Responden[]
|
||||
}
|
||||
|
||||
model UmurResponden {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
Responden Responden[]
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
|
||||
//========================================= STRUKTUR PPID ========================================= //
|
||||
|
||||
@@ -16,6 +16,9 @@ import potensi from "./data/list-potensi.json";
|
||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
||||
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
||||
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
||||
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||
@@ -546,6 +549,54 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("visi misi PPID success ...");
|
||||
|
||||
for (const j of jenisKelamin) {
|
||||
await prisma.jenisKelaminResponden.upsert({
|
||||
where: {
|
||||
id: j.id,
|
||||
},
|
||||
update: {
|
||||
name: j.name,
|
||||
},
|
||||
create: {
|
||||
id: j.id,
|
||||
name: j.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("jenis kelamin responden success ...");
|
||||
|
||||
for (const r of pilihanRatingResponden) {
|
||||
await prisma.pilihanRatingResponden.upsert({
|
||||
where: {
|
||||
id: r.id,
|
||||
},
|
||||
update: {
|
||||
name: r.name,
|
||||
},
|
||||
create: {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("pilihan rating responden success ...");
|
||||
|
||||
for (const u of umurResponden) {
|
||||
await prisma.umurResponden.upsert({
|
||||
where: {
|
||||
id: u.id,
|
||||
},
|
||||
update: {
|
||||
name: u.name,
|
||||
},
|
||||
create: {
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("umur responden success ...");
|
||||
|
||||
for (const v of dasarHukumPPID) {
|
||||
await prisma.dasarHukumPPID.upsert({
|
||||
where: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -58,6 +59,8 @@ const berita = proxy({
|
||||
},
|
||||
},
|
||||
|
||||
// State untuk berita utama (hanya 1)
|
||||
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.BeritaGetPayload<{
|
||||
@@ -70,38 +73,43 @@ const berita = proxy({
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
|
||||
async load(page = 1, limit = 10) {
|
||||
berita.findMany.loading = true;
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||
berita.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
berita.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.berita["find-many"].get({
|
||||
query: {
|
||||
page,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
berita.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
if (kategori) query.kategori = kategori;
|
||||
|
||||
const res = await ApiFetch.api.desa.berita["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
berita.findMany.data = res.data.data ?? [];
|
||||
berita.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
berita.findMany.data = [];
|
||||
berita.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch berita paginated:", err);
|
||||
berita.findMany.data = [];
|
||||
berita.findMany.totalPages = 1;
|
||||
} finally {
|
||||
berita.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
findUnique: {
|
||||
data: null as
|
||||
| Prisma.BeritaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
kategoriBerita: true;
|
||||
};
|
||||
}> | null,
|
||||
data: null as Prisma.BeritaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
kategoriBerita: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/berita/${id}`);
|
||||
@@ -109,11 +117,11 @@ const berita = proxy({
|
||||
const data = await res.json();
|
||||
berita.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error('Failed to fetch berita:', res.statusText);
|
||||
console.error("Failed to fetch berita:", res.statusText);
|
||||
berita.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching berita:', error);
|
||||
console.error("Error fetching berita:", error);
|
||||
berita.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
@@ -127,14 +135,14 @@ const berita = proxy({
|
||||
berita.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/berita/delete/${id}`, {
|
||||
method: 'DELETE',
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Berita berhasil dihapus");
|
||||
await berita.findMany.load(); // refresh list
|
||||
@@ -159,21 +167,21 @@ const berita = proxy({
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/desa/berita/${id}`, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
@@ -190,7 +198,9 @@ const berita = proxy({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading berita:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
@@ -204,14 +214,14 @@ const berita = proxy({
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
berita.edit.loading = true;
|
||||
|
||||
|
||||
const response = await fetch(`/api/desa/berita/${this.id}`, {
|
||||
method: 'PUT',
|
||||
method: "PUT",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
judul: this.form.judul,
|
||||
@@ -221,14 +231,16 @@ const berita = proxy({
|
||||
imageId: this.form.imageId,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update berita");
|
||||
await berita.findMany.load(); // refresh list
|
||||
@@ -238,7 +250,11 @@ const berita = proxy({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating berita:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update berita");
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update berita"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
berita.edit.loading = false;
|
||||
@@ -258,21 +274,22 @@ const berita = proxy({
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load() {
|
||||
// findFirst.load()
|
||||
async load(kategori?: string) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.berita["find-first"].get();
|
||||
const res = await ApiFetch.api.desa.berita["find-first"].get({
|
||||
query: kategori ? { kategori } : {},
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
// Add type assertion to ensure type safety
|
||||
berita.findFirst.data = res.data.data as Prisma.BeritaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
kategoriBerita: true;
|
||||
};
|
||||
}> | null;
|
||||
this.data = res.data.data || null;
|
||||
} else {
|
||||
this.data = null;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch berita terbaru:", err);
|
||||
this.data = null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -286,7 +303,7 @@ const berita = proxy({
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
@@ -300,7 +317,7 @@ const berita = proxy({
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
//=============== Kategori Berita ===============
|
||||
@@ -328,10 +345,9 @@ const kategoriBerita = proxy({
|
||||
|
||||
try {
|
||||
kategoriBerita.create.loading = true;
|
||||
const res =
|
||||
await ApiFetch.api.desa.kategoriberita[
|
||||
"create"
|
||||
].post(kategoriBerita.create.form);
|
||||
const res = await ApiFetch.api.desa.kategoriberita["create"].post(
|
||||
kategoriBerita.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
kategoriBerita.findMany.load();
|
||||
return toast.success("Data Kategori Berita Berhasil Dibuat");
|
||||
@@ -354,10 +370,7 @@ const kategoriBerita = proxy({
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.desa.kategoriberita[
|
||||
"findMany"
|
||||
].get();
|
||||
const res = await ApiFetch.api.desa.kategoriberita["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
kategoriBerita.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
@@ -372,9 +385,7 @@ const kategoriBerita = proxy({
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/desa/kategoriberita/${id}`
|
||||
);
|
||||
const res = await fetch(`/api/desa/kategoriberita/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
kategoriBerita.findUnique.data = data.data ?? null;
|
||||
@@ -396,15 +407,12 @@ const kategoriBerita = proxy({
|
||||
try {
|
||||
kategoriBerita.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/desa/kategoriberita/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/desa/kategoriberita/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
@@ -414,7 +422,9 @@ const kategoriBerita = proxy({
|
||||
);
|
||||
await kategoriBerita.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Data Kategori Berita");
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus Data Kategori Berita"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
@@ -435,15 +445,12 @@ const kategoriBerita = proxy({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/desa/kategoriberita/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/desa/kategoriberita/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -481,18 +488,15 @@ const kategoriBerita = proxy({
|
||||
try {
|
||||
kategoriBerita.update.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/desa/kategoriberita/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/desa/kategoriberita/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
@@ -508,7 +512,9 @@ const kategoriBerita = proxy({
|
||||
await kategoriBerita.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update data kategori berita");
|
||||
throw new Error(
|
||||
result.message || "Gagal update data kategori berita"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data kategori berita:", error);
|
||||
@@ -529,7 +535,6 @@ const kategoriBerita = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// 5. State global
|
||||
const stateDashboardBerita = proxy({
|
||||
kategoriBerita,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -68,10 +69,34 @@ const foto = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
foto.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
foto.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
foto.findMany.page = page;
|
||||
foto.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
foto.findMany.data = res.data.data ?? [];
|
||||
foto.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
foto.findMany.data = [];
|
||||
foto.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch foto paginated:", err);
|
||||
foto.findMany.data = [];
|
||||
foto.findMany.totalPages = 1;
|
||||
} finally {
|
||||
foto.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -215,6 +240,28 @@ const foto = proxy({
|
||||
foto.update.form = { ...defaultFormFoto };
|
||||
},
|
||||
},
|
||||
findRecent: {
|
||||
data: [] as Prisma.GalleryFotoGetPayload<{
|
||||
include: {
|
||||
imageGalleryFoto: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-recent"].get();
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
this.data = res.data.data ?? [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal fetch foto recent:", error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const video = proxy({
|
||||
@@ -257,10 +304,34 @@ const video = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.gallery.video["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
video.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
video.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
video.findMany.page = page;
|
||||
video.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.gallery.video["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
video.findMany.data = res.data.data ?? [];
|
||||
video.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
video.findMany.data = [];
|
||||
video.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch video paginated:", err);
|
||||
video.findMany.data = [];
|
||||
video.findMany.totalPages = 1;
|
||||
} finally {
|
||||
video.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,228 @@ import { toast } from "react-toastify";
|
||||
import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
const templateKategoriPengumuman = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultKategoriPengumuman = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const category = proxy({
|
||||
create: {
|
||||
form: { ...defaultKategoriPengumuman },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateKategoriPengumuman.safeParse(category.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
category.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.kategoripengumuman["create"].post(
|
||||
category.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
category.findMany.load();
|
||||
return toast.success("Data Kategori Pengumuman Berhasil Dibuat");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return toast.error("failed create");
|
||||
} finally {
|
||||
category.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as (Prisma.CategoryPengumumanGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> & {
|
||||
_count: {
|
||||
pengumumans: number;
|
||||
};
|
||||
})[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.kategoripengumuman["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
category.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.CategoryPengumumanGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/kategoripengumuman/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
category.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
category.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
category.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
category.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/kategoripengumuman/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Data Kategori Pengumuman berhasil dihapus"
|
||||
);
|
||||
await category.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus Data Kategori Pengumuman"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menghapus Data Kategori Pengumuman"
|
||||
);
|
||||
} finally {
|
||||
category.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultKategoriPengumuman },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/desa/kategoripengumuman/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori pengumuman:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateKategoriPengumuman.safeParse(category.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
category.update.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/desa/kategoripengumuman/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update data kategori pengumuman");
|
||||
await category.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal update data kategori pengumuman"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data kategori pengumuman:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update data kategori pengumuman"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
category.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
category.update.id = "";
|
||||
category.update.form = { ...defaultKategoriPengumuman };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templateFormPengumuman = z.object({
|
||||
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
@@ -12,33 +234,15 @@ const templateFormPengumuman = z.object({
|
||||
categoryPengumumanId: z.string().nonempty(),
|
||||
});
|
||||
|
||||
const category = proxy({
|
||||
findMany: {
|
||||
data: null as
|
||||
| null
|
||||
| Prisma.CategoryPengumumanGetPayload<{ omit: { isActive: true } }>[],
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.pengumuman.category[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
category.findMany.data = (res.data?.data as any) ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type PengumumanForm = Prisma.PengumumanGetPayload<{
|
||||
select: {
|
||||
judul: true;
|
||||
deskripsi: true;
|
||||
content: true;
|
||||
categoryPengumumanId: true;
|
||||
};
|
||||
}>;
|
||||
const defaultForm = {
|
||||
judul: "",
|
||||
deskripsi: "",
|
||||
content: "",
|
||||
categoryPengumumanId: "",
|
||||
};
|
||||
const pengumuman = proxy({
|
||||
create: {
|
||||
form: {} as PengumumanForm,
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateFormPengumuman.safeParse(pengumuman.create.form);
|
||||
@@ -74,11 +278,35 @@ const pengumuman = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.pengumuman["find-many"].get();
|
||||
console.log(res);
|
||||
if (res.status === 200) {
|
||||
pengumuman.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||
pengumuman.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
pengumuman.findMany.page = page;
|
||||
pengumuman.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
if (kategori) query.kategori = kategori;
|
||||
|
||||
const res = await ApiFetch.api.desa.pengumuman["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pengumuman.findMany.data = res.data.data ?? [];
|
||||
pengumuman.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
pengumuman.findMany.data = [];
|
||||
pengumuman.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch pengumuman paginated:", err);
|
||||
pengumuman.findMany.data = [];
|
||||
pengumuman.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pengumuman.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -112,7 +340,7 @@ const pengumuman = proxy({
|
||||
try {
|
||||
pengumuman.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/pengumuman/delete/${id}`, {
|
||||
const response = await fetch(`/api/desa/pengumuman/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -135,9 +363,9 @@ const pengumuman = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
edit: {
|
||||
id: "",
|
||||
form: {} as PengumumanForm,
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
@@ -153,6 +381,7 @@ const pengumuman = proxy({
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -168,20 +397,21 @@ const pengumuman = proxy({
|
||||
content: data.content,
|
||||
categoryPengumumanId: data.categoryPengumumanId || "",
|
||||
};
|
||||
return data;
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal mengambil data pengumuman");
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error((error as Error).message);
|
||||
toast.error("Terjadi kesalahan saat mengambil data pengumuman");
|
||||
} finally {
|
||||
pengumuman.update.loading = false;
|
||||
console.error("Error loading pengumuman:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templateFormPengumuman.safeParse(pengumuman.update.form);
|
||||
const cek = templateFormPengumuman.safeParse(pengumuman.edit.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
@@ -191,7 +421,7 @@ const pengumuman = proxy({
|
||||
}
|
||||
|
||||
try {
|
||||
pengumuman.update.loading = true;
|
||||
pengumuman.edit.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/pengumuman/${this.id}`, {
|
||||
method: "PUT",
|
||||
@@ -202,7 +432,7 @@ const pengumuman = proxy({
|
||||
judul: this.form.judul,
|
||||
deskripsi: this.form.deskripsi,
|
||||
content: this.form.content,
|
||||
categoryPengumumanId: this.form.categoryPengumumanId,
|
||||
categoryPengumumanId: this.form.categoryPengumumanId || null,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -231,9 +461,14 @@ const pengumuman = proxy({
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
pengumuman.update.loading = false;
|
||||
pengumuman.edit.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
pengumuman.edit.id = "";
|
||||
pengumuman.edit.form = { ...defaultForm };
|
||||
},
|
||||
},
|
||||
findFirst: {
|
||||
data: null as Prisma.PengumumanGetPayload<{
|
||||
|
||||
788
src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts
Normal file
788
src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts
Normal file
@@ -0,0 +1,788 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
// Template form responden
|
||||
|
||||
const templateResponden = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
tanggal: z.string().min(1, "Tanggal harus diisi"),
|
||||
jenisKelaminId: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||
ratingId: z.string().min(1, "Rating harus diisi"),
|
||||
kelompokUmurId: z.string().min(1, "Kelompok umur harus diisi"),
|
||||
});
|
||||
|
||||
const defaultFormResponden = {
|
||||
name: "",
|
||||
tanggal: "",
|
||||
jenisKelaminId: "",
|
||||
ratingId: "",
|
||||
kelompokUmurId: "",
|
||||
};
|
||||
|
||||
const responden = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormResponden },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateResponden.safeParse(responden.create.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
responden.create.loading = true;
|
||||
const res = await ApiFetch.api.landingpage.responden["create"].post(
|
||||
responden.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
toast.success("Responden berhasil ditambahkan");
|
||||
await responden.findMany.load();
|
||||
} else {
|
||||
toast.error(res.data?.message ?? "Gagal tambah responden");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan responden");
|
||||
} finally {
|
||||
responden.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
responden.findMany.loading = true; // Use the full path to access the property
|
||||
responden.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.responden["findMany"].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
responden.findMany.data = res.data.data || [];
|
||||
responden.findMany.total = res.data.total || 0;
|
||||
responden.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load responden:", res.data?.message);
|
||||
responden.findMany.data = [];
|
||||
responden.findMany.total = 0;
|
||||
responden.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading responden:", error);
|
||||
responden.findMany.data = [];
|
||||
responden.findMany.total = 0;
|
||||
responden.findMany.totalPages = 1;
|
||||
} finally {
|
||||
responden.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.RespondenGetPayload<{
|
||||
include: {
|
||||
jenisKelamin: true;
|
||||
rating: true;
|
||||
kelompokUmur: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/landingpage/responden/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
responden.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
responden.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading responden:", error);
|
||||
responden.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormResponden },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templateResponden.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(`/api/landingpage/responden/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await responden.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error update data:", error);
|
||||
toast.error("Gagal update data responden");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
responden.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/landingpage/responden/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "responden berhasil dihapus");
|
||||
await responden.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus responden");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus responden");
|
||||
} finally {
|
||||
responden.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Template form jenis kelamin responden
|
||||
const templateJenisKelaminResponden = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultFormJenisKelaminResponden = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const jenisKelaminResponden = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormJenisKelaminResponden },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateJenisKelaminResponden.safeParse(
|
||||
jenisKelaminResponden.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
jenisKelaminResponden.create.loading = true;
|
||||
try {
|
||||
jenisKelaminResponden.create.loading = true;
|
||||
const res = await ApiFetch.api.landingpage.jeniskelaminresponden[
|
||||
"create"
|
||||
].post(jenisKelaminResponden.create.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Jenis kelamin responden berhasil ditambahkan");
|
||||
await jenisKelaminResponden.findMany.load();
|
||||
} else {
|
||||
toast.error(
|
||||
res.data?.message ?? "Gagal tambah jenis kelamin responden"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menambahkan jenis kelamin responden"
|
||||
);
|
||||
} finally {
|
||||
jenisKelaminResponden.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
jenisKelaminResponden.findMany.loading = true; // Use the full path to access the property
|
||||
jenisKelaminResponden.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.jeniskelaminresponden[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
jenisKelaminResponden.findMany.data = res.data.data || [];
|
||||
jenisKelaminResponden.findMany.total = res.data.total || 0;
|
||||
jenisKelaminResponden.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to load jenis kelamin responden:",
|
||||
res.data?.message
|
||||
);
|
||||
jenisKelaminResponden.findMany.data = [];
|
||||
jenisKelaminResponden.findMany.total = 0;
|
||||
jenisKelaminResponden.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading jenis kelamin responden:", error);
|
||||
jenisKelaminResponden.findMany.data = [];
|
||||
jenisKelaminResponden.findMany.total = 0;
|
||||
jenisKelaminResponden.findMany.totalPages = 1;
|
||||
} finally {
|
||||
jenisKelaminResponden.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.JenisKelaminRespondenGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/landingpage/jeniskelaminresponden/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
jenisKelaminResponden.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
jenisKelaminResponden.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading jenis kelamin responden:", error);
|
||||
jenisKelaminResponden.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormJenisKelaminResponden },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templateJenisKelaminResponden.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/landingpage/jeniskelaminresponden/${id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await jenisKelaminResponden.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error update data:", error);
|
||||
toast.error("Gagal update data jenis kelamin responden");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
jenisKelaminResponden.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/landingpage/jeniskelaminresponden/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "jenis kelamin responden berhasil dihapus"
|
||||
);
|
||||
await jenisKelaminResponden.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus jenis kelamin responden"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus jenis kelamin responden");
|
||||
} finally {
|
||||
jenisKelaminResponden.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Template form pilihan rating responden
|
||||
|
||||
const templatePilihanRatingResponden = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultFormPilihanRatingResponden = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const pilihanRatingResponden = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormPilihanRatingResponden },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templatePilihanRatingResponden.safeParse(
|
||||
pilihanRatingResponden.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
pilihanRatingResponden.create.loading = true;
|
||||
try {
|
||||
pilihanRatingResponden.create.loading = true;
|
||||
const res = await ApiFetch.api.landingpage.pilihanratingresponden[
|
||||
"create"
|
||||
].post(pilihanRatingResponden.create.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Jenis kelamin responden berhasil ditambahkan");
|
||||
await pilihanRatingResponden.findMany.load();
|
||||
} else {
|
||||
toast.error(
|
||||
res.data?.message ?? "Gagal tambah jenis kelamin responden"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menambahkan jenis kelamin responden"
|
||||
);
|
||||
} finally {
|
||||
pilihanRatingResponden.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
pilihanRatingResponden.findMany.loading = true; // Use the full path to access the property
|
||||
pilihanRatingResponden.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.pilihanratingresponden[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pilihanRatingResponden.findMany.data = res.data.data || [];
|
||||
pilihanRatingResponden.findMany.total = res.data.total || 0;
|
||||
pilihanRatingResponden.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to load pilihan rating responden:",
|
||||
res.data?.message
|
||||
);
|
||||
pilihanRatingResponden.findMany.data = [];
|
||||
pilihanRatingResponden.findMany.total = 0;
|
||||
pilihanRatingResponden.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pilihan rating responden:", error);
|
||||
pilihanRatingResponden.findMany.data = [];
|
||||
pilihanRatingResponden.findMany.total = 0;
|
||||
pilihanRatingResponden.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pilihanRatingResponden.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PilihanRatingRespondenGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/landingpage/pilihanratingresponden/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
pilihanRatingResponden.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
pilihanRatingResponden.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pilihan rating responden:", error);
|
||||
pilihanRatingResponden.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormPilihanRatingResponden },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templatePilihanRatingResponden.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/landingpage/pilihanratingresponden/${id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await pilihanRatingResponden.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error update data:", error);
|
||||
toast.error("Gagal update data pilihan rating responden");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
pilihanRatingResponden.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/landingpage/pilihanratingresponden/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "pilihan rating responden berhasil dihapus"
|
||||
);
|
||||
await pilihanRatingResponden.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus pilihan rating responden"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus pilihan rating responden");
|
||||
} finally {
|
||||
pilihanRatingResponden.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Template form kelompok umur responden
|
||||
|
||||
const templateKelompokUmurResponden = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultFormKelompokUmurResponden = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const kelompokUmurResponden = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormKelompokUmurResponden },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateKelompokUmurResponden.safeParse(
|
||||
kelompokUmurResponden.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
kelompokUmurResponden.create.loading = true;
|
||||
try {
|
||||
kelompokUmurResponden.create.loading = true;
|
||||
const res = await ApiFetch.api.landingpage.umurresponden["create"].post(
|
||||
kelompokUmurResponden.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
toast.success("Kelompok umur responden berhasil ditambahkan");
|
||||
await kelompokUmurResponden.findMany.load();
|
||||
} else {
|
||||
toast.error(
|
||||
res.data?.message ?? "Gagal tambah kelompok umur responden"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menambahkan kelompok umur responden"
|
||||
);
|
||||
} finally {
|
||||
kelompokUmurResponden.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
kelompokUmurResponden.findMany.loading = true; // Use the full path to access the property
|
||||
kelompokUmurResponden.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.umurresponden[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kelompokUmurResponden.findMany.data = res.data.data || [];
|
||||
kelompokUmurResponden.findMany.total = res.data.total || 0;
|
||||
kelompokUmurResponden.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to load kelompok umur responden:",
|
||||
res.data?.message
|
||||
);
|
||||
kelompokUmurResponden.findMany.data = [];
|
||||
kelompokUmurResponden.findMany.total = 0;
|
||||
kelompokUmurResponden.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kelompok umur responden:", error);
|
||||
kelompokUmurResponden.findMany.data = [];
|
||||
kelompokUmurResponden.findMany.total = 0;
|
||||
kelompokUmurResponden.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kelompokUmurResponden.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.UmurRespondenGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/landingpage/umurresponden/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
kelompokUmurResponden.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
kelompokUmurResponden.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kelompok umur responden:", error);
|
||||
kelompokUmurResponden.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormKelompokUmurResponden },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templateKelompokUmurResponden.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(`/api/landingpage/umurresponden/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await kelompokUmurResponden.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error update data:", error);
|
||||
toast.error("Gagal update data kelompok umur responden");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
kelompokUmurResponden.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/landingpage/umurresponden/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "kelompok umur responden berhasil dihapus"
|
||||
);
|
||||
await kelompokUmurResponden.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus kelompok umur responden"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus kelompok umur responden");
|
||||
} finally {
|
||||
kelompokUmurResponden.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const indeksKepuasanState = proxy({
|
||||
responden,
|
||||
kelompokUmurResponden,
|
||||
jenisKelaminResponden,
|
||||
pilihanRatingResponden
|
||||
})
|
||||
|
||||
export default indeksKepuasanState
|
||||
@@ -8,7 +8,7 @@ import { z } from "zod";
|
||||
const templateGrafikJenisKelamin = z.object({
|
||||
laki: z.string().min(1, "Data laki-laki harus diisi"),
|
||||
perempuan: z.string().min(1, "Data perempuan harus diisi"),
|
||||
});
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
laki: "",
|
||||
@@ -17,10 +17,12 @@ const defaultForm = {
|
||||
|
||||
const grafikBerdasarkanJenisKelamin = proxy({
|
||||
create: {
|
||||
form: {...defaultForm},
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async create(){
|
||||
const cek = templateGrafikJenisKelamin.safeParse(grafikBerdasarkanJenisKelamin.create.form);
|
||||
async create() {
|
||||
const cek = templateGrafikJenisKelamin.safeParse(
|
||||
grafikBerdasarkanJenisKelamin.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
@@ -33,14 +35,20 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
||||
"create"
|
||||
].post(grafikBerdasarkanJenisKelamin.create.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Grafik berdasarkan jenis kelamin berhasil ditambahkan");
|
||||
toast.success(
|
||||
"Grafik berdasarkan jenis kelamin berhasil ditambahkan"
|
||||
);
|
||||
await grafikBerdasarkanJenisKelamin.findMany.load();
|
||||
} else {
|
||||
toast.error(res.data?.message ?? "Gagal tambah grafik berdasarkan jenis kelamin");
|
||||
toast.error(
|
||||
res.data?.message ?? "Gagal tambah grafik berdasarkan jenis kelamin"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan grafik berdasarkan jenis kelamin");
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menambahkan grafik berdasarkan jenis kelamin"
|
||||
);
|
||||
} finally {
|
||||
grafikBerdasarkanJenisKelamin.create.loading = false;
|
||||
}
|
||||
@@ -52,8 +60,9 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
grafikBerdasarkanJenisKelamin.findMany.loading = true; // Use the full path to access the property
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
grafikBerdasarkanJenisKelamin.findMany.loading = true; // Use the full path to access the property
|
||||
grafikBerdasarkanJenisKelamin.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
|
||||
@@ -61,13 +70,17 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikBerdasarkanJenisKelamin.findMany.data = res.data.data || [];
|
||||
grafikBerdasarkanJenisKelamin.findMany.total = res.data.total || 0;
|
||||
grafikBerdasarkanJenisKelamin.findMany.totalPages = res.data.totalPages || 1;
|
||||
grafikBerdasarkanJenisKelamin.findMany.totalPages =
|
||||
res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load grafik berdasarkan jenis kelamin:", res.data?.message);
|
||||
console.error(
|
||||
"Failed to load grafik berdasarkan jenis kelamin:",
|
||||
res.data?.message
|
||||
);
|
||||
grafikBerdasarkanJenisKelamin.findMany.data = [];
|
||||
grafikBerdasarkanJenisKelamin.findMany.total = 0;
|
||||
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
|
||||
@@ -106,7 +119,7 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: {...defaultForm},
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
@@ -119,20 +132,24 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
||||
}
|
||||
const cek = templateGrafikJenisKelamin.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
@@ -156,29 +173,40 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
||||
try {
|
||||
grafikBerdasarkanJenisKelamin.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Grafik berdasarkan jenis kelamin berhasil dihapus");
|
||||
toast.success(
|
||||
result.message ||
|
||||
"Grafik berdasarkan jenis kelamin berhasil dihapus"
|
||||
);
|
||||
await grafikBerdasarkanJenisKelamin.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus grafik berdasarkan jenis kelamin");
|
||||
toast.error(
|
||||
result?.message ||
|
||||
"Gagal menghapus grafik berdasarkan jenis kelamin"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin");
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin"
|
||||
);
|
||||
} finally {
|
||||
grafikBerdasarkanJenisKelamin.delete.loading = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default grafikBerdasarkanJenisKelamin;
|
||||
|
||||
63
src/app/admin/(dashboard)/_state/state-file-storage.ts
Normal file
63
src/app/admin/(dashboard)/_state/state-file-storage.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { proxy } from "valtio";
|
||||
|
||||
interface FileItem {
|
||||
id: string;
|
||||
name: string;
|
||||
path: string;
|
||||
link: string;
|
||||
mimeType: string;
|
||||
category: string;
|
||||
realName: string;
|
||||
isActive: boolean;
|
||||
createdAt: string | Date;
|
||||
updatedAt: string | Date;
|
||||
deletedAt: string | Date | null;
|
||||
}
|
||||
|
||||
const stateFileStorage = proxy<{
|
||||
list: FileItem[] | null;
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number | undefined;
|
||||
load: (params?: { search?: string }) => Promise<void>;
|
||||
del: (params: { id: string }) => Promise<void>;
|
||||
}>({
|
||||
list: null,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: undefined,
|
||||
async load(params?: { search?: string }) {
|
||||
const { search = "" } = params ?? {};
|
||||
try {
|
||||
const { data } = await ApiFetch.api.fileStorage.findMany.get({
|
||||
query: {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
search,
|
||||
category: 'image'
|
||||
},
|
||||
});
|
||||
|
||||
if (data?.data) {
|
||||
this.list = data.data as FileItem[];
|
||||
this.total = data.meta?.totalPages;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading files:', error);
|
||||
this.list = [];
|
||||
this.total = 0;
|
||||
}
|
||||
},
|
||||
async del({ id }: { id: string }) {
|
||||
try {
|
||||
await ApiFetch.api.fileStorage.delete({ id });
|
||||
await this.load();
|
||||
} catch (error) {
|
||||
console.error('Error deleting file:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default stateFileStorage;
|
||||
@@ -89,7 +89,7 @@ function DetailBerita() {
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (beritaState.berita.findUnique.data) {
|
||||
router.push(`/admin/desa/berita/${beritaState.berita.findUnique.data.id}/edit`);
|
||||
router.push(`/admin/desa/berita/list-berita/${beritaState.berita.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!beritaState.berita.findUnique.data}
|
||||
|
||||
@@ -39,22 +39,16 @@ function ListBerita({ search }: { search: string }) {
|
||||
} = beritaState.berita.findMany;
|
||||
|
||||
|
||||
// Fetch pertama kali
|
||||
// Fetch data when page or search changes
|
||||
useShallowEffect(() => {
|
||||
load(page, 10); // awal page = 1
|
||||
}, [page]);
|
||||
|
||||
const filteredData = (data || []).filter((item) => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
item.kategoriBerita?.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
if (loading || !data) {
|
||||
return <Skeleton h={500} />;
|
||||
}
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
|
||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||
import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -18,6 +19,11 @@ function EditFoto() {
|
||||
const params = useParams();
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
name: fotoState.update.form.name || '',
|
||||
deskripsi: fotoState.update.form.deskripsi || '',
|
||||
imagesId: fotoState.update.form.imagesId || ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadFoto = async () => {
|
||||
@@ -26,6 +32,11 @@ function EditFoto() {
|
||||
try {
|
||||
const data = await fotoState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
imagesId: data.imageGalleryFoto?.id || ''
|
||||
});
|
||||
if (data?.imageGalleryFoto?.link) {
|
||||
setPreviewImage(data.imageGalleryFoto.link);
|
||||
}
|
||||
@@ -40,6 +51,12 @@ function EditFoto() {
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
fotoState.update.form = {
|
||||
...fotoState.update.form,
|
||||
name: formData.name,
|
||||
deskripsi: formData.deskripsi,
|
||||
imagesId: formData.imagesId
|
||||
};
|
||||
if (file) {
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file,
|
||||
@@ -74,30 +91,55 @@ function EditFoto() {
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||
placeholder='Masukkan judul foto'
|
||||
value={fotoState.update.form.name}
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
(fotoState.update.form.name = e.target.value)
|
||||
(formData.name = e.target.value)
|
||||
}
|
||||
/>
|
||||
<FileInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||
value={file}
|
||||
onChange={async (e) => {
|
||||
if (!e) return;
|
||||
setFile(e);
|
||||
const base64 = await e.arrayBuffer().then((buf) =>
|
||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||
);
|
||||
setPreviewImage(base64);
|
||||
}}
|
||||
/>
|
||||
{previewImage ? (
|
||||
<Image alt="" src={previewImage} w={200} h={200} />
|
||||
) : (
|
||||
<Center w={200} h={200} bg={"gray"}>
|
||||
<IconImageInPicture />
|
||||
</Center>
|
||||
)}
|
||||
<Box>
|
||||
<Text>Upload Foto</Text>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const selectedFile = files[0]; // Ambil file pertama
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||
}
|
||||
}}
|
||||
onReject={() => toast.error('File tidak valid.')}
|
||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||
accept={{ 'image/*': [] }}
|
||||
>
|
||||
<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>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag gambar ke sini atau klik untuk pilih file
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Maksimal 5MB dan harus format gambar
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
|
||||
{previewImage ? (
|
||||
<Image alt="" src={previewImage} w={200} h={200} />
|
||||
) : (
|
||||
<Center w={200} h={200} bg={"gray"}>
|
||||
<IconImageInPicture />
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||
<EditEditor
|
||||
|
||||
@@ -3,8 +3,9 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -69,25 +70,62 @@ function CreateFoto() {
|
||||
fotoState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<FileInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||
value={file}
|
||||
onChange={async (e) => {
|
||||
if (!e) return;
|
||||
setFile(e);
|
||||
const base64 = await e.arrayBuffer().then((buf) =>
|
||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||
);
|
||||
setPreviewImage(base64);
|
||||
}}
|
||||
/>
|
||||
{previewImage ? (
|
||||
<Image alt="" src={previewImage} w={200} h={200} />
|
||||
) : (
|
||||
<Center w={200} h={200} bg={"gray"}>
|
||||
<IconImageInPicture />
|
||||
</Center>
|
||||
)}
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const selectedFile = files[0]; // Ambil file pertama
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||
}
|
||||
}}
|
||||
onReject={() => toast.error('File tidak valid.')}
|
||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||
accept={{ 'image/*': [] }}
|
||||
>
|
||||
<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>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag gambar ke sini atau klik untuk pilih file
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Maksimal 5MB dan harus format gambar
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</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>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||
<CreateEditor
|
||||
|
||||
@@ -1,93 +1,124 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import JudulListTab from '../../../_com/judulListTab';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateGallery from '../../../_state/desa/gallery';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { useState } from 'react';
|
||||
"use client";
|
||||
import colors from "@/con/colors";
|
||||
import stateFileStorage from "@/state/state-list-image";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Flex,
|
||||
Group,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
import { IconSearch, IconTrash, IconX } from "@tabler/icons-react";
|
||||
import { motion } from "framer-motion";
|
||||
import toast from "react-simple-toasts";
|
||||
import { useSnapshot } from "valtio";
|
||||
|
||||
function Foto() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Posisi Organisasi'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListFoto search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListFoto({ search }: { search: string }) {
|
||||
const fotoState = useProxy(stateGallery.foto)
|
||||
const router = useRouter();
|
||||
export default function ListImage() {
|
||||
const { list, total } = useSnapshot(stateFileStorage);
|
||||
|
||||
useShallowEffect(() => {
|
||||
fotoState.findMany.load()
|
||||
}, [])
|
||||
stateFileStorage.load();
|
||||
}, []);
|
||||
|
||||
const filteredData = (fotoState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!fotoState.findMany.data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
let timeOut: NodeJS.Timer;
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulListTab
|
||||
title='List Foto'
|
||||
href='/admin/desa/gallery/foto/create'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={16} />}
|
||||
<Stack p={"lg"}>
|
||||
<Flex justify="space-between">
|
||||
<Title order={3}>List Foto</Title>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
leftSection={<IconSearch />}
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
variant="transparent"
|
||||
onClick={() => {
|
||||
stateFileStorage.load();
|
||||
}}
|
||||
>
|
||||
<IconX />
|
||||
</ActionIcon>
|
||||
}
|
||||
placeholder="Pencarian"
|
||||
onChange={(e) => {
|
||||
if (timeOut) clearTimeout(timeOut);
|
||||
timeOut = setTimeout(() => {
|
||||
stateFileStorage.load({ search: e.target.value });
|
||||
}, 200);
|
||||
}}
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Judul Foto</TableTh>
|
||||
<TableTh>Tanggal Foto</TableTh>
|
||||
<TableTh>Deskripsi Foto</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||
<TableTd>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/desa/gallery/foto/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Flex>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 3,
|
||||
md: 5,
|
||||
lg: 10,
|
||||
}}
|
||||
>
|
||||
{list &&
|
||||
list.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} shadow="sm">
|
||||
<Stack pos={"relative"} gap={0} justify="space-between">
|
||||
<motion.div
|
||||
onClick={() => {
|
||||
// copy to clipboard
|
||||
navigator.clipboard.writeText(v.url);
|
||||
toast("Berhasil disalin");
|
||||
}}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.8 }}
|
||||
>
|
||||
<Image
|
||||
h={100}
|
||||
src={v.url + "?size=100"}
|
||||
alt={v.name}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
objectPosition: "center",
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
<Box p={"md"} h={54}>
|
||||
<Text lineClamp={2} fz={"xs"}>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Group justify="end">
|
||||
<IconTrash
|
||||
color="red"
|
||||
onClick={() => {
|
||||
stateFileStorage.del({ name: v.name }).finally(() => {
|
||||
toast("Berhasil dihapus");
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
</Box>
|
||||
{total && (
|
||||
<Pagination
|
||||
total={total}
|
||||
onChange={(e) => {
|
||||
stateFileStorage.page = e;
|
||||
stateFileStorage.load();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Foto;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import LayoutTabsGallery from "../../ppid/_com/layoutTabsGallery"
|
||||
import LayoutTabsGallery from "./lib/layoutTabs"
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
|
||||
@@ -5,30 +5,20 @@ import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
function LayoutTabsGallery({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Grafik Hasil Kepuasan Masyarakat",
|
||||
value: "grafikhasilkepuasamanmasyarakat",
|
||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat"
|
||||
label: "Foto",
|
||||
value: "foto",
|
||||
href: "/admin/desa/gallery/foto"
|
||||
},
|
||||
{
|
||||
label: "Grafik Berdasarkan Jenis Kelamin Responden",
|
||||
value: "grafikberdasarkanjeniskelaminresponden",
|
||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden"
|
||||
label: "Video",
|
||||
value: "video",
|
||||
href: "/admin/desa/gallery/video"
|
||||
},
|
||||
{
|
||||
label: "Grafik Berdasarkan Pilihan Responden",
|
||||
value: "grafikberdasarkanpilihanresponden",
|
||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden"
|
||||
},
|
||||
{
|
||||
label: "Grafik Berdasarkan Umur Responden",
|
||||
value: "grafikberdasarkanumurresponden",
|
||||
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur"
|
||||
}
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
@@ -50,7 +40,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Indeks Kepuasan Masyarakat (IKM) Desa Darmasaba</Title>
|
||||
<Title order={3}>Gallery</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
@@ -69,4 +59,4 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabsGallery;
|
||||
@@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import JudulListTab from '../../../_com/judulListTab';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateGallery from '../../../_state/desa/gallery';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import stateGallery from '../../../_state/desa/gallery';
|
||||
|
||||
function Video() {
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -29,35 +29,34 @@ function Video() {
|
||||
function ListVideo({ search }: { search: string }) {
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const router = useRouter();
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = videoState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
videoState.findMany.load()
|
||||
}, [])
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const filteredData = (videoState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = (data || [])
|
||||
|
||||
if (!videoState.findMany.data) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulListTab
|
||||
<JudulList
|
||||
title='List Video'
|
||||
href='/admin/desa/gallery/video/create'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={16} />}
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
@@ -71,10 +70,25 @@ function ListVideo({ search }: { search: string }) {
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||
<TableTd>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Box w={200}>
|
||||
<Text lineClamp={1}>{item.name}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}>
|
||||
@@ -86,6 +100,15 @@ function ListVideo({ search }: { search: string }) {
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
||||
|
||||
function EditPengumuman() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Pengumuman</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
||||
placeholder='Masukkan judul'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
||||
placeholder='Masukkan deskripsi singkat'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal</Text>}
|
||||
placeholder='Masukkan tanggal'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Waktu</Text>}
|
||||
placeholder='Masukkan waktu'
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
||||
<KeamananEditor
|
||||
showSubmit={false}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPengumuman;
|
||||
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "List Pengumuman",
|
||||
value: "listpengumuman",
|
||||
href: "/admin/desa/pengumuman/list-pengumuman"
|
||||
},
|
||||
{
|
||||
label: "Kategori Pengumuman",
|
||||
value: "kategoripengumuman",
|
||||
href: "/admin/desa/pengumuman/kategori-pengumuman"
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Pengumuman</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsLayanan;
|
||||
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditKategoriPengumuman() {
|
||||
const editState = useProxy(stateDesaPengumuman.category)
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [formData, setFormData] = useState({
|
||||
name: editState.update.form.name || '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadKategori = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori Pengumuman:", error);
|
||||
toast.error("Gagal memuat data kategori Pengumuman");
|
||||
}
|
||||
};
|
||||
|
||||
loadKategori();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
editState.update.form = {
|
||||
...editState.update.form,
|
||||
name: formData.name,
|
||||
};
|
||||
await editState.update.update();
|
||||
toast.success('Kategori Pengumuman berhasil diperbarui!');
|
||||
router.push('/admin/desa/pengumuman/kategori-pengumuman');
|
||||
} catch (error) {
|
||||
console.error('Error updating kategori Pengumuman:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui kategori Pengumuman');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit Kategori Pengumuman</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Pengumuman</Text>}
|
||||
placeholder="masukkan nama kategori Pengumuman"
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Simpan</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditKategoriPengumuman;
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateKategoriPengumuman() {
|
||||
const createState = useProxy(stateDesaPengumuman.category)
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.form = {
|
||||
name: "",
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await createState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/desa/pengumuman/kategori-pengumuman")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Kategori Pengumuman</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Pengumuman</Text>}
|
||||
placeholder='Masukkan nama kategori Pengumuman'
|
||||
value={createState.create.form.name}
|
||||
onChange={(val) => {
|
||||
createState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateKategoriPengumuman;
|
||||
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||
|
||||
|
||||
|
||||
function KategoriPengumuman() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Kategori Pengumuman'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListKategoriPengumuman search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListKategoriPengumuman({ search }: { search: string }) {
|
||||
const listDataState = useProxy(stateDesaPengumuman.category)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
listDataState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
listDataState.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
listDataState.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listDataState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Kategori Pengumuman'
|
||||
href='/admin/desa/pengumuman/kategori-pengumuman/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/desa/pengumuman/kategori-pengumuman/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={listDataState.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus kategori Pengumuman ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default KategoriPengumuman;
|
||||
12
src/app/admin/(dashboard)/desa/pengumuman/layout.tsx
Normal file
12
src/app/admin/(dashboard)/desa/pengumuman/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import LayoutTabs from './_com/layoutTabs';
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
</LayoutTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
@@ -0,0 +1,140 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client";
|
||||
|
||||
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||
import stateDesaPengumuman from "@/app/admin/(dashboard)/_state/desa/pengumuman";
|
||||
import colors from "@/con/colors";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import { IconArrowBack } from "@tabler/icons-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useProxy } from "valtio/utils";
|
||||
|
||||
|
||||
function EditPengumuman() {
|
||||
const editState = useProxy(stateDesaPengumuman);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
judul: editState.pengumuman.edit.form.judul || '',
|
||||
deskripsi: editState.pengumuman.edit.form.deskripsi || '',
|
||||
categoryPengumumanId: editState.pengumuman.edit.form.categoryPengumumanId || '',
|
||||
content: editState.pengumuman.edit.form.content || ''
|
||||
});
|
||||
|
||||
// Load pengumuman by id saat pertama kali
|
||||
useEffect(() => {
|
||||
editState.category.findMany.load()
|
||||
const loadpengumuman = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await stateDesaPengumuman.pengumuman.edit.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
judul: data.judul || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
categoryPengumumanId: data.categoryPengumumanId || '',
|
||||
content: data.content || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pengumuman:", error);
|
||||
toast.error("Gagal memuat data pengumuman");
|
||||
}
|
||||
};
|
||||
|
||||
loadpengumuman();
|
||||
}, [params?.id]); // ✅ hapus editState dari dependency
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
// edit global state with form data
|
||||
editState.pengumuman.edit.form = {
|
||||
...editState.pengumuman.edit.form,
|
||||
judul: formData.judul,
|
||||
deskripsi: formData.deskripsi,
|
||||
content: formData.content,
|
||||
categoryPengumumanId: formData.categoryPengumumanId || ''
|
||||
};
|
||||
await editState.pengumuman.edit.update();
|
||||
toast.success("pengumuman berhasil diperbarui!");
|
||||
router.push("/admin/desa/pengumuman/list-pengumuman");
|
||||
} catch (error) {
|
||||
console.error("Error updating pengumuman:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui pengumuman");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit pengumuman</Title>
|
||||
<TextInput
|
||||
value={formData.judul}
|
||||
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||
placeholder="masukkan judul"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
value={formData.deskripsi}
|
||||
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
|
||||
placeholder="masukkan deskripsi"
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||
<EditEditor
|
||||
value={formData.content}
|
||||
onChange={(htmlContent) => {
|
||||
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
||||
editState.pengumuman.edit.form.content = htmlContent;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Select
|
||||
value={formData.categoryPengumumanId}
|
||||
onChange={(val) => setFormData({ ...formData, categoryPengumumanId: val || "" })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||
placeholder='Pilih kategori'
|
||||
data={
|
||||
editState.category.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.name
|
||||
})) || []
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
required
|
||||
error={!formData.categoryPengumumanId ? "Pilih kategori" : undefined}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Edit pengumuman</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPengumuman;
|
||||
@@ -1,40 +1,46 @@
|
||||
'use client'
|
||||
|
||||
import { Box, Button, Paper } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import { useState } from 'react';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
|
||||
|
||||
function DetailPengumuman() {
|
||||
// const pengumumanState = useProxy(stateDesaPengumuman)
|
||||
// const [modalHapus, setModalHapus] = useState(false)
|
||||
// const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
// const params = useParams()
|
||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
// useShallowEffect(() => {
|
||||
// pengumumanState.pengumuman.findUnique.load(params?.id as string)
|
||||
// }, [])
|
||||
useShallowEffect(() => {
|
||||
pengumumanState.pengumuman.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
|
||||
// const handleHapus = () => {
|
||||
// if (selectedId) {
|
||||
// pengumumanState.pengumuman.delete.byId(selectedId)
|
||||
// setModalHapus(false)
|
||||
// setSelectedId(null)
|
||||
// router.push("/admin/desa/pengumuman")
|
||||
// }
|
||||
// }
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
pengumumanState.pengumuman.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/desa/pengumuman/list-pengumuman")
|
||||
}
|
||||
}
|
||||
|
||||
// if (!pengumumanState.pengumuman.findUnique.data) {
|
||||
// return (
|
||||
// <Stack py={10}>
|
||||
// <Skeleton h={400} />
|
||||
// </Stack>
|
||||
// )
|
||||
// }
|
||||
if (!pengumumanState.pengumuman.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={400} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -44,7 +50,7 @@ function DetailPengumuman() {
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
{/* <Stack>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Pengumuman</Text>
|
||||
{pengumumanState.pengumuman.findUnique.data ? (
|
||||
<Paper key={pengumumanState.pengumuman.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
@@ -79,11 +85,11 @@ function DetailPengumuman() {
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (pengumumanState.pengumuman.findUnique.data) {
|
||||
router.push(`/admin/desa/pengumuman/${pengumumanState.pengumuman.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (pengumumanState.pengumuman.findUnique.data) {
|
||||
router.push(`/admin/desa/pengumuman/list-pengumuman/${pengumumanState.pengumuman.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!pengumumanState.pengumuman.findUnique.data}
|
||||
color={"green"}
|
||||
>
|
||||
@@ -93,16 +99,16 @@ function DetailPengumuman() {
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Stack> */}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||
/> */}
|
||||
text='Apakah anda yakin ingin menghapus pengumuman ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import CreateEditor from '../../../_com/createEditor';
|
||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||
|
||||
|
||||
function CreatePengumuman() {
|
||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
pengumumanState.category.findMany.load()
|
||||
}, [])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await pengumumanState.pengumuman.create.create()
|
||||
resetForm()
|
||||
router.push("/admin/desa/pengumuman")
|
||||
router.push("/admin/desa/pengumuman/list-pengumuman")
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -45,10 +49,21 @@ function CreatePengumuman() {
|
||||
pengumumanState.pengumuman.create.form.judul = val.target.value
|
||||
}}
|
||||
/>
|
||||
<SelectCategory
|
||||
<Select
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||
placeholder='Pilih kategori'
|
||||
data={pengumumanState.category.findMany.data?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(val) => {
|
||||
pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id;
|
||||
const selected = pengumumanState.category.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
pengumumanState.pengumuman.create.form.categoryPengumumanId = selected.id;
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
||||
@@ -76,35 +91,4 @@ function CreatePengumuman() {
|
||||
);
|
||||
}
|
||||
|
||||
function SelectCategory({
|
||||
onChange,
|
||||
}: {
|
||||
onChange: (value: Prisma.CategoryPengumumanGetPayload<{ select: { name: true; id: true; } }>) => void;
|
||||
}) {
|
||||
const categoryState = useProxy(stateDesaPengumuman.category);
|
||||
|
||||
useShallowEffect(() => {
|
||||
categoryState.findMany.load();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||
placeholder='Pilih kategori'
|
||||
data={categoryState.findMany.data?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(val) => {
|
||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
onChange(selected);
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePengumuman;
|
||||
@@ -1,14 +1,13 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Grid, GridCol, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import stateDesaPengumuman from '../../_state/desa/pengumuman';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||
|
||||
|
||||
function Pengumuman() {
|
||||
@@ -16,7 +15,7 @@ function Pengumuman() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Posisi Organisasi'
|
||||
title='List Pengumuman'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
@@ -30,31 +29,21 @@ function Pengumuman() {
|
||||
function ListPengumuman({ search }: { search: string }) {
|
||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||
const router = useRouter()
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = pengumumanState.pengumuman.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
pengumumanState.pengumuman.findMany.load()
|
||||
}, [])
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const filteredData = (data || [])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
pengumumanState.pengumuman.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = (pengumumanState.pengumuman.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
item.CategoryPengumuman?.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!pengumumanState.pengumuman.findMany.data) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
@@ -71,7 +60,7 @@ function ListPengumuman({ search }: { search: string }) {
|
||||
<Text fz={"xl"} fw={"bold"}>List Pengumuman</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button onClick={() => router.push("/admin/desa/pengumuman/create")} bg={colors['blue-button']}>
|
||||
<Button onClick={() => router.push("/admin/desa/pengumuman/list-pengumuman/create")} bg={colors['blue-button']}>
|
||||
<IconCircleDashedPlus size={25} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
@@ -96,7 +85,7 @@ function ListPengumuman({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd >{item.CategoryPengumuman?.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/desa/pengumuman/detail`)}>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/desa/pengumuman/list-pengumuman/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
@@ -107,14 +96,15 @@ function ListPengumuman({ search }: { search: string }) {
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||
/>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabsIKM({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Indeks Kepuasan Masyarakat",
|
||||
value: "indekskepuasannamasyarakat",
|
||||
href: "/admin/ppid/ikm-desa-darmasaba/indeks-kepuasan-masyarakat"
|
||||
},
|
||||
{
|
||||
label: "Responden",
|
||||
value: "responden",
|
||||
href: "/admin/ppid/ikm-desa-darmasaba/responden"
|
||||
},
|
||||
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>IKM Desa Darmasaba</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsIKM;
|
||||
@@ -1,78 +0,0 @@
|
||||
'use client'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
|
||||
function EditGrafikBerdasarkanJenisKelaminResponden() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||
const id = params.id
|
||||
|
||||
useEffect(() => {
|
||||
if(id){
|
||||
stategrafikBerdasarkanJenisKelamin.findUnique.load(id).then(() => {
|
||||
const data = stategrafikBerdasarkanJenisKelamin.findUnique.data
|
||||
if(data){
|
||||
stategrafikBerdasarkanJenisKelamin.update.form = {
|
||||
laki: data.laki || '',
|
||||
perempuan: data.perempuan || '',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
stategrafikBerdasarkanJenisKelamin.update.id = id;
|
||||
await stategrafikBerdasarkanJenisKelamin.update.submit();
|
||||
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden')
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Laki-laki"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanJenisKelamin.update.form.laki}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanJenisKelamin.update.form.laki = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Perempuan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanJenisKelamin.update.form.perempuan}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanJenisKelamin.update.form.perempuan = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditGrafikBerdasarkanJenisKelaminResponden;
|
||||
@@ -1,83 +0,0 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useState } from 'react';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
|
||||
function GrafikBerdasarkanJenisKelaminRespondenCreate() {
|
||||
const router = useRouter();
|
||||
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
|
||||
const resetForm = () => {
|
||||
stategrafikBerdasarkanJenisKelamin.create.form = {
|
||||
...stategrafikBerdasarkanJenisKelamin.create.form,
|
||||
laki: "",
|
||||
perempuan: "",
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const id = await stategrafikBerdasarkanJenisKelamin.create.create();
|
||||
if (typeof id !== 'undefined') {
|
||||
const idStr = String(id);
|
||||
await stategrafikBerdasarkanJenisKelamin.findUnique.load(idStr);
|
||||
if (stategrafikBerdasarkanJenisKelamin.findUnique.data) {
|
||||
setDonutData([stategrafikBerdasarkanJenisKelamin.findUnique.data]);
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden");
|
||||
} catch (error) {
|
||||
console.error('Error submitting form:', error);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Laki-laki"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanJenisKelamin.create.form.laki}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanJenisKelamin.create.form.laki = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Perempuan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanJenisKelamin.create.form.perempuan}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanJenisKelamin.create.form.perempuan = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanJenisKelaminRespondenCreate;
|
||||
@@ -1,227 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function GrafikBerdasarkanJenisKelamin() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Grafik Berdasarkan Jenis Kelamin Responden'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGrafikBerdasarkanJenisKelamin search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGrafikBerdasarkanJenisKelamin({ search }: { search: string }) {
|
||||
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter();
|
||||
const { data, page, totalPages, loading, load } = stategrafikBerdasarkanJenisKelamin.findMany
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
load(page, 10)
|
||||
}, [page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const totalLaki = data.reduce((acc: number, cur: any) => acc + Number(cur.laki || 0), 0);
|
||||
const totalPerempuan = data.reduce((acc: number, cur: any) => acc + Number(cur.perempuan || 0), 0);
|
||||
setDonutData([
|
||||
{ name: 'laki', value: totalLaki, color: colors['blue-button'], key: 'laki' },
|
||||
{ name: 'perempuan', value: totalPerempuan, color: '#10A85AFF', key: 'perempuan' }
|
||||
]);
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.laki.toString().toLowerCase().includes(keyword) ||
|
||||
item.perempuan.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (selectedId) {
|
||||
await grafikBerdasarkanJenisKelamin.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stategrafikBerdasarkanJenisKelamin.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={730} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Berdasarkan Jenis Kelamin Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Laki-laki</TableTh>
|
||||
<TableTh>Perempuan</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data berdasarkan jenis kelamin responden yang tersedia</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={"md"} h={{ base: 730, md: 650 }}>
|
||||
<JudulList
|
||||
title='List Data Berdasarkan Jenis Kelamin Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Laki-laki</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Perempuan</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.laki}</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.perempuan}</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stategrafikBerdasarkanJenisKelamin.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Jenis Kelamin Responden</Title>
|
||||
{mounted && donutData.length === 0 ? (<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>) : (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart
|
||||
width={800} height={300}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
cx={500}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={115}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Laki-laki: {donutData.find((entry) => entry.name === 'laki')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>Perempuan: {donutData.find((entry) => entry.name === 'perempuan')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik berdasarkan hasil responden ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanJenisKelamin;
|
||||
@@ -1,98 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import grafikBerdasarkanResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
|
||||
function EditGrafikBerdasarkanResponden() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stateGrafikResponden = useProxy(grafikBerdasarkanResponden)
|
||||
const id = params.id
|
||||
|
||||
useEffect(() => {
|
||||
if(id){
|
||||
stateGrafikResponden.findUnique.load(id).then(() => {
|
||||
const data = stateGrafikResponden.findUnique.data
|
||||
if(data){
|
||||
stateGrafikResponden.update.form = {
|
||||
sangatbaik: data.sangatbaik || '',
|
||||
baik: data.baik || '',
|
||||
kurangbaik: data.kurangbaik || '',
|
||||
tidakbaik: data.tidakbaik || '',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
stateGrafikResponden.update.id = id;
|
||||
await stateGrafikResponden.update.submit();
|
||||
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden')
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Sangat Baik"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stateGrafikResponden.update.form.sangatbaik}
|
||||
onChange={(val) => {
|
||||
stateGrafikResponden.update.form.sangatbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Baik"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stateGrafikResponden.update.form.baik}
|
||||
onChange={(val) => {
|
||||
stateGrafikResponden.update.form.baik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Kurang Baik"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stateGrafikResponden.update.form.kurangbaik}
|
||||
onChange={(val) => {
|
||||
stateGrafikResponden.update.form.kurangbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tidak Baik"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stateGrafikResponden.update.form.tidakbaik}
|
||||
onChange={(val) => {
|
||||
stateGrafikResponden.update.form.tidakbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditGrafikBerdasarkanResponden;
|
||||
@@ -1,98 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikBerdasarkanResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarkanRespondenCreate() {
|
||||
const router = useRouter()
|
||||
const stategrafikBerdasarkanResponden = useProxy(grafikBerdasarkanResponden)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
|
||||
const resetForm = () => {
|
||||
stategrafikBerdasarkanResponden.create.form = {
|
||||
...stategrafikBerdasarkanResponden.create.form,
|
||||
sangatbaik: "",
|
||||
baik: "",
|
||||
kurangbaik: "",
|
||||
tidakbaik: "",
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stategrafikBerdasarkanResponden.create.create();
|
||||
if (id) {
|
||||
const idStr = String(id);
|
||||
await stategrafikBerdasarkanResponden.findUnique.load(idStr);
|
||||
if (stategrafikBerdasarkanResponden.findUnique.data) {
|
||||
setDonutData([stategrafikBerdasarkanResponden.findUnique.data]);
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Berdasarkan Responden</Title>
|
||||
<TextInput
|
||||
label="Sangat Baik"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanResponden.create.form.sangatbaik}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.sangatbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Baik"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanResponden.create.form.baik}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.baik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Kurang Baik"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanResponden.create.form.kurangbaik}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.kurangbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tidak Baik"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanResponden.create.form.tidakbaik}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.tidakbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanRespondenCreate;
|
||||
@@ -1,251 +0,0 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikBerdasarkanResponden from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||
|
||||
function GrafikBerdasarkanResponden() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Grafik Berdasarkan Pilihan Responden'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGrafikBerdasarkanResponden search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGrafikBerdasarkanResponden({ search }: { search: string }) {
|
||||
const stategrafikBerdasarkanResponden = useSnapshot(grafikBerdasarkanResponden)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter();
|
||||
|
||||
const { data, page, totalPages, loading, load } = stategrafikBerdasarkanResponden.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
load(page, 10)
|
||||
}, [page])
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.sangatbaik.toString().toLowerCase().includes(keyword) ||
|
||||
item.baik.toString().toLowerCase().includes(keyword) ||
|
||||
item.kurangbaik.toString().toLowerCase().includes(keyword) ||
|
||||
item.tidakbaik.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const totalSangatBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.sangatbaik || 0), 0);
|
||||
const totalBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.baik || 0), 0);
|
||||
const totalKurangBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
||||
const totalTidakBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.tidakbaik || 0), 0);
|
||||
setDonutData([
|
||||
{ name: 'sangatbaik', value: totalSangatBaik, color: colors['blue-button'], key: 'sangatbaik' },
|
||||
{ name: 'baik', value: totalBaik, color: '#10A85AFF', key: 'baik' },
|
||||
{ name: 'kurangbaik', value: totalKurangBaik, color: '#B3AA12FF', key: 'kurangbaik' },
|
||||
{ name: 'tidakbaik', value: totalTidakBaik, color: '#B21313FF', key: 'tidakbaik' }
|
||||
]);
|
||||
}
|
||||
|
||||
}, [data])
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (selectedId) {
|
||||
await stategrafikBerdasarkanResponden.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
|
||||
// Refresh data agar chart & tabel ikut update
|
||||
stategrafikBerdasarkanResponden.findMany.load();
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={730} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md" >
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Berdasarkan Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Sangat Baik</TableTh>
|
||||
<TableTh>Baik</TableTh>
|
||||
<TableTh>Kurang Baik</TableTh>
|
||||
<TableTh>Tidak Baik</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data grafik berdasarkan responden yang tersedia</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={"md"} h={{ base: 730, md: 650 }}>
|
||||
<JudulList
|
||||
title='List Data Berdasarkan Pilihan Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Sangat Baik</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Baik</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Kurang Baik</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Tidak Baik</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{item.sangatbaik}</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>{item.baik}</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>{item.kurangbaik}</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>{item.tidakbaik}</TableTd>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stategrafikBerdasarkanResponden.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
|
||||
</Table>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Pilihan Responden</Title>
|
||||
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart
|
||||
width={800} height={300}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
cx={500}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={115}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Sangat Baik: {donutData.find((entry) => entry.name === 'sangatbaik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>Baik: {donutData.find((entry) => entry.name === 'baik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B3AA12FF'} w={20} h={20} />
|
||||
<Text>Kurang Baik: {donutData.find((entry) => entry.name === 'kurangbaik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B21313FF'} w={20} h={20} />
|
||||
<Text>Tidak Baik: {donutData.find((entry) => entry.name === 'tidakbaik')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik berdasarkan hasil responden ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanResponden;
|
||||
@@ -1,97 +0,0 @@
|
||||
'use client'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditGrafikBerdasarakanUmur() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||
const id = params.id
|
||||
|
||||
useEffect(() => {
|
||||
if(id){
|
||||
stategrafikBerdasarkanUmur.findUnique.load(id).then(() => {
|
||||
const data = stategrafikBerdasarkanUmur.findUnique.data
|
||||
if(data){
|
||||
stategrafikBerdasarkanUmur.update.form = {
|
||||
remaja: data.remaja || '',
|
||||
dewasa: data.dewasa || '',
|
||||
orangtua: data.orangtua || '',
|
||||
lansia: data.lansia || '',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
stategrafikBerdasarkanUmur.update.id = id;
|
||||
await stategrafikBerdasarkanUmur.update.submit();
|
||||
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur')
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Remaja"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.update.form.remaja}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.update.form.remaja = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Dewasa"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.update.form.dewasa}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.update.form.dewasa = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Orangtua"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.update.form.orangtua}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.update.form.orangtua = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Lansia"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.update.form.lansia}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.update.form.lansia = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditGrafikBerdasarakanUmur;
|
||||
@@ -1,98 +0,0 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarakanUmurCreate() {
|
||||
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const router = useRouter()
|
||||
|
||||
const resetForm = () => {
|
||||
stategrafikBerdasarkanUmur.create.form = {
|
||||
...stategrafikBerdasarkanUmur.create.form,
|
||||
remaja: "",
|
||||
dewasa: "",
|
||||
orangtua: "",
|
||||
lansia: "",
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stategrafikBerdasarkanUmur.create.create();
|
||||
if (id) {
|
||||
const idStr = String(id);
|
||||
await stategrafikBerdasarkanUmur.findUnique.load(idStr);
|
||||
if (stategrafikBerdasarkanUmur.findUnique.data) {
|
||||
setDonutData([stategrafikBerdasarkanUmur.findUnique.data]);
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Remaja"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.create.form.remaja}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.create.form.remaja = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Dewasa"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.create.form.dewasa}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.create.form.dewasa = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Orangtua"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.create.form.orangtua}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.create.form.orangtua = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Lansia"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafikBerdasarkanUmur.create.form.lansia}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanUmur.create.form.lansia = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarakanUmurCreate;
|
||||
@@ -1,252 +0,0 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import JudulListTab from '../../../_com/judulListTab';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
|
||||
function GrafikBerdasarakanUmur() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Grafik Berdasarkan Umur Responden'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGrafikBerdasarakanUmur search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGrafikBerdasarakanUmur({ search }: { search: string }) {
|
||||
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
const { data, page, totalPages, loading, load } = stategrafikBerdasarkanUmur.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
load(page, 10)
|
||||
}, [page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const totalRemaja = data.reduce((acc: number, cur: any) => acc + Number(cur.remaja || 0), 0);
|
||||
const totalDewasa = data.reduce((acc: number, cur: any) => acc + Number(cur.dewasa || 0), 0);
|
||||
const totalOrangtua = data.reduce((acc: number, cur: any) => acc + Number(cur.orangtua || 0), 0);
|
||||
const totalLansia = data.reduce((acc: number, cur: any) => acc + Number(cur.lansia || 0), 0);
|
||||
setDonutData([
|
||||
{ name: 'remaja', value: totalRemaja, color: colors['blue-button'], key: 'remaja' },
|
||||
{ name: 'dewasa', value: totalDewasa, color: '#D32711FF', key: 'dewasa' },
|
||||
{ name: 'orangtua', value: totalOrangtua, color: '#B46B04FF', key: 'orangtua' },
|
||||
{ name: 'lansia', value: totalLansia, color: '#038617FF', key: 'lansia' }
|
||||
]);
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.remaja.toString().toLowerCase().includes(keyword) ||
|
||||
item.dewasa.toString().toLowerCase().includes(keyword) ||
|
||||
item.orangtua.toString().toLowerCase().includes(keyword) ||
|
||||
item.lansia.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (selectedId) {
|
||||
await grafikBerdasarkanUmur.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stategrafikBerdasarkanUmur.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={730} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md" >
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Berdasarkan Umur Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Remaja</TableTh>
|
||||
<TableTh>Dewasa</TableTh>
|
||||
<TableTh>Orangtua</TableTh>
|
||||
<TableTh>Lansia</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data grafik berdasarkan umur responden yang tersedia</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={"md"} h={{ base: 730, md: 650 }}>
|
||||
<JudulListTab
|
||||
title='List Data Berdasarkan Umur Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur/create'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={16} />}
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '2%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Remaja</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Dewasa</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Orangtua</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Lansia</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ textAlign: 'center' }}>{filteredData.indexOf(item) + 1}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>{item.remaja}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>{item.dewasa}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>{item.orangtua}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>{item.lansia}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stategrafikBerdasarkanUmur.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
|
||||
</Table>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Umur Berdasarkan Responden</Title>
|
||||
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart
|
||||
width={800} height={300}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
cx={500}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={115}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Remaja: {donutData.find((entry) => entry.name === 'remaja')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#D32711FF'} w={20} h={20} />
|
||||
<Text>Dewasa: {donutData.find((entry) => entry.name === 'dewasa')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B46B04FF'} w={20} h={20} />
|
||||
<Text>Orangtua: {donutData.find((entry) => entry.name === 'orangtua')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#038617FF'} w={20} h={20} />
|
||||
<Text>Lansia: {donutData.find((entry) => entry.name === 'lansia')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik berdasarkan hasil responden ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarakanUmur;
|
||||
@@ -1,81 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
|
||||
import grafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditGrafikHasilKepuasan() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const grafikHasilKepuasan = useProxy(grafikHasilKepuasanMasyarakat)
|
||||
|
||||
const id = params.id
|
||||
|
||||
// Load data saat komponen mount
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
grafikHasilKepuasan.findUnique.load(id).then(() => {
|
||||
const data = grafikHasilKepuasan.findUnique.data
|
||||
if (data) {
|
||||
grafikHasilKepuasan.update.form = {
|
||||
label: data.label || '',
|
||||
kepuasan: data.kepuasan || '',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Set the ID before submitting
|
||||
grafikHasilKepuasan.update.id = id;
|
||||
await grafikHasilKepuasan.update.submit();
|
||||
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat')
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Grafik Hasil Kepuasan Masyarakat</Title>
|
||||
<TextInput
|
||||
label="Label"
|
||||
placeholder="masukkan label"
|
||||
value={grafikHasilKepuasan.update.form.label}
|
||||
onChange={(val) => {
|
||||
grafikHasilKepuasan.update.form.label = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Kepuasan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah kepuasan"
|
||||
value={grafikHasilKepuasan.update.form.kepuasan}
|
||||
onChange={(val) => {
|
||||
grafikHasilKepuasan.update.form.kepuasan = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Simpan Perubahan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditGrafikHasilKepuasan;
|
||||
@@ -1,83 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
|
||||
import grafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function GrafikHasilKepuasan() {
|
||||
const router = useRouter()
|
||||
const grafikHasilKepuasan = useProxy(grafikHasilKepuasanMasyarakat)
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
|
||||
|
||||
const resetForm = () => {
|
||||
grafikHasilKepuasan.create.form = {
|
||||
...grafikHasilKepuasan.create.form,
|
||||
label: "",
|
||||
kepuasan: "",
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await grafikHasilKepuasan.create.create();
|
||||
if (id) {
|
||||
// Ensure id is a string
|
||||
const idStr = String(id);
|
||||
await grafikHasilKepuasan.findUnique.load(idStr);
|
||||
if (grafikHasilKepuasan.findUnique.data) {
|
||||
setChartData([grafikHasilKepuasan.findUnique.data]);
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Label"
|
||||
placeholder="masukkan label"
|
||||
value={grafikHasilKepuasan.create.form.label}
|
||||
onChange={(val) => {
|
||||
grafikHasilKepuasan.create.form.label = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Kepuasan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah kepuasan"
|
||||
value={grafikHasilKepuasan.create.form.kepuasan}
|
||||
onChange={(val) => {
|
||||
grafikHasilKepuasan.create.form.kepuasan = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default GrafikHasilKepuasan;
|
||||
@@ -1,211 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikHasilKepuasanMasyarakat from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||
|
||||
|
||||
function GrafikHasilKepuasanMasyarakat() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Grafik Hasil Kepuasan Masyarakat'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGrafikHasilKepuasanMasyarakat search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
||||
type IKMGrafik = {
|
||||
id: string;
|
||||
label: string;
|
||||
kepuasan: number;
|
||||
}
|
||||
|
||||
|
||||
const stateGrafikHasilKepuasan = useSnapshot(grafikHasilKepuasanMasyarakat)
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [chartData, setChartData] = useState<IKMGrafik[]>([]);
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter();
|
||||
|
||||
const { data, page, totalPages, loading, load } = stateGrafikHasilKepuasan.findMany
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
load(page, 10)
|
||||
}, [page])
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setChartData(
|
||||
data.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
kepuasan: Number(item.kepuasan),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.label.toLowerCase().includes(keyword) ||
|
||||
item.kepuasan.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateGrafikHasilKepuasan.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stateGrafikHasilKepuasan.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={730} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md" >
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Hasil Kepuasan Masyarakat'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Label</TableTh>
|
||||
<TableTh>Jumlah Kepuasan</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data grafik hasil kepuasan masyarakat yang tersedia</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={'md'} h={{ base: 730, md: 650 }}>
|
||||
<JudulList
|
||||
title='List Data Hasil Kepuasan Masyarakat'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Label</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jumlah Kepuasan</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>{item.label}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>{item.kepuasan}</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stateGrafikHasilKepuasan.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 500, minHeight: 300 }}>
|
||||
<Paper style={{ width: '100%', minWidth: 300, height: 500, minHeight: 300 }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<BarChart width={isMobile ? 300 : isTablet ? 300 : 300} height={380} data={chartData} >
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
||||
</BarChart>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik hasil kepuasan masyarakat ini?'
|
||||
/>
|
||||
</Box>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikHasilKepuasanMasyarakat;
|
||||
@@ -0,0 +1,199 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client';
|
||||
import colors from '@/con/colors';
|
||||
import { PieChart } from '@mantine/charts'; // ✅ Ganti recharts dengan Mantine
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Flex,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
||||
|
||||
interface ChartDataItem {
|
||||
name: string;
|
||||
value: number;
|
||||
color: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(indeksKepuasanState.responden);
|
||||
const { data, loading } = state.findMany;
|
||||
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (data) {
|
||||
// Hitung total berdasarkan jenis kelamin
|
||||
const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length;
|
||||
const totalPerempuan = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'perempuan').length;
|
||||
|
||||
// Hitung total berdasarkan rating
|
||||
const totalSangatBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat baik').length;
|
||||
const totalBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'baik').length;
|
||||
const totalKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'kurang baik').length;
|
||||
const totalSangatKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat kurang baik').length;
|
||||
|
||||
// Hitung total berdasarkan kelompok umur
|
||||
const totalMuda = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'muda').length;
|
||||
const totalDewasa = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'dewasa').length;
|
||||
const totalLansia = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'lansia').length;
|
||||
|
||||
// Update gender chart data
|
||||
setDonutDataJenisKelamin([
|
||||
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] },
|
||||
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
|
||||
]);
|
||||
|
||||
// Update rating chart data
|
||||
setDonutDataRating([
|
||||
{ name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] },
|
||||
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
|
||||
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
|
||||
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
|
||||
]);
|
||||
|
||||
// Update age group chart data
|
||||
setDonutDataKelompokUmur([
|
||||
{ name: 'Muda', value: totalMuda, color: colors['blue-button'] },
|
||||
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
|
||||
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
|
||||
]);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={730} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
|
||||
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Jenis Kelamin</Title>
|
||||
{donutDataJenisKelamin.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Box>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={250}
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataJenisKelamin.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart Rating */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Pilihan</Title>
|
||||
{donutDataRating.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Box>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={250}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataRating.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart Kelompok Umur */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Umur</Title>
|
||||
{donutDataKelompokUmur.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Box>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={250}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataKelompokUmur.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -1,10 +1,13 @@
|
||||
'use client'
|
||||
import LayoutTabs from "../_com/layoutTabs";
|
||||
import React from 'react';
|
||||
import LayoutTabsIKM from './_lib/layoutTabs';
|
||||
|
||||
export default function Layout({children} : {children: React.ReactNode}) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
</LayoutTabs>
|
||||
)
|
||||
}
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabsIKM>
|
||||
{children}
|
||||
</LayoutTabsIKM>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
'use client'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput, Text, Select } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
|
||||
|
||||
function EditResponden() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const state = useProxy(indeksKepuasanState.responden)
|
||||
const id = params.id
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
state.findUnique.load(id).then(() => {
|
||||
const data = state.findUnique.data
|
||||
if (data) {
|
||||
state.update.form = {
|
||||
name: data.name || '',
|
||||
tanggal: data.tanggal ? new Date(data.tanggal).toISOString() : new Date().toISOString(),
|
||||
jenisKelaminId: data.jenisKelaminId || '',
|
||||
ratingId: data.ratingId || '',
|
||||
kelompokUmurId: data.kelompokUmurId || '',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
state.update.id = id;
|
||||
await state.update.submit();
|
||||
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Responden</Title>
|
||||
<TextInput
|
||||
label="Nama"
|
||||
type='text'
|
||||
placeholder="masukkan nama"
|
||||
value={state.update.form.name}
|
||||
onChange={(val) => {
|
||||
state.update.form.name = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal"
|
||||
type="date"
|
||||
placeholder="masukkan tanggal"
|
||||
value={state.update.form.tanggal}
|
||||
onChange={(val) => {
|
||||
state.update.form.tanggal = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={state.update.form.jenisKelaminId}
|
||||
onChange={(val) => {
|
||||
state.update.form.jenisKelaminId = val || "";
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Jenis Kelamin</Text>}
|
||||
placeholder='Pilih jenis kelamin'
|
||||
data={
|
||||
indeksKepuasanState.jenisKelaminResponden.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama
|
||||
})) || []
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
required
|
||||
error={!state.update.form.jenisKelaminId ? "Pilih jenis kelamin" : undefined}
|
||||
/>
|
||||
<Select
|
||||
value={state.update.form.ratingId}
|
||||
onChange={(val) => {
|
||||
state.update.form.ratingId = val || "";
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Rating</Text>}
|
||||
placeholder='Pilih rating'
|
||||
data={
|
||||
indeksKepuasanState.pilihanRatingResponden.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama
|
||||
})) || []
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
required
|
||||
error={!state.update.form.ratingId ? "Pilih rating" : undefined}
|
||||
/>
|
||||
|
||||
<Select
|
||||
value={state.update.form.kelompokUmurId}
|
||||
onChange={(val) => {
|
||||
state.update.form.kelompokUmurId = val || "";
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kelompok Umur</Text>}
|
||||
placeholder='Pilih kelompok umur'
|
||||
data={
|
||||
indeksKepuasanState.kelompokUmurResponden.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama
|
||||
})) || []
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
required
|
||||
error={!state.update.form.kelompokUmurId ? "Pilih kelompok umur" : undefined}
|
||||
/>
|
||||
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditResponden;
|
||||
@@ -0,0 +1,91 @@
|
||||
'use client'
|
||||
|
||||
import { ModalKonfirmasiHapus } from "@/app/admin/(dashboard)/_com/modalKonfirmasiHapus"
|
||||
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan"
|
||||
import colors from "@/con/colors"
|
||||
import { Box, Button, Paper, Skeleton, Stack, Text } from "@mantine/core"
|
||||
import { useShallowEffect } from "@mantine/hooks"
|
||||
import { IconArrowBack } from "@tabler/icons-react"
|
||||
import { useRouter, useParams } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { useProxy } from "valtio/utils"
|
||||
|
||||
export default function DetailResponden(){
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const stateDetail = useProxy(indeksKepuasanState.responden)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDetail.findUnique.load(params?.id as string)
|
||||
}, [params?.id])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateDetail.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ppid/ikm-desa-darmasaba/responden")
|
||||
}
|
||||
}
|
||||
|
||||
if(!stateDetail.findUnique.data){
|
||||
return(
|
||||
<Stack>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return(
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Responden</Text>
|
||||
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Nama Responden</Text>
|
||||
<Text fz={"lg"}>{stateDetail.findUnique.data?.name}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Tanggal</Text>
|
||||
<Text fz={"lg"}>{
|
||||
stateDetail.findUnique.data?.tanggal
|
||||
? new Date(stateDetail.findUnique.data.tanggal).toLocaleDateString('id-ID')
|
||||
: '-'
|
||||
}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Jenis Kelamin</Text>
|
||||
<Text fz={"lg"}>{stateDetail.findUnique.data?.jenisKelamin?.name}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Rating</Text>
|
||||
<Text fz={"lg"}>{stateDetail.findUnique.data?.rating?.name}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Kelompok Umur</Text>
|
||||
<Text fz={"lg"}>{stateDetail.findUnique.data?.kelompokUmur?.name}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus responden ini?"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useState } from 'react';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput, Select, Text } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
function RespondenCreate() {
|
||||
const router = useRouter();
|
||||
const stategrafikBerdasarkanResponden = useProxy(indeksKepuasanState.responden)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
|
||||
const resetForm = () => {
|
||||
stategrafikBerdasarkanResponden.create.form = {
|
||||
...stategrafikBerdasarkanResponden.create.form,
|
||||
name: "",
|
||||
tanggal: "",
|
||||
jenisKelaminId: "",
|
||||
ratingId: "",
|
||||
kelompokUmurId: "",
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
indeksKepuasanState.jenisKelaminResponden.findMany.load()
|
||||
indeksKepuasanState.pilihanRatingResponden.findMany.load()
|
||||
indeksKepuasanState.kelompokUmurResponden.findMany.load()
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const id = await stategrafikBerdasarkanResponden.create.create();
|
||||
if (typeof id !== 'undefined') {
|
||||
const idStr = String(id);
|
||||
await stategrafikBerdasarkanResponden.findUnique.load(idStr);
|
||||
if (stategrafikBerdasarkanResponden.findUnique.data) {
|
||||
setDonutData([stategrafikBerdasarkanResponden.findUnique.data]);
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ppid/ikm-desa-darmasaba/responden");
|
||||
} catch (error) {
|
||||
console.error('Error submitting form:', error);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
label="Nama"
|
||||
type='text'
|
||||
placeholder="masukkan nama"
|
||||
value={stategrafikBerdasarkanResponden.create.form.name}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.name = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal"
|
||||
type="date"
|
||||
placeholder="masukkan tanggal"
|
||||
value={stategrafikBerdasarkanResponden.create.form.tanggal}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.tanggal = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
key={"jenisKelamin"}
|
||||
label={"Jenis Kelamin"}
|
||||
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
|
||||
value={stategrafikBerdasarkanResponden.create.form.jenisKelaminId || ""}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.jenisKelaminId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"rating_responden"}
|
||||
label={"Rating"}
|
||||
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
|
||||
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.ratingId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"kelompokUmur"}
|
||||
label={"Kelompok Umur"}
|
||||
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
|
||||
value={stategrafikBerdasarkanResponden.create.form.kelompokUmurId || ""}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.kelompokUmurId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default RespondenCreate;
|
||||
@@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
||||
|
||||
function Responden() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Responden'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListResponden search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
interface ListRespondenProps {
|
||||
search: string;
|
||||
}
|
||||
|
||||
function ListResponden({ search }: ListRespondenProps) {
|
||||
const state = useProxy(indeksKepuasanState.responden);
|
||||
const router = useRouter();
|
||||
const { data, page, totalPages, loading, load } = state.findMany;
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10)
|
||||
}, [page]);
|
||||
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={730} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Berdasarkan Jenis Kelamin Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/responden/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Tanggal</TableTh>
|
||||
<TableTh>Jenis Kelamin</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data berdasarkan jenis kelamin responden yang tersedia</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Paper bg={colors['white-1']} p="md" h={{ base: 730, md: 650 }}>
|
||||
<JudulList
|
||||
title='List Data Responden'
|
||||
href='/admin/ppid/ikm-desa-darmasaba/responden/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Nama</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Text ta='center' c='dimmed'>Belum ada data responden</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.name}</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.tanggal
|
||||
? new Date(item.tanggal).toLocaleDateString('id-ID')
|
||||
: '-'}</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.jenisKelamin.name}</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Responden;
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export const navBar = [
|
||||
{
|
||||
id: "PPID_8",
|
||||
name: "IKM Desa Darmasaba",
|
||||
path: "/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat"
|
||||
path: "/admin/ppid/ikm-desa-darmasaba/indeks-kepuasan-masyarakat"
|
||||
},
|
||||
|
||||
]
|
||||
@@ -108,7 +108,7 @@ export const navBar = [
|
||||
{
|
||||
id: "Desa_4",
|
||||
name: "Pengumuman",
|
||||
path: "/admin/desa/pengumuman"
|
||||
path: "/admin/desa/pengumuman/list-pengumuman"
|
||||
},
|
||||
{
|
||||
id: "Desa_5",
|
||||
|
||||
@@ -1,44 +1,71 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function beritaFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const kategori = (context.query.kategori as string) || ''; // 🔥 Parameter kategori baru
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter berdasarkan kategori (jika ada)
|
||||
if (kategori) {
|
||||
where.kategoriBerita = {
|
||||
name: {
|
||||
equals: kategori,
|
||||
mode: 'insensitive' // Tidak case-sensitive
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ judul: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
{ content: { contains: search, mode: 'insensitive' } },
|
||||
{ kategoriBerita: { name: { contains: search, mode: 'insensitive' } } }
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.berita.findMany({
|
||||
where: { isActive: true },
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.berita.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
prisma.berita.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch berita with pagination",
|
||||
message: "Berhasil ambil berita dengan pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch berita with pagination",
|
||||
message: "Gagal mengambil data berita",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default beritaFindMany;
|
||||
export default beritaFindMany;
|
||||
@@ -1,30 +1,41 @@
|
||||
import prisma from '@/lib/prisma';
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// find-first.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function beritaFindFirst(context: Context) {
|
||||
const kategori = (context.query.kategori as string) || '';
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
if (kategori) {
|
||||
where.kategoriBerita = {
|
||||
name: { equals: kategori, mode: 'insensitive' }
|
||||
};
|
||||
}
|
||||
|
||||
export default async function beritaFindFirst() {
|
||||
try {
|
||||
const result = await prisma.berita.findFirst({
|
||||
where: {
|
||||
isActive: true, // opsional kalau kamu punya field ini
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc', // ambil yang paling terbaru
|
||||
},
|
||||
const data = await prisma.berita.findFirst({
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Berhasil ambil berita terbaru',
|
||||
data: result,
|
||||
message: "Berhasil ambil berita terbaru",
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[findFirstBerita] Error:', error);
|
||||
} catch (e) {
|
||||
console.error("Error di findFirst:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Gagal ambil berita terbaru',
|
||||
message: "Gagal ambil berita terbaru",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default beritaFindFirst;
|
||||
14
src/app/api/[[...slugs]]/_lib/desa/berita/findManyUI.ts
Normal file
14
src/app/api/[[...slugs]]/_lib/desa/berita/findManyUI.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function findManyUI() {
|
||||
const data = await prisma.berita.findMany({
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import findRecentBerita from "./findRecent";
|
||||
|
||||
const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
|
||||
.get("/find-many", beritaFindMany)
|
||||
.get("/find-many-ui", beritaFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await findBeritaById(new Request(context.request));
|
||||
return response;
|
||||
|
||||
@@ -1,25 +1,57 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function galleryFotoFindMany() {
|
||||
try {
|
||||
const data = await prisma.galleryFoto.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
});
|
||||
async function galleryFotoFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch gallery foto",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch gallery foto",
|
||||
};
|
||||
}
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.galleryFoto.findMany({
|
||||
where,
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.galleryFoto.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil foto dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data foto",
|
||||
};
|
||||
}
|
||||
}
|
||||
export default galleryFotoFindMany
|
||||
|
||||
export default galleryFotoFindMany;
|
||||
@@ -0,0 +1,18 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function galleryFotoFindRecent() {
|
||||
const result = await prisma.galleryFoto.findMany({
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 3, // ambil 4 data terbaru
|
||||
include: {
|
||||
imageGalleryFoto: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import galleryFotoDelete from "./del";
|
||||
import galleryFotoFindMany from "./find-many";
|
||||
import galleryFotoUpdate from "./updt";
|
||||
import galleryFotoFindUnique from "./findUnique";
|
||||
import galleryFotoFindRecent from "./findRecent";
|
||||
|
||||
const GalleryFoto = new Elysia({ prefix: "/gallery/foto", tags: ["Desa/Gallery/Foto"] })
|
||||
.get("/find-many", galleryFotoFindMany)
|
||||
@@ -18,6 +19,7 @@ const GalleryFoto = new Elysia({ prefix: "/gallery/foto", tags: ["Desa/Gallery/F
|
||||
imagesId: t.String(),
|
||||
}),
|
||||
})
|
||||
.get("/find-recent", galleryFotoFindRecent)
|
||||
.delete("/del/:id", galleryFotoDelete)
|
||||
.put("/:id", async (context) => {
|
||||
const response = await galleryFotoUpdate(context);
|
||||
|
||||
@@ -1,22 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function galleryVideoFindMany() {
|
||||
try {
|
||||
const data = await prisma.galleryVideo.findMany({
|
||||
where: { isActive: true },
|
||||
});
|
||||
async function galleryVideoFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch gallery video",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch gallery video",
|
||||
};
|
||||
}
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } }
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.galleryVideo.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.galleryVideo.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil video dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data video",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default galleryVideoFindMany;
|
||||
@@ -9,6 +9,7 @@ import LayananDesa from "./layanan";
|
||||
import Penghargaan from "./penghargaan";
|
||||
import KategoriPotensi from "./potensi/kategori-potensi";
|
||||
import KategoriBerita from "./berita/kategori-berita";
|
||||
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
||||
|
||||
|
||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
@@ -22,5 +23,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
.use(Penghargaan)
|
||||
.use(KategoriPotensi)
|
||||
.use(KategoriBerita)
|
||||
.use(KategoriPengumuman)
|
||||
|
||||
export default Desa;
|
||||
|
||||
@@ -1,23 +1,70 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function pengumumanFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const kategori = (context.query.kategori as string) || ''; // 🔥 Parameter kategori baru
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter berdasarkan kategori (jika ada)
|
||||
if (kategori) {
|
||||
where.CategoryPengumuman = {
|
||||
name: {
|
||||
equals: kategori,
|
||||
mode: 'insensitive' // Tidak case-sensitive
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ judul: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
{ content: { contains: search, mode: 'insensitive' } },
|
||||
{ CategoryPengumuman: { name: { contains: search, mode: 'insensitive' } } }
|
||||
];
|
||||
}
|
||||
|
||||
export default async function pengumumanFindMany() {
|
||||
try {
|
||||
const data = await prisma.pengumuman.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
CategoryPengumuman: true,
|
||||
},
|
||||
});
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.pengumuman.findMany({
|
||||
where,
|
||||
include: {
|
||||
CategoryPengumuman: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.pengumuman.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch pengumuman",
|
||||
message: "Berhasil ambil pengumuman dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many error:", e);
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch pengumuman",
|
||||
message: "Gagal mengambil data pengumuman",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default pengumumanFindMany;
|
||||
@@ -1,19 +1,16 @@
|
||||
import Elysia from "elysia";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { pengumumanCreate } from "./create";
|
||||
import pengumumanFindMany from "./find-many";
|
||||
import { t } from "elysia";
|
||||
import pengumumanCategoryFindMany from "./category";
|
||||
import pengumumanDelete from "./del";
|
||||
import pengumumanFindById from "./find-by-id";
|
||||
import pengumumanUpdate from "./updt";
|
||||
import pengumumanFindMany from "./find-many";
|
||||
import pengumumanFindFirst from "./findFirst";
|
||||
import pengumumanFindRecent from "./findRecent";
|
||||
import pengumumanUpdate from "./updt";
|
||||
|
||||
const Pengumuman = new Elysia({ prefix: "/pengumuman", tags: ["Desa/Pengumuman"] })
|
||||
.get("/category/find-many", pengumumanCategoryFindMany)
|
||||
.get("/find-many", pengumumanFindMany)
|
||||
.get("/:id", pengumumanFindById)
|
||||
.delete("/delete/:id", pengumumanDelete)
|
||||
.delete("/del/:id", pengumumanDelete)
|
||||
.post("/create", pengumumanCreate, {
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
@@ -26,7 +23,6 @@ const Pengumuman = new Elysia({ prefix: "/pengumuman", tags: ["Desa/Pengumuman"]
|
||||
.get("/find-recent", pengumumanFindRecent)
|
||||
.put("/:id", pengumumanUpdate, {
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
judul: t.String(),
|
||||
deskripsi: t.String(),
|
||||
content: t.String(),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function kategoriPengumumanCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.categoryPengumuman.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat kategori pengumuman",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating kategori pengumuman:", error);
|
||||
throw new Error("Gagal membuat kategori pengumuman: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function kategoriPengumumanDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.categoryPengumuman.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete kategori pengumuman",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
async function kategoriPengumumanFindMany() {
|
||||
const data = await prisma.categoryPengumuman.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
pengumumans: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return { data };
|
||||
}
|
||||
|
||||
export default kategoriPengumumanFindMany
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function kategoriPengumumanFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.categoryPengumuman.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get kategori pengumuman",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import kategoriPengumumanCreate from "./create";
|
||||
import kategoriPengumumanDelete from "./del";
|
||||
import kategoriPengumumanFindMany from "./findMany";
|
||||
import kategoriPengumumanFindUnique from "./findUnique";
|
||||
import kategoriPengumumanUpdate from "./updt";
|
||||
|
||||
const KategoriPengumuman = new Elysia({
|
||||
prefix: "/kategoripengumuman",
|
||||
tags: ["Desa / Pengumuman / Kategori Pengumuman"],
|
||||
})
|
||||
|
||||
.post("/create", kategoriPengumumanCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", kategoriPengumumanFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await kategoriPengumumanFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", kategoriPengumumanUpdate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", kategoriPengumumanDelete);
|
||||
|
||||
export default KategoriPengumuman;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function kategoriPengumumanUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.categoryPengumuman.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate kategori pengumuman",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating kategori pengumuman:", error);
|
||||
throw new Error("Gagal mengupdate kategori pengumuman: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -3,37 +3,75 @@ import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = Prisma.PengumumanGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
judul: true;
|
||||
deskripsi: true;
|
||||
content: true;
|
||||
categoryPengumumanId: true;
|
||||
imageId: true;
|
||||
};
|
||||
select: {
|
||||
id: true;
|
||||
judul: true;
|
||||
deskripsi: true;
|
||||
content: true;
|
||||
categoryPengumumanId: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
async function pengumumanUpdate(context: Context) {
|
||||
const body = context.body as FormUpdate;
|
||||
|
||||
await prisma.pengumuman.update({
|
||||
where: { id: body.id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
content: body.content,
|
||||
categoryPengumumanId: body.categoryPengumumanId,
|
||||
},
|
||||
try {
|
||||
const id = context.params?.id as string; // ambil dari URL
|
||||
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||
|
||||
const {
|
||||
judul,
|
||||
deskripsi,
|
||||
content,
|
||||
categoryPengumumanId,
|
||||
} = body;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "ID tidak boleh kosong" }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await prisma.pengumuman.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
CategoryPengumuman: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
if (!existing) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "pengumuman tidak ditemukan" }),
|
||||
{ status: 404, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
const updated = await prisma.pengumuman.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul,
|
||||
deskripsi,
|
||||
content,
|
||||
categoryPengumumanId: categoryPengumumanId || null,
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Success update pengumuman",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
message: "pengumuman berhasil diupdate",
|
||||
data: updated,
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error updating pengumuman:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengupdate pengumuman",
|
||||
}),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default pengumumanUpdate;
|
||||
|
||||
export default pengumumanUpdate;
|
||||
|
||||
@@ -1,12 +1,65 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export const fileStorageFindMany = async (context: Context) => {
|
||||
const category = context.query?.category as string | undefined;
|
||||
|
||||
const data = await prisma.fileStorage.findMany({
|
||||
where: category ? { category } : {},
|
||||
});
|
||||
|
||||
return { data };
|
||||
type WhereClause = {
|
||||
category?: string;
|
||||
isActive?: boolean;
|
||||
OR?: Array<{
|
||||
name?: { contains: string; mode: 'insensitive' };
|
||||
realName?: { contains: string; mode: 'insensitive' };
|
||||
}>;
|
||||
};
|
||||
|
||||
export const fileStorageFindMany = async (context: Context) => {
|
||||
try {
|
||||
// Get query parameters with defaults
|
||||
const page = Math.max(Number(context.query?.page) || 1, 1);
|
||||
const limit = 10; // Fixed at 10 items per page
|
||||
const category = context.query?.category as string | undefined;
|
||||
const search = context.query?.search as string | undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Build where clause with proper TypeScript types
|
||||
const where: WhereClause = { isActive: true };
|
||||
|
||||
if (category) where.category = category;
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ realName: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
// Get paginated data and total count
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.fileStorage.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.fileStorage.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in fileStorageFindMany:', error);
|
||||
return {
|
||||
data: [],
|
||||
meta: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function jenisKelaminRespondenCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.jenisKelaminResponden.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat jenis kelamin responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating jenis kelamin responden:", error);
|
||||
throw new Error("Gagal membuat jenis kelamin responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function jenisKelaminRespondenDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.jenisKelaminResponden.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete jenis kelamin responden",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function jenisKelaminRespondenFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.jenisKelaminResponden.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.jenisKelaminResponden.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil jenis kelamin responden dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data jenis kelamin responden",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default jenisKelaminRespondenFindMany;
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function jenisKelaminRespondenFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.jenisKelaminResponden.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get jenis kelamin responden",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import jenisKelaminRespondenCreate from "./create";
|
||||
import jenisKelaminRespondenDelete from "./del";
|
||||
import jenisKelaminRespondenFindMany from "./findMany";
|
||||
import jenisKelaminRespondenFindUnique from "./findUnique";
|
||||
import jenisKelaminRespondenUpdate from "./updt";
|
||||
|
||||
const JenisKelaminResponden = new Elysia({
|
||||
prefix: "/jeniskelaminresponden",
|
||||
tags: ["PPID / Indeks Kepuasan / Jenis Kelamin Responden"],
|
||||
})
|
||||
|
||||
.post("/create", jenisKelaminRespondenCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", jenisKelaminRespondenFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await jenisKelaminRespondenFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", jenisKelaminRespondenUpdate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", jenisKelaminRespondenDelete);
|
||||
|
||||
export default JenisKelaminResponden;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function jenisKelaminRespondenUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.jenisKelaminResponden.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate jenis kelamin responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating jenis kelamin responden:", error);
|
||||
throw new Error("Gagal mengupdate jenis kelamin responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function pilihanRatingRespondenCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.pilihanRatingResponden.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat pilihan rating responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating pilihan rating responden:", error);
|
||||
throw new Error("Gagal membuat pilihan rating responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function pilihanRatingRespondenDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.pilihanRatingResponden.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete pilihan rating responden",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function pilihanRatingRespondenFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.pilihanRatingResponden.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.pilihanRatingResponden.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil pilihan rating responden dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data pilihan rating responden",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default pilihanRatingRespondenFindMany;
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function pilihanRatingRespondenFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.pilihanRatingResponden.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get pilihan rating responden",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import pilihanRatingRespondenCreate from "./create";
|
||||
import pilihanRatingRespondenDelete from "./del";
|
||||
import pilihanRatingRespondenFindMany from "./findMany";
|
||||
import pilihanRatingRespondenFindUnique from "./findUnique";
|
||||
import pilihanRatingRespondenUpdate from "./updt";
|
||||
|
||||
const PilihanRatingResponden = new Elysia({
|
||||
prefix: "/pilihanratingresponden",
|
||||
tags: ["PPID / Indeks Kepuasan / Pilihan Rating Responden"],
|
||||
})
|
||||
|
||||
.post("/create", pilihanRatingRespondenCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", pilihanRatingRespondenFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await pilihanRatingRespondenFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", pilihanRatingRespondenUpdate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", pilihanRatingRespondenDelete);
|
||||
|
||||
export default PilihanRatingResponden;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function pilihanRatingRespondenUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.pilihanRatingResponden.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate pilihan rating responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating pilihan rating responden:", error);
|
||||
throw new Error("Gagal mengupdate pilihan rating responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
tanggal: string;
|
||||
jenisKelaminId: string;
|
||||
ratingId: string;
|
||||
kelompokUmurId: string;
|
||||
}
|
||||
|
||||
export default async function respondenCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
// Convert the date string to a Date object
|
||||
const tanggal = new Date(body.tanggal);
|
||||
|
||||
// Validate the date
|
||||
if (isNaN(tanggal.getTime())) {
|
||||
throw new Error('Tanggal tidak valid');
|
||||
}
|
||||
|
||||
const result = await prisma.responden.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
tanggal: tanggal, // Use the Date object
|
||||
jenisKelaminId: body.jenisKelaminId,
|
||||
ratingId: body.ratingId,
|
||||
kelompokUmurId: body.kelompokUmurId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating responden:", error);
|
||||
throw new Error("Gagal membuat responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function respondenDelete(context: Context) {
|
||||
const id = context.params?.id as string;
|
||||
|
||||
const responden = await prisma.responden.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
jenisKelamin: true,
|
||||
rating: true,
|
||||
kelompokUmur: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!responden) {
|
||||
return {
|
||||
status: 404,
|
||||
success: false,
|
||||
message: "Responden tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.responden.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete responden",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function respondenFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ tanggal: { contains: search, mode: 'insensitive' } },
|
||||
{ jenisKelamin: { name: { contains: search, mode: 'insensitive' } } },
|
||||
{ rating: { name: { contains: search, mode: 'insensitive' } } },
|
||||
{ kelompokUmur: { name: { contains: search, mode: 'insensitive' } } }
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.responden.findMany({
|
||||
where,
|
||||
include: {
|
||||
jenisKelamin: true,
|
||||
rating: true,
|
||||
kelompokUmur: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.responden.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil responden dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data responden",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default respondenFindMany;
|
||||
@@ -0,0 +1,51 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function respondenFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.responden.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
jenisKelamin: true,
|
||||
rating: true,
|
||||
kelompokUmur: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch responden by ID",
|
||||
data,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil responden: " + (e instanceof Error ? e.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import Elysia from "elysia";
|
||||
import { t } from "elysia";
|
||||
import respondenFindMany from "./findMany";
|
||||
import respondenFindUnique from "./findUnique";
|
||||
import respondenCreate from "./create";
|
||||
import respondenUpdate from "./updt";
|
||||
import respondenDelete from "./del";
|
||||
|
||||
const Responden = new Elysia({ prefix: "/responden", tags: ["Desa/Responden"] })
|
||||
.get("/findMany", respondenFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await respondenFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", respondenCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
tanggal: t.String(),
|
||||
jenisKelaminId: t.String(),
|
||||
ratingId: t.String(),
|
||||
kelompokUmurId: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", respondenDelete)
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await respondenUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
tanggal: t.String(),
|
||||
jenisKelaminId: t.String(),
|
||||
ratingId: t.String(),
|
||||
kelompokUmurId: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export default Responden;
|
||||
@@ -0,0 +1,36 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
tanggal: string;
|
||||
jenisKelaminId: string;
|
||||
ratingId: string;
|
||||
kelompokUmurId: string;
|
||||
}
|
||||
|
||||
export default async function respondenUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.responden.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
tanggal: body.tanggal,
|
||||
jenisKelaminId: body.jenisKelaminId,
|
||||
ratingId: body.ratingId,
|
||||
kelompokUmurId: body.kelompokUmurId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating responden:", error);
|
||||
throw new Error("Gagal mengupdate responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function umurRespondenCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.umurResponden.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat umur responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating umur responden:", error);
|
||||
throw new Error("Gagal membuat umur responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function umurRespondenDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.umurResponden.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete umur responden",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umurRespondenFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.umurResponden.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.umurResponden.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil umur responden dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data umur responden",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umurRespondenFindMany;
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function umurRespondenFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.umurResponden.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get umur responden",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import umurRespondenCreate from "./create";
|
||||
import umurRespondenDelete from "./del";
|
||||
import umurRespondenFindMany from "./findMany";
|
||||
import umurRespondenFindUnique from "./findUnique";
|
||||
import umurRespondenUpdate from "./updt";
|
||||
|
||||
const UmurResponden = new Elysia({
|
||||
prefix: "/umurresponden",
|
||||
tags: ["PPID / Indeks Kepuasan / Umur Responden"],
|
||||
})
|
||||
|
||||
.post("/create", umurRespondenCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", umurRespondenFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await umurRespondenFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", umurRespondenUpdate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", umurRespondenDelete);
|
||||
|
||||
export default UmurResponden;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function umurRespondenUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.umurResponden.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate umur responden",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating umur responden:", error);
|
||||
throw new Error("Gagal mengupdate umur responden: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import SDGSDesa from "./sdgs-desa";
|
||||
import APBDes from "./apbdes";
|
||||
import PrestasiDesa from "./prestasi-desa";
|
||||
import KategoriPrestasi from "./prestasi-desa/kategori-prestasi";
|
||||
import JenisKelaminResponden from "./indeks_kepuasan/jenis-kelamin-responden";
|
||||
import PilihanRatingResponden from "./indeks_kepuasan/pilihan-rating-responden";
|
||||
import UmurResponden from "./indeks_kepuasan/umur-responden";
|
||||
import Responden from "./indeks_kepuasan/responden";
|
||||
|
||||
const LandingPage = new Elysia({
|
||||
prefix: "/api/landingpage",
|
||||
@@ -23,5 +27,9 @@ const LandingPage = new Elysia({
|
||||
.use(APBDes)
|
||||
.use(PrestasiDesa)
|
||||
.use(KategoriPrestasi)
|
||||
.use(JenisKelaminResponden)
|
||||
.use(PilihanRatingResponden)
|
||||
.use(UmurResponden)
|
||||
.use(Responden)
|
||||
|
||||
export default LandingPage
|
||||
|
||||
168
src/app/darmasaba/(pages)/desa/berita/[kategori]/Content.tsx
Normal file
168
src/app/darmasaba/(pages)/desa/berita/[kategori]/Content.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
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(stateDashboardBerita.berita);
|
||||
const featuredState = useProxy(stateDashboardBerita.berita.findFirst);
|
||||
|
||||
const featured = featuredState.data;
|
||||
const paginatedNews = state.findMany.data || [];
|
||||
const totalPages = state.findMany.totalPages || 1;
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
stateDashboardBerita.berita.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' }}>
|
||||
{/* === Berita Utama === */}
|
||||
{featuredState.loading ? (
|
||||
<Center><Skeleton h={400} /></Center>
|
||||
) : featured ? (
|
||||
<Box mb={50}>
|
||||
<Text fz="h2" fw={700} mb="md">Berita 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.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Title order={2} mb="md">{featured.judul}</Title>
|
||||
<Text color="dimmed" lineClamp={3} mb="md">{featured.deskripsi}</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/desa/berita/${kategori}/${featured.id}`)}
|
||||
>
|
||||
Baca Selengkapnya
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
{/* === Daftar Berita === */}
|
||||
<Box mt={50}>
|
||||
<Title order={2} mb="md">Daftar Berita</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 berita 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/desa/berita/${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.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
<Text size="sm" color="dimmed" lineClamp={3} mt="xs">{item.deskripsi}</Text>
|
||||
<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,78 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
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';
|
||||
import BackButton from '../../../layanan/_com/BackButto';
|
||||
|
||||
|
||||
|
||||
function Page() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||
const state = useProxy(stateDashboardBerita.berita)
|
||||
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 dan Pelayanan Administrasi Digital
|
||||
</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?.content || '' }} />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
13
src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
Normal file
13
src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsPanel, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import BackButton from '../../layanan/_com/BackButto';
|
||||
|
||||
@@ -15,15 +14,66 @@ type HeaderSearchProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
function LayoutTabsBerita({
|
||||
children,
|
||||
function LayoutTabsBerita({
|
||||
children,
|
||||
placeholder = "pencarian",
|
||||
searchIcon = <IconSearch size={20} />,
|
||||
value,
|
||||
onChange }: HeaderSearchProps) {
|
||||
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]);
|
||||
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
// 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/desa/berita/${activeTab}?${params.toString()}`);
|
||||
}
|
||||
}, 500); // 500ms debounce delay
|
||||
|
||||
setSearchTimeout(newTimeout);
|
||||
};
|
||||
const tabs = [
|
||||
{
|
||||
label: "Semua",
|
||||
@@ -62,70 +112,68 @@ function LayoutTabsBerita({
|
||||
},
|
||||
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (!value) return;
|
||||
const tab = tabs.find(t => t.value === value);
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
router.push(`/darmasaba/desa/berita/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
};
|
||||
|
||||
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">
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
<Tabs color={colors['blue-button']} variant="pills" defaultValue="semua" value={activeTab} onChange={handleTabChange}>
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']} >
|
||||
{/* 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">
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki 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((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
{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={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={placeholder}
|
||||
leftSection={searchIcon}
|
||||
w="100%"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
|
||||
{children}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'FESTIVAL SENI BUDAYA KAB. BADUNG',
|
||||
image: "/api/img/tari-3.jpg",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'LATIHAN TARI REJANG GIRI PUTRI',
|
||||
image: "/api/img/tari-3.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'LATIHAN TARI REJANG GIRI PUTRI',
|
||||
image: "/api/img/tari-3.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Budaya() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/budaya-1.jpg"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 610 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
FESTIVAL SENI BUDAYA KAB. BADUNG
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
Semeton Darmasaba yang suka menikmati seni seperti baleganjur, gong kebyar, tari, dan lainnya. Nih! ada acara keren di Puspem Badung tepatnya di Balai Budaya Giri Nata Mandala yaitu Festival Seni Budaya dari tanggal 1 November 2023 s.d. 16 November 2023.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/tari-3.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
PELATIHAN TARI WALI
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/tari-3.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
LATIHAN TARI REJANG GIRI PUTRI
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt=""
|
||||
fit="cover"
|
||||
h={282}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Budaya;
|
||||
@@ -1,185 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'PROGRAM KETAHANAN PANGAN PEMERINTAH DESA DARMASABA TAHUN 2023',
|
||||
image: "/api/img/ekonomi-sampingan-3.png",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Sinergitas Pemkab Badung-TNI Wujudkan Kedaulatan Pangan di Subak Aban Darmasaba',
|
||||
image: "/api/img/ekonomi-sampingan.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'ANTUSIASME WARGA DARMASABA MELAKUKAN PEMBUKAAN REKENING BANK BPD BALI ',
|
||||
image: "/api/img/ekonomi-sampingan-2.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Ekonomi() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/ekonomi-utama.png"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 660 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
PROGRAM KETAHANAN PANGAN PEMERINTAH DESA DARMASABA TAHUN 2023
|
||||
</Text>
|
||||
<Text size="md" lineClamp={2}>
|
||||
Pemerintah Desa Darmasaba melalui kegiatan ketahanan pangan ini menjalankan dua kategori yaitu pertanian dan peternakan untuk kategori pertanian telah membuahkan hasil panen pertama pada hari Kamis, 24 Agustus 2023 melakukan panen bawang merah di lokasi ketahanan pangan Br. Taman, Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung. Adapun varietas bawang yang dipanen adalah Bawang Bali Karet (Batu Ijo) pada lahan seluas kurang lebih 7 are.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/ekonomi-sampingan.png"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Sinergitas Pemkab Badung-TNI Wujudkan Kedaulatan Pangan di Subak Aban Darmasaba
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/ekonomi-sampingan-2.png"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={1}>
|
||||
ANTUSIASME WARGA DARMASABA MELAKUKAN PEMBUKAAN REKENING BANK BPD BALI
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Ekonomi;
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import LayoutTabsBerita from './_lib/layoutTabs';
|
||||
// app/desa/berita/BeritaLayoutClient.tsx
|
||||
'use client'
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabsBerita>
|
||||
{children}
|
||||
</LayoutTabsBerita>
|
||||
);
|
||||
}
|
||||
const LayoutTabsBerita = dynamic(
|
||||
() => import('./_lib/layoutTabs'),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default Layout;
|
||||
export default function BeritaLayoutClient({ children }: { children: React.ReactNode }) {
|
||||
return <LayoutTabsBerita>{children}</LayoutTabsBerita>;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user