Compare commits
6 Commits
nico/20-au
...
nico/21-au
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a275c2a32 | |||
| 8469ebd2e1 | |||
| 760ba4b6d2 | |||
| 20d4c90e60 | |||
| fafbb12a08 | |||
| 01aa0da5cc |
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const APBDes = new Elysia({
|
||||
})
|
||||
|
||||
// ✅ Find all
|
||||
.get("/find-many", apbdesFindMany)
|
||||
.get("/findMany", apbdesFindMany)
|
||||
|
||||
// ✅ Find by ID
|
||||
.get("/:id", apbdesFindUnique)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
||||
@@ -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,
|
||||
take: limit,
|
||||
orderBy: { posisi: { hierarki: 'asc' } },
|
||||
}),
|
||||
prisma.pegawaiPPID.count({
|
||||
where: { isActive: true }
|
||||
where,
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
182
src/app/darmasaba/(pages)/ekonomi/jumlah-pengangguran/page.tsx
Normal file
182
src/app/darmasaba/(pages)/ekonomi/jumlah-pengangguran/page.tsx
Normal 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;
|
||||
@@ -1,54 +1,50 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, SimpleGrid, Paper } from '@mantine/core';
|
||||
import { Stack, Box, Text, SimpleGrid, Paper, Skeleton, Center } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { LineChart } from '@mantine/charts';
|
||||
import { CartesianGrid, Legend, Line, LineChart as RechartsLineChart, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
interface StatistikData {
|
||||
id: string;
|
||||
tahun: number;
|
||||
jumlah: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ProgramKemiskinanData {
|
||||
id: string;
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
ikonUrl: string | null;
|
||||
statistik: StatistikData | null;
|
||||
isActive: boolean;
|
||||
statistikId: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'Bantuan Tunai',
|
||||
deskripsi: 'Bantuan keuangan langsung bagi keluarga kurang mampu'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Pelatihan Kerja',
|
||||
deskripsi: 'Program pelatihan keterampilan untuk meningkatkan peluang kerja'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'Subsidi Pangan',
|
||||
deskripsi: 'Distribusi bahan pangan bersubsidi bagi masyarakat kurang mampu'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
judul: 'Layanan Kesehatan Gratis',
|
||||
deskripsi: 'Akses kesehatan gratis bagi masyarakat kurang mampu'
|
||||
},
|
||||
]
|
||||
const dataStatistik = [
|
||||
{
|
||||
id: 1,
|
||||
tahun: '2022',
|
||||
Kemiskinan: 400000
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tahun: '2023',
|
||||
Kemiskinan: 450000
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tahun: '2024',
|
||||
Kemiskinan: 500000
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
tahun: '2025',
|
||||
Kemiskinan: 400000
|
||||
},
|
||||
]
|
||||
function Page() {
|
||||
const state = useProxy(programKemiskinanState)
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findMany.load()
|
||||
}, [])
|
||||
|
||||
const data = programKemiskinanState.findMany.data
|
||||
|
||||
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 }}>
|
||||
@@ -69,26 +65,58 @@ function Page() {
|
||||
md: 2
|
||||
}}
|
||||
>
|
||||
{data.map((v, k) => {
|
||||
{state.findMany.data.map((v, k) => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.judul}</Text>
|
||||
<Text fz={'lg'} c={'black'}>{v.deskripsi}</Text>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
|
||||
<Text fz={'lg'} c={'black'} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Paper p={'xl'}>
|
||||
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Statistik Kemiskinan Masyarakat</Text>
|
||||
<LineChart
|
||||
h={300}
|
||||
data={dataStatistik}
|
||||
dataKey="tahun"
|
||||
series={[
|
||||
{ name: 'Kemiskinan', color: colors['blue-button'] },
|
||||
]}
|
||||
curveType="linear"
|
||||
/>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text>
|
||||
<Box style={{ width: '100%', height: 'auto' }}>
|
||||
{state.findMany.data.length > 0 && state.findMany.data[0]?.statistik ? (
|
||||
<Box w="100%" style={{ overflowX: 'auto' }}>
|
||||
<Center>
|
||||
<RechartsLineChart
|
||||
width={800}
|
||||
height={300}
|
||||
data={state.findMany.data
|
||||
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } =>
|
||||
item.statistik !== null
|
||||
)
|
||||
.map(item => ({
|
||||
tahun: item.statistik.tahun,
|
||||
jumlah: item.statistik.jumlah
|
||||
}))
|
||||
.sort((a, b) => a.tahun - b.tahun)
|
||||
}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="tahun" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
formatter={(value: number, name: string) => [`${value} orang`, name]}
|
||||
labelFormatter={(label: number) => `Tahun: ${label}`}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
name="Jumlah Masyarakat Miskin"
|
||||
stroke={colors['blue-button']}
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
</RechartsLineChart>
|
||||
</Center>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data statistik yang tersedia</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, Paper } from '@mantine/core';
|
||||
import { Stack, Box, Text, Paper, 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 grafikSektorUnggulan from '@/app/admin/(dashboard)/_state/ekonomi/sektor-unggulan-desa';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
sektor: 'Sektor Pertanian',
|
||||
Ton: 20
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sektor: 'Sektor Peternakan',
|
||||
Ton: 5
|
||||
},
|
||||
|
||||
]
|
||||
function Page() {
|
||||
const state = useProxy(grafikSektorUnggulan)
|
||||
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
} = state.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findMany.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 }}>
|
||||
@@ -31,28 +41,24 @@ function Page() {
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Paper p={'xl'}>
|
||||
<Text fw={'bold'} fz={'h4'}>Jumlah Penduduk Miskin Per Tahun</Text>
|
||||
<Text fz={'h4'} ta={'justify'}>
|
||||
Pertanian di Darmasaba berfokus pada padi, sayuran, dan hortikultura yang dikembangkan dengan metode pertanian
|
||||
organik serta sistem irigasi tradisional yang efisien. Keberlanjutan dalam pertanian juga didukung dengan pemanfaatan
|
||||
teknologi modern untuk meningkatkan produktivitas hasil panen.
|
||||
</Text>
|
||||
</Paper>
|
||||
<Paper p={'xl'}>
|
||||
<Text fw={'bold'} fz={'h4'}>Sektor Peternakan</Text>
|
||||
<Text fz={'h4'} ta={'justify'}>
|
||||
Di bidang peternakan, Desa Darmasaba memiliki potensi besar dalam pengembangan sapi, ayam, dan babi. Sistem
|
||||
peternakan yang diterapkan mengutamakan pengelolaan pakan alami dan perawatan hewan yang sehat, sehingga
|
||||
menghasilkan produk ternak berkualitas tinggi.
|
||||
</Text>
|
||||
</Paper>
|
||||
{data.map((v, k) => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Text fw={'bold'} fz={'h4'}>{v.name}</Text>
|
||||
<Text fz={'h4'} ta={'justify'} dangerouslySetInnerHTML={{ __html: v.description || '' }} />
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
<Paper p={'xl'}>
|
||||
<Text pb={10} fw={'bold'} fz={'h4'}>Statistik Sektor Unggulan Darmasaba</Text>
|
||||
<BarChart
|
||||
p={10}
|
||||
h={300}
|
||||
data={data}
|
||||
data={data.map((item) => ({
|
||||
id: item.id,
|
||||
sektor: item.name,
|
||||
Ton: item.value,
|
||||
}))}
|
||||
dataKey="sektor"
|
||||
series={[
|
||||
{ name: 'Ton', color: colors['blue-button'] },
|
||||
|
||||
@@ -25,9 +25,9 @@ function ArtikelKesehatanPage() {
|
||||
<Box>
|
||||
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
|
||||
<Stack gap={'xs'}>
|
||||
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
|
||||
{state.findMany.data.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
|
||||
<Image pt={5} src={'/api/img/dbd.png'} alt="" />
|
||||
<Text fz={'h4'} fw={'bold'} >
|
||||
{item.title}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Divider, Group, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Divider, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -139,14 +139,14 @@ function Page() {
|
||||
Email : <Text span fz={'h4'}>puskesmasabiansemal3@gmail.com</Text>
|
||||
</Text>
|
||||
</Paper>
|
||||
<Paper p={'lg'} w={{ base: "100%", md: "100%" }}>
|
||||
{/* <Paper p={'lg'} w={{ base: "100%", md: "100%" }}>
|
||||
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3945.272172359321!2d115.21836257533302!3d-8.569807186941553!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23e9d99b9395f%3A0xb002795fdcb33b30!2sUPTD%20Puskesmas%20Abiansemal%20III!5e0!3m2!1sid!2sid!4v1744792682341!5m2!1sid!2sid" width="600" height="450" style={{ border: 2, width: "100%", borderRadius: 10 }} loading="lazy"></iframe>
|
||||
</Paper>
|
||||
<Group>
|
||||
<Button fz={'lg'} bg={colors['blue-button']}>
|
||||
Download Brosur Layanan (PDF)
|
||||
</Button>
|
||||
</Group>
|
||||
</Group> */}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Flex, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarkanJenisKelamin() {
|
||||
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, [])
|
||||
|
||||
const updateChartData = (data: any) => {
|
||||
if (data && data.length > 0) {
|
||||
const totalLaki = data.reduce((acc: number, cur: any) => acc + Number(cur.laki || 0), 0);
|
||||
const totalPerempuan = data.reduce((acc: number, cur: any) => acc + Number(cur.perempuan || 0), 0);
|
||||
|
||||
setDonutData([
|
||||
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'], key: 'laki-laki' },
|
||||
{ name: 'Perempuan', value: totalPerempuan, color: '#FF6384', key: 'perempuan' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
await stategrafikBerdasarkanJenisKelamin.findMany.load();
|
||||
if (stategrafikBerdasarkanJenisKelamin.findMany.data) {
|
||||
updateChartData(stategrafikBerdasarkanJenisKelamin.findMany.data);
|
||||
}
|
||||
};
|
||||
|
||||
if(!stategrafikBerdasarkanJenisKelamin.findMany.data) return <Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Jenis Kelamin Responden</Title>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
|
||||
return (
|
||||
<Stack gap={"xl"}>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Jenis Kelamin Responden</Title>
|
||||
{mounted && donutData.length > 0 && (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 300 }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
width={1000} height={530}
|
||||
data={donutData}
|
||||
>
|
||||
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
innerRadius={120}
|
||||
outerRadius={230}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</Center>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#FF6384'} w={20} h={20} />
|
||||
<Text>Perempuan: {donutData.find((entry) => entry.name === 'Perempuan')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Laki-laki: {donutData.find((entry) => entry.name === 'Laki-laki')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanJenisKelamin;
|
||||
@@ -1,96 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikBerdasarkanResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Flex, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarkanResponden() {
|
||||
const stategrafikBerdasarkanResponden = useProxy(grafikBerdasarkanResponden)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, [])
|
||||
|
||||
const updateChartData = (data: any) => {
|
||||
if (data && data.length > 0) {
|
||||
const totalSangatBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.sangatbaik || 0), 0);
|
||||
const totalBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.baik || 0), 0);
|
||||
const totalKurangBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
||||
const totalTidakBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.tidakbaik || 0), 0);
|
||||
setDonutData([
|
||||
{ name: 'sangatbaik', value: totalSangatBaik, color: colors['blue-button'], key: 'sangatbaik' },
|
||||
{ name: 'baik', value: totalBaik, color: '#10A85AFF', key: 'baik' },
|
||||
{ name: 'kurangbaik', value: totalKurangBaik, color: '#B3AA12FF', key: 'kurangbaik' },
|
||||
{ name: 'tidakbaik', value: totalTidakBaik, color: '#B21313FF', key: 'tidakbaik' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
await stategrafikBerdasarkanResponden.findMany.load();
|
||||
if (stategrafikBerdasarkanResponden.findMany.data) {
|
||||
updateChartData(stategrafikBerdasarkanResponden.findMany.data);
|
||||
}
|
||||
};
|
||||
|
||||
if (!stategrafikBerdasarkanResponden.findMany.data) return <Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
return (
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||
{mounted && donutData.length > 0 && (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 300 }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
width={1000} height={530}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
innerRadius={120}
|
||||
outerRadius={230}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</Center>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Sangat Baik: {donutData.find((entry) => entry.name === 'sangatbaik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>Baik: {donutData.find((entry) => entry.name === 'baik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B3AA12FF'} w={20} h={20} />
|
||||
<Text>Kurang Baik: {donutData.find((entry) => entry.name === 'kurangbaik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B21313FF'} w={20} h={20} />
|
||||
<Text>Tidak Baik: {donutData.find((entry) => entry.name === 'tidakbaik')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanResponden;
|
||||
@@ -1,97 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Flex, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarakanUmur() {
|
||||
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const updateChartData = (data: any) => {
|
||||
if (data && data.length > 0) {
|
||||
const totalRemaja = data.reduce((acc: number, cur: any) => acc + Number(cur.remaja || 0), 0);
|
||||
const totalDewasa = data.reduce((acc: number, cur: any) => acc + Number(cur.dewasa || 0), 0);
|
||||
const totalOrangtua = data.reduce((acc: number, cur: any) => acc + Number(cur.orangtua || 0), 0);
|
||||
const totalLansia = data.reduce((acc: number, cur: any) => acc + Number(cur.lansia || 0), 0);
|
||||
|
||||
setDonutData([
|
||||
{ name: 'Remaja', value: totalRemaja, color: colors['blue-button'], key: 'remaja' },
|
||||
{ name: 'Dewasa', value: totalDewasa, color: '#D32711FF', key: 'dewasa' },
|
||||
{ name: 'Orangtua', value: totalOrangtua, color: '#B46B04FF', key: 'orangtua' },
|
||||
{ name: 'Lansia', value: totalLansia, color: '#038617FF', key: 'lansia' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
await stategrafikBerdasarkanUmur.findMany.load();
|
||||
if (stategrafikBerdasarkanUmur.findMany.data) {
|
||||
updateChartData(stategrafikBerdasarkanUmur.findMany.data);
|
||||
}
|
||||
}
|
||||
|
||||
if(!stategrafikBerdasarkanUmur.findMany.data) return <Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Umur Responden</Title>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
return (
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Umur Responden</Title>
|
||||
{mounted && donutData.length > 0 && (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 300 }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
width={1000} height={530}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
innerRadius={120}
|
||||
outerRadius={230}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</Center>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>17 - 25 tahun: {donutData.find((entry) => entry.name === 'remaja')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#D32711FF'} w={20} h={20} />
|
||||
<Text>26 - 45 tahun: {donutData.find((entry) => entry.name === 'dewasa')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B46B04FF'} w={20} h={20} />
|
||||
<Text>46 - 60 tahun: {donutData.find((entry) => entry.name === 'orangtua')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#038617FF'} w={20} h={20} />
|
||||
<Text>di atas 60 tahun: {donutData.find((entry) => entry.name === 'lansia')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarakanUmur;
|
||||
@@ -1,57 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikHasilKepuasan() {
|
||||
const grafikHasilKepuasan = useProxy(grafikHasilKepuasanMasyarakat)
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, [])
|
||||
|
||||
useShallowEffect(() => {
|
||||
const fetchData = async () => {
|
||||
await grafikHasilKepuasan.findMany.load();
|
||||
if (grafikHasilKepuasan.findMany.data && grafikHasilKepuasan.findMany.data.length > 0) {
|
||||
setChartData(grafikHasilKepuasan.findMany.data);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
if(!grafikHasilKepuasan.findMany.data) return <Stack>
|
||||
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
|
||||
return (
|
||||
<Stack gap={"xl"}>
|
||||
<Text fw={"bold"} fz={{ base: 'h4', md: 'h3' }} ta={"center"}>
|
||||
Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik
|
||||
</Text>
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<BarChart width={isMobile ? 300 : isTablet ? 400 : 450} height={380} data={chartData} >
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend style={{justifyContent: 'center'}} />
|
||||
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
||||
</BarChart>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikHasilKepuasan;
|
||||
@@ -1,47 +1,674 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Paper, Stack, Text } from '@mantine/core';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import GrafikBerdasarkanJenisKelamin from './grafik_berdasarkan_jenis_kelamin/page';
|
||||
import GrafikBerdasarkanResponden from './grafik_berdasarkan_pilihan_responden/page';
|
||||
import GrafikBerdasarakanUmur from './grafik_berdasarkan_umur_responden/page';
|
||||
import GrafikHasilKepuasan from './grafik_hasil_kepuasan_masyarakat/page';
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan";
|
||||
import colors from "@/con/colors";
|
||||
import { BarChart, PieChart } from '@mantine/charts';
|
||||
import { Box, Button, Center, Container, Flex, Group, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||
import { useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
|
||||
function Page() {
|
||||
interface ChartDataItem {
|
||||
name: string;
|
||||
value: number;
|
||||
color: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Kepuasan() {
|
||||
const state = useProxy(indeksKepuasanState.responden);
|
||||
const { data, loading } = state.findMany;
|
||||
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
||||
const [opened, { open, close }] = useDisclosure(false)
|
||||
|
||||
const resetForm = () => {
|
||||
state.create.form = {
|
||||
...state.create.form,
|
||||
name: "",
|
||||
tanggal: "",
|
||||
jenisKelaminId: "",
|
||||
ratingId: "",
|
||||
kelompokUmurId: "",
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
indeksKepuasanState.jenisKelaminResponden.findMany.load()
|
||||
indeksKepuasanState.pilihanRatingResponden.findMany.load()
|
||||
indeksKepuasanState.kelompokUmurResponden.findMany.load()
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const id = await state.create.create();
|
||||
if (typeof id !== 'undefined') {
|
||||
const idStr = String(id);
|
||||
await state.findUnique.load(idStr);
|
||||
}
|
||||
resetForm();
|
||||
close()
|
||||
} catch (error) {
|
||||
console.error('Error submitting form:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load data on component mount
|
||||
useShallowEffect(() => {
|
||||
if (!data && !loading) {
|
||||
state.findMany.load(1, 1000); // Load first page with a large limit to get all data
|
||||
return;
|
||||
}
|
||||
|
||||
if (data && data.length > 0) {
|
||||
// Hitung total berdasarkan jenis kelamin
|
||||
const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length;
|
||||
const totalPerempuan = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'perempuan').length;
|
||||
|
||||
// Hitung total berdasarkan rating
|
||||
const totalSangatBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat baik').length;
|
||||
const totalBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'baik').length;
|
||||
const totalKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'kurang baik').length;
|
||||
const totalSangatKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat kurang baik').length;
|
||||
|
||||
// Hitung total berdasarkan kelompok umur
|
||||
const totalMuda = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'muda').length;
|
||||
const totalDewasa = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'dewasa').length;
|
||||
const totalLansia = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'lansia').length;
|
||||
|
||||
// Update gender chart data
|
||||
setDonutDataJenisKelamin([
|
||||
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] },
|
||||
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
|
||||
]);
|
||||
|
||||
// Update rating chart data
|
||||
setDonutDataRating([
|
||||
{ name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] },
|
||||
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
|
||||
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
|
||||
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
|
||||
]);
|
||||
|
||||
// Update age group chart data
|
||||
setDonutDataKelompokUmur([
|
||||
{ name: 'Muda', value: totalMuda, color: colors['blue-button'] },
|
||||
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
|
||||
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
|
||||
]);
|
||||
|
||||
// Process data for bar chart (group by month)
|
||||
const monthYearMap = new Map<string, number>();
|
||||
|
||||
data.forEach((item: any) => {
|
||||
// Try both createdAt and tanggal fields
|
||||
const dateValue = item.tanggal || item.createdAt;
|
||||
if (!dateValue) return;
|
||||
|
||||
const parsedDate = new Date(dateValue);
|
||||
if (isNaN(parsedDate.getTime())) return;
|
||||
|
||||
const month = parsedDate.getMonth() + 1;
|
||||
const year = parsedDate.getFullYear();
|
||||
const monthYearKey = `${year}-${String(month).padStart(2, '0')}`;
|
||||
|
||||
monthYearMap.set(monthYearKey, (monthYearMap.get(monthYearKey) || 0) + 1);
|
||||
});
|
||||
|
||||
// Convert map to array and sort by date
|
||||
const barData = Array.from(monthYearMap.entries())
|
||||
.map(([key, count]) => {
|
||||
const [year, month] = key.split('-');
|
||||
const monthName = new Date(Number(year), Number(month) - 1, 1)
|
||||
.toLocaleString('id-ID', { month: 'long' });
|
||||
return {
|
||||
month: `${monthName} ${year}`,
|
||||
count,
|
||||
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.sortKey - b.sortKey)
|
||||
.map(({ month, count }) => ({ month, count }));
|
||||
|
||||
setBarChartData(barData);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if ((loading && !data) || !data) {
|
||||
return (
|
||||
<Stack py={10} px="xl">
|
||||
<Skeleton height={300} mb="md" />
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={300} />
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack p="sm">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Center>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
</Center>
|
||||
<Center mt={10}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Flex justify={"space-between"} align={"center"}>
|
||||
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString('id-ID')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Box py={"xl"}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 1,
|
||||
lg: 1,
|
||||
xl: 3
|
||||
}}
|
||||
>
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Jenis Kelamin</Title>
|
||||
{donutDataJenisKelamin.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={200}
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataJenisKelamin.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart Rating */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Pilihan</Title>
|
||||
{donutDataRating.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={200}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataRating.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart Kelompok Umur */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Umur</Title>
|
||||
{donutDataKelompokUmur.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={190}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataKelompokUmur.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Modal */}
|
||||
<Modal opened={opened} onClose={close} title="Ajukan Responden" centered>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Nama"
|
||||
type='text'
|
||||
placeholder="masukkan nama"
|
||||
value={state.create.form.name}
|
||||
onChange={(val) => {
|
||||
state.create.form.name = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal"
|
||||
type="date"
|
||||
placeholder="masukkan tanggal"
|
||||
value={state.create.form.tanggal}
|
||||
onChange={(val) => {
|
||||
state.create.form.tanggal = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
key={"jenisKelamin"}
|
||||
label={"Jenis Kelamin"}
|
||||
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
|
||||
value={state.create.form.jenisKelaminId || ""}
|
||||
onChange={(val) => {
|
||||
state.create.form.jenisKelaminId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"rating_responden"}
|
||||
label={"Rating"}
|
||||
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
|
||||
value={state.create.form.ratingId || ""}
|
||||
onChange={(val) => {
|
||||
state.create.form.ratingId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"kelompokUmur"}
|
||||
label={"Kelompok Umur"}
|
||||
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
|
||||
value={state.create.form.kelompokUmurId || ""}
|
||||
onChange={(val) => {
|
||||
state.create.form.kelompokUmurId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Modal>
|
||||
</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: "h2", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Indeks Kepuasan Masyarakat (IKM)
|
||||
</Text>
|
||||
<Text ta={"center"} fz={{ base: "h2", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Desa Darmasaba
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Paper bg={colors['white-1']} p={'xl'}>
|
||||
<GrafikHasilKepuasan />
|
||||
<Stack p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Group justify={"center"}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Flex justify={"space-between"} align={"center"}>
|
||||
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString('id-ID')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Box py={"xl"}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 1,
|
||||
lg: 1,
|
||||
xl: 3
|
||||
}}
|
||||
>
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Jenis Kelamin</Title>
|
||||
{donutDataJenisKelamin.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={200}
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataJenisKelamin.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart Rating */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Pilihan</Title>
|
||||
{donutDataRating.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={200}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataRating.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart Kelompok Umur */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={4}>Umur</Title>
|
||||
{donutDataKelompokUmur.every(item => item.value === 0) ? (
|
||||
<Text c="dimmed" ta="center" my="md">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={190}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataKelompokUmur.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<GrafikBerdasarkanJenisKelamin/>
|
||||
{/* Modal */}
|
||||
<Modal opened={opened} onClose={close} title="Ajukan Responden" centered>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Nama"
|
||||
type='text'
|
||||
placeholder="masukkan nama"
|
||||
value={state.create.form.name}
|
||||
onChange={(val) => {
|
||||
state.create.form.name = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal"
|
||||
type="date"
|
||||
placeholder="masukkan tanggal"
|
||||
value={state.create.form.tanggal}
|
||||
onChange={(val) => {
|
||||
state.create.form.tanggal = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
key={"jenisKelamin"}
|
||||
label={"Jenis Kelamin"}
|
||||
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
|
||||
value={state.create.form.jenisKelaminId || ""}
|
||||
onChange={(val) => {
|
||||
state.create.form.jenisKelaminId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"rating_responden"}
|
||||
label={"Rating"}
|
||||
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
|
||||
value={state.create.form.ratingId || ""}
|
||||
onChange={(val) => {
|
||||
state.create.form.ratingId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"kelompokUmur"}
|
||||
label={"Kelompok Umur"}
|
||||
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
|
||||
value={state.create.form.kelompokUmurId || ""}
|
||||
onChange={(val) => {
|
||||
state.create.form.kelompokUmurId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<GrafikBerdasarkanResponden/>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<GrafikBerdasarakanUmur/>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Modal>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Kepuasan;
|
||||
|
||||
@@ -197,8 +197,8 @@ const navbarListMenu = [
|
||||
},
|
||||
{
|
||||
id: "5.5",
|
||||
name: "Jumlah Pengangguran 2024-2025",
|
||||
href: "/darmasaba/ekonomi/jumlah-pengangguran-2024-2025"
|
||||
name: "Jumlah Pengangguran",
|
||||
href: "/darmasaba/ekonomi/jumlah-pengangguran"
|
||||
},
|
||||
{
|
||||
id: "5.6",
|
||||
@@ -207,8 +207,8 @@ const navbarListMenu = [
|
||||
},
|
||||
{
|
||||
id: "5.7",
|
||||
name: "Jumlah Penduduk Miskin 2024-2025",
|
||||
href: "/darmasaba/ekonomi/jumlah-penduduk-miskin-2024-2025"
|
||||
name: "Jumlah Penduduk Miskin",
|
||||
href: "/darmasaba/ekonomi/jumlah-penduduk-miskin"
|
||||
},
|
||||
{
|
||||
id: "5.8",
|
||||
|
||||
Reference in New Issue
Block a user