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 ========================================= // // ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan { model GrafikKepuasan {
id String @id @default(cuid()) id String @id @default(cuid())
label String nama String
jumlah String tanggal DateTime
createdAt DateTime @default(now()) jenisKelamin String
updatedAt DateTime @updatedAt alamat String
deletedAt DateTime @default(now()) penyakit String
isActive Boolean @default(true) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
} }
// ========================================= ARTIKEL KESEHATAN ========================================= // // ========================================= ARTIKEL KESEHATAN ========================================= //

View File

@@ -5,6 +5,7 @@ import { toast } from "react-toastify";
import { proxy } from "valtio"; import { proxy } from "valtio";
import { z } from "zod"; import { z } from "zod";
//fasilitas kesehatan aja
// Validasi form // Validasi form
const templateForm = z.object({ const templateForm = z.object({
name: z.string().min(1, "Nama harus diisi"), name: z.string().min(1, "Nama harus diisi"),
@@ -61,7 +62,7 @@ const defaultForm = {
}, },
}; };
const fasilitasKesehatanState = proxy({ const fasilitasKesehatan = proxy({
create: { create: {
form: { ...defaultForm }, form: { ...defaultForm },
loading: false, loading: false,
@@ -86,7 +87,7 @@ const fasilitasKesehatanState = proxy({
if (res.status === 200) { if (res.status === 200) {
toast.success("Berhasil menambahkan fasilitas kesehatan"); toast.success("Berhasil menambahkan fasilitas kesehatan");
this.resetForm(); this.resetForm();
await fasilitasKesehatanState.findMany.load(); await fasilitasKesehatan.findMany.load();
return res.data; return res.data;
} }
} catch (err: any) { } catch (err: any) {
@@ -102,7 +103,6 @@ const fasilitasKesehatanState = proxy({
this.form = { ...defaultForm }; this.form = { ...defaultForm };
}, },
}, },
findMany: { findMany: {
data: null as data: null as
| Prisma.FasilitasKesehatanGetPayload<{ | Prisma.FasilitasKesehatanGetPayload<{
@@ -156,7 +156,7 @@ const fasilitasKesehatanState = proxy({
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`); const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
fasilitasKesehatanState.findUnique.data = data.data ?? null; fasilitasKesehatan.findUnique.data = data.data ?? null;
} else { } else {
toast.error("Gagal load data fasilitas kesehatan"); toast.error("Gagal load data fasilitas kesehatan");
} }
@@ -176,8 +176,8 @@ const fasilitasKesehatanState = proxy({
const result = await res.json(); const result = await res.json();
const data = result.data; const data = result.data;
fasilitasKesehatanState.edit.id = data.id; fasilitasKesehatan.edit.id = data.id;
fasilitasKesehatanState.edit.form = { fasilitasKesehatan.edit.form = {
name: data.name, name: data.name,
informasiUmum: { informasiUmum: {
fasilitas: data.informasiumum.fasilitas, fasilitas: data.informasiumum.fasilitas,
@@ -205,7 +205,7 @@ const fasilitasKesehatanState = proxy({
}; };
}, },
async submit() { async submit() {
const cek = templateForm.safeParse(fasilitasKesehatanState.edit.form); const cek = templateForm.safeParse(fasilitasKesehatan.edit.form);
if (!cek.success) { if (!cek.success) {
const errMsg = cek.error.issues const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`) .map((v) => `${v.path.join(".")}: ${v.message}`)
@@ -215,42 +215,38 @@ const fasilitasKesehatanState = proxy({
} }
try { try {
fasilitasKesehatanState.edit.loading = true; fasilitasKesehatan.edit.loading = true;
const payload = { const payload = {
name: fasilitasKesehatanState.edit.form.name, name: fasilitasKesehatan.edit.form.name,
informasiUmum: { informasiUmum: {
fasilitas: fasilitas: fasilitasKesehatan.edit.form.informasiUmum.fasilitas,
fasilitasKesehatanState.edit.form.informasiUmum.fasilitas, alamat: fasilitasKesehatan.edit.form.informasiUmum.alamat,
alamat: fasilitasKesehatanState.edit.form.informasiUmum.alamat,
jamOperasional: jamOperasional:
fasilitasKesehatanState.edit.form.informasiUmum.jamOperasional, fasilitasKesehatan.edit.form.informasiUmum.jamOperasional,
}, },
layananUnggulan: { layananUnggulan: {
content: fasilitasKesehatanState.edit.form.layananUnggulan.content, content: fasilitasKesehatan.edit.form.layananUnggulan.content,
}, },
dokterdanTenagaMedis: { dokterdanTenagaMedis: {
name: fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.name, name: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.name,
specialist: specialist:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.specialist, fasilitasKesehatan.edit.form.dokterdanTenagaMedis.specialist,
jadwal: jadwal: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.jadwal,
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.jadwal,
}, },
fasilitasPendukung: { fasilitasPendukung: {
content: content: fasilitasKesehatan.edit.form.fasilitasPendukung.content,
fasilitasKesehatanState.edit.form.fasilitasPendukung.content,
}, },
prosedurPendaftaran: { prosedurPendaftaran: {
content: content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content,
fasilitasKesehatanState.edit.form.prosedurPendaftaran.content,
}, },
tarifDanLayanan: { tarifDanLayanan: {
layanan: fasilitasKesehatanState.edit.form.tarifDanLayanan.layanan, layanan: fasilitasKesehatan.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatanState.edit.form.tarifDanLayanan.tarif, tarif: fasilitasKesehatan.edit.form.tarifDanLayanan.tarif,
}, },
}; };
const res = await fetch( const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatanState.edit.id}`, `/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatan.edit.id}`,
{ {
method: "PUT", method: "PUT",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -264,7 +260,7 @@ const fasilitasKesehatanState = proxy({
} }
toast.success("Berhasil update fasilitas kesehatan"); toast.success("Berhasil update fasilitas kesehatan");
await fasilitasKesehatanState.findMany.load(); await fasilitasKesehatan.findMany.load();
return true; return true;
} catch (err) { } catch (err) {
toast.error( toast.error(
@@ -272,37 +268,297 @@ const fasilitasKesehatanState = proxy({
); );
return false; return false;
} finally { } finally {
fasilitasKesehatanState.edit.loading = false; fasilitasKesehatan.edit.loading = false;
} }
}, },
resetForm() { resetForm() {
fasilitasKesehatanState.edit.id = ""; fasilitasKesehatan.edit.id = "";
fasilitasKesehatanState.edit.form = { ...defaultForm }; fasilitasKesehatan.edit.form = { ...defaultForm };
}, },
}, },
delete: { delete: {
loading: false, loading: false,
async byId(id: string){ async byId(id: string) {
try { try {
fasilitasKesehatanState.delete.loading = true; fasilitasKesehatan.delete.loading = true;
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/del/${id}`, { const res = await fetch(
method: "DELETE", `/api/kesehatan/fasilitas-kesehatan/del/${id}`,
}); {
method: "DELETE",
}
);
const result = await res.json(); const result = await res.json();
if (res.ok && result.success) { if (res.ok && result.success) {
toast.success("Fasilitas kesehatan berhasil dihapus"); toast.success("Fasilitas kesehatan berhasil dihapus");
await fasilitasKesehatanState.findMany.load(); await fasilitasKesehatan.findMany.load();
} else { } else {
toast.error(result.message || "Gagal menghapus"); toast.error(result.message || "Gagal menghapus");
} }
} catch { } catch {
toast.error("Terjadi kesalahan saat menghapus"); toast.error("Terjadi kesalahan saat menghapus");
} finally { } 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; export default fasilitasKesehatanState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -5,20 +6,19 @@ import { proxy } from "valtio";
import { z } from "zod"; import { z } from "zod";
const templateGrafikKepuasan = z.object({ const templateGrafikKepuasan = z.object({
label: z.string().min(2, "Label harus diisi"), nama: z.string().min(2, "Nama harus diisi"),
jumlah: z.string().min(1, "Jumlah 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<{ const defaultForm = {
select: { nama: "",
label: true; tanggal: "",
jumlah: true; jenisKelamin: "",
}; alamat: "",
}>; penyakit: "",
const defaultForm: GrafikKepuasan = {
label: "",
jumlah: ""
}; };
const grafikkepuasan = proxy({ const grafikkepuasan = proxy({
@@ -36,16 +36,15 @@ const grafikkepuasan = proxy({
} }
try { try {
grafikkepuasan.create.loading = true; 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) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.data;
if (id) { if (id) {
toast.success("Success create"); toast.success("Success create");
grafikkepuasan.create.form = { grafikkepuasan.create.form = { ...defaultForm };
label: "",
jumlah: "",
};
grafikkepuasan.findMany.load(); grafikkepuasan.findMany.load();
return id; return id;
} }
@@ -62,21 +61,49 @@ const grafikkepuasan = proxy({
}, },
findMany: { findMany: {
data: null as data: null as
| Prisma.GrafikKepuasanGetPayload<{ omit: { isActive: true } }>[] | Prisma.GrafikKepuasanGetPayload<{
omit: {
isActive: true;
};
}>[]
| null, | null,
async load() { page: 1,
const res = await ApiFetch.api.kesehatan.grafikkepuasan[ totalPages: 1,
"find-many" loading: false,
].get(); search: "",
if (res.status === 200) { load: async (page = 1, limit = 10, search = "") => {
grafikkepuasan.findMany.data = res.data?.data ?? []; 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: { findUnique: {
data: null as Prisma.GrafikKepuasanGetPayload<{ data: null as Prisma.GrafikKepuasanGetPayload<{
omit: { isActive: true } omit: { isActive: true };
}> | null, }> | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`); const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
@@ -95,88 +122,137 @@ const grafikkepuasan = proxy({
}, },
update: { update: {
id: "", id: "",
form: {...defaultForm}, form: { ...defaultForm },
loading: false, loading: false,
async byId() { async load(id: string) {
}, if (!id) {
async submit() { toast.warn("ID tidak valid");
const id = this.id; return null;
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");
} }
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 if (!response.ok) {
await grafikkepuasan.findMany.load(); throw new Error(`HTTP error! status: ${response.status}`);
}
return result.data; const result = await response.json();
} 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 response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, { if (result?.success) {
method: "DELETE", const data = result.data;
headers: { this.id = data.id;
"Content-Type": "application/json", this.form = {
}, nama: data.nama,
}); tanggal: data.tanggal,
jenisKelamin: data.jenisKelamin,
const result = await response.json(); alamat: data.alamat,
penyakit: data.penyakit,
if (response.ok && result?.success) { };
toast.success( return data; // Return the loaded data
result.message || "Grafik kepuasan berhasil dihapus" } else {
); throw new Error(result?.message || "Gagal memuat data");
await grafikkepuasan.findMany.load(); // refresh list }
} else { } catch (error) {
console.error("Error loading grafik kepuasan:", error);
toast.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); async submit() {
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan"); const id = this.id;
} finally { if (!id) {
grafikkepuasan.delete.loading = false; 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; export default grafikkepuasan;

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import { useProxy } from 'valtio/utils';
function CreateFasilitasKesehatan() { function CreateFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState) const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const router = useRouter(); 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' 'use client'
import colors from '@/con/colors'; 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 { 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 { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
@@ -13,22 +13,37 @@ import { useState } from 'react';
function FasilitasKesehatan() { function FasilitasKesehatan() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const router = useRouter();
return ( return (
<Box> <Box>
<HeaderSearch <Grid>
title='Fasilitas Kesehatan' <GridCol span={12}>
placeholder='pencarian' <HeaderSearch
searchIcon={<IconSearch size={20} />} title='Fasilitas Kesehatan'
value={search} placeholder='pencarian'
onChange={(e) => setSearch(e.currentTarget.value)} searchIcon={<IconSearch size={20} />}
/> value={search}
<ListFasilitasKesehatan search={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> </Box>
); );
} }
function ListFasilitasKesehatan({ search }: { search: string }) { function ListFasilitasKesehatan({ search }: { search: string }) {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState) const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const router = useRouter(); const router = useRouter();
useShallowEffect(() => { useShallowEffect(() => {
@@ -47,47 +62,47 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
if (!stateFasilitasKesehatan.findMany.data) { if (!stateFasilitasKesehatan.findMany.data) {
return ( return (
<Box py={10}> <Box py={10}>
<Skeleton h={500}/> <Skeleton h={500} />
</Box> </Box>
) )
} }
return ( return (
<Box py={10}> <Box py={10}>
<Paper bg={colors['white-1']} p={'md'}> <Paper bg={colors['white-1']} p={'md'}>
<Stack> <Stack>
<JudulList <JudulList
title='List Fasilitas Kesehatan' title='List Fasilitas Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create' href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
/> />
<Box style={{ overflowX: "auto" }}> <Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}> <Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Fasilitas Kesehatan</TableTh> <TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh> <TableTh>Dokter</TableTh>
<TableTh>Jam Operasional</TableTh> <TableTh>Layanan</TableTh>
<TableTh>Detail</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>
</TableTr> </TableTr>
))} </TableThead>
</TableTbody> <TableTbody>
</Table> {filteredData.map((item) => (
</Box> <TableTr key={item.id}>
</Stack> <TableTd>{item.name}</TableTd>
</Paper> <TableTd>{item.dokterdantenagamedis.name}</TableTd>
</Box> <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' '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'; 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 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(() => { const handleHapus = () => {
if (id) { if (selectedId) {
stateGrafikKepuasan.findUnique.load(id).then(() => { state.delete.byId(selectedId)
const data = stateGrafikKepuasan.findUnique.data setModalHapus(false)
if (data) { setSelectedId(null)
stateGrafikKepuasan.update.form = { router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan")
label: data.label || '',
jumlah: data.jumlah || '',
}
}
})
} }
}, [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>
<Box mb={10}> <Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}> <Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} /> <IconArrowBack color={colors['blue-button']} size={25} />
</Button> </Button>
</Box> </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> <Stack>
<Title order={3}>Edit Grafik Hasil Kepuasan</Title> <Text fz={"xl"} fw={"bold"}>Detail Data Grafik Hasil Kepuasan</Text>
<TextInput {state.findUnique.data ? (
label="Label" <Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
placeholder="masukkan label" <Stack gap={"xs"}>
value={stateGrafikKepuasan.update.form.label} <Box>
onChange={(val) => { <Text fw={"bold"} fz={"lg"}>Nama</Text>
stateGrafikKepuasan.update.form.label = val.currentTarget.value; <Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
}} </Box>
/> <Box>
<TextInput <Text fw={"bold"} fz={"lg"}>Tanggal</Text>
label="Jumlah" <Text fz={"lg"}>
type="number" {new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
placeholder="masukkan jumlah" day: '2-digit',
value={stateGrafikKepuasan.update.form.jumlah} month: 'long',
onChange={(val) => { year: 'numeric'
stateGrafikKepuasan.update.form.jumlah = val.currentTarget.value; })}
}} </Text>
/> </Box>
<Button <Box>
mt={10} <Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
bg={colors['blue-button']} <Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
onClick={handleSubmit} </Box>
> <Box>
Simpan <Text fw={"bold"} fz={"lg"}>Alamat</Text>
</Button> <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> </Stack>
</Paper> </Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus data ini?'
/>
</Box> </Box>
) );
} }
export default EditGrafikHasilKepuasan; export default DetailGrafikHasilKepuasan;

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_k
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; 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 { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -47,7 +47,7 @@ function ListKelahiran({ search }: { search: string }) {
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, search)
}, [search]) }, [page, search])
const filteredData = data || [] const filteredData = data || []
@@ -93,7 +93,7 @@ function ListKelahiran({ search }: { search: string }) {
<TableTd>{item.alamat}</TableTd> <TableTd>{item.alamat}</TableTd>
<TableTd> <TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}> <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> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran'; import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors'; 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 { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react'; import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -157,20 +157,18 @@ function GrafikPersentaseKelahiranKematian() {
) : ( ) : (
<> <>
{/* Year Selector */} {/* Year Selector */}
<Box mb="md"> <Box mb="md" style={{ maxWidth: '200px' }}>
<Text mb="xs" fw={500}>Pilih Tahun:</Text> <Select
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}> label="Pilih Tahun"
{chartData.map(({ tahun }) => ( placeholder="Pilih Tahun"
<Button data={chartData.map((item) => ({
key={tahun} value: item.tahun,
variant={selectedYear === tahun ? 'filled' : 'outline'} label: item.tahun
onClick={() => setSelectedYear(tahun)} }))}
size="xs" value={selectedYear}
> onChange={(value) => setSelectedYear(value || '')}
{tahun} size="xs"
</Button> />
))}
</div>
</Box> </Box>
{/* Main Chart */} {/* 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 "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia"; import { Context } from "elysia";
type FormCreate = Prisma.GrafikKepuasanGetPayload<{ type FormCreate = {
select: { nama: string;
label: true; tanggal: string;
jumlah: true jenisKelamin: string;
}; alamat: string;
}>; penyakit: string;
};
export default async function grafikKepuasanCreate(context: Context) { export default async function grafikKepuasanCreate(context: Context) {
const body = context.body as FormCreate; const body = context.body as FormCreate;
const created = await prisma.grafikKepuasan.create({ const created = await prisma.grafikKepuasan.create({
data: { data: {
label: body.label, nama: body.nama,
jumlah: body.jumlah, tanggal: new Date(body.tanggal),
jenisKelamin: body.jenisKelamin,
alamat: body.alamat,
penyakit: body.penyakit,
}, },
select: { select: {
id: true, nama: true,
label: true, tanggal: true,
jumlah: true, jenisKelamin: true,
alamat: true,
penyakit: true,
} }
}); });
return { 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 { 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) .get("/find-many", grafikKepuasanFindMany)
.post("/create", grafikKepuasanCreate, { .post("/create", grafikKepuasanCreate, {
body: t.Object({ body: t.Object({
label: t.String(), nama: t.String(),
jumlah: t.String(), tanggal: t.String(),
jenisKelamin: t.String(),
alamat: t.String(),
penyakit: t.String(),
}), }),
}) })
.put("/:id", grafikKepuasanUpdate, { .put("/:id", grafikKepuasanUpdate, {
params: t.Object({
id: t.String(),
}),
body: t.Object({ body: t.Object({
label: t.String(), nama: t.String(),
jumlah: t.String(), tanggal: t.String(),
}), jenisKelamin: t.String(),
}) alamat: t.String(),
.delete("/del/:id", grafikKepuasanDelete, { penyakit: t.String(),
params: t.Object({
id: t.String(),
}), }),
}) })
.delete("/del/:id", grafikKepuasanDelete)
export default GrafikKepuasan export default GrafikKepuasan

View File

@@ -1,45 +1,36 @@
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { Context } from "elysia"; import { Context } from "elysia";
type FormUpdate = {
nama: string;
tanggal: string;
jenisKelamin: string;
alamat: string;
penyakit: string;
}
export default async function grafikKepuasanUpdate(context: Context) { export default async function grafikKepuasanUpdate(context: Context) {
const id = context.params?.id; const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
if (!id) {
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 { return {
success: false, success: true,
message: "ID tidak ditemukan" message: "Berhasil mengupdate data grafik kepuasan",
} data: result,
} };
} catch (error) {
const {label, jumlah} = context.body as { console.error("Error updating data grafik kepuasan:", error);
label: string; throw new Error("Gagal mengupdate data grafik kepuasan: " + (error as Error).message);
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,
} }
} }

View File

@@ -18,6 +18,7 @@ import JadwalKegiatan from "./data_kesehatan_warga/jadwal_kegiatan";
import ArtikelKesehatan from "./data_kesehatan_warga/artikel_kesehatan"; import ArtikelKesehatan from "./data_kesehatan_warga/artikel_kesehatan";
import Kelahiran from "./data_kesehatan_warga/persentase_kelahiran_kematian/kelahiran"; import Kelahiran from "./data_kesehatan_warga/persentase_kelahiran_kematian/kelahiran";
import Kematian from "./data_kesehatan_warga/persentase_kelahiran_kematian/kematian"; 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({ const Kesehatan = new Elysia({
@@ -43,4 +44,5 @@ const Kesehatan = new Elysia({
.use(ArtikelKesehatan) .use(ArtikelKesehatan)
.use(Kelahiran) .use(Kelahiran)
.use(Kematian) .use(Kematian)
.use(DokterTenagaMedis)
export default Kesehatan; 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 colors from '@/con/colors';
import { Stack, Box, Text, Paper, Center, Flex, ColorSwatch, SimpleGrid, Anchor, Divider, Image } from '@mantine/core'; import { BarChart as MantineBarChart } from '@mantine/charts';
import React from 'react'; 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 BackButton from '../../desa/layanan/_com/BackButto';
import { BarChart } from '@mantine/charts';
import Link from 'next/link'; 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() { 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 ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
@@ -61,63 +107,50 @@ function Page() {
<Flex pb={30} justify={'flex-end'} gap={'xl'} align={'center'}> <Flex pb={30} justify={'flex-end'} gap={'xl'} align={'center'}>
<Box> <Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}> <Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian Kasar</Text> <Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text>
<ColorSwatch color="#26308A" size={30} /> <ColorSwatch color="#EF3E3E" size={30} />
</Flex> </Flex>
</Box> </Box>
<Box> <Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}> <Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian Bayi</Text> <Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran</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>
<ColorSwatch color="#3290CA" size={30} /> <ColorSwatch color="#3290CA" size={30} />
</Flex> </Flex>
</Box> </Box>
</Flex> </Flex>
<Center> {chartData.length === 0 ? (
<BarChart <Text c="dimmed" ta="center" py="xl">
h={400} Belum ada data yang tersedia untuk ditampilkan
data={dataKematian} </Text>
dataKey="tahun" ) : (
series={[ <>
{ name: 'kematianKasar', color: '#26308A' }, {/* Main Chart */}
{ name: 'kematianBayi', color: '#135A9B' }, <Center>
{ name: 'kelahiranKasar', color: '#3290CA' }, <Box h={400}>
]} <Box style={{
tickLine="y" width: isMobile ? '90vw' : isTablet ? '700px' : '800px',
/> maxWidth: '100%',
</Center> margin: '0 auto'
</Box> }}>
</Paper> <MantineBarChart
</Box> h={350}
{/* Bar Chart Penyakit */} data={chartData}
<Box> dataKey="tahun"
<Paper p={"xl"} bg={colors['white-trans-1']}> series={[
<Box pb={30}> { name: 'totalKelahiran', label: 'Total Kelahiran', color: '#3290CA' },
<Text pb={30} fw={"bold"} fz={{ base: 'h4', md: 'h3' }} ta={"center"}> { name: 'totalKematian', label: 'Total Kematian', color: '#f03e3e' }
Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik ]}
</Text> tickLine="y"
<Center> />
<BarChart </Box>
p={20} </Box>
mb={30} </Center>
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>
</Box> </Box>
</Paper> </Paper>
</Box> </Box>
<GrafikPenyakit />
{/* Artikel Kesehatan */} {/* Artikel Kesehatan */}
<Box> <Box>
<SimpleGrid <SimpleGrid
@@ -193,7 +226,7 @@ function Page() {
</Text> </Text>
</Anchor> </Anchor>
</Box> </Box>
<Divider color={colors['blue-button']} px={'xl'}/> <Divider color={colors['blue-button']} px={'xl'} />
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>
@@ -233,7 +266,7 @@ function Page() {
09:00-14:00 WITA 09:00-14:00 WITA
</Text> </Text>
<Text fz={'h4'}> <Text fz={'h4'}>
Puskesmas Abiansemal III Puskesmas Abiansemal III
</Text> </Text>
<Anchor component={Link} href={'/darmasaba/kesehatan/data-kesehatan-warga/jadwal-kegiatan'} c={colors['blue-button']} variant='transparent'> <Anchor component={Link} href={'/darmasaba/kesehatan/data-kesehatan-warga/jadwal-kegiatan'} c={colors['blue-button']} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} > <Text c={colors['blue-button']} fz={'h4'} >
@@ -249,16 +282,16 @@ function Page() {
<Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}> <Paper p={'xl'} h={'112vh'} bg={colors['white-trans-1']}>
<Stack gap={'xs'}> <Stack gap={'xs'}>
<Box> <Box>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text> <Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
<Image pt={5} src={'/api/img/dbd.png'} alt="" /> <Image pt={5} src={'/api/img/dbd.png'} alt="" />
<Text fz={'h4'} fw={'bold'} > <Text fz={'h4'} fw={'bold'} >
Tips Mencegah Demam Berdarah Saat Musim Hujan Tips Mencegah Demam Berdarah Saat Musim Hujan
</Text> </Text>
<Text fz={'h6'} pb={10}> <Text fz={'h6'} pb={10}>
Diposting: 12 Februari 2025 | Dinas Kesehatan Diposting: 12 Februari 2025 | Dinas Kesehatan
</Text> </Text>
<Text fz={'h4'} pb={10}> <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> </Text>
<Anchor c={'black'} component={Link} href={'/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan'} variant='transparent'> <Anchor c={'black'} component={Link} href={'/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan'} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} > <Text c={colors['blue-button']} fz={'h4'} >
@@ -268,16 +301,16 @@ function Page() {
</Box> </Box>
<Divider color={colors['blue-button']} px={'xl'} /> <Divider color={colors['blue-button']} px={'xl'} />
<Box> <Box>
<Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text> <Text ta={'center'} fw={"bold"} fz={'h3'} c={colors['blue-button']}>Artikel Kesehatan</Text>
<Image pt={5} src={'/api/img/dbd.png'} alt="" /> <Image pt={5} src={'/api/img/dbd.png'} alt="" />
<Text fz={'h4'} fw={'bold'} > <Text fz={'h4'} fw={'bold'} >
Tips Mencegah Demam Berdarah Saat Musim Hujan Tips Mencegah Demam Berdarah Saat Musim Hujan
</Text> </Text>
<Text fz={'h6'} pb={10}> <Text fz={'h6'} pb={10}>
Diposting: 12 Februari 2025 | Dinas Kesehatan Diposting: 12 Februari 2025 | Dinas Kesehatan
</Text> </Text>
<Text fz={'h4'} pb={10}> <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> </Text>
<Anchor c={'black'} href={'/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan'} variant='transparent'> <Anchor c={'black'} href={'/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan'} variant='transparent'>
<Text c={colors['blue-button']} fz={'h4'} > <Text c={colors['blue-button']} fz={'h4'} >