Compare commits

...

15 Commits

Author SHA1 Message Date
8a275c2a32 Fix Tampilan Data Kesehatan Warga User 2025-08-22 16:30:35 +08:00
8469ebd2e1 Sinkronisasi UI & API Admin - User Submenu Demografi Pekerjaan, Menu Ekonomi 2025-08-22 01:10:18 +08:00
760ba4b6d2 Sinkronisasi Sinkronisasi UI & API Admin - User Submenu Program Kemiskinan 2025-08-22 00:26:58 +08:00
20d4c90e60 Sinkronisasi UI & API Admin - User Submenu Jumlah Pengangguran - Jumlah Penduduk Miskin 2025-08-21 17:15:06 +08:00
fafbb12a08 Sinkronisasi UI & API Admin - User Submenu Pendapatan Asli Desa 2025-08-21 14:50:23 +08:00
01aa0da5cc Fix admin menu Landing page 2025-08-21 10:16:05 +08:00
b580978f8e Fix eror edit admin lowongan kerja 2025-08-20 17:52:10 +08:00
1c01397c0d Sinkronisasi UI & API Admin - User Submenu lowongan kerja lokal, Menu Ekonomi 2025-08-20 17:17:04 +08:00
90a6605efd Sinkronisasi UI & API Admin - User Submenu Pasar Desa, Menu Ekonomi 2025-08-20 17:01:20 +08:00
c22d865283 Sinkronisasi UI & API Admin - User Submenu Tips Keamanan, Menu Keamanan 2025-08-20 14:35:08 +08:00
49067f0218 Add Detail Semua Polsek, Submenu Polsek Terdekat, Menu Keamanan 2025-08-19 17:40:59 +08:00
d79425d529 Sinkronisasi UI & API Menu Keamanan, Admin - User Submenu Keamanan Lingkungan & Polse Terdekat 2025-08-19 11:12:39 +08:00
4491d23bea Sinkronisasi UI & API Admin - User Submenu Penanganan Darurat, User Submenu Kontak Darurat & User Submenu Info Wabah / Penyakit 2025-08-18 20:56:18 +08:00
1e154ced86 Sinkronisasi UI & API Admin - User Submenu Program Kesehatan 2025-08-18 17:14:33 +08:00
bcc51aec12 Sinkronisasi UI & API Admin - User Submenu Data Kesehatan Warga 2025-08-18 15:01:39 +08:00
131 changed files with 6607 additions and 3761 deletions

View File

@@ -223,7 +223,7 @@ model KategoriPrestasiDesa {
model Responden {
id String @id @default(cuid())
name String @unique
tanggal DateTime // misal: 2025-05-01
tanggal String // misal: 2025-05-01
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
jenisKelaminId String
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])
@@ -292,6 +292,9 @@ model PosisiOrganisasiPPID {
pegawai PegawaiPPID[]
strukturOrganisasi StrukturPPID[] // Relasi balik
parentId String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
children PosisiOrganisasiPPID[] @relation("Parent")
}
@@ -1547,7 +1550,7 @@ model DataDemografiPekerjaan {
model DetailDataPengangguran {
id String @id @default(uuid()) @db.Uuid
month String @db.VarChar(20)
year Int
year DateTime
totalUnemployment Int
educatedUnemployment Int
uneducatedUnemployment Int

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -61,13 +62,39 @@ const lowonganKerjaState = proxy({
findMany: {
data: null as
| Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true };
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
if (res.status === 200) {
lowonganKerjaState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
lowonganKerjaState.findMany.page = page;
lowonganKerjaState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
lowonganKerjaState.findMany.data = res.data.data ?? [];
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
lowonganKerjaState.findMany.data = [];
lowonganKerjaState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch lowongan kerja paginated:", err);
lowonganKerjaState.findMany.data = [];
lowonganKerjaState.findMany.totalPages = 1;
} finally {
lowonganKerjaState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,22 +54,47 @@ const pasarDesa = proxy({
},
},
findMany: {
data: null as Array<
Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
data: null as
| Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
};
};
};
};
}>
> | null,
async load() {
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
if (res.status === 200) {
pasarDesa.findMany.data = res.data?.data ?? [];
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
pasarDesa.findMany.loading = true;
pasarDesa.findMany.page = page;
pasarDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
if (categoryId) query.categoryId = categoryId;
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
pasarDesa.findMany.data = res.data.data ?? [];
pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
} else {
pasarDesa.findMany.data = [];
pasarDesa.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch keamanan lingkungan paginated:", err);
pasarDesa.findMany.data = [];
pasarDesa.findMany.totalPages = 1;
} finally {
pasarDesa.findMany.loading = false;
}
},
},
@@ -272,14 +298,41 @@ const kategoriProduk = proxy({
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
if (res.status === 200) {
kategoriProduk.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.KategoriProdukGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search2: "",
load: async (page = 1, limit = 10, search2 = "") => {
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriProduk.findMany.page = page;
kategoriProduk.findMany.search2 = search2;
try {
const query: any = { page, limit };
if (search2) query.search2 = search2;
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriProduk.findMany.data = res.data.data ?? [];
kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kategoriProduk.findMany.data = [];
kategoriProduk.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori produk paginated:", err);
kategoriProduk.findMany.data = [];
kategoriProduk.findMany.totalPages = 1;
} finally {
kategoriProduk.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -11,8 +12,7 @@ const templateForm = z.object({
statistik: z.object({
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
})
}),
});
const defaultForm = {
@@ -21,8 +21,8 @@ const defaultForm = {
ikonUrl: "",
statistik: {
tahun: "",
jumlah: ""
}
jumlah: "",
},
};
const programKemiskinanState = proxy({
@@ -64,12 +64,35 @@ const programKemiskinanState = proxy({
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.programkemiskinan[
"find-many"
].get();
if (res.status === 200) {
programKemiskinanState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
search: "",
load: async (page = 1, limit = 10, search = "") => {
programKemiskinanState.findMany.loading = true; // ✅ Akses langsung via nama path
programKemiskinanState.findMany.page = page;
programKemiskinanState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.programkemiskinan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
programKemiskinanState.findMany.data = res.data.data ?? [];
programKemiskinanState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
programKemiskinanState.findMany.data = [];
programKemiskinanState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch program kemiskinan paginated:", err);
programKemiskinanState.findMany.data = [];
programKemiskinanState.findMany.totalPages = 1;
} finally {
programKemiskinanState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,15 +54,39 @@ const keamananLingkunganState = proxy({
findMany: {
data: null as
| Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"find-many"
].get();
if (res.status === 200) {
keamananLingkunganState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
keamananLingkunganState.findMany.loading = true; // ✅ Akses langsung via nama path
keamananLingkunganState.findMany.page = page;
keamananLingkunganState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.keamananlingkungan["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
keamananLingkunganState.findMany.data = res.data.data ?? [];
keamananLingkunganState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
keamananLingkunganState.findMany.data = [];
keamananLingkunganState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch keamanan lingkungan paginated:", err);
keamananLingkunganState.findMany.data = [];
keamananLingkunganState.findMany.totalPages = 1;
} finally {
keamananLingkunganState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -63,13 +64,41 @@ const polsekTerdekatState = proxy({
findMany: {
data: null as
| Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
include: {
layananPolsek: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
if (res.status === 200) {
polsekTerdekatState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
polsekTerdekatState.findMany.loading = true; // ✅ Akses langsung via nama path
polsekTerdekatState.findMany.page = page;
polsekTerdekatState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get(
{ query }
);
if (res.status === 200 && res.data?.success) {
polsekTerdekatState.findMany.data = res.data.data ?? [];
polsekTerdekatState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
polsekTerdekatState.findMany.data = [];
polsekTerdekatState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch polsek terdekat paginated:", err);
polsekTerdekatState.findMany.data = [];
polsekTerdekatState.findMany.totalPages = 1;
} finally {
polsekTerdekatState.findMany.loading = false;
}
},
},
@@ -237,6 +266,29 @@ const polsekTerdekatState = proxy({
polsekTerdekatState.edit.form = { ...defaultForm };
},
},
findFirst: {
data: null as Prisma.PolsekTerdekatGetPayload<{
include: {
layananPolsek: true;
};
}> | null,
loading: false,
load: async () => { // Changed to arrow function
polsekTerdekatState.findFirst.loading = true;
try {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-first"].get();
if (res.status === 200 && res.data?.success) {
polsekTerdekatState.findFirst.data = res.data.data || null;
} else {
polsekTerdekatState.findFirst.data = null;
}
} catch (err) {
console.error("Gagal fetch polsek terdekat terbaru:", err);
} finally {
polsekTerdekatState.findFirst.loading = false;
}
}
}
});
export default polsekTerdekatState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,15 +54,39 @@ const tipsKeamananState = proxy({
findMany: {
data: null as
| Prisma.MenuTipsKeamananGetPayload<{
include: { image: true };
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.menutipskeamanan[
"find-many"
].get();
if (res.status === 200) {
tipsKeamananState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
tipsKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
tipsKeamananState.findMany.page = page;
tipsKeamananState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.menutipskeamanan["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
tipsKeamananState.findMany.data = res.data.data ?? [];
tipsKeamananState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
tipsKeamananState.findMany.data = [];
tipsKeamananState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch menu tips keamanan paginated:", err);
tipsKeamananState.findMany.data = [];
tipsKeamananState.findMany.totalPages = 1;
} finally {
tipsKeamananState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -20,17 +21,41 @@ const defaultForm = {
const infoWabahPenyakit = proxy({
findMany: {
data: [] as Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
"find-many"
].get();
if (res.status === 200) {
infoWabahPenyakit.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
infoWabahPenyakit.findMany.loading = true; // ✅ Akses langsung via nama path
infoWabahPenyakit.findMany.page = page;
infoWabahPenyakit.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.infowabahpenyakit["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
infoWabahPenyakit.findMany.data = res.data.data ?? [];
infoWabahPenyakit.findMany.totalPages = res.data.totalPages ?? 1;
} else {
infoWabahPenyakit.findMany.data = [];
infoWabahPenyakit.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch info wabah penyakit paginated:", err);
infoWabahPenyakit.findMany.data = [];
infoWabahPenyakit.findMany.totalPages = 1;
} finally {
infoWabahPenyakit.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,204 +6,241 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
})
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
}
const kontakDarurat = proxy({
findMany: {
data: [] as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"find-many"
].get();
if (res.status === 200) {
kontakDarurat.findMany.data = res.data?.data ?? [];
}
},
},
create:{
form: {...defaultForm},
loading: false,
async create() {
const cek = templateForm.safeParse(kontakDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"create"
].post(kontakDarurat.create.form);
if (res.status === 200) {
kontakDarurat.findMany.load();
return toast.success("Kontak Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan kontak darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDarurat.create.loading = false;
}
},
resetForm() {
kontakDarurat.create.form = {...defaultForm};
}
},
findUnique: {
data: null as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
if (res.ok) {
const data = await res.json();
kontakDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
kontakDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDarurat.delete.loading = false;
}
}
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/kontakdarurat/${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,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching kontak darurat:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(kontakDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.edit.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
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(result.message || "Kontak darurat berhasil diupdate");
await kontakDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update kontak darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate kontak darurat");
return false;
} finally {
kontakDarurat.edit.loading = false;
}
},
reset() {
kontakDarurat.edit.id = "";
kontakDarurat.edit.form = { ...defaultForm };
},
},
name: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
export default kontakDarurat
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const kontakDarurat = proxy({
findMany: {
data: null as
| Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
kontakDarurat.findMany.loading = true; // ✅ Akses langsung via nama path
kontakDarurat.findMany.page = page;
kontakDarurat.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
kontakDarurat.findMany.data = res.data.data ?? [];
kontakDarurat.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kontakDarurat.findMany.data = [];
kontakDarurat.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kontak darurat paginated:", err);
kontakDarurat.findMany.data = [];
kontakDarurat.findMany.totalPages = 1;
} finally {
kontakDarurat.findMany.loading = false;
}
},
},
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(kontakDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.kontakdarurat["create"].post(
kontakDarurat.create.form
);
if (res.status === 200) {
kontakDarurat.findMany.load();
return toast.success("Kontak Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan kontak darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDarurat.create.loading = false;
}
},
resetForm() {
kontakDarurat.create.form = { ...defaultForm };
},
},
findUnique: {
data: null as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
if (res.ok) {
const data = await res.json();
kontakDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
kontakDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDarurat.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/kontakdarurat/${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,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching kontak darurat:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(kontakDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.edit.loading = true;
const response = await fetch(
`/api/kesehatan/kontakdarurat/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
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(result.message || "Kontak darurat berhasil diupdate");
await kontakDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update kontak darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat mengupdate kontak darurat"
);
return false;
} finally {
kontakDarurat.edit.loading = false;
}
},
reset() {
kontakDarurat.edit.id = "";
kontakDarurat.edit.form = { ...defaultForm };
},
},
});
export default kontakDarurat;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -17,21 +18,45 @@ const defaultForm = {
}
const penangananDarurat = proxy({
findMany: {
data: [] as Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.penanganandarurat[
"find-many"
].get();
if (res.status === 200) {
penangananDarurat.findMany.data = res.data?.data ?? [];
}
},
findMany: {
data: null as
| Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
penangananDarurat.findMany.loading = true; // ✅ Akses langsung via nama path
penangananDarurat.findMany.page = page;
penangananDarurat.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.penanganandarurat["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
penangananDarurat.findMany.data = res.data.data ?? [];
penangananDarurat.findMany.totalPages = res.data.totalPages ?? 1;
} else {
penangananDarurat.findMany.data = [];
penangananDarurat.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
penangananDarurat.findMany.data = [];
penangananDarurat.findMany.totalPages = 1;
} finally {
penangananDarurat.findMany.loading = false;
}
},
},
create:{
form: {...defaultForm},
loading: false,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -20,17 +21,43 @@ const defaultForm = {
const programKesehatan = proxy({
findMany: {
data: [] as Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.programkesehatan[
"find-many"
].get();
if (res.status === 200) {
programKesehatan.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
programKesehatan.findMany.loading = true; // ✅ Akses langsung via nama path
programKesehatan.findMany.page = page;
programKesehatan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.programkesehatan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
programKesehatan.findMany.data = res.data.data ?? [];
programKesehatan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
programKesehatan.findMany.data = [];
programKesehatan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
programKesehatan.findMany.data = [];
programKesehatan.findMany.totalPages = 1;
} finally {
programKesehatan.findMany.loading = false;
}
},
},
@@ -97,12 +124,15 @@ const programKesehatan = proxy({
try {
programKesehatan.delete.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const response = await fetch(
`/api/kesehatan/programkesehatan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
@@ -156,57 +186,70 @@ const programKesehatan = proxy({
}
} catch (error) {
console.error("Error fetching program kesehatan:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(programKesehatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
const cek = templateForm.safeParse(programKesehatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKesehatan.edit.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
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(result.message || "Program kesehatan berhasil diupdate");
await programKesehatan.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update program kesehatan");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate program kesehatan");
return false;
} finally {
programKesehatan.edit.loading = false;
try {
programKesehatan.edit.loading = true;
const response = await fetch(
`/api/kesehatan/programkesehatan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
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(
result.message || "Program kesehatan berhasil diupdate"
);
await programKesehatan.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update program kesehatan");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat mengupdate program kesehatan"
);
return false;
} finally {
programKesehatan.edit.loading = false;
}
},
reset() {
programKesehatan.edit.id = "";
programKesehatan.edit.form = { ...defaultForm };
programKesehatan.edit.id = "";
programKesehatan.edit.form = { ...defaultForm };
},
},
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -163,13 +164,43 @@ const puskesmasState = proxy({
},
findMany: {
data: null as Prisma.PuskesmasGetPayload<{
include: { image: true; jam: true; kontak: true };
}>[] | null,
async load() {
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get();
if (res.status === 200) {
puskesmasState.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.PuskesmasGetPayload<{
include: {
image: true;
jam: true;
kontak: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
puskesmasState.findMany.loading = true; // ✅ Akses langsung via nama path
puskesmasState.findMany.page = page;
puskesmasState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
puskesmasState.findMany.data = res.data.data ?? [];
puskesmasState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
puskesmasState.findMany.data = [];
puskesmasState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
puskesmasState.findMany.data = [];
puskesmasState.findMany.totalPages = 1;
} finally {
puskesmasState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -50,18 +51,50 @@ const apbdes = proxy({
},
},
findMany: {
data: null as Array<
Prisma.APBDesGetPayload<{
include: {
image: true;
file: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.apbdes["find-many"].get();
if (res.status === 200) {
apbdes.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.APBDesGetPayload<{
include: {
image: true;
file: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
apbdes.findMany.loading = true; // Use the full path to access the property
apbdes.findMany.page = page;
apbdes.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.apbdes[
"findMany"
].get({
query
});
if (res.status === 200 && res.data?.success) {
apbdes.findMany.data = res.data.data || [];
apbdes.findMany.total = res.data.total || 0;
apbdes.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
apbdes.findMany.data = [];
apbdes.findMany.total = 0;
apbdes.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
apbdes.findMany.data = [];
apbdes.findMany.total = 0;
apbdes.findMany.totalPages = 1;
} finally {
apbdes.findMany.loading = false;
}
},
},

View File

@@ -60,16 +60,22 @@ const desaAntikorupsi = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
desaAntikorupsi.findMany.page = page;
desaAntikorupsi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.desaantikorupsi[
"findMany"
].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {
desaAntikorupsi.findMany.data = res.data.data || [];
desaAntikorupsi.findMany.total = res.data.total || 0;
@@ -305,20 +311,25 @@ const kategoriDesaAntiKorupsi = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
kategoriDesaAntiKorupsi.findMany.page = page;
kategoriDesaAntiKorupsi.findMany.search = search;
try {
const res = await ApiFetch.api.landingpage.kategoridak[
"findMany"
].get({
query: { page, limit },
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.kategoridak["findMany"].get({
query,
});
if (res.status === 200 && res.data?.success) {
kategoriDesaAntiKorupsi.findMany.data = res.data.data || [];
kategoriDesaAntiKorupsi.findMany.total = res.data.total || 0;
kategoriDesaAntiKorupsi.findMany.totalPages = res.data.totalPages || 1;
kategoriDesaAntiKorupsi.findMany.totalPages =
res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
kategoriDesaAntiKorupsi.findMany.data = [];
@@ -363,27 +374,30 @@ const kategoriDesaAntiKorupsi = proxy({
try {
kategoriDesaAntiKorupsi.delete.loading = true;
const response = await fetch(
`/api/landingpage/kategoridak/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const response = await fetch(`/api/landingpage/kategoridak/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori desa anti korupsi berhasil dihapus");
toast.success(
result.message || "Kategori desa anti korupsi berhasil dihapus"
);
await kategoriDesaAntiKorupsi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori desa anti korupsi");
toast.error(
result?.message || "Gagal menghapus kategori desa anti korupsi"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori desa anti korupsi");
toast.error(
"Terjadi kesalahan saat menghapus kategori desa anti korupsi"
);
} finally {
kategoriDesaAntiKorupsi.delete.loading = false;
}

View File

@@ -181,7 +181,13 @@ const responden = proxy({
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
body: JSON.stringify({
name: this.form.name,
tanggal: this.form.tanggal,
jenisKelaminId: this.form.jenisKelaminId,
ratingId: this.form.ratingId,
kelompokUmurId: this.form.kelompokUmurId,
}),
});
const result = await response.json();
if (!response.ok || !result?.success) {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -62,12 +63,34 @@ const prestasiDesa = proxy({
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.prestasidesa[
"find-many"
].get();
if (res.status === 200) {
prestasiDesa.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
prestasiDesa.findMany.loading = true; // ✅ Akses langsung via nama path
prestasiDesa.findMany.page = page;
prestasiDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.prestasidesa["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
prestasiDesa.findMany.data = res.data.data ?? [];
prestasiDesa.findMany.totalPages = res.data.totalPages ?? 1;
} else {
prestasiDesa.findMany.data = [];
prestasiDesa.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch prestasi desa paginated:", err);
prestasiDesa.findMany.data = [];
prestasiDesa.findMany.totalPages = 1;
} finally {
prestasiDesa.findMany.loading = false;
}
},
},
@@ -283,12 +306,34 @@ const kategoriPrestasi = proxy({
id: string;
name: string;
}> | null,
async load() {
const res = await ApiFetch.api.landingpage.kategoriprestasi[
"find-many"
].get();
if (res.status === 200) {
kategoriPrestasi.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
kategoriPrestasi.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriPrestasi.findMany.page = page;
kategoriPrestasi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.kategoriprestasi["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriPrestasi.findMany.data = res.data.data ?? [];
kategoriPrestasi.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kategoriPrestasi.findMany.data = [];
kategoriPrestasi.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori prestasi paginated:", err);
kategoriPrestasi.findMany.data = [];
kategoriPrestasi.findMany.totalPages = 1;
} finally {
kategoriPrestasi.findMany.loading = false;
}
},
},

View File

@@ -65,14 +65,19 @@ const programInovasi = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
programInovasi.findMany.loading = true; // Use the full path to access the property
programInovasi.findMany.page = page;
programInovasi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.programinovasi[
"findMany"
].get({
query: { page, limit },
query
});
if (res.status === 200 && res.data?.success) {
@@ -482,14 +487,19 @@ const mediaSosial = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
mediaSosial.findMany.loading = true; // Use the full path to access the property
mediaSosial.findMany.page = page;
try {
mediaSosial.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.mediasosial[
"findMany"
].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

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

View File

@@ -49,35 +49,38 @@ const daftarInformasiPublik = proxy({
},
},
findMany: {
data: null as any[] | null,
data: null as
| Prisma.DaftarInformasiPublikGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
daftarInformasiPublik.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
daftarInformasiPublik.findMany.loading = true; // ✅ Akses langsung via nama path
daftarInformasiPublik.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get({
query: { page, limit },
});
daftarInformasiPublik.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
daftarInformasiPublik.findMany.data = res.data.data || [];
daftarInformasiPublik.findMany.total = res.data.total || 0;
daftarInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
daftarInformasiPublik.findMany.data = res.data.data ?? [];
daftarInformasiPublik.findMany.totalPages = res.data.totalPages ?? 1;
} else {
console.error("Failed to load daftar informasi publik:", res.data?.message);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading daftar informasi publik:", error);
} catch (err) {
console.error("Gagal fetch daftar informasi publik paginated:", err);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
} finally {
daftarInformasiPublik.findMany.loading = false;

View File

@@ -348,18 +348,34 @@ const posisiOrganisasi = proxy({
deskripsi: string | null;
hierarki: number;
}>,
async load() {
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path
posisiOrganisasi.findMany.page = page;
posisiOrganisasi.findMany.search = search;
try {
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[
"find-many"
].get();
if (res.status === 200) {
// The API now returns the id field, so we can use it directly
this.data = res.data?.data ?? [];
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
posisiOrganisasi.findMany.data = res.data.data ?? [];
posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1;
} else {
posisiOrganisasi.findMany.data = [];
posisiOrganisasi.findMany.totalPages = 1;
}
} catch (error) {
console.error("Find many error:", error);
this.data = [];
} catch (err) {
console.error("Gagal fetch posisi organisasi paginated:", err);
posisiOrganisasi.findMany.data = [];
posisiOrganisasi.findMany.totalPages = 1;
} finally {
posisiOrganisasi.findMany.loading = false;
}
},
},
@@ -438,9 +454,9 @@ const pegawai = proxy({
try {
pegawai.create.loading = true;
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
"create"
].post(pegawai.create.form);
const res = await ApiFetch.api.ppid.strukturppid.pegawai["create"].post(
pegawai.create.form
);
if (res.status === 200) {
toast.success("Pegawai berhasil ditambahkan");
await pegawai.findMany.load();
@@ -457,42 +473,55 @@ const pegawai = proxy({
},
// In struktur-organisasi.ts
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
pegawai.findMany.loading = true; // Use the full path to access the property
pegawai.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
"find-many"
].get({
query: { page, limit },
});
findMany: {
data: null as
| Prisma.PegawaiPPIDGetPayload<{
include: {
image: true;
posisi: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
pegawai.findMany.loading = true; // Use the full path to access the property
pegawai.findMany.page = page;
pegawai.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
if (res.status === 200 && res.data?.success) {
pegawai.findMany.data = res.data.data || [];
pegawai.findMany.total = res.data.total || 0;
pegawai.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
pegawai.findMany.data = res.data.data || [];
pegawai.findMany.total = res.data.total || 0;
pegawai.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
}
} catch (error) {
console.error("Error loading pegawai:", error);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
}
},
},
},
findUnique: {
data: null as
| (Prisma.PegawaiGetPayload<{
@@ -521,12 +550,9 @@ findMany: {
if (!id) return toast.warn("ID tidak valid");
try {
pegawai.delete.loading = true;
const res = await fetch(
`/api/ppid/strukturppid/pegawai/del/${id}`,
{
method: "DELETE",
}
);
const res = await fetch(`/api/ppid/strukturppid/pegawai/del/${id}`, {
method: "DELETE",
});
const json = await res.json();
if (res.ok) {
toast.success(json.message ?? "Berhasil hapus pegawai");
@@ -555,15 +581,12 @@ findMany: {
}
try {
const response = await fetch(
`/api/ppid/strukturppid/pegawai/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const response = await fetch(`/api/ppid/strukturppid/pegawai/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@@ -677,7 +700,7 @@ findMany: {
const stateStrukturPPID = proxy({
stateStruktur,
posisiOrganisasi,
pegawai
pegawai,
});
export default stateStrukturPPID;

View File

@@ -143,8 +143,8 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
dataKey="pekerjaan"
type="stacked"
series={[
{ name: 'lakiLaki', color: 'red.6', label: 'Laki - Laki' },
{ name: 'perempuan', color: 'orange.6', label: 'Perempuan' },
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
]}
/>
</Box>

View File

@@ -35,7 +35,7 @@ function EditJumlahPendudukMiskin() {
// Set the ID before submitting
stateJPM.update.id = id;
await stateJPM.update.submit();
router.push('/admin/ekonomi/jumlah-penduduk-miskin-2024-2025')
router.push('/admin/ekonomi/jumlah-penduduk-miskin')
}
return (
<Box>

View File

@@ -32,7 +32,7 @@ function CreateJumlahPendudukMiskin() {
}
}
resetForm();
router.push("/admin/ekonomi/jumlah-penduduk-miskin-2024-2025");
router.push("/admin/ekonomi/jumlah-penduduk-miskin");
}
return (
<Box>

View File

@@ -91,7 +91,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Jumlah Penduduk Miskin'
href='/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/create'
href='/admin/ekonomi/jumlah-penduduk-miskin/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
@@ -108,7 +108,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
<TableTd>{item.year}</TableTd>
<TableTd>{item.totalPoorPopulation}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/${item.id}`)}>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>

View File

@@ -8,29 +8,36 @@ 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 HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
function GrafikBerdasarkanPendidikan() {
const [search, setSearch] = useState("")
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
<ListGrafikBerdasarkanPendidikan />
<HeaderSearch
title='Detail Data Pengangguran Berdasarkan Pendidikan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListGrafikBerdasarkanPendidikan search={search}/>
</Stack>
</Box>
);
}
function ListGrafikBerdasarkanPendidikan() {
function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
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 [search, setSearch] = useState("");
const handleDelete = async () => {
@@ -56,11 +63,11 @@ function ListGrafikBerdasarkanPendidikan() {
const D3 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
const S1 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
setDonutData([
{ name: 'SD', value: SD, color: colors['blue-button'], key: 'SD' },
{ name: 'SMP', value: SMP, color: '#10A85AFF', key: 'SMP' },
{ name: 'SMA', value: SMA, color: '#C07B13FF', key: 'SMA' },
{ name: 'D3', value: D3, color: '#1094A8FF', key: 'D3' },
{ name: 'S1', value: S1, color: '#A83610FF', key: 'S1' },
{ name: 'SD', value: SD, color: '#4b6Ef5', key: 'SD' },
{ name: 'SMP', value: SMP, color: '#14b885', key: 'SMP' },
{ name: 'SMA', value: SMA, color: '#E6A03B', key: 'SMA' },
{ name: 'D3', value: D3, color: '#DB524D', key: 'D3' },
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
]);
}
}, [stategrafik.findMany.data])
@@ -88,13 +95,9 @@ function ListGrafikBerdasarkanPendidikan() {
<Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={"md"}>
<JudulListTab
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
<JudulList
title='List Grafik Pengangguran Berdasarkan Pendidikan'
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>

View File

@@ -8,29 +8,37 @@ 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 HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
const [search, setSearch] = useState("")
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur />
<HeaderSearch
title='Detail Data Pengangguran'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
</Stack>
</Box>
);
}
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur() {
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}) {
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
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 [search, setSearch] = useState("");
const handleDelete = async () => {
@@ -85,13 +93,9 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur() {
<Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={"md"}>
<JudulListTab
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
<JudulList
title='List Pengangguran Berdasarkan Usia Kerja'
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>

View File

@@ -25,7 +25,7 @@ function DetailJumlahPengangguran() {
stateDetail.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran")
router.push("/admin/ekonomi/jumlah-pengangguran")
}
}
@@ -86,7 +86,7 @@ function DetailJumlahPengangguran() {
color={"red"}>
<IconX size={20} />
</Button>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
<IconEdit size={20} />
</Button>
</Flex>

View File

@@ -90,7 +90,7 @@ function CreateJumlahPengangguran() {
/>
<TextInput
label="Tahun"
type="number"
type="date"
value={stateDetail.create.form.year}
onChange={(e) =>
(stateDetail.create.form.year = Number(e.currentTarget.value))

View File

@@ -8,21 +8,29 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import { BarChart } from '@mantine/charts';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
import JudulListTab from '../../_com/judulListTab';
function DetailDataPengangguran() {
const [search, setSearch] = useState("")
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Detail Data Pengangguran</Title>
<ListDetailDataPengangguran />
<HeaderSearch
title='Detail Data Pengangguran'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListDetailDataPengangguran search={search} />
</Stack>
</Box>
);
}
function ListDetailDataPengangguran() {
function ListDetailDataPengangguran({search}: {search: string}) {
type DetailDataPengangguran = {
id: string;
@@ -37,7 +45,6 @@ function ListDetailDataPengangguran() {
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
const router = useRouter();
const [search, setSearch] = useState("")
useShallowEffect(() => {
setMounted(true)
@@ -78,13 +85,9 @@ function ListDetailDataPengangguran() {
<Box>
<Stack gap={"md"}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Detail Data Pengangguran'
href='/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/create'
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
href='/admin/ekonomi/jumlah-pengangguran/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
@@ -102,7 +105,7 @@ function ListDetailDataPengangguran() {
<TableTd>{item.educatedUnemployment}</TableTd>
<TableTd>{item.uneducatedUnemployment}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${item.id}`)}>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>

View File

@@ -55,17 +55,17 @@ function EditLowonganKerja() {
const handleSubmit = async () => {
try {
lowonganKerjaState.update.form = {
...lowonganKerjaState.update.form,
posisi: formData.posisi,
namaPerusahaan: formData.namaPerusahaan,
lokasi: formData.lokasi,
tipePekerjaan: formData.tipePekerjaan,
gaji: formData.gaji,
deskripsi: formData.deskripsi,
kualifikasi: formData.kualifikasi,
}
await lowonganState.update.update()
// Set the ID for the update
lowonganState.update.id = params?.id as string;
// Update the form state
lowonganState.update.form = {
...lowonganState.update.form,
...formData
};
// Call the update function
await lowonganState.update.update();
toast.success("Lowongan kerja berhasil diperbarui!");
router.push("/admin/ekonomi/lowongan-kerja-lokal");
} catch (error) {
@@ -88,7 +88,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.posisi}
onChange={(val) => {
formData.posisi = val.target.value;
setFormData(prev => ({ ...prev, posisi: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
placeholder='Masukkan posisi'
@@ -96,7 +96,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.namaPerusahaan}
onChange={(val) => {
formData.namaPerusahaan = val.target.value;
setFormData(prev => ({ ...prev, namaPerusahaan: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
placeholder='Masukkan nama perusahaan'
@@ -104,7 +104,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.lokasi}
onChange={(val) => {
formData.lokasi = val.target.value;
setFormData(prev => ({ ...prev, lokasi: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
placeholder='Masukkan lokasi'
@@ -112,7 +112,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.tipePekerjaan}
onChange={(val) => {
formData.tipePekerjaan = val.target.value;
setFormData(prev => ({ ...prev, tipePekerjaan: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
placeholder='Masukkan tipe pekerjaan'
@@ -120,7 +120,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.gaji}
onChange={(val) => {
formData.gaji = val.target.value;
setFormData(prev => ({ ...prev, gaji: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
placeholder='Masukkan gaji'
@@ -130,7 +130,7 @@ function EditLowonganKerja() {
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
formData.deskripsi = val;
setFormData(prev => ({ ...prev, deskripsi: val }));
}}
/>
</Box>
@@ -139,7 +139,7 @@ function EditLowonganKerja() {
<EditEditor
value={formData.kualifikasi}
onChange={(val) => {
formData.kualifikasi = val;
setFormData(prev => ({ ...prev, kualifikasi: val }));
}}
/>
</Box>

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,20 +30,21 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
const lowonganState = useProxy(lowonganKerjaState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = lowonganState.findMany
useShallowEffect(() => {
lowonganState.findMany.load();
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (lowonganState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.posisi.toLowerCase().includes(keyword) ||
item.namaPerusahaan.toLowerCase().includes(keyword) ||
item.lokasi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!lowonganState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -60,18 +61,24 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Bekerja Sebagai</TableTh>
<TableTh>Nama Usaha</TableTh>
<TableTh>Alamat Usaha</TableTh>
<TableTh>Pekerjaan</TableTh>
<TableTh>Nama Perusahaan</TableTh>
<TableTh>Lokasi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.posisi}</TableTd>
<TableTd>{item.namaPerusahaan}</TableTd>
<TableTd>{item.lokasi}</TableTd>
<TableTd>
<Text fz={"md"}>{item.posisi}</Text>
</TableTd>
<TableTd>
<Text fz={"md"}>{item.namaPerusahaan}</Text>
</TableTd>
<TableTd>
<Text fz={"md"}>{item.lokasi}</Text>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
<IconDeviceImac size={20} />
@@ -82,6 +89,14 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -13,31 +13,39 @@ import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
function PasarDesa() {
const [search, setSearch] = useState("")
const [search2, setSearch2] = useState("")
return (
<Box>
<HeaderSearch
title='Kategori Produk'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
value={search2}
onChange={(e) => setSearch2(e.currentTarget.value)}
/>
<ListPasarDesa search={search} />
<ListPasarDesa search2={search2} />
</Box>
);
}
function ListPasarDesa({ search }: { search: string }) {
function ListPasarDesa({ search2 }: { search2: string }) {
const statePasar = useProxy(pasarDesaState.kategoriProduk)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
// const params = useParams()
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = statePasar.findMany
useShallowEffect(() => {
statePasar.findMany.load()
}, [])
load(page, 10, search2)
}, [page, search2])
const handleHapus = () => {
if (selectedId) {
@@ -47,14 +55,9 @@ function ListPasarDesa({ search }: { search: string }) {
}
}
const filteredData = (statePasar.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!statePasar.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -99,6 +102,14 @@ function ListPasarDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
@@ -30,21 +30,21 @@ function ListPasarDesa({ search }: { search: string }) {
const statePasar = useProxy(pasarDesaState.pasarDesa)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = statePasar.findMany
useShallowEffect(() => {
statePasar.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (statePasar.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.harga.toString().toLowerCase().includes(keyword) ||
item.rating.toString().toLowerCase().includes(keyword) ||
item.alamatUsaha.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!statePasar.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -86,6 +86,14 @@ function ListPasarDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
);
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -23,7 +23,7 @@ function ProgramKemiskinan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListProgramKemiskinan search={search}/>
<ListProgramKemiskinan search={search} />
</Box>
);
}
@@ -34,14 +34,22 @@ function ListProgramKemiskinan({ search }: { search: string }) {
const [lineChart, setLineChart] = useState<any[]>([]);
const [mounted, setMounted] = useState(false);
const {
data,
page,
totalPages,
loading,
load,
} = programState.findMany;
useShallowEffect(() => {
setMounted(true)
programState.findMany.load()
load(page, 10, search)
}, [])
useEffect(() => {
if (programState.findMany.data) {
const chartData = programState.findMany.data
if (data) {
const chartData = data
.filter(item => item.statistik)
.map(item => ({
tahun: item.statistik?.tahun,
@@ -52,18 +60,11 @@ function ListProgramKemiskinan({ search }: { search: string }) {
setLineChart(chartData);
}
}, [programState.findMany.data])
}, [data])
const filteredData = (programState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.statistik?.tahun.toString().includes(keyword)
);
});
const filteredData = data || []
if (!programState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -112,7 +113,7 @@ function ListProgramKemiskinan({ search }: { search: string }) {
<Box >
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
{mounted && lineChart.length > 0 ? (<Box style={{ width: '100%', height: 'auto', }}>
<Box w={"100%"} style={{overflowX: 'auto'}}>
<Box w={"100%"} style={{ overflowX: 'auto' }}>
<LineChart
width={820}
height={300}
@@ -143,6 +144,14 @@ function ListProgramKemiskinan({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
</Box>
);

View File

@@ -2,15 +2,16 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Stack, Text, 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';
import grafikSektorUnggulan from '../../../_state/ekonomi/sektor-unggulan-desa';
import CreateEditor from '../../../_com/createEditor';
function CreateSektorUnggulanDesa() {
const stateGrafik= useProxy(grafikSektorUnggulan);
const stateGrafik = useProxy(grafikSektorUnggulan);
const [chartData, setChartData] = useState<any[]>([]);
const router = useRouter()
@@ -54,15 +55,15 @@ function CreateSektorUnggulanDesa() {
stateGrafik.create.form.name = val.currentTarget.value;
}}
/>
<TextInput
label="Deskripsi Sektor Unggulan"
type="text"
value={stateGrafik.create.form.description}
placeholder="Masukkan deskripsi sektor unggulan"
onChange={(val) => {
stateGrafik.create.form.description = val.currentTarget.value;
}}
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Sektor Ungggulan</Text>
<CreateEditor
value={stateGrafik.create.form.description}
onChange={(val) => {
stateGrafik.create.form.description = val;
}}
/>
</Box>
<TextInput
label="Jumlah"
type="number"

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,7 +30,7 @@ function SektorUnggulanDesa() {
function ListSektorUnggulanDesa({ search }: { search: string }) {
const router = useRouter();
const state = useProxy(grafikSektorUnggulan);
const [chartData, setChartData] = useState<{id: string; name: string; description: string | null; value: number | null}[]>([]);
const [chartData, setChartData] = useState<{ id: string; name: string; description: string | null; value: number | null }[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
@@ -61,38 +61,41 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
}, [state.findMany.data]);
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Sektor Unggulan Desa'
href='/admin/ekonomi/sektor-unggulan-desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Sektor Unggulan</TableTh>
<TableTh>Deskripsi Sektor Unggulan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.description}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
<Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Sektor Unggulan Desa'
href='/admin/ekonomi/sektor-unggulan-desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Sektor Unggulan</TableTh>
<TableTh>Deskripsi Sektor Unggulan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text truncate={"end"} fz={'sm'} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '' }}></Text>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
{/* Chart */}
{!mounted && !chartData ? (
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
@@ -115,6 +118,7 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
</Paper>
</Box>
)}
</Stack>
</Box>
);
}

View File

@@ -54,6 +54,10 @@ function DetailKeamananLingkungan() {
{keamananState.findUnique.data ? (
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 490}} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
@@ -62,10 +66,6 @@ function DetailKeamananLingkungan() {
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,21 @@ function ListKeamananLingkungan({ search }: { search: string }) {
const keamananState = useProxy(keamananLingkunganState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = keamananState.findMany;
useShallowEffect(() => {
keamananState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (keamananState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!keamananState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -67,9 +69,15 @@ function ListKeamananLingkungan({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={180}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
@@ -81,6 +89,14 @@ function ListKeamananLingkungan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
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';
@@ -21,7 +21,7 @@ function PolsekTerdekat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPolsekTerdekat search={search}/>
<ListPolsekTerdekat search={search} />
</Box>
);
}
@@ -30,20 +30,21 @@ function ListPolsekTerdekat({ search }: { search: string }) {
const polsekState = useProxy(polsekTerdekat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = polsekState.findMany;
useShallowEffect(() => {
polsekState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (polsekState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.jarakKeDesa.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!polsekState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -64,24 +65,44 @@ function ListPolsekTerdekat({ search }: { search: string }) {
<TableTh>Jarak Polsek</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.nama}</TableTd>
<TableTd>{item.jarakKeDesa}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Box w={180}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.nama}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={180}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.jarakKeDesa}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.alamat}</Text>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,21 @@ function ListTipsKeamanan({ search }: { search: string }) {
const stateKeamanan = useProxy(tipsKeamananState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = stateKeamanan.findMany
useShallowEffect(() => {
stateKeamanan.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (stateKeamanan.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.judul.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!stateKeamanan.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -67,9 +69,15 @@ function ListTipsKeamanan({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.judul}</TableTd>
<TableTd>
<Text fz={"xs"} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
<Box w={150}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.judul}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
@@ -81,6 +89,14 @@ function ListTipsKeamanan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -22,7 +22,7 @@ function ArtikelKesehatan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListArtikelKesehatan search={search}/>
<ListArtikelKesehatan search={search} />
</Box>
);
}
@@ -46,45 +46,53 @@ function ListArtikelKesehatan({ search }: { search: string }) {
if (!stateArtikelKesehatan.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500}/>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Artikel Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Content</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.title}</TableTd>
<TableTd>{item.content}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Artikel Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Content</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.title}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={200}>
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.content}</Text>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}

View File

@@ -51,7 +51,7 @@ function DetailFasilitasKesehatan() {
<GridCol span={12}>
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
</GridCol>
<GridCol span={12}>
{/* <GridCol span={12}>
<Flex gap={"xs"}>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/dokter-tenaga-medis`)}>
Tambah Dokter
@@ -60,7 +60,7 @@ function DetailFasilitasKesehatan() {
Tambah Layanan
</Button>
</Flex>
</GridCol>
</GridCol> */}
</Grid>
{stateFasilitasKesehatan.findUnique.data ? (
<Paper bg={colors['BG-trans']} p={'md'}>

View File

@@ -1,22 +1,22 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Grid, GridCol, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconList, IconSearch } from '@tabler/icons-react';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { useState } from 'react';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
function FasilitasKesehatan() {
const [search, setSearch] = useState("");
const router = useRouter();
// const router = useRouter();
return (
<Box>
<Grid>
{/* <Grid>
<GridCol span={12}>
<HeaderSearch
title='Fasilitas Kesehatan'
@@ -36,7 +36,14 @@ function FasilitasKesehatan() {
</Button>
</Flex>
</GridCol>
</Grid>
</Grid> */}
<HeaderSearch
title='Fasilitas Kesehatan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListFasilitasKesehatan search={search} />
</Box>
);

View File

@@ -1,11 +1,112 @@
import React from 'react';
'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 { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function Page() {
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { useState } from 'react';
function TarifLayanan() {
const [search, setSearch] = useState("");
const router = useRouter();
return (
<div>
Page
</div>
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<HeaderSearch
title='Dokter dan Tenaga Medis'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListTarifLayanan search={search} />
</Box>
);
}
export default Page;
function ListTarifLayanan({ search }: { search: string }) {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.dokter)
const router = useRouter();
const {
data,
loading,
load,
page,
totalPages
} = stateFasilitasKesehatan.findMany
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = data || []
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href={`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/create`}
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Jam Operasional</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.specialist}</TableTd>
<TableTd>
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}
export default TarifLayanan;

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function InfoWabahPenyakit() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListInfoWabahPenyakit search={search}/>
<ListInfoWabahPenyakit search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = infoWabahPenyakitState.findMany;
useShallowEffect(() => {
infoWabahPenyakitState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (infoWabahPenyakitState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsiSingkat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!infoWabahPenyakitState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -51,49 +53,60 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Info Wabah Penyakit'
href='/admin/kesehatan/info-wabah-penyakit/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi Singkat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Info Wabah Penyakit'
href='/admin/kesehatan/info-wabah-penyakit/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi Singkat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function KontakDarurat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListKontakDarurat search={search}/>
<ListKontakDarurat search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListKontakDarurat({ search }: { search: string }) {
const kontakDaruratState = useProxy(kontakDarurat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = kontakDaruratState.findMany;
useShallowEffect(() => {
kontakDaruratState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (kontakDaruratState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!kontakDaruratState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -77,7 +79,9 @@ function ListKontakDarurat({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={100}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
@@ -94,6 +98,15 @@ function ListKontakDarurat({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function PenangananDarurat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPenangananDarurat search={search}/>
<ListPenangananDarurat search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListPenangananDarurat({ search }: { search: string }) {
const penangananDaruratState = useProxy(penangananDarurat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = penangananDaruratState.findMany;
useShallowEffect(() => {
penangananDaruratState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (penangananDaruratState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!penangananDaruratState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -52,48 +54,59 @@ function ListPenangananDarurat({ search }: { search: string }) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Penanganan Darurat'
href='/admin/kesehatan/penanganan-darurat/create'
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Penanganan Darurat'
href='/admin/kesehatan/penanganan-darurat/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>
<Box w={100}>
<Text lineClamp={1} truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function ProgramKesehatan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListProgramKesehatan search={search}/>
<ListProgramKesehatan search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListProgramKesehatan({ search }: { search: string }) {
const programKesehatanState = useProxy(programKesehatan)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = programKesehatanState.findMany;
useShallowEffect(() => {
programKesehatanState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (programKesehatanState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsiSingkat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!programKesehatanState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -77,7 +79,9 @@ function ListProgramKesehatan({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
<Box w={100}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
@@ -94,6 +98,15 @@ function ListProgramKesehatan({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -21,7 +21,7 @@ function Puskesmas() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPuskesmas search={search}/>
<ListPuskesmas search={search} />
</Box>
);
}
@@ -30,64 +30,75 @@ function ListPuskesmas({ search }: { search: string }) {
const statePuskesmas = useProxy(puskesmasState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = statePuskesmas.findMany;
useShallowEffect(() => {
statePuskesmas.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (statePuskesmas.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!statePuskesmas.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500}/>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Puskesmas'
href='/admin/kesehatan/puskesmas/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Nama Puskesmas</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Image w={100} src={item.image.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Puskesmas'
href='/admin/kesehatan/puskesmas/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Nama Puskesmas</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Image w={100} src={item.image.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { ActionIcon, Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconFile, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,22 @@ function APBDes() {
function ListAPBDes({ search }: { search: string }) {
const listState = useProxy(apbdes)
const router = useRouter();
useEffect(() => {
listState.findMany.load()
}, [])
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.jumlah.toLowerCase().includes(keyword)
)
});
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany
if (!listState.findMany.data) {
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = data || []
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -88,7 +91,7 @@ function ListAPBDes({ search }: { search: string }) {
rel="noopener noreferrer"
variant='transparent'
>
<IconFile size={25} color={colors['blue-button']}/>
<IconFile size={25} color={colors['blue-button']} />
</ActionIcon>
) : (
<Text>Tidak ada dokumen tersedia</Text>
@@ -106,6 +109,14 @@ function ListAPBDes({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
)
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -50,19 +50,11 @@ function ListKategoriKegiatan({ search }: { search: string }) {
}
}
useEffect(() => {
load(page, 10)
}, [page])
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'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 { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -38,22 +38,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
load,
} = listState.findMany;
useEffect(() => {
load(page, 10);
}, [page]);
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword) ||
item.kategori?.name?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => b.createdAt - a.createdAt);
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -42,18 +42,21 @@ function ListKategoriPrestasi({ search }: { search: string }) {
}
}
const {
data,
page,
totalPages,
loading,
load,
} = stateKategori.findMany
useShallowEffect(() => {
stateKategori.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (stateKategori.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!stateKategori.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -100,6 +103,14 @@ function ListKategoriPrestasi({ search }: { search: string }) {
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,7 +1,7 @@
/* 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 { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -30,20 +30,22 @@ function ListPrestasiDesa() {
function ListPrestasi({ search }: { search: string }) {
const listState = useProxy(prestasiState.prestasiDesa)
const router = useRouter();
const{
data,
page,
totalPages,
loading,
load,
} = listState.findMany
useEffect(() => {
listState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.kategori?.name?.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!listState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -95,6 +97,14 @@ function ListPrestasi({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
)
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -38,20 +38,11 @@ function ListMediaSosial({ search }: { search: string }) {
load,
} = stateMediaSosial.findMany;
useEffect(() => {
load(page, 10)
}, [page])
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.iconUrl?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -55,7 +55,10 @@ function DetailProgramInovasi() {
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.description}</Text>
<Text
fz={"lg"}
>{stateProgramInovasi.findUnique.data?.description}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Link</Text>
@@ -63,6 +66,12 @@ function DetailProgramInovasi() {
href={stateProgramInovasi.findUnique.data?.link || "#"}
target="_blank"
rel="noopener noreferrer"
style={{
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
overflowWrap: 'break-word',
width: '100%'
}}
>
{stateProgramInovasi.findUnique.data?.link || "Tidak ada link"}
</a>

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'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 { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -38,21 +38,11 @@ function ListProgramInovasi({ search }: { search: string }) {
load,
} = stateProgramInovasi.findMany;
useEffect(() => {
load(page, 10);
}, [page]);
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.description?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
if (loading || !data) {
return (

View File

@@ -1,14 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */
'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 { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
import JudulList from '../../_com/judulList';
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
function SdgsDesa() {
@@ -39,20 +39,11 @@ function ListSdgsDesa({ search }: { search: string }) {
load,
} = listState.findMany;
useEffect(() => {
load(page, 10)
}, [])
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.jumlah?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -106,8 +106,16 @@ function ListDaftarInformasi({ search }: { search: string }) {
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>{item.jenisInformasi}</TableTd>
<TableTd style={{ wordWrap: 'break-word' }} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>
<Box w={200}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.jenisInformasi}</Text>
</Box>
</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>
<Box w={200}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></Text>
</Box>
</TableTd>
<TableTd style={{ textAlign: 'center' }}>
<Button bg={"green"} onClick={() => router.push(`/admin/ppid/daftar-informasi-publik-desa-darmasaba/${item.id}`)}>
<IconDeviceImacCog size={25} />

View File

@@ -41,15 +41,11 @@ function EditResponden() {
try {
const data = await state.update.load(id);
if (data) {
const formattedDate = data.tanggal && !isNaN(new Date(data.tanggal).getTime())
? new Date(data.tanggal).toISOString().split('T')[0]
: '';
// ⬇️ FIX PENTING: tambahkan ini
state.update.id = id;
state.update.form = {
name: data.name,
tanggal: formattedDate,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
@@ -76,6 +72,7 @@ function EditResponden() {
const handleSubmit = async () => {
state.update.id = id;
state.update.form = { ...formData }; // <-- sinkronisasi manual
await state.update.submit();
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
}
@@ -103,12 +100,15 @@ function EditResponden() {
}}
/>
<TextInput
label="Tanggal"
type="date"
value={formData.tanggal}
placeholder='Pilih tanggal'
value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
onChange={(e) => {
const selectedDate = e.currentTarget.value;
setFormData({
...formData,
tanggal: e.currentTarget.value, // ✅ sudah format YYYY-MM-DD
tanggal: selectedDate,
});
}}
/>

View File

@@ -1,13 +1,12 @@
'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 { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } 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() {
@@ -60,10 +59,7 @@ function ListResponden({ search }: ListRespondenProps) {
<Box py={10}>
<Paper p="md">
<Stack>
<JudulList
title='List Data Berdasarkan Jenis Kelamin Responden'
href='/admin/ppid/ikm-desa-darmasaba/responden/create'
/>
<Title>Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
@@ -86,10 +82,7 @@ function ListResponden({ search }: ListRespondenProps) {
<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'
/>
<Title mb={10} order={3}>List Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>

View File

@@ -4,7 +4,7 @@ import colors from '@/con/colors';
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, ThemeIcon } from '@mantine/core';
import { IconCheck, IconDeviceImacCog, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -39,22 +39,10 @@ function ListPegawaiPPID({ search }: { search: string }) {
} = stateOrganisasi.findMany;
useEffect(() => {
load(page, 10);
}, [page]);
load(page, 10, search);
}, [page, search]);
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.namaLengkap?.toLowerCase().includes(keyword) ||
item.gelarAkademik?.toLowerCase().includes(keyword) ||
item.telepon?.toLowerCase().includes(keyword) ||
item.posisi?.nama?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -1,7 +1,7 @@
/* 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 { Box, Button, Center, Pagination, 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';
@@ -33,9 +33,17 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const {
data,
page,
totalPages,
loading,
load,
} = stateOrganisasi.findMany;
useEffect(() => {
stateOrganisasi.findMany.load()
}, [])
load(page, 10, search);
}, [page, search]);
const handleHapus = async () => {
if (selectedId) {
@@ -45,17 +53,9 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
}
}
const filteredData = (stateOrganisasi.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword) ||
item.hierarki?.toString().toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.hierarki - b.hierarki);
const filteredData = data || []
if (!stateOrganisasi.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -120,6 +120,14 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,21 +1,55 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function lowonganKerjaFindMany() {
try {
const data = await prisma.lowonganPekerjaan.findMany({
where: { isActive: true },
});
async function lowonganKerjaFindMany(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 lowongan kerja",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch lowongan kerja",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ posisi: { contains: search, mode: 'insensitive' } },
{ namaPerusahaan: { contains: search, mode: 'insensitive' } },
{ lokasi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.lowonganPekerjaan.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.lowonganPekerjaan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil lowongan kerja 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 lowongan kerja",
};
}
}
export default lowonganKerjaFindMany;

View File

@@ -1,26 +1,84 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pasarDesaFindMany() {
const data = await prisma.pasarDesa.findMany({
where: {
isActive: true, // Opsional filter
},
orderBy: {
createdAt: "desc",
},
include: {
image: true,
KategoriToPasar: {
async function pasarDesaFindMany(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 categoryId = context.query.categoryId as string | undefined;
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan filter kategori (jika ada)
if (categoryId) {
where.KategoriToPasar = {
some: {
kategoriId: categoryId
}
};
}
// Tambahkan pencarian (jika ada)
if (search) {
where.AND = where.AND || [];
where.AND.push({
OR: [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamatUsaha: { contains: search, mode: 'insensitive' } },
{
KategoriToPasar: {
some: {
kategori: {
nama: { contains: search, mode: 'insensitive' }
}
}
}
}
]
});
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.pasarDesa.findMany({
where,
include: {
kategori: true,
image: true,
KategoriToPasar: {
include: {
kategori: true
}
}
},
},
},
});
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pasarDesa.count({ where }),
]);
return {
success: true,
message: "Berhasil mengambil semua data pasar desa",
data,
};
return {
success: true,
message: "Berhasil ambil pasar desa 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 pasar desa",
};
}
}
export default pasarDesaFindMany;

View File

@@ -1,15 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriProdukFindMany() {
const data = await prisma.kategoriProduk.findMany();
return {
success: true,
data: data.map((item: any) => {
return {
id: item.id,
nama: item.nama,
}
}),
async function kategoriProdukFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search2 = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search2) {
where.OR = [
{ nama: { contains: search2, mode: 'insensitive' } },
{KategoriToPasar : {
some: {
kategori: {
nama: { contains: search2, mode: 'insensitive' }
}
}
}}
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.kategoriProduk.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.kategoriProduk.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil kategori produk 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 kategori produk",
};
}
}
export default kategoriProdukFindMany;

View File

@@ -1,18 +1,53 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function programKemiskinanFindMany() {
const data = await prisma.programKemiskinan.findMany({
include: {
statistik: true, // ikut sertakan relasinya
},
orderBy: {
createdAt: "desc",
},
});
return {
success: true,
message: "Success get all program layanan",
data,
};
export default async function programKemiskinanFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.programKemiskinan.findMany({
where,
include: {
statistik: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.programKemiskinan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil program kemiskinan 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 program kemiskinan",
};
}
}

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function keamananLingkunganFindMany() {
try {
const data = await prisma.keamananLingkungan.findMany({
where: { isActive: true },
include: {
image: true,
},
});
async function keamananLingkunganFindMany(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 keamanan lingkungan",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch keamanan lingkungan",
};
}
}
// 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.keamananLingkungan.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.keamananLingkungan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil keamanan lingkungan 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 keamanan lingkungan",
};
}
}
export default keamananLingkunganFindMany;

View File

@@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// find-first.ts
import prisma from "@/lib/prisma";
async function polsekTerdekatFindFirst() {
const where: any = { isActive: true };
try {
const data = await prisma.polsekTerdekat.findFirst({
where,
include: {
layananPolsek: true,
},
orderBy: { createdAt: 'desc' },
});
return {
success: true,
message: "Berhasil ambil polsek terdekat terbaru",
data,
};
} catch (e) {
console.error("Error di findFirst:", e);
return {
success: false,
message: "Gagal ambil polsek terdekat terbaru",
};
}
}
export default polsekTerdekatFindFirst;

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function polsekTerdekatFindMany() {
try {
const data = await prisma.polsekTerdekat.findMany({
where: { isActive: true },
include: {
layananPolsek: true,
},
});
async function polsekTerdekatFindMany(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 polsek terdekat",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch polsek terdekat",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamat: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.polsekTerdekat.findMany({
where,
include: {
layananPolsek: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.polsekTerdekat.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil polsek terdekat 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 polsek terdekat",
};
}
}
export default polsekTerdekatFindMany;

View File

@@ -4,6 +4,7 @@ import polsekTerdekatDelete from "./del";
import polsekTerdekatFindMany from "./findMany";
import polsekTerdekatFindUnique from "./findUnique";
import polsekTerdekatUpdate from "./updt";
import polsekTerdekatFindFirst from "./findFirst";
const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/Polsek Terdekat"] })
@@ -12,6 +13,7 @@ const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/
const response = await polsekTerdekatFindUnique(new Request(context.request));
return response;
})
.get("/find-first", polsekTerdekatFindFirst)
.post("/create", polsekTerdekatCreate, {
body: t.Object({
nama: t.String(),

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function menuTipsKeamananFindMany() {
try {
const data = await prisma.menuTipsKeamanan.findMany({
where: { isActive: true },
include: {
image: true,
},
});
async function tipsKeamananFindMany(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 menu tips keamanan",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch menu tips keamanan",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ judul: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.menuTipsKeamanan.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.menuTipsKeamanan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil menu tips keamanan 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 tips keamanan",
};
}
}
export default tipsKeamananFindMany;

View File

@@ -1,25 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function infoWabahPenyakitFindMany() {
try {
const data = await prisma.infoWabahPenyakit.findMany({
where: {
isActive: true,
},
include: {
image: true,
}
})
return {
success: true,
message: "Success fetch info wabah penyakit",
data,
}
} catch (error) {
console.error("Find many error:", error);
return {
success: false,
message: "Failed fetch info wabah penyakit",
}
}
}
async function infoWabahPenyakitFindMany(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' } },
{ deskripsiLengkap: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.infoWabahPenyakit.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.infoWabahPenyakit.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil info wabah penyakit 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 info wabah penyakit",
};
}
}
export default infoWabahPenyakitFindMany;

View File

@@ -1,25 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kontakDaruratFindMany() {
try {
const data = await prisma.kontakDarurat.findMany({
where: {
isActive: true,
},
include: {
image: true,
}
})
return {
success: true,
message: "Success fetch kontak darurat",
data,
}
} catch (error) {
console.error("Find many error:", error);
return {
success: false,
message: "Failed fetch kontak darurat",
}
}
}
async function kontakDaruratFindMany(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' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.kontakDarurat.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.kontakDarurat.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil kontak darurat 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 kontak darurat",
};
}
}
export default kontakDaruratFindMany;

View File

@@ -1,25 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function penangananDaruratFindMany() {
try {
const data = await prisma.penangananDarurat.findMany({
where: {
isActive: true,
},
include: {
image: true,
}
})
return {
success: true,
message: "Success fetch penanganan darurat",
data,
}
} catch (error) {
console.error("Find many error:", error);
return {
success: false,
message: "Failed fetch penanganan darurat",
}
}
}
async function penangananDaruratFindMany(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' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.penangananDarurat.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.penangananDarurat.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil penanganan darurat 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 penanganan darurat",
};
}
}
export default penangananDaruratFindMany;

View File

@@ -1,25 +1,56 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function programKesehatanFindMany() {
try {
const data = await prisma.programKesehatan.findMany({
where: {
isActive: true,
},
include: {
image: true,
}
})
return {
success: true,
message: "Success fetch program kesehatan",
data,
}
} catch (error) {
console.error("Find many error:", error);
return {
success: false,
message: "Failed fetch program kesehatan",
}
}
}
async function programKesehatanFindMany(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.programKesehatan.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.programKesehatan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil program kesehatan 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 program kesehatan",
};
}
}
export default programKesehatanFindMany;

View File

@@ -1,27 +1,59 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function puskesmasFindMany() {
try {
const data = await prisma.puskesmas.findMany({
where: {
isActive: true,
},
include: {
image: true,
jam: true,
kontak: true,
}
})
return {
success: true,
message: "Success fetch puskesmas",
data,
}
} catch (error) {
console.error("Find many error:", error);
return {
success: false,
message: "Failed fetch puskesmas",
}
}
}
async function puskesmasFindMany(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' } },
{ alamat: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.puskesmas.findMany({
where,
include: {
image: true,
jam: true,
kontak: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.puskesmas.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil puskesmas 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 berita",
};
}
}
export default puskesmasFindMany;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,23 @@ import { Context } from "elysia";
async function apbdesFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.aPBDes.findMany({
where: { isActive: true },
where,
include: {
image: true,
file: true,
@@ -20,7 +32,7 @@ async function apbdesFindMany(context: Context) {
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.aPBDes.count({
where: { isActive: true },
where,
}),
]);

View File

@@ -11,7 +11,7 @@ const APBDes = new Elysia({
})
// ✅ Find all
.get("/find-many", apbdesFindMany)
.get("/findMany", apbdesFindMany)
// ✅ Find by ID
.get("/:id", apbdesFindUnique)

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,23 +6,35 @@ import { Context } from "elysia";
async function desaAntiKorupsiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || "";
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
{ deskripsi: { contains: search, mode: "insensitive" } },
{ kategori: { name: { contains: search, mode: "insensitive" } } },
];
}
try {
const [data, total] = await Promise.all([
prisma.desaAntiKorupsi.findMany({
where: { isActive: true },
where,
include: {
kategori: true,
file: true,
},
skip,
take: limit,
orderBy: { name: 'asc' }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.desaAntiKorupsi.count({
where: { isActive: true }
})
where,
}),
]);
return {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,18 +6,29 @@ import { Context } from "elysia";
async function kategoriDesaAntiKorupsiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.kategoriDesaAntiKorupsi.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { name: 'asc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.kategoriDesaAntiKorupsi.count({
where: { isActive: true }
where,
})
]);

View File

@@ -15,16 +15,12 @@ export default async function respondenCreate(context: Context) {
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
tanggal: tanggal.toISOString(), // Use the Date object
jenisKelaminId: body.jenisKelaminId,
ratingId: body.ratingId,
kelompokUmurId: body.kelompokUmurId,

View File

@@ -2,35 +2,40 @@ import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
name: string;
tanggal: string;
jenisKelaminId: string;
ratingId: string;
kelompokUmurId: string;
}
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;
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);
}
try {
const result = await prisma.responden.update({
where: { id },
data: {
name: body.name,
tanggal: new Date(body.tanggal).toISOString(),
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);
// Instead of throwing an error, return a proper JSON response
return {
success: false,
message: "Gagal mengupdate responden: " + (error as Error).message,
data: null,
};
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,25 @@ import { Context } from "elysia";
async function prestasiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
{ kategori: { name: { contains: search, mode: 'insensitive' } } },
];
}
try {
const [data, total] = await Promise.all([
prisma.prestasiDesa.findMany({
where: { isActive: true },
where,
include: {
image: true,
kategori: true,
@@ -20,7 +34,7 @@ async function prestasiDesaFindMany(context: Context) {
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.prestasiDesa.count({
where: { isActive: true },
where,
}),
]);

View File

@@ -1,15 +1,52 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriPrestasiFindMany() {
const data = await prisma.kategoriPrestasiDesa.findMany();
return {
success: true,
data: data.map((item: any) => {
return {
id: item.id,
name: item.name,
}
}),
async function kategoriPrestasiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.kategoriPrestasiDesa.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.kategoriPrestasiDesa.count({
where,
}),
]);
return {
success: true,
message: "Success fetch Kategori Prestasi Desa with pagination",
data,
page,
totalPages: Math.ceil(total / limit),
total,
};
}
} catch (e) {
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch Kategori Prestasi Desa with pagination",
};
}
}
export default kategoriPrestasiFindMany;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,23 @@ import { Context } from "elysia";
async function mediaSosialFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.mediaSosial.findMany({
where: { isActive: true },
where,
include: {
image: true,
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// // /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,23 @@ import { Context } from "elysia";
async function programInovasiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.programInovasi.findMany({
where: { isActive: true },
where,
include: {
image: true,
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,12 +6,23 @@ import { Context } from "elysia";
async function sdgsDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.sDGSDesa.findMany({
where: { isActive: true },
where,
include: {
image: true,
},
@@ -19,7 +31,7 @@ async function sdgsDesaFindMany(context: Context) {
orderBy: { jumlah: "desc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.sDGSDesa.count({
where: { isActive: true },
where,
}),
]);

View File

@@ -1,44 +1,54 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
// Di findMany.ts
export default async function daftarInformasiPublikFindMany(context: Context) {
async function daftarInformasiPublikFindMany(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 = [
{ jenisInformasi: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.daftarInformasiPublik.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.daftarInformasiPublik.count({
where: { isActive: true }
})
prisma.daftarInformasiPublik.count({ where }),
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch daftar informasi publik with pagination",
message: "Berhasil ambil daftar informasi publik dengan pagination",
data,
page,
totalPages,
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 daftar informasi publik with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
message: "Gagal mengambil data daftar informasi publik",
};
}
}
}
export default daftarInformasiPublikFindMany;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,22 +6,34 @@ import { Context } from "elysia";
export default async function pegawaiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ namaLengkap: { contains: search, mode: 'insensitive' } },
{ alamat: { contains: search, mode: 'insensitive' } },
];
}
try {
const [data, total] = await Promise.all([
prisma.pegawaiPPID.findMany({
where: { isActive: true },
where,
include: {
posisi: true,
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
orderBy: { posisi: { hierarki: 'asc' } },
}),
prisma.pegawaiPPID.count({
where: { isActive: true }
where,
})
]);

View File

@@ -1,17 +1,58 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/posisi-organisasi/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function posisiOrganisasiFindMany() {
const data = await prisma.posisiOrganisasiPPID.findMany();
async function posisiOrganisasiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || "";
const where: any = { isActive: true };
if (search) {
where.OR = [
{ nama: { contains: search, mode: "insensitive" } },
{ deskripsi: { contains: search, mode: "insensitive" } },
];
}
try {
const whereClause = {
...where,
isActive: true
};
const [data, total] = await Promise.all([
prisma.posisiOrganisasiPPID.findMany({
where: whereClause,
skip: (page - 1) * limit,
take: limit,
orderBy: { hierarki: "asc" },
}),
prisma.posisiOrganisasiPPID.count({ where: whereClause }),
]);
return {
success: true,
message: "Berhasil mengambil semua data posisi organisasi",
data: data.map((item: any) => ({
id: item.id,
nama: item.nama,
deskripsi: item.deskripsi,
hierarki: item.hierarki,
})),
success: true,
message: "Berhasil mengambil data posisi organisasi dengan pagination",
data: data.map((item: any) => ({
id: item.id,
nama: item.nama,
deskripsi: item.deskripsi,
hierarki: item.hierarki,
})),
page,
totalPages: Math.ceil(total / limit),
total,
};
}
} catch (e) {
console.error("Find many paginated error:", e);
return {
success: false,
message: "Gagal mengambil data posisi organisasi",
};
}
}
export default posisiOrganisasiFindMany;

View File

@@ -1,21 +1,157 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Stack, Box, Text, Image, Paper } from '@mantine/core';
import React from 'react';
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useShallowEffect } from '@mantine/hooks';
function Page() {
const state = useProxy(PendapatanAsliDesa.ApbDesa);
useShallowEffect(() => {
state.findMany.load();
}, []);
useShallowEffect(() => {
PendapatanAsliDesa.pembiayaan.findMany.load();
PendapatanAsliDesa.belanja.findMany.load();
PendapatanAsliDesa.pendapatan.findMany.load();
}, []);
// Get the latest APB data
const latestApb = state.findMany.data?.[0];
// Calculate totals
const totalPendapatan = latestApb?.pendapatan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
const totalBelanja = latestApb?.belanja?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
const totalPembiayaan = latestApb?.pembiayaan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Pendapatan Asli Desa
</Text>
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
Pendapatan Asli Desa
</Text>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
<Paper bg={colors['white-1']} p={"xl"}>
<Image src="/pa-desa.png" alt=''/>
<Stack gap="lg" justify="center">
<Paper bg={colors['white-1']} p="xl">
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
{/* Pendapatan Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
<Stack gap={"xs"}>
<Title order={3}>Pendapatan</Title>
{PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(item.value)}</Text>
</GridCol>
</Grid>
</Box>
))}
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="xl" fw={700} c={colors['blue-button']}>
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(totalPendapatan)}
</Text>
</GridCol>
</Grid>
</Stack>
</Box>
{/* Belanja Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
<Stack gap={"xs"}>
<Title order={3}>Belanja</Title>
{PendapatanAsliDesa.belanja.findMany.data?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(item.value)}</Text>
</GridCol>
</Grid>
</Box>
))}
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="xl" fw={700} c="orange">
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(totalBelanja)}
</Text>
</GridCol>
</Grid>
</Stack>
</Box>
{/* Pembiayaan Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
<Stack gap={"xs"}>
<Title order={3}>Pembiayaan</Title>
{PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(item.value)}</Text>
</GridCol>
</Grid>
</Box>
))}
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="xl" fw={700} c="green">
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(totalPembiayaan)}
</Text>
</GridCol>
</Grid>
</Stack>
</Box>
</SimpleGrid>
</Paper>
</Stack>
</Box>

View File

@@ -1,192 +1,32 @@
'use client'
import colors from '@/con/colors';
import { Stack, Box, Paper, Text, ColorSwatch, Flex } from '@mantine/core';
import { Stack, Box, Paper, Text, ColorSwatch, Flex, Skeleton } from '@mantine/core';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { BarChart } from '@mantine/charts';
import { useProxy } from 'valtio/utils';
import grafikDemografiPekerjaan from '@/app/admin/(dashboard)/_state/ekonomi/demografi-pekerjaan';
import { useShallowEffect } from '@mantine/hooks';
const data = [
{
id: 1,
Pekerjaan: 'Guru',
laki: 2,
perempuan: 3
},
{
id: 2,
Pekerjaan: 'Belajar/Mahasiswa',
laki: 37,
perempuan: 38
},
{
id: 3,
Pekerjaan: 'Karyawan Bumdn',
laki: 1,
perempuan: 0
},
{
id: 4,
Pekerjaan: 'Buruh Tani/Perkebunan',
laki: 1,
perempuan: 0
},
{
id: 5,
Pekerjaan: 'Karyawan Swasta',
laki: 3,
perempuan: 17
},
{
id: 6,
Pekerjaan: 'Karyawan Honorer',
laki: 2,
perempuan: 1
},
{
id: 7,
Pekerjaan: 'Buruh Harian Lepas',
laki: 8,
perempuan: 5
},
{
id: 8,
Pekerjaan: 'Belum/Tidak Bekerja',
laki: 87,
perempuan: 44
},
{
id: 9,
Pekerjaan: ' Kepolisian RI (Polri)',
laki: 4,
perempuan: 0
},
{
id: 10,
Pekerjaan: 'Wiraswasta Mengurus Rumah Tangga',
laki: 1,
perempuan: 7
},
{
id: 11,
Pekerjaan: 'Dosen',
laki: 1,
perempuan: 1
},
{
id: 12,
Pekerjaan: 'Perangkat Desa',
laki: 17,
perempuan: 19
},
{
id: 13,
Pekerjaan: 'Nelayan',
laki: 3,
perempuan: 0
},
{
id: 14,
Pekerjaan: 'Penyuluh Pertanian',
laki: 33,
perempuan: 24
},
{
id: 15,
Pekerjaan: 'Tukang Las/Pandai Besi',
laki: 5,
perempuan: 0
},
{
id: 16,
Pekerjaan: 'Sopir/Driver',
laki: 10,
perempuan: 3
},
{
id: 17,
Pekerjaan: 'Teknisi/Listrik',
laki: 25,
perempuan: 0
},
{
id: 18,
Pekerjaan: 'Montir/Mekanik',
laki: 25,
perempuan: 0
},
{
id: 19,
Pekerjaan: 'Karyawan Hotel/Pariwisata',
laki: 2,
perempuan: 52
},
{
id: 20,
Pekerjaan: 'Pengrajin (Batik, Anyaman, Kayu)',
laki: 5,
perempuan: 25
},
{
id: 21,
Pekerjaan: 'Tukang Bangunan',
laki: 25,
perempuan: 5
},
{
id: 22,
Pekerjaan: 'Tukang Kayu/Furnitur',
laki: 25,
perempuan: 0
},
{
id: 23,
Pekerjaan: 'Penjahit',
laki: 2,
perempuan: 35
},
{
id: 24,
Pekerjaan: 'Pedagang Pasar',
laki: 25,
perempuan: 30
},
{
id: 25,
Pekerjaan: 'Warung Makan/Penjual Makanan',
laki: 15,
perempuan: 30
},
{
id: 26,
Pekerjaan: 'Satpam/Security',
laki: 20,
perempuan: 5
},
{
id: 27,
Pekerjaan: 'Pengusaha Kecil (UMKM)',
laki: 5,
perempuan: 20
},
{
id: 28,
Pekerjaan: 'Karyawan Restoran/Kafe',
laki: 5,
perempuan: 15
},
{
id: 29,
Pekerjaan: 'Freelancer',
laki: 20,
perempuan: 10
},
{
id: 30,
Pekerjaan: 'Fotografer',
laki: 25,
perempuan: 9
},
]
function Page() {
const state = useProxy(grafikDemografiPekerjaan)
const {
data,
} = state.findMany
useShallowEffect(() => {
state.findMany.load()
}, [])
if (!data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -207,7 +47,13 @@ function Page() {
p={10}
mb={50}
h={400}
data={data}
w={150}
data={data.map((item) => ({
id: item.id,
Pekerjaan: item.pekerjaan,
laki: item.lakiLaki,
perempuan: item.perempuan,
}))}
dataKey="Pekerjaan"
series={[
{ name: 'laki', color: '#5082EE' },

View File

@@ -1,119 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, CheckIcon, Combobox, ComboboxChevron, ComboboxOption, ComboboxOptions, ComboboxTarget, Group, InputBase, InputPlaceholder, Paper, SimpleGrid, Stack, Text, useCombobox } from '@mantine/core';
import { useState } from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { BarChart } from '@mantine/charts';
const data = [
{
id: 1,
tahun: '2024',
Penduduk: 400000
},
{
id: 2,
tahun: '2025',
Penduduk: 450000
},
]
const tahun = [
'2024',
'2025'
];
function Page() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
onDropdownOpen: (eventSource) => {
if (eventSource === 'keyboard') {
combobox.selectActiveOption();
} else {
combobox.updateSelectedOptionIndex('active');
}
},
});
const [value, setValue] = useState<string | null>('2024');
const options = tahun.map((item) => (
<ComboboxOption value={item} key={item} active={item === value}>
<Group gap="xs">
{item === value && <CheckIcon size={12} />}
<span>{item}</span>
</Group>
</ComboboxOption>
));
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Jumlah Penduduk Miskin Tahun 2024-2025
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
<SimpleGrid
pb={20}
cols={{
base: 1,
md: 2
}}
>
<Paper p={'xl'}>
<Text fz={'h3'}>Tahun: 2024</Text>
<Text fw={"bold"} fz={'h1'}>4,800,000 Orang</Text>
</Paper>
<Paper p={'xl'}>
<Text>Pilih Tahun</Text>
<Combobox
store={combobox}
resetSelectionOnOptionHover
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val);
combobox.updateSelectedOptionIndex('active');
}}
>
<ComboboxTarget targetType="button">
<InputBase
component="button"
type="button"
pointer
rightSection={<ComboboxChevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <InputPlaceholder>Pick value</InputPlaceholder>}
</InputBase>
</ComboboxTarget>
<Combobox.Dropdown>
<ComboboxOptions>{options}</ComboboxOptions>
</Combobox.Dropdown>
</Combobox>
</Paper>
</SimpleGrid>
<Paper p={'xl'}>
<Text pb={10} fw={'bold'} fz={'h4'}>Jumlah Penduduk Miskin Per Tahun</Text>
<BarChart
p={10}
h={300}
data={data}
dataKey="tahun"
series={[
{ name: 'Penduduk', color: '#8785D3' },
]}
tickLine="y"
/>
</Paper>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,82 @@
'use client'
import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin';
import colors from '@/con/colors';
import { BarChart } from '@mantine/charts';
import { Box, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
type JPMGrafik = {
id: string;
year: number;
totalPoorPopulation: number;
}
const state = useProxy(jumlahPendudukMiskin)
const [chartData, setChartData] = useState<JPMGrafik[]>([])
useShallowEffect(() => {
state.findMany.load()
}, [])
useEffect(() => {
if (state.findMany.data) {
setChartData(state.findMany.data.map((item) => ({
id: item.id,
year: Number(item.year),
totalPoorPopulation: Number(item.totalPoorPopulation),
})));
}
}, [state.findMany.data]);
if (!state.findMany.data) {
return (
<Stack>
<Skeleton h={500} />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Jumlah Penduduk Miskin
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
<Paper p={'xl'}>
<Text fz={'h3'}>Jumlah Data Penduduk Miskin</Text>
<Text fw={"bold"} fz={'h1'}>
{state.findMany.data?.reduce((sum, item) => sum + (Number(item.totalPoorPopulation) || 0), 0).toLocaleString()} Orang
</Text>
</Paper>
<Paper p={'xl'}>
<Text pb={10} fw={'bold'} fz={'h4'}>Jumlah Penduduk Miskin Per Tahun</Text>
<BarChart
h={300}
data={chartData}
dataKey="year"
series={[
{ name: 'totalPoorPopulation', color: 'blue.6', label: 'Jumlah Penduduk Miskin' },
]}
xAxisLabel="Tahun"
yAxisLabel="Jumlah Penduduk"
/>
</Paper>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,62 +1,76 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
import colors from '@/con/colors';
import { Stack, Box, Text, Center, Paper, ColorSwatch, Flex } from '@mantine/core';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { PieChart } from '@mantine/charts';
import { Box, Center, ColorSwatch, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const datausiaKerja = [
{
id: 1,
name: '18 - 25',
value: 45,
color: 'indigo.6'
},
{
id: 2,
name: '26 - 35',
value: 35,
color: 'teal.6'
},
{
id: 3,
name: '36 - 45',
value: 15,
color: 'yellow.6'
},
{
id: 4,
name: '46+',
value: 5,
color: 'red.6'
},
]
const datakerjaPendidikan = [
{
id: 1,
name: 'SD',
value: 10,
color: 'indigo.6'
},
{
id: 2,
name: 'SMP',
value: 20,
color: 'teal.6'
},
{
id: 3,
name: 'SMA/SMK',
value: 45,
color: 'yellow.6'
},
{
id: 4,
name: 'D3/S1',
value: 25,
color: 'red.6'
},
]
function Page() {
const stateGrafikNganggur = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
const stateGrafikNganggurPendidikan = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
const [donutGrafikNganggurData, setDonutGrafikNganggurData] = useState<any[]>([])
const [donutGrafikNganggurDataPendidikan, setDonutGrafikNganggurDataPendidikan] = useState<any[]>([])
const [mounted, setMounted] = useState(false)
const [mounted2, setMounted2] = useState(false)
useShallowEffect(() => {
setMounted(true)
setMounted2(true)
stateGrafikNganggur.findMany.load()
stateGrafikNganggurPendidikan.findMany.load()
}, [])
useEffect(() => {
if (stateGrafikNganggur.findMany.data) {
const totalUsia18_25 = stateGrafikNganggur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia18_25 || 0), 0);
const totalUsia26_35 = stateGrafikNganggur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia26_35 || 0), 0);
const totalUsia36_45 = stateGrafikNganggur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia36_45 || 0), 0);
const totalUsia46_keatas = stateGrafikNganggur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia46_keatas || 0), 0);
setDonutGrafikNganggurData([
{ name: 'usia 18 - 25', value: totalUsia18_25, color: '#4b6Ef5', key: 'usia18_25' },
{ name: 'usia 26 - 35', value: totalUsia26_35, color: '#14b885', key: 'usia26_35' },
{ name: 'usia 36 - 45', value: totalUsia36_45, color: '#E6A03B', key: 'usia36_45' },
{ name: 'usia 46 +', value: totalUsia46_keatas, color: '#DB524D', key: 'usia46_keatas' },
])
}
}, [stateGrafikNganggur.findMany.data])
useEffect(() => {
if (stateGrafikNganggurPendidikan.findMany.data) {
const SD = stateGrafikNganggurPendidikan.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SD || 0), 0);
const SMP = stateGrafikNganggurPendidikan.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SMP || 0), 0);
const SMA = stateGrafikNganggurPendidikan.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SMA || 0), 0);
const D3 = stateGrafikNganggurPendidikan.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
const S1 = stateGrafikNganggurPendidikan.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
setDonutGrafikNganggurDataPendidikan([
{ name: 'SD', value: SD, color: '#4b6Ef5', key: 'SD' },
{ name: 'SMP', value: SMP, color: '#14b885', key: 'SMP' },
{ name: 'SMA', value: SMA, color: '#E6A03B', key: 'SMA' },
{ name: 'D3', value: D3, color: '#DB524D', key: 'D3' },
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
]);
}
}, [stateGrafikNganggurPendidikan.findMany.data])
if (!stateGrafikNganggur.findMany.data) {
return (
<Box>
<Skeleton h={500} />
</Box>
)
}
if (!stateGrafikNganggur.findMany.data) {
return (
<Box>
<Skeleton h={500} />
</Box>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -71,9 +85,18 @@ function Page() {
<Stack gap={'lg'} justify='center'>
<Paper p={'lg'}>
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Usia</Text>
<Center>
<PieChart size={300} withLabelsLine labelsPosition="outside" labelsType="percent" withLabels data={datausiaKerja} withTooltip tooltipDataSource="segment" mx="auto" />
</Center>
{mounted && donutGrafikNganggurData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
<PieChart
size={300}
withLabelsLine
labelsPosition="outside"
labelsType="percent"
withLabels
data={donutGrafikNganggurData}
withTooltip
tooltipDataSource="segment"
mx="auto" />
</Box>) : <Skeleton h={500} />}
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<Box>
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
@@ -103,9 +126,18 @@ function Page() {
</Paper>
<Paper p={'lg'}>
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Pendidikan</Text>
<Center>
<PieChart size={300} withLabelsLine labelsPosition="outside" labelsType="percent" withLabels data={datakerjaPendidikan} withTooltip tooltipDataSource="segment" mx="auto" />
</Center>
{mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (<Center>
<PieChart
size={300}
withLabelsLine
labelsPosition="outside"
labelsType="percent"
withLabels
data={donutGrafikNganggurDataPendidikan}
withTooltip
tooltipDataSource="segment"
mx="auto" />
</Center>) : <Skeleton h={500} />}
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<Box>
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
@@ -127,10 +159,16 @@ function Page() {
</Box>
<Box>
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>D3/S1</Text>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>D3</Text>
<ColorSwatch color="#DB524D" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>S1</Text>
<ColorSwatch color="#1018A8FF" size={30} />
</Flex>
</Box>
</Flex>
</Paper>
</Stack>

View File

@@ -1,177 +0,0 @@
import colors from '@/con/colors';
import { BarChart } from '@mantine/charts';
import { Box, Button, Center, ColorSwatch, Flex, Group, Paper, SimpleGrid, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconChevronDown, IconDownload, IconSchool, IconSchoolOff, IconUserOff } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
const data1 = [
{
id: 1,
icon: <IconUserOff size={35} />,
judul: 'Total Pengangguran',
jumlah: '140',
persentase: <Text fz={'h4'} c={'green'}>-12.5% dari 2024</Text>
},
{
id: 2,
icon: <IconSchool size={35} />,
judul: 'Pengangguran Terdidik',
jumlah: '80',
persentase: <Text fz={'h4'} c={'gray'}>57.1% dari total</Text>
},
{
id: 3,
icon: <IconSchoolOff size={35} />,
judul: 'Pengangguran Tidak Terdidik',
jumlah: '60',
persentase: <Text fz={'h4'} c={'gray'}>42.9% dari total</Text>
},
]
const dataPengangguran = [
{
id: 1,
bulan: 'Jan',
berpendidikan: 98,
takberpendidikan: 74,
},
{
id: 2,
bulan: 'Feb',
berpendidikan: 85,
takberpendidikan: 74,
},
{
id: 3,
bulan: 'Mar',
berpendidikan: 76,
takberpendidikan: 55,
},
{
id: 4,
bulan: 'Apr',
berpendidikan: 98,
takberpendidikan: 74,
},
{
id: 5,
bulan: 'Mei',
berpendidikan: 74,
takberpendidikan: 54,
},
{
id: 6,
bulan: 'Jun',
berpendidikan: 55,
takberpendidikan: 50,
},
]
const dataTable = [
{ bulan: 'Jan', total: 160, terdidik: 95, takterdidik: 65, perubahan: '-' },
{ bulan: 'Feb', total: 155, terdidik: 90, takterdidik: 65, perubahan: '-3.1%' },
{ bulan: 'Mar', total: 150, terdidik: 88, takterdidik: 62, perubahan: '-3.2%' },
{ bulan: 'Apr', total: 148, terdidik: 85, takterdidik: 63, perubahan: '-1.3%' },
{ bulan: 'Mei', total: 145, terdidik: 82, takterdidik: 63, perubahan: '-2.0%' },
{ bulan: 'Jun', total: 140, terdidik: 80, takterdidik: 60, perubahan: '-3.4%' },
]
function Page() {
const rows = dataTable.map((element) => (
<TableTr key={element.bulan}>
<TableTd ta={'center'}>{element.bulan}</TableTd>
<TableTd ta={'center'}>{element.total}</TableTd>
<TableTd ta={'center'}>{element.terdidik}</TableTd>
<TableTd ta={'center'}>{element.takterdidik}</TableTd>
<TableTd ta={'center'}>{element.perubahan}</TableTd>
</TableTr>
));
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Jumlah Pengangguran 2024 - 2025
</Text>
<Group py={20} align='center' justify='space-between'>
<Text fz={'h4'} fw={"bold"}>DATA PENGANGGURAN DESA</Text>
<Flex gap={'xl'}>
<Button c={'black'} bg={colors['white-1']} rightSection={<IconChevronDown size={20} />}>2025</Button>
<Button leftSection={<IconDownload size={20} />}>Export</Button>
</Flex>
</Group>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
<SimpleGrid
cols={1}
pb={20}
>
{data1.map((v, k) => {
return (
<Paper px={25} key={k} py={'lg'} bg={colors['white-1']} shadow={'md'}>
<Flex justify={'space-between'} align={'center'}>
<Box>
<Stack>
<Text fz={'h4'}>{v.judul}</Text>
<Text fz={'h2'} fw={'bold'}>{v.jumlah}</Text>
{v.persentase}
</Stack>
</Box>
{v.icon}
</Flex>
</Paper>
)
})}
</SimpleGrid>
<Paper p={'lg'}>
<Flex pb={30} justify={'flex-end'} gap={'xl'} align={'center'}>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Pengangguran Berpendidikan</Text>
<ColorSwatch color="#5082EE" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Pengangguran Tak Berpendidikan</Text>
<ColorSwatch color="#DA524C" size={30} />
</Flex>
</Box>
</Flex>
<Center>
<BarChart
p={10}
h={400}
data={dataPengangguran}
dataKey="bulan"
series={[
{ name: 'berpendidikan', color: '#5082EE' },
{ name: 'takberpendidikan', color: '#DA524C' },
]}
tickLine="y"
/>
</Center>
</Paper>
<Paper p={'lg'}>
<Text fw={'bold'} fz={'h4'}>Detail Data Pengangguran</Text>
<Table striped highlightOnHover>
<TableThead>
<TableTr>
<TableTh ta={'center'}>Bulan</TableTh>
<TableTh ta={'center'}>Total</TableTh>
<TableTh ta={'center'}>Terdidik</TableTh>
<TableTh ta={'center'}>Tidak Terdidik</TableTh>
<TableTh ta={'center'}>Perubahan</TableTh>
</TableTr>
</TableThead>
<TableTbody>{rows}</TableTbody>
</Table>
</Paper>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,182 @@
'use client'
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
import colors from '@/con/colors';
import { BarChart } from '@mantine/charts';
import { Box, ColorSwatch, Flex, Group, Paper, SimpleGrid, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSchool, IconSchoolOff, IconUserOff } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
type DataPengangguran = {
id: string;
bulan: string;
berpendidikan: number;
takberpendidikan: number;
}
function Page() {
const [chartData, setChartData] = useState<DataPengangguran[]>([])
const [mounted, setMounted] = useState(false)
const state = useProxy(jumlahPengangguranState.jumlahPengangguran)
// const [selectedYear, setSelectedYear] = useState<string | null>(null);
useShallowEffect(() => {
setMounted(true)
state.findMany.load()
}, [])
useEffect(() => {
setMounted(true);
if (state.findMany.data) {
setChartData(state.findMany.data.map((item) => ({
id: item.id,
bulan: item.month,
berpendidikan: Number(item.educatedUnemployment),
takberpendidikan: Number(item.uneducatedUnemployment),
})));
}
}, [state.findMany.data]);
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Jumlah Pengangguran
</Text>
<Group py={20} align='center' justify='space-between'>
<Text fz={'h4'} fw={"bold"}>DATA PENGANGGURAN DESA</Text>
</Group>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
<SimpleGrid
cols={1}
pb={20}
>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
{/* Total Unemployment Card */}
<Paper px={25} py={'lg'} bg={colors['white-1']} shadow="md">
<Flex direction="column" gap="md">
<IconUserOff size={35} color={colors['blue-button']} />
<Text fz="h4" fw={600}>Total Pengangguran</Text>
<Text fz="h2" fw={700} c={colors['blue-button']}>
{state.findMany.data?.[0]?.totalUnemployment || 0} Orang
</Text>
<Text fz="sm" c="dimmed">
Data tahun {state.findMany.data?.[0]?.year || ''}
</Text>
</Flex>
</Paper>
{/* Educated Unemployment Card */}
<Paper px={25} py={'lg'} bg={colors['white-1']} shadow="md">
<Flex direction="column" gap="md">
<IconSchool size={35} color="#5082EE" />
<Text fz="h4" fw={600}>Pengangguran Terdidik</Text>
<Text fz="h2" fw={700} c="#5082EE">
{state.findMany.data?.[0]?.educatedUnemployment || 0} Orang
</Text>
<Text fz="sm" c="dimmed">
{state.findMany.data?.[0]?.totalUnemployment ?
`${Math.round((state.findMany.data[0].educatedUnemployment / state.findMany.data[0].totalUnemployment) * 100)}% dari total` :
'0% dari total'}
</Text>
</Flex>
</Paper>
{/* Uneducated Unemployment Card */}
<Paper px={25} py={'lg'} bg={colors['white-1']} shadow="md">
<Flex direction="column" gap="md">
<IconSchoolOff size={35} color="#DA524C" />
<Text fz="h4" fw={600}>Pengangguran Tidak Terdidik</Text>
<Text fz="h2" fw={700} c="#DA524C">
{state.findMany.data?.[0]?.uneducatedUnemployment || 0} Orang
</Text>
<Text fz="sm" c="dimmed">
{state.findMany.data?.[0]?.totalUnemployment ?
`${Math.round((state.findMany.data[0].uneducatedUnemployment / state.findMany.data[0].totalUnemployment) * 100)}% dari total` :
'0% dari total'}
</Text>
</Flex>
</Paper>
</SimpleGrid>
</SimpleGrid>
<Paper p={'lg'}>
<Flex pb={30} justify={'flex-end'} gap={'xl'} align={'center'}>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Pengangguran Berpendidikan</Text>
<ColorSwatch color="#5082EE" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Pengangguran Tak Berpendidikan</Text>
<ColorSwatch color="#DA524C" size={30} />
</Flex>
</Box>
</Flex>
{!mounted || chartData.length === 0 ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
<Box w={{ base: '100%', md: '70%' }}>
<BarChart
h={450}
data={chartData}
dataKey="bulan"
series={[
{ name: 'berpendidikan', color: '#5082EE', label: 'Terdidik' },
{ name: 'takberpendidikan', color: '#DA524C', label: 'Tidak Terdidik' },
]}
/>
</Box>
</Paper>
</Box>
)}
</Paper>
<Paper p={'lg'}>
<Text fw={'bold'} fz={'h4'}>Detail Data Pengangguran</Text>
<Table striped highlightOnHover>
<TableThead>
<TableTr>
<TableTh ta={'center'}>Bulan</TableTh>
<TableTh ta={'center'}>Total</TableTh>
<TableTh ta={'center'}>Terdidik</TableTh>
<TableTh ta={'center'}>Tidak Terdidik</TableTh>
<TableTh ta={'center'}>Perubahan</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{state.findMany.data?.map((item, index) => (
<TableTr key={item?.id ? String(item.id) : `row-${index}`}>
<TableTd ta={'center'}>{item.month}</TableTd>
<TableTd ta={'center'}>{item.totalUnemployment}</TableTd>
<TableTd ta={'center'}>{item.educatedUnemployment}</TableTd>
<TableTd ta={'center'}>{item.uneducatedUnemployment}</TableTd>
<TableTd ta={'center'}>{item.percentageChange}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,64 +1,52 @@
'use client'
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
import colors from '@/con/colors';
import { Stack, Box, Text, TextInput, Group, SimpleGrid, Paper, Flex, Button } from '@mantine/core';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { Box, Button, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconBriefcase, IconClock, IconMapPin, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const data = [
{
id: 1,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
const formatCurrency = (value: string | number) => {
// Convert to string if it's a number
const numStr = typeof value === 'number' ? value.toString() : value;
// Remove all non-digit characters
const digitsOnly = numStr.replace(/\D/g, '');
// Format with thousand separators
const formatted = digitsOnly.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
return `Rp.${formatted}`;
};
},
{
id: 2,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 3,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 4,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 5,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
{
id: 6,
kerja: 'Kasir',
tempat: 'Toko Sumber Rejeki',
alamat: 'Desa Munggu , Badung',
gaji: 'Rp. 2.500.000 / bulan'
},
]
function Page() {
const state = useProxy(lowonganKerjaState)
const router = useRouter()
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany
useEffect(() => {
load(page, 6, debouncedSearch)
}, [page, debouncedSearch, load])
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -74,6 +62,8 @@ function Page() {
w={{ base: 500, md: 700 }}
placeholder='Cari Pekerjaan'
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Group>
</Box>
@@ -93,15 +83,15 @@ function Page() {
<Flex gap={'xl'} align={'center'}>
<IconBriefcase color={colors['blue-button']} size={50} />
<Box>
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.kerja}</Text>
<Text fz={'h4'}>{v.tempat}</Text>
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.posisi}</Text>
<Text fz={'h4'}>{v.namaPerusahaan}</Text>
</Box>
</Flex>
</Box>
<Box>
<Flex gap={'xl'} align={'center'}>
<IconMapPin color={colors['blue-button']} size={50} />
<Text fz={'h4'}>{v.alamat}</Text>
<Text fz={'h4'}>{v.lokasi}</Text>
</Flex>
</Box>
<Box>
@@ -109,7 +99,7 @@ function Page() {
<IconClock color={colors['blue-button']} size={50} />
<Box>
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>Full Time</Text>
<Text fz={'h4'}>{v.gaji}</Text>
<Text fz={'h4'}>{formatCurrency(v.gaji)}</Text>
</Box>
</Flex>
</Box>
@@ -119,6 +109,14 @@ function Page() {
)
})}
</SimpleGrid>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Stack>
</Box >
</Stack >

View File

@@ -1,90 +1,76 @@
'use client'
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
import colors from '@/con/colors';
import { Box, Combobox, Flex, Image, InputBase, InputPlaceholder, Paper, SimpleGrid, Stack, Text, TextInput, useCombobox } from '@mantine/core';
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconMapPinFilled, IconSearch, IconShoppingCartFilled, IconStarFilled } from '@tabler/icons-react';
import { motion } from 'motion/react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const groceries = [
'Makanan',
'Minuman',
'Pakaian',
'Alat Dapur',
'Alat Mandi',
'Furniture',
];
const dataBarang = [
{
id: 1,
image: '/api/img/semat.png',
judul: 'Semat Bambu / Semat Banten',
harga: 'Rp. 3000 / pcs',
bintang: '4.9',
alamat: 'Jl. Kecubung no.6'
},
{
id: 2,
image: '/api/img/kerupuk.png',
judul: 'Kerupuk Babi',
harga: 'Rp. 12000 / pcs',
bintang: '4.9',
alamat: 'Jl. Kenari no.7'
},
{
id: 3,
image: '/api/img/beras.png',
judul: 'beras Merah Organik',
harga: 'Rp. 40000 / 1 kg',
bintang: '4.9',
alamat: 'Jl. Mawar no.8'
},
{
id: 4,
image: '/api/img/genteng.png',
judul: 'Genteng',
harga: 'Rp. 3600 / pcs',
bintang: '4.9',
alamat: 'Jl. Kecubung no.16'
},
]
function Page() {
const [search, setSearch] = useState('');
const combobox = useCombobox({
onDropdownClose: () => {
combobox.resetSelectedOption();
combobox.focusTarget();
setSearch('');
},
onDropdownOpen: () => {
combobox.focusSearchInput();
},
});
const [value, setValue] = useState<string | null>(null);
const options = groceries
.filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))
.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
const router = useRouter()
const state = useProxy(pasarDesaState.pasarDesa)
const [search, setSearch] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const {
data,
page,
loading,
totalPages,
load,
} = state.findMany
useShallowEffect(() => {
pasarDesaState.kategoriProduk.findMany.load()
}, [])
// Filter data based on selected category
const filteredData = selectedCategory
? data?.filter(item =>
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
)
: data;
useShallowEffect(() => {
load(page, 4, search, selectedCategory || undefined)
}, [page, search, selectedCategory])
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Pasar Desa
</Text>
<Text px={{ base: 20, md: 150 }} ta={"center"} fz={{ base: "h4", md: "h3" }} >
<Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Pasar Desa
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Produk'
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
/>
</GridCol>
</Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz={{ base: "h4", md: "h3" }} >
Pasar Desa Online merupakan Media Promosi yang bertujuan untuk membantu warga desa dalam memasarkan dan memperkenalkan produknya kepada masyarakat.
</Text>
</Box>
@@ -98,48 +84,23 @@ function Page() {
}}
>
<Box>
<Combobox
store={combobox}
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
onClick={() => combobox.toggleDropdown()}
rightSectionPointerEvents="none"
>
{value || <InputPlaceholder>Kategori</InputPlaceholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Search
value={search}
onChange={(event) => setSearch(event.currentTarget.value)}
placeholder="Search groceries"
/>
<Combobox.Options>
{options.length > 0 ? options : <Combobox.Empty>Nothing found</Combobox.Empty>}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
</Box>
<Box>
<TextInput
placeholder='Cari Produk'
leftSection={<IconSearch size={20} />}
<Select
placeholder="Pilih Kategori"
data={pasarDesaState.kategoriProduk.findMany.data?.map((v) => ({
value: v.id,
label: v.nama
})) || []}
value={selectedCategory}
onChange={setSelectedCategory}
clearable
searchable
nothingFoundMessage="Tidak ada kategori ditemukan"
style={{ width: '100%' }}
/>
</Box>
</SimpleGrid>
<SimpleGrid cols={{ base: 1, md: 4 }}>
{dataBarang.map((v, k) => {
{filteredData?.map((v, k) => {
return (
<Stack key={k}>
<motion.div
@@ -148,18 +109,25 @@ function Page() {
whileTap={{ scale: 0.8 }}
>
<Paper p={'lg'}>
<Image radius={'lg'} src={v.image} alt='' />
<Text py={10} fw={'bold'} fz={'lg'}>{v.judul}</Text>
<Text fz={'md'}>{v.harga}</Text>
<Image
radius={'lg'}
src={v.image?.link || '/placeholder-product.jpg'}
alt={v.nama}
h={200}
w='100%'
style={{ objectFit: 'cover' }}
/>
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text>
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text>
<Flex py={10} gap={'md'}>
<IconStarFilled size={20} color='#EBCB09' />
<Text fz={'sm'} ml={2}>{v.bintang}</Text>
<Text fz={'sm'} ml={2}>{v.rating}</Text>
</Flex>
<Flex justify={'space-between'} align={'center'}>
<Box>
<Flex gap={'md'} align={'center'}>
<IconMapPinFilled size={20} color='red' />
<Text fz={'sm'} ml={2}>{v.alamat}</Text>
<Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text>
</Flex>
</Box>
<IconShoppingCartFilled size={20} color={colors['blue-button']} />
@@ -170,6 +138,14 @@ function Page() {
)
})}
</SimpleGrid>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
my="md"
/>
</Center>
</Stack>
</Box>
</Stack>

Some files were not shown because too many files have changed in this diff Show More