Sinkronisasi UI & API Admin - User Submenu Data Kesehatan Warga

-Dibagian Tanggal Gak Auto Ngambil Tanggal Yang Udah Dipakai
-Dibagian fasilitas kesehatan : data dokter dan tarif rencananya mau pakai select
This commit is contained in:
2025-08-15 14:07:56 +08:00
parent d7a592c635
commit 8d15563f15
31 changed files with 1778 additions and 536 deletions

View File

@@ -951,13 +951,16 @@ model Kematian {
// ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan {
id String @id @default(cuid())
label String
jumlah String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
nama String
tanggal DateTime
jenisKelamin String
alamat String
penyakit String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= ARTIKEL KESEHATAN ========================================= //

View File

@@ -5,6 +5,7 @@ import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
//fasilitas kesehatan aja
// Validasi form
const templateForm = z.object({
name: z.string().min(1, "Nama harus diisi"),
@@ -61,7 +62,7 @@ const defaultForm = {
},
};
const fasilitasKesehatanState = proxy({
const fasilitasKesehatan = proxy({
create: {
form: { ...defaultForm },
loading: false,
@@ -86,7 +87,7 @@ const fasilitasKesehatanState = proxy({
if (res.status === 200) {
toast.success("Berhasil menambahkan fasilitas kesehatan");
this.resetForm();
await fasilitasKesehatanState.findMany.load();
await fasilitasKesehatan.findMany.load();
return res.data;
}
} catch (err: any) {
@@ -102,7 +103,6 @@ const fasilitasKesehatanState = proxy({
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.FasilitasKesehatanGetPayload<{
@@ -156,7 +156,7 @@ const fasilitasKesehatanState = proxy({
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
fasilitasKesehatanState.findUnique.data = data.data ?? null;
fasilitasKesehatan.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data fasilitas kesehatan");
}
@@ -176,8 +176,8 @@ const fasilitasKesehatanState = proxy({
const result = await res.json();
const data = result.data;
fasilitasKesehatanState.edit.id = data.id;
fasilitasKesehatanState.edit.form = {
fasilitasKesehatan.edit.id = data.id;
fasilitasKesehatan.edit.form = {
name: data.name,
informasiUmum: {
fasilitas: data.informasiumum.fasilitas,
@@ -205,7 +205,7 @@ const fasilitasKesehatanState = proxy({
};
},
async submit() {
const cek = templateForm.safeParse(fasilitasKesehatanState.edit.form);
const cek = templateForm.safeParse(fasilitasKesehatan.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
@@ -215,42 +215,38 @@ const fasilitasKesehatanState = proxy({
}
try {
fasilitasKesehatanState.edit.loading = true;
fasilitasKesehatan.edit.loading = true;
const payload = {
name: fasilitasKesehatanState.edit.form.name,
name: fasilitasKesehatan.edit.form.name,
informasiUmum: {
fasilitas:
fasilitasKesehatanState.edit.form.informasiUmum.fasilitas,
alamat: fasilitasKesehatanState.edit.form.informasiUmum.alamat,
fasilitas: fasilitasKesehatan.edit.form.informasiUmum.fasilitas,
alamat: fasilitasKesehatan.edit.form.informasiUmum.alamat,
jamOperasional:
fasilitasKesehatanState.edit.form.informasiUmum.jamOperasional,
fasilitasKesehatan.edit.form.informasiUmum.jamOperasional,
},
layananUnggulan: {
content: fasilitasKesehatanState.edit.form.layananUnggulan.content,
content: fasilitasKesehatan.edit.form.layananUnggulan.content,
},
dokterdanTenagaMedis: {
name: fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.name,
name: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.name,
specialist:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.specialist,
jadwal:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.jadwal,
fasilitasKesehatan.edit.form.dokterdanTenagaMedis.specialist,
jadwal: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.jadwal,
},
fasilitasPendukung: {
content:
fasilitasKesehatanState.edit.form.fasilitasPendukung.content,
content: fasilitasKesehatan.edit.form.fasilitasPendukung.content,
},
prosedurPendaftaran: {
content:
fasilitasKesehatanState.edit.form.prosedurPendaftaran.content,
content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content,
},
tarifDanLayanan: {
layanan: fasilitasKesehatanState.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatanState.edit.form.tarifDanLayanan.tarif,
layanan: fasilitasKesehatan.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatan.edit.form.tarifDanLayanan.tarif,
},
};
const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatanState.edit.id}`,
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatan.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
@@ -264,7 +260,7 @@ const fasilitasKesehatanState = proxy({
}
toast.success("Berhasil update fasilitas kesehatan");
await fasilitasKesehatanState.findMany.load();
await fasilitasKesehatan.findMany.load();
return true;
} catch (err) {
toast.error(
@@ -272,37 +268,297 @@ const fasilitasKesehatanState = proxy({
);
return false;
} finally {
fasilitasKesehatanState.edit.loading = false;
fasilitasKesehatan.edit.loading = false;
}
},
resetForm() {
fasilitasKesehatanState.edit.id = "";
fasilitasKesehatanState.edit.form = { ...defaultForm };
fasilitasKesehatan.edit.id = "";
fasilitasKesehatan.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string){
async byId(id: string) {
try {
fasilitasKesehatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/del/${id}`, {
method: "DELETE",
});
fasilitasKesehatan.delete.loading = true;
const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result.success) {
toast.success("Fasilitas kesehatan berhasil dihapus");
await fasilitasKesehatanState.findMany.load();
await fasilitasKesehatan.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
fasilitasKesehatanState.delete.loading = false;
fasilitasKesehatan.delete.loading = false;
}
}
},
},
});
//dokter & tenaga medis
const templateDokterForm = z.object({
name: z.string().min(1, "Nama tidak boleh kosong"),
specialist: z.string().min(1, "Spesialis tidak boleh kosong"),
jadwal: z.string().min(1, "Jadwal tidak boleh kosong"),
});
const defaultDokterForm = {
name: "",
specialist: "",
jadwal: "",
};
const dokter = proxy({
create: {
create: {
form: defaultDokterForm,
loading: false,
async create() {
const cek = templateDokterForm.safeParse(dokter.create.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
dokter.create.create.loading = true;
const res = await ApiFetch.api.kesehatan.doktertenagamedis[
"create"
].post(dokter.create.create.form);
if (res.status === 200) {
const id = res.data?.data;
if (id) {
toast.success("Success create");
dokter.create.create.form = { ...defaultDokterForm };
dokter.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
dokter.create.create.loading = false;
}
},
},
},
findMany: {
data: null as
| Prisma.DokterdanTenagaMedisGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
dokter.findMany.loading = true; // ✅ Akses langsung via nama path
dokter.findMany.page = page;
dokter.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.doktertenagamedis[
"findMany"
].get({ query });
if (res.status === 200 && res.data?.success) {
dokter.findMany.data = res.data.data ?? [];
dokter.findMany.totalPages = res.data.totalPages ?? 1;
} else {
dokter.findMany.data = [];
dokter.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch dokter tenaga medis paginated:", err);
dokter.findMany.data = [];
dokter.findMany.totalPages = 1;
} finally {
dokter.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.DokterdanTenagaMedisGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/doktertenagamedis/${id}`);
if (res.ok) {
const data = await res.json();
dokter.findUnique.data = data.data ?? null;
} else {
console.error(
"Failed to fetch dokter dan tenaga medis",
res.statusText
);
dokter.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching dokter dan tenaga medis", error);
dokter.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultDokterForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/doktertenagamedis/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
specialist: data.specialist,
jadwal: data.jadwal,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading dokter dan tenaga medis:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
name: this.form.name,
specialist: this.form.specialist,
jadwal: this.form.jadwal,
};
const cek = templateDokterForm.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/doktertenagamedis/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await dokter.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data dokter dan tenaga medis");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
dokter.delete.loading = true;
const response = await fetch(
`/api/kesehatan/doktertenagamedis/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Dokter dan tenaga medis berhasil dihapus"
);
await dokter.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus dokter dan tenaga medis"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus dokter dan tenaga medis");
} finally {
dokter.delete.loading = false;
}
},
},
});
const fasilitasKesehatanState = proxy({
fasilitasKesehatan,
dokter
});
export default fasilitasKesehatanState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,20 +6,19 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikKepuasan = z.object({
label: z.string().min(2, "Label harus diisi"),
jumlah: z.string().min(1, "Jumlah harus diisi"),
nama: z.string().min(2, "Nama harus diisi"),
tanggal: z.string().min(1, "Tanggal harus diisi"),
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
alamat: z.string().min(1, "Alamat harus diisi"),
penyakit: z.string().min(1, "Penyakit harus diisi"),
});
type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{
select: {
label: true;
jumlah: true;
};
}>;
const defaultForm: GrafikKepuasan = {
label: "",
jumlah: ""
const defaultForm = {
nama: "",
tanggal: "",
jenisKelamin: "",
alamat: "",
penyakit: "",
};
const grafikkepuasan = proxy({
@@ -36,16 +36,15 @@ const grafikkepuasan = proxy({
}
try {
grafikkepuasan.create.loading = true;
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(grafikkepuasan.create.form);
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(
grafikkepuasan.create.form
);
if (res.status === 200) {
const id = res.data?.data?.id;
const id = res.data?.data;
if (id) {
toast.success("Success create");
grafikkepuasan.create.form = {
label: "",
jumlah: "",
};
grafikkepuasan.create.form = { ...defaultForm };
grafikkepuasan.findMany.load();
return id;
}
@@ -62,21 +61,49 @@ const grafikkepuasan = proxy({
},
findMany: {
data: null as
| Prisma.GrafikKepuasanGetPayload<{ omit: { isActive: true } }>[]
| Prisma.GrafikKepuasanGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
"find-many"
].get();
if (res.status === 200) {
grafikkepuasan.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
grafikkepuasan.findMany.loading = true; // ✅ Akses langsung via nama path
grafikkepuasan.findMany.page = page;
grafikkepuasan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
grafikkepuasan.findMany.data = res.data.data ?? [];
grafikkepuasan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
grafikkepuasan.findMany.data = [];
grafikkepuasan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
grafikkepuasan.findMany.data = [];
grafikkepuasan.findMany.totalPages = 1;
} finally {
grafikkepuasan.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.GrafikKepuasanGetPayload<{
omit: { isActive: true }
}> | null,
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
@@ -95,88 +122,137 @@ const grafikkepuasan = proxy({
},
update: {
id: "",
form: {...defaultForm},
form: { ...defaultForm },
loading: false,
async byId() {
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
this.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
toast.success("Berhasil update data!");
try {
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
await grafikkepuasan.findMany.load();
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik kepuasan");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const result = await response.json();
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Grafik kepuasan berhasil dihapus"
);
await grafikkepuasan.findMany.load(); // refresh list
} else {
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
tanggal: data.tanggal,
jenisKelamin: data.jenisKelamin,
alamat: data.alamat,
penyakit: data.penyakit,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading grafik kepuasan:", error);
toast.error(
result?.message || "Gagal menghapus grafik kepuasan"
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
} finally {
grafikkepuasan.delete.loading = false;
}
}
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
nama: this.form.nama,
tanggal: this.form.tanggal,
jenisKelamin: this.form.jenisKelamin,
alamat: this.form.alamat,
penyakit: this.form.penyakit,
};
const cek = templateGrafikKepuasan.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await grafikkepuasan.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data grafik kepuasan");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const response = await fetch(
`/api/kesehatan/grafikkepuasan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik kepuasan berhasil dihapus");
await grafikkepuasan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik kepuasan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
} finally {
grafikkepuasan.delete.loading = false;
}
},
},
});
export default grafikkepuasan;

View File

@@ -39,7 +39,7 @@ interface FasilitasKesehatanFormBase {
}
function EditFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState);
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const router = useRouter();
const params = useParams();

View File

@@ -2,7 +2,7 @@
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Button, Flex, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -12,7 +12,7 @@ import { useProxy } from 'valtio/utils';
function DetailFasilitasKesehatan() {
const params = useParams()
const router = useRouter();
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null)
@@ -45,9 +45,23 @@ function DetailFasilitasKesehatan() {
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
<Grid>
<GridCol span={12}>
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
</GridCol>
<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
</Button>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/layanan-unggulan/create`)}>
Tambah Layanan
</Button>
</Flex>
</GridCol>
</Grid>
{stateFasilitasKesehatan.findUnique.data ? (
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
@@ -68,6 +82,14 @@ function DetailFasilitasKesehatan() {
<Text fz={"md"} fw={"bold"}>Layanan Unggulan</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.layananunggulan.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Dokter dan Tenaga Medis</Text>
<Text fz={"md"} fw={"bold"}>Nama Dokter</Text>
@@ -77,14 +99,6 @@ function DetailFasilitasKesehatan() {
<Text fz={"md"} fw={"bold"}>Jadwal</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.jadwal}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tarif dan Layanan</Text>
<Text fz={"md"} fw={"bold"}>Layanan</Text>
@@ -111,6 +125,7 @@ function DetailFasilitasKesehatan() {
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Hapus */}

View File

@@ -10,7 +10,7 @@ import { useProxy } from 'valtio/utils';
function CreateFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const router = useRouter();

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -0,0 +1,75 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateDokter() {
const params = useParams()
const createState = useProxy(fasilitasKesehatanState.dokter)
const router = useRouter();
const resetForm = () => {
createState.create.create.form = {
name: "",
specialist: "",
jadwal: "",
};
};
const handleSubmit = async () => {
await createState.create.create.create();
resetForm();
router.push(`/admin/kesehatan/fasilitas-kesehatan/${params?.id}/dokter-tenaga-medis`)
};
return (
<Box component="form" onSubmit={handleSubmit}>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Create Dokter</Title>
<TextInput
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
placeholder="masukkan nama dokter"
value={createState.create.create.form.name}
onChange={(e) => {
createState.create.create.form.name = e.target.value;
}}
/>
<Text fz="md" fw="bold">Specialist</Text>
<TextInput
label={<Text fz="sm" fw="bold">Specialist</Text>}
placeholder="masukkan specialist"
value={createState.create.create.form.specialist}
onChange={(e) => {
createState.create.create.form.specialist = e.target.value;
}}
/>
<Box>
<Text fz="md" fw="bold">Jadwal</Text>
<CreateEditor
value={createState.create.create.form.jadwal}
onChange={(htmlContent) => {
createState.create.create.form.jadwal = htmlContent;
}}
/>
</Box>
<Button onClick={handleSubmit} bg={colors['blue-button']}>
Simpan
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreateDokter;

View File

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

View File

@@ -1,8 +1,8 @@
'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, Flex, Grid, GridCol, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { IconDeviceImacCog, IconList, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
@@ -13,22 +13,37 @@ import { useState } from 'react';
function FasilitasKesehatan() {
const [search, setSearch] = useState("");
const router = useRouter();
return (
<Box>
<HeaderSearch
title='Fasilitas Kesehatan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListFasilitasKesehatan search={search}/>
<Grid>
<GridCol span={12}>
<HeaderSearch
title='Fasilitas Kesehatan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</GridCol>
<GridCol span={12}>
<Flex gap={"xs"}>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis`)}>
<IconList size={20} /> List Dokter
</Button>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan`)}>
<IconList size={20} /> List Layanan
</Button>
</Flex>
</GridCol>
</Grid>
<ListFasilitasKesehatan search={search} />
</Box>
);
}
function ListFasilitasKesehatan({ search }: { search: string }) {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const router = useRouter();
useShallowEffect(() => {
@@ -47,47 +62,47 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
if (!stateFasilitasKesehatan.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500}/>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Jam Operasional</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.informasiumum.alamat}</TableTd>
<TableTd>{item.informasiumum.jamOperasional}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Dokter</TableTh>
<TableTh>Layanan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.dokterdantenagamedis.name}</TableTd>
<TableTd>{item.tarifdanlayanan.layanan}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -0,0 +1,116 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditGrafikHasilKepuasan() {
const editState = useProxy(grafikkepuasan)
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
nama: editState.update.form.nama || '',
tanggal: editState.update.form.tanggal || '',
jenisKelamin: editState.update.form.jenisKelamin || '',
alamat: editState.update.form.alamat || '',
penyakit: editState.update.form.penyakit || '',
});
useEffect(() => {
const loadKelahiran = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
if (data) {
setFormData({
nama: data.nama || '',
tanggal: data.tanggal || '',
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyakit: data.penyakit || '',
});
}
} catch (error) {
console.error("Error loading grafik hasil kepuasan:", error);
toast.error("Gagal memuat data grafik hasil kepuasan");
}
};
loadKelahiran();
}, [params?.id]);
const handleSubmit = async () => {
try {
editState.update.form = {
...editState.update.form,
nama: formData.nama,
tanggal: formData.tanggal,
jenisKelamin: formData.jenisKelamin,
alamat: formData.alamat,
penyakit: formData.penyakit,
};
await editState.update.submit();
toast.success('grafik hasil kepuasan berhasil diperbarui!');
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
} catch (error) {
console.error('Error updating grafik hasil kepuasan:', error);
toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan');
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={30} />
</Button>
</Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit grafik hasil kepuasan</Title>
<TextInput
value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
placeholder="masukkan nama"
/>
<TextInput
type='date'
value={formData.tanggal}
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
placeholder="masukkan tanggal"
/>
<TextInput
value={formData.jenisKelamin}
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
placeholder="masukkan jenis kelamin"
/>
<TextInput
value={formData.alamat}
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
placeholder="masukkan alamat"
/>
<TextInput
value={formData.penyakit}
onChange={(e) => setFormData({ ...formData, penyakit: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Penyakit</Text>}
placeholder="masukkan penyakit"
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditGrafikHasilKepuasan;

View File

@@ -1,80 +1,125 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function EditGrafikHasilKepuasan() {
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
function DetailGrafikHasilKepuasan() {
const state = useProxy(grafikkepuasan)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
const params = useParams() as { id: string }
const stateGrafikKepuasan = useProxy(grafikkepuasan)
const id = params.id
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
// Load data saat komponen mount
useEffect(() => {
if (id) {
stateGrafikKepuasan.findUnique.load(id).then(() => {
const data = stateGrafikKepuasan.findUnique.data
if (data) {
stateGrafikKepuasan.update.form = {
label: data.label || '',
jumlah: data.jumlah || '',
}
}
})
const handleHapus = () => {
if (selectedId) {
state.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan")
}
}, [id])
const handleSubmit = async () => {
// Set the ID before submitting
stateGrafikKepuasan.update.id = id;
await stateGrafikKepuasan.update.submit();
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan')
}
return (
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={40} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Title order={3}>Edit Grafik Hasil Kepuasan</Title>
<TextInput
label="Label"
placeholder="masukkan label"
value={stateGrafikKepuasan.update.form.label}
onChange={(val) => {
stateGrafikKepuasan.update.form.label = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah"
type="number"
placeholder="masukkan jumlah"
value={stateGrafikKepuasan.update.form.jumlah}
onChange={(val) => {
stateGrafikKepuasan.update.form.jumlah = val.currentTarget.value;
}}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Simpan
</Button>
<Text fz={"xl"} fw={"bold"}>Detail Data Grafik Hasil Kepuasan</Text>
{state.findUnique.data ? (
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Nama</Text>
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
<Text fz={"lg"}>
{new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
})}
</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Penyakit</Text>
<Text fz={"lg"} >{state.findUnique.data?.penyakit}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (state.findUnique.data) {
setSelectedId(state.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={state.delete.loading || !state.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (state.findUnique.data) {
router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${state.findUnique.data.id}/edit`);
}
}}
disabled={!state.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus data ini?'
/>
</Box>
)
);
}
export default EditGrafikHasilKepuasan;
export default DetailGrafikHasilKepuasan;

View File

@@ -16,20 +16,16 @@ function CreateGrafikHasilKepuasanMasyarakat() {
const resetForm = () => {
stateGrafikKepuasan.create.form = {
label: "",
jumlah: "",
nama: "",
tanggal: "",
jenisKelamin: "",
alamat: "",
penyakit: "",
}
}
const handleSubmit = async () => {
const id = await stateGrafikKepuasan.create.create();
if (id) {
const idStr = String(id);
await stateGrafikKepuasan.findUnique.load(idStr);
if (stateGrafikKepuasan.findUnique.data) {
setChartData([stateGrafikKepuasan.findUnique.data]);
}
}
await stateGrafikKepuasan.create.create();
resetForm();
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
}
@@ -45,21 +41,48 @@ function CreateGrafikHasilKepuasanMasyarakat() {
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
<Stack gap={"xs"}>
<TextInput
label="Label"
label="Nama"
type="text"
value={stateGrafikKepuasan.create.form.label}
placeholder="Masukkan label"
value={stateGrafikKepuasan.create.form.nama}
placeholder="Masukkan nama"
onChange={(val) => {
stateGrafikKepuasan.create.form.label = val.currentTarget.value;
stateGrafikKepuasan.create.form.nama = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah"
type="number"
value={stateGrafikKepuasan.create.form.jumlah}
placeholder="Masukkan jumlah"
label="Tanggal"
type="date"
value={stateGrafikKepuasan.create.form.tanggal}
placeholder="Masukkan tanggal"
onChange={(val) => {
stateGrafikKepuasan.create.form.jumlah = val.currentTarget.value;
stateGrafikKepuasan.create.form.tanggal = val.currentTarget.value;
}}
/>
<TextInput
label="Jenis Kelamin"
type="text"
value={stateGrafikKepuasan.create.form.jenisKelamin}
placeholder="Masukkan jenis kelamin"
onChange={(val) => {
stateGrafikKepuasan.create.form.jenisKelamin = val.currentTarget.value;
}}
/>
<TextInput
label="Alamat"
type="text"
value={stateGrafikKepuasan.create.form.alamat}
placeholder="Masukkan alamat"
onChange={(val) => {
stateGrafikKepuasan.create.form.alamat = val.currentTarget.value;
}}
/>
<TextInput
label="Penyakit"
type="text"
value={stateGrafikKepuasan.create.form.penyakit}
placeholder="Masukkan penyakit"
onChange={(val) => {
stateGrafikKepuasan.create.form.penyakit = val.currentTarget.value;
}}
/>
<Group>

View File

@@ -1,16 +1,16 @@
/* 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, Title } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
function GrafikHasilKepuasanMasyarakat() {
const [search, setSearch] = useState("");
@@ -30,53 +30,79 @@ function GrafikHasilKepuasanMasyarakat() {
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
type PDKMGrafik = {
id: string;
label: string;
jumlah: number;
nama: string;
tanggal: string | Date; // Allow both string and Date types
jenisKelamin: string;
alamat: string;
penyakit: string;
createdAt?: Date; // Add optional fields that might come from the API
updatedAt?: Date;
deletedAt?: Date | null;
}
const stateGrafikKepuasan = useProxy(grafikkepuasan);
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter();
const handleDelete = () => {
if (selectedId) {
stateGrafikKepuasan.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
stateGrafikKepuasan.findMany.load()
}
}
const {
data,
page,
totalPages,
loading,
load
} = stateGrafikKepuasan.findMany;
useShallowEffect(() => {
setMounted(true)
stateGrafikKepuasan.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
useEffect(() => {
setMounted(true);
if (stateGrafikKepuasan.findMany.data) {
setChartData(stateGrafikKepuasan.findMany.data.map((item) => ({
if (data) {
setChartData(data.map((item) => ({
id: item.id,
label: item.label,
jumlah: Number(item.jumlah),
nama: item.nama,
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal,
jenisKelamin: item.jenisKelamin,
alamat: item.alamat,
penyakit: item.penyakit,
})));
}
}, [stateGrafikKepuasan.findMany.data]);
}, [data]);
const filteredData = (stateGrafikKepuasan.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.label.toLowerCase().includes(keyword) ||
item.jumlah.toString().toLowerCase().includes(keyword)
);
});
// Add this function to process the data
const processDiseaseData = (data: PDKMGrafik[]) => {
const diseaseCount: Record<string, number> = {};
if (!stateGrafikKepuasan.findMany.data) {
data.forEach(item => {
const penyakit = item.penyakit.trim();
if (penyakit) {
diseaseCount[penyakit] = (diseaseCount[penyakit] || 0) + 1;
}
});
return Object.entries(diseaseCount).map(([name, count]) => ({
name,
count
}));
};
// Add this state to store the processed chart data
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]);
// Update the chart data when data changes
useEffect(() => {
if (data && data.length > 0) {
setDiseaseChartData(processDiseaseData(data));
}
}, [data]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack>
<Skeleton h={500} />
@@ -89,40 +115,36 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
<Stack gap={"xs"}>
{/* Form Input */}
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Grafik Hasil Kepuasan Masyarakat'
href='/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Label</TableTh>
<TableTh>Jumlah</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Tanggal</TableTh>
<TableTh>Jenis Kelamin</TableTh>
<TableTh>Penyakit</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.label}</TableTd>
<TableTd>{item.jumlah}</TableTd>
<TableTd>{item.nama}</TableTd>
<TableTd>
{new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
})}
</TableTd>
<TableTd>{item.jenisKelamin}</TableTd>
<TableTd>{item.penyakit}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={stateGrafikKepuasan.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
<IconDeviceImacCog size={20} />
</Button>
</TableTd>
</TableTr>
@@ -130,6 +152,15 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{/* Chart */}
@@ -141,30 +172,29 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
<XAxis dataKey="label" />
{mounted && diseaseChartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
<XAxis
dataKey="name"
tick={{ fontSize: 12 }}
interval={0}
angle={-45}
textAnchor="end"
height={70}
/>
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus grafik hasil kepuasan masyarakat ini?'
/>
</Box>
);
}

View File

@@ -5,7 +5,7 @@ import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_k
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 { IconArrowBack, IconEdit, IconSearch } from '@tabler/icons-react';
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -47,7 +47,7 @@ function ListKelahiran({ search }: { search: string }) {
useShallowEffect(() => {
load(page, 10, search)
}, [search])
}, [page, search])
const filteredData = data || []
@@ -93,7 +93,7 @@ function ListKelahiran({ search }: { search: string }) {
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}>
<IconEdit size={20} />
<IconDeviceImacCog size={20} />
</Button>
</TableTd>
</TableTr>

View File

@@ -2,7 +2,7 @@
'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Center, Flex, Paper, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
import { ActionIcon, Box, Center, Flex, Paper, Select, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -157,20 +157,18 @@ function GrafikPersentaseKelahiranKematian() {
) : (
<>
{/* Year Selector */}
<Box mb="md">
<Text mb="xs" fw={500}>Pilih Tahun:</Text>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{chartData.map(({ tahun }) => (
<Button
key={tahun}
variant={selectedYear === tahun ? 'filled' : 'outline'}
onClick={() => setSelectedYear(tahun)}
size="xs"
>
{tahun}
</Button>
))}
</div>
<Box mb="md" style={{ maxWidth: '200px' }}>
<Select
label="Pilih Tahun"
placeholder="Pilih Tahun"
data={chartData.map((item) => ({
value: item.tahun,
label: item.tahun
}))}
value={selectedYear}
onChange={(value) => setSelectedYear(value || '')}
size="xs"
/>
</Box>
{/* Main Chart */}

View File

@@ -0,0 +1,31 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormCreate = {
name: string;
specialist: string;
jadwal: string;
};
export default async function dokterTenagaMedisCreate(context: Context) {
const body = context.body as FormCreate;
const created = await prisma.dokterdanTenagaMedis.create({
data: {
name: body.name,
specialist: body.specialist,
jadwal: body.jadwal,
},
select: {
name: true,
specialist: true,
jadwal: true,
}
});
return {
success: true,
message: "Success create dokter tenaga medis",
data: created,
};
}

View File

@@ -0,0 +1,37 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function dokterTenagaMedisDelete(context: Context) {
const id = context.params?.id;
if (!id) {
return {
success: false,
message: "ID tidak ditemukan",
};
}
const existing = await prisma.dokterdanTenagaMedis.findUnique({
where: {
id: id,
},
});
if (!existing) {
return {
success: false,
message: "Data tidak ditemukan",
};
}
const deleted = await prisma.dokterdanTenagaMedis.delete({
where: { id },
});
return {
success: true,
message: "Data berhasil dihapus",
data: deleted,
};
}

View File

@@ -0,0 +1,55 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function dokterTenagaMedisFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ specialist: { contains: search, mode: 'insensitive' } },
{ jadwal: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.dokterdanTenagaMedis.findMany({
where,
skip,
take: limit,
orderBy: { name: 'asc' },
}),
prisma.dokterdanTenagaMedis.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil data dokter tenaga medis 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 dokter tenaga medis",
};
}
}
export default dokterTenagaMedisFindMany;

View File

@@ -0,0 +1,47 @@
import prisma from "@/lib/prisma";
export default async function dokterTenagaMedisFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return Response.json({
success: false,
message: 'ID tidak boleh kosong',
}, {status: 400})
}
try {
if (typeof id !== 'string') {
return Response.json({
success: false,
message: "ID tidak valid",
}, { status: 400 });
}
const data = await prisma.dokterdanTenagaMedis.findUnique({
where: { id },
});
if (!data) {
return Response.json({
success: false,
message: "Data tidak ditemukan",
}, { status: 404 });
}
return Response.json({
success: true,
message: "Berhasil mengambil data berdasarkan ID",
data,
}, { status: 200 });
} catch (error) {
console.error("Error fetching data:", error);
return Response.json({
success: false,
message: "Terjadi kesalahan saat mengambil data",
}, { status: 500 });
}
}

View File

@@ -0,0 +1,32 @@
import Elysia, { t } from "elysia";
import dokterTenagaMedisCreate from "./create";
import dokterTenagaMedisFindMany from "./findMany";
import dokterTenagaMedisFindUnique from "./findUnique";
import dokterTenagaMedisUpdate from "./updt";
import dokterTenagaMedisDelete from "./del";
const DokterTenagaMedis = new Elysia({
prefix: "/doktertenagamedis",
tags: ["Data Kesehatan/Fasilitas Kesehatan/Dokter Tenaga Medis"]
})
.get("/:id", async (context) => {
const response = await dokterTenagaMedisFindUnique(new Request(context.request));
return response;
})
.get("/findMany", dokterTenagaMedisFindMany)
.post("/create", dokterTenagaMedisCreate, {
body: t.Object({
name: t.String(),
specialist: t.String(),
jadwal: t.String(),
}),
})
.put("/:id", dokterTenagaMedisUpdate, {
body: t.Object({
name: t.String(),
specialist: t.String(),
jadwal: t.String(),
}),
})
.delete("/del/:id", dokterTenagaMedisDelete)
export default DokterTenagaMedis

View File

@@ -0,0 +1,32 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
name: string;
specialist: string;
jadwal: string;
}
export default async function dokterTenagaMedisUpdate(context: Context) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.dokterdanTenagaMedis.update({
where: { id },
data: {
name: body.name,
specialist: body.specialist,
jadwal: body.jadwal,
},
});
return {
success: true,
message: "Berhasil mengupdate data dokter tenaga medis",
data: result,
};
} catch (error) {
console.error("Error updating data dokter tenaga medis:", error);
throw new Error("Gagal mengupdate data dokter tenaga medis: " + (error as Error).message);
}
}

View File

@@ -1,25 +1,31 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.GrafikKepuasanGetPayload<{
select: {
label: true;
jumlah: true
};
}>;
type FormCreate = {
nama: string;
tanggal: string;
jenisKelamin: string;
alamat: string;
penyakit: string;
};
export default async function grafikKepuasanCreate(context: Context) {
const body = context.body as FormCreate;
const created = await prisma.grafikKepuasan.create({
data: {
label: body.label,
jumlah: body.jumlah,
nama: body.nama,
tanggal: new Date(body.tanggal),
jenisKelamin: body.jenisKelamin,
alamat: body.alamat,
penyakit: body.penyakit,
},
select: {
id: true,
label: true,
jumlah: true,
nama: true,
tanggal: true,
jenisKelamin: true,
alamat: true,
penyakit: true,
}
});
return {

View File

@@ -1,8 +1,56 @@
import prisma from "@/lib/prisma"
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function grafikKepuasanFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamat: { contains: search, mode: 'insensitive' } },
{ jenisKelamin: { contains: search, mode: 'insensitive' } },
{ penyakit: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.grafikKepuasan.findMany({
where,
skip,
take: limit,
orderBy: { nama: 'asc' },
}),
prisma.grafikKepuasan.count({ where }),
]);
export default async function grafikKepuasanFindMany() {
const res = await prisma.grafikKepuasan.findMany()
return {
data: res
}
}
success: true,
message: "Berhasil ambil data grafik kepuasan 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 grafik kepuasan",
};
}
}
export default grafikKepuasanFindMany;

View File

@@ -16,22 +16,21 @@ const GrafikKepuasan = new Elysia({
.get("/find-many", grafikKepuasanFindMany)
.post("/create", grafikKepuasanCreate, {
body: t.Object({
label: t.String(),
jumlah: t.String(),
nama: t.String(),
tanggal: t.String(),
jenisKelamin: t.String(),
alamat: t.String(),
penyakit: t.String(),
}),
})
.put("/:id", grafikKepuasanUpdate, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
label: t.String(),
jumlah: t.String(),
}),
})
.delete("/del/:id", grafikKepuasanDelete, {
params: t.Object({
id: t.String(),
nama: t.String(),
tanggal: t.String(),
jenisKelamin: t.String(),
alamat: t.String(),
penyakit: t.String(),
}),
})
.delete("/del/:id", grafikKepuasanDelete)
export default GrafikKepuasan

View File

@@ -1,45 +1,36 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
nama: string;
tanggal: string;
jenisKelamin: string;
alamat: string;
penyakit: string;
}
export default async function grafikKepuasanUpdate(context: Context) {
const id = context.params?.id;
if (!id) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.grafikKepuasan.update({
where: { id },
data: {
nama: body.nama,
tanggal: new Date(body.tanggal),
jenisKelamin: body.jenisKelamin,
alamat: body.alamat,
penyakit: body.penyakit,
},
});
return {
success: false,
message: "ID tidak ditemukan"
}
}
const {label, jumlah} = context.body as {
label: string;
jumlah: string;
}
const existing = await prisma.grafikKepuasan.findUnique({
where: {
id: id,
},
})
if (!existing) {
return {
success: false,
message: "Data tidak ditemukan",
}
}
const updated = await prisma.grafikKepuasan.update({
where: { id },
data: {
label,
jumlah,
},
})
return {
success: true,
message: "Data berhasil diupdate",
data: updated,
success: true,
message: "Berhasil mengupdate data grafik kepuasan",
data: result,
};
} catch (error) {
console.error("Error updating data grafik kepuasan:", error);
throw new Error("Gagal mengupdate data grafik kepuasan: " + (error as Error).message);
}
}

View File

@@ -18,6 +18,7 @@ import JadwalKegiatan from "./data_kesehatan_warga/jadwal_kegiatan";
import ArtikelKesehatan from "./data_kesehatan_warga/artikel_kesehatan";
import Kelahiran from "./data_kesehatan_warga/persentase_kelahiran_kematian/kelahiran";
import Kematian from "./data_kesehatan_warga/persentase_kelahiran_kematian/kematian";
import DokterTenagaMedis from "./data_kesehatan_warga/fasilitas_kesehatan/dokter-tenaga-medis";
const Kesehatan = new Elysia({
@@ -43,4 +44,5 @@ const Kesehatan = new Elysia({
.use(ArtikelKesehatan)
.use(Kelahiran)
.use(Kematian)
.use(DokterTenagaMedis)
export default Kesehatan;

View File

@@ -0,0 +1,132 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import grafikkepuasan from "@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan";
import colors from "@/con/colors";
import { Box, Center, Paper, 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 GrafikPenyakit() {
type PDKMGRAFIK = {
id: string;
nama: string;
tanggal: string | Date; // Allow both string and Date types
jenisKelamin: string;
alamat: string;
penyakit: string;
createdAt?: Date; // Add optional fields that might come from the API
updatedAt?: Date;
deletedAt?: Date | null;
}
const statePenyakit = useProxy(grafikkepuasan)
const [chartData, setChartData] = useState<PDKMGRAFIK[]>([])
const [mounted, setMounted] = useState(false)
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const {
data,
page,
loading,
load,
} = statePenyakit.findMany
useShallowEffect(() => {
setMounted(true)
load(page, 10)
}, [page])
useEffect(() => {
setMounted(true)
if (data) {
setChartData(data.map((item) => ({
id: item.id,
nama: item.nama,
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal,
jenisKelamin: item.jenisKelamin,
alamat: item.alamat,
penyakit: item.penyakit,
})))
}
}, [data])
const processDiseaseData = (data: PDKMGRAFIK[]) => {
const diseaseCount: Record<string, number> = {};
data.forEach(item => {
const penyakit = item.penyakit.trim();
if (penyakit) {
diseaseCount[penyakit] = (diseaseCount[penyakit] || 0) + 1;
}
});
return Object.entries(diseaseCount).map(([name, count]) => ({
name,
count
}));
};
// Add this state to store the processed chart data
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]);
// Update the chart data when data changes
useEffect(() => {
if (data && data.length > 0) {
setDiseaseChartData(processDiseaseData(data));
}
}, [data]);
if (loading || !data) {
return (
<Stack>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Center>
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
</Center>
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
<Paper bg={colors["white-trans-1"]} p={'md'}>
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
{mounted && diseaseChartData.length > 0 && (
<Center>
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
<XAxis
dataKey="name"
tick={{ fontSize: 12 }}
interval={0}
angle={-45}
textAnchor="end"
height={70}
/>
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Penderita" />
</BarChart>
</Center>
)}
</Paper>
</Box>
)}
</Box>
);
}
export default GrafikPenyakit;

View File

@@ -1,49 +1,95 @@
'use client'
/* eslint-disable @typescript-eslint/no-explicit-any */
import colors from '@/con/colors';
import { Stack, Box, Text, Paper, Center, Flex, ColorSwatch, SimpleGrid, Anchor, Divider, Image } from '@mantine/core';
import React from 'react';
import { BarChart as MantineBarChart } from '@mantine/charts';
import { Anchor, Box, Center, ColorSwatch, Divider, Flex, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { BarChart } from '@mantine/charts';
import Link from 'next/link';
// import { useRouter } from 'next/navigation';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import { useProxy } from 'valtio/utils';
import GrafikPenyakit from './grafik-penyakit/page';
const dataKematian = [
{
id: 1,
tahun: '2023',
kematianKasar: '1.7',
kematianBayi: '1.4',
kelahiranKasar: '0.5'
},
{
id: 2,
tahun: '2024',
kematianKasar: '1.4',
kematianBayi: '1.8',
kelahiranKasar: '1.5'
},
{
id: 3,
tahun: '2025',
kematianKasar: '2.0',
kematianBayi: '1.5',
kelahiranKasar: '1.2'
},
]
const dataPenyakit = [
{ penyakit: 'Covid', penderita: 335 },
{ penyakit: 'Tuli', penderita: 105 },
{ penyakit: 'Bisul', penderita: 98 },
{ penyakit: 'Panas', penderita: 96 },
{ penyakit: 'Batuk', penderita: 87 },
{ penyakit: 'Sembelit', penderita: 72 },
{ penyakit: 'Demam', penderita: 51 },
{ penyakit: 'Gred', penderita: 36 },
{ penyakit: 'Magh', penderita: 34 },
{ penyakit: 'Farangitis Akut', penderita: 17 },
]
function Page() {
type DataTahunan = {
tahun: string;
totalKelahiran: number;
totalKematian: number;
data: Array<{
id: string;
bulan: string;
kelahiran: number;
kematian: number;
}>;
};
// Count occurrences per year
const countByYear = (data: any[], dateField: string) => {
const counts: Record<string, number> = {};
data?.forEach(item => {
const year = new Date(item[dateField]).getFullYear().toString();
counts[year] = (counts[year] || 0) + 1;
});
return counts;
};
const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<DataTahunan[]>([]);
const isTablet = useMediaQuery('(max-width: 1024px)');
const isMobile = useMediaQuery('(max-width: 768px)');
useShallowEffect(() => {
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data
}, []);
useEffect(() => {
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
// Count kelahiran and kematian by year
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
// Get all unique years
const allYears = new Set([
...Object.keys(kelahiranByYear),
...Object.keys(kematianByYear)
]);
// Create data structure for the chart
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
acc[year] = {
tahun: year,
totalKelahiran: kelahiranByYear[year] || 0,
totalKematian: kematianByYear[year] || 0,
data: []
};
return acc;
}, {});
const sortedData = Object.values(dataByYear).sort((a, b) =>
parseInt(a.tahun) - parseInt(b.tahun)
);
setChartData(sortedData);
}
}, [
statePersentase.kelahiran.findMany.data,
statePersentase.kematian.findMany.data,
]);
if (!statePersentase.kelahiran.findMany.data || !statePersentase.kematian.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 }}>
@@ -61,63 +107,50 @@ function Page() {
<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' }}>Angka Kematian Kasar</Text>
<ColorSwatch color="#26308A" size={30} />
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text>
<ColorSwatch color="#EF3E3E" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian Bayi</Text>
<ColorSwatch color="#135A9B" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran Kasar</Text>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran</Text>
<ColorSwatch color="#3290CA" size={30} />
</Flex>
</Box>
</Flex>
<Center>
<BarChart
h={400}
data={dataKematian}
dataKey="tahun"
series={[
{ name: 'kematianKasar', color: '#26308A' },
{ name: 'kematianBayi', color: '#135A9B' },
{ name: 'kelahiranKasar', color: '#3290CA' },
]}
tickLine="y"
/>
</Center>
</Box>
</Paper>
</Box>
{/* Bar Chart Penyakit */}
<Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Box pb={30}>
<Text pb={30} fw={"bold"} fz={{ base: 'h4', md: 'h3' }} ta={"center"}>
Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik
</Text>
<Center>
<BarChart
p={20}
mb={30}
h={500}
data={dataPenyakit}
dataKey='penyakit'
orientation='vertical'
yAxisProps={{ width: 80 }}
barProps={{ radius: 10 }}
series={[{ name: 'penderita', color: colors['blue-button'] }]}
/>
</Center>
<Text ta={"center"} fw={"bold"} fz={"h4"}>Jumlah Penderita</Text>
{chartData.length === 0 ? (
<Text c="dimmed" ta="center" py="xl">
Belum ada data yang tersedia untuk ditampilkan
</Text>
) : (
<>
{/* Main Chart */}
<Center>
<Box h={400}>
<Box style={{
width: isMobile ? '90vw' : isTablet ? '700px' : '800px',
maxWidth: '100%',
margin: '0 auto'
}}>
<MantineBarChart
h={350}
data={chartData}
dataKey="tahun"
series={[
{ name: 'totalKelahiran', label: 'Total Kelahiran', color: '#3290CA' },
{ name: 'totalKematian', label: 'Total Kematian', color: '#f03e3e' }
]}
tickLine="y"
/>
</Box>
</Box>
</Center>
</>
)}
</Box>
</Paper>
</Box>
<GrafikPenyakit />
{/* Artikel Kesehatan */}
<Box>
<SimpleGrid
@@ -193,7 +226,7 @@ function Page() {
</Text>
</Anchor>
</Box>
<Divider color={colors['blue-button']} px={'xl'}/>
<Divider color={colors['blue-button']} px={'xl'} />
</Stack>
</Paper>
</Box>
@@ -233,7 +266,7 @@ function Page() {
09:00-14:00 WITA
</Text>
<Text fz={'h4'}>
Puskesmas Abiansemal III
Puskesmas Abiansemal III
</Text>
<Anchor component={Link} href={'/darmasaba/kesehatan/data-kesehatan-warga/jadwal-kegiatan'} c={colors['blue-button']} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} >
@@ -249,16 +282,16 @@ function Page() {
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
<Stack gap={'xs'}>
<Box>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
<Image pt={5} src={'/api/img/dbd.png'} alt="" />
<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'} >
Tips Mencegah Demam Berdarah Saat Musim Hujan
Tips Mencegah Demam Berdarah Saat Musim Hujan
</Text>
<Text fz={'h6'} pb={10}>
Diposting: 12 Februari 2025 | Dinas Kesehatan
Diposting: 12 Februari 2025 | Dinas Kesehatan
</Text>
<Text fz={'h4'} pb={10}>
Yuk Kenali gelaja dan cara penanganan DBD yang efektif untuk melindungi keluarga anda selama musim hujan.
Yuk Kenali gelaja dan cara penanganan DBD yang efektif untuk melindungi keluarga anda selama musim hujan.
</Text>
<Anchor c={'black'} component={Link} href={'/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan'} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} >
@@ -268,16 +301,16 @@ function Page() {
</Box>
<Divider color={colors['blue-button']} px={'xl'} />
<Box>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
<Image pt={5} src={'/api/img/dbd.png'} alt="" />
<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'} >
Tips Mencegah Demam Berdarah Saat Musim Hujan
Tips Mencegah Demam Berdarah Saat Musim Hujan
</Text>
<Text fz={'h6'} pb={10}>
Diposting: 12 Februari 2025 | Dinas Kesehatan
Diposting: 12 Februari 2025 | Dinas Kesehatan
</Text>
<Text fz={'h4'} pb={10}>
Yuk Kenali gelaja dan cara penanganan DBD yang efektif untuk melindungi keluarga anda selama musim hujan.
Yuk Kenali gelaja dan cara penanganan DBD yang efektif untuk melindungi keluarga anda selama musim hujan.
</Text>
<Anchor c={'black'} href={'/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan'} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} >