Menambahkan menu dokter dan tenaga medis, admin bisa create, edit, delet dokter
Menambahkan menu tarif dan layanan, admin bisa create, edit, delete tarif dan layanan Dibagian fasilitas kesehatan admin bisa multiselect bagian dokter dan tarif layanan Di tampilan user juga sudah disesuaikan dengan datanya bisa muncul lebih dari 1 dokter dan 1 tarif layanan
This commit is contained in:
@@ -783,24 +783,22 @@ model Penghargaan {
|
||||
|
||||
// ========================================= FASILITAS KESEHATAN ========================================= //
|
||||
model FasilitasKesehatan {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id])
|
||||
informasiUmumId String
|
||||
layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id])
|
||||
layananUnggulanId String
|
||||
dokterdantenagamedis DokterdanTenagaMedis @relation(fields: [dokterdanTenagaMedisId], references: [id])
|
||||
dokterdanTenagaMedisId String
|
||||
fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id])
|
||||
fasilitasPendukungId String
|
||||
prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id])
|
||||
prosedurPendaftaranId String
|
||||
tarifdanlayanan TarifDanLayanan @relation(fields: [tarifDanLayananId], references: [id])
|
||||
tarifDanLayananId String
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id])
|
||||
informasiUmumId String
|
||||
layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id])
|
||||
layananUnggulanId String
|
||||
dokterdantenagamedis DokterdanTenagaMedis[] @relation("Dokter")
|
||||
fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id])
|
||||
fasilitasPendukungId String
|
||||
prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id])
|
||||
prosedurPendaftaranId String
|
||||
tarifdanlayanan TarifDanLayanan[] @relation("Tarif")
|
||||
}
|
||||
|
||||
model InformasiUmum {
|
||||
@@ -826,15 +824,20 @@ model LayananUnggulan {
|
||||
}
|
||||
|
||||
model DokterdanTenagaMedis {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
specialist String
|
||||
jadwal String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
FasilitasKesehatan FasilitasKesehatan[]
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
specialist String
|
||||
jadwal String
|
||||
jadwalLibur String
|
||||
jamBukaOperasional String
|
||||
jamTutupOperasional String
|
||||
jamBukaLibur String
|
||||
jamTutupLibur String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
FasilitasKesehatan FasilitasKesehatan[] @relation("Dokter")
|
||||
}
|
||||
|
||||
model FasilitasPendukung {
|
||||
@@ -865,7 +868,7 @@ model TarifDanLayanan {
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
FasilitasKesehatan FasilitasKesehatan[]
|
||||
FasilitasKesehatan FasilitasKesehatan[] @relation("Tarif")
|
||||
}
|
||||
|
||||
// ========================================= JADWAL KEGIATAN ========================================= //
|
||||
|
||||
@@ -9,29 +9,30 @@ import { z } from "zod";
|
||||
// Validasi form
|
||||
const templateForm = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
|
||||
informasiUmum: z.object({
|
||||
fasilitas: z.string().min(1, "Fasilitas harus diisi"),
|
||||
alamat: z.string().min(1, "Alamat harus diisi"),
|
||||
jamOperasional: z.string().min(1, "Jam operasional harus diisi"),
|
||||
fasilitas: z.string().min(1),
|
||||
alamat: z.string().min(1),
|
||||
jamOperasional: z.string().min(1),
|
||||
}),
|
||||
|
||||
layananUnggulan: z.object({
|
||||
content: z.string().min(1, "Layanan unggulan harus diisi"),
|
||||
}),
|
||||
dokterdanTenagaMedis: z.object({
|
||||
name: z.string().min(1, "Nama dokter harus diisi"),
|
||||
specialist: z.string().min(1, "Spesialis harus diisi"),
|
||||
jadwal: z.string().min(1, "Jadwal harus diisi"),
|
||||
content: z.string().min(1),
|
||||
}),
|
||||
|
||||
// NOW ARRAY OF STRING (ID)
|
||||
dokterdanTenagaMedis: z.array(z.string()).min(1, "Minimal pilih 1 dokter"),
|
||||
|
||||
fasilitasPendukung: z.object({
|
||||
content: z.string().min(1, "Fasilitas pendukung harus diisi"),
|
||||
content: z.string().min(1),
|
||||
}),
|
||||
|
||||
prosedurPendaftaran: z.object({
|
||||
content: z.string().min(1, "Prosedur pendaftaran harus diisi"),
|
||||
}),
|
||||
tarifDanLayanan: z.object({
|
||||
layanan: z.string().min(1, "Layanan harus diisi"),
|
||||
tarif: z.string().min(1, "Tarif harus diisi"),
|
||||
content: z.string().min(1),
|
||||
}),
|
||||
|
||||
// NOW ARRAY OF STRING (ID)
|
||||
tarifDanLayanan: z.array(z.string()).min(1, "Minimal pilih 1 tarif"),
|
||||
});
|
||||
|
||||
// Default form kosong
|
||||
@@ -45,21 +46,34 @@ const defaultForm = {
|
||||
layananUnggulan: {
|
||||
content: "",
|
||||
},
|
||||
dokterdanTenagaMedis: {
|
||||
name: "",
|
||||
specialist: "",
|
||||
jadwal: "",
|
||||
},
|
||||
|
||||
dokterdanTenagaMedis: [] as string[], // ← array kosong
|
||||
tarifDanLayanan: [] as string[], // ← array kosong
|
||||
|
||||
fasilitasPendukung: {
|
||||
content: "",
|
||||
},
|
||||
prosedurPendaftaran: {
|
||||
content: "",
|
||||
},
|
||||
tarifDanLayanan: {
|
||||
layanan: "",
|
||||
tarif: "",
|
||||
},
|
||||
};
|
||||
|
||||
type DokterItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
specialist: string;
|
||||
jadwal: string;
|
||||
jadwalLibur: string;
|
||||
jamBukaOperasional: string;
|
||||
jamTutupOperasional: string;
|
||||
jamBukaLibur: string;
|
||||
jamTutupLibur: string;
|
||||
};
|
||||
|
||||
type TarifItem = {
|
||||
id: string;
|
||||
layanan: string;
|
||||
tarif: string;
|
||||
};
|
||||
|
||||
const fasilitasKesehatan = proxy({
|
||||
@@ -186,33 +200,26 @@ const fasilitasKesehatan = proxy({
|
||||
|
||||
const result = await res.json();
|
||||
const data = result.data;
|
||||
|
||||
fasilitasKesehatan.edit.id = data.id;
|
||||
fasilitasKesehatan.edit.form = {
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
informasiUmum: {
|
||||
fasilitas: data.informasiumum.fasilitas,
|
||||
alamat: data.informasiumum.alamat,
|
||||
jamOperasional: data.informasiumum.jamOperasional,
|
||||
},
|
||||
layananUnggulan: {
|
||||
content: data.layananunggulan.content,
|
||||
},
|
||||
dokterdanTenagaMedis: {
|
||||
name: data.dokterdantenagamedis.name,
|
||||
specialist: data.dokterdantenagamedis.specialist,
|
||||
jadwal: data.dokterdantenagamedis.jadwal,
|
||||
},
|
||||
fasilitasPendukung: {
|
||||
content: data.fasilitaspendukung.content,
|
||||
},
|
||||
prosedurPendaftaran: {
|
||||
content: data.prosedurpendaftaran.content,
|
||||
},
|
||||
tarifDanLayanan: {
|
||||
layanan: data.tarifdanlayanan.layanan,
|
||||
tarif: data.tarifdanlayanan.tarif,
|
||||
// map relasi -> array of IDs
|
||||
layananUnggulan: {
|
||||
content: data.layananunggulan.content,
|
||||
},
|
||||
dokterdanTenagaMedis: data.dokterdantenagamedis?.map((v: DokterItem) => v.id) ?? [],
|
||||
tarifDanLayanan: data.tarifdanlayanan?.map((v: TarifItem) => v.id) ?? [],
|
||||
};
|
||||
},
|
||||
async submit() {
|
||||
@@ -238,22 +245,15 @@ const fasilitasKesehatan = proxy({
|
||||
layananUnggulan: {
|
||||
content: fasilitasKesehatan.edit.form.layananUnggulan.content,
|
||||
},
|
||||
dokterdanTenagaMedis: {
|
||||
name: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.name,
|
||||
specialist:
|
||||
fasilitasKesehatan.edit.form.dokterdanTenagaMedis.specialist,
|
||||
jadwal: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.jadwal,
|
||||
},
|
||||
dokterdanTenagaMedis:
|
||||
fasilitasKesehatan.edit.form.dokterdanTenagaMedis,
|
||||
fasilitasPendukung: {
|
||||
content: fasilitasKesehatan.edit.form.fasilitasPendukung.content,
|
||||
},
|
||||
prosedurPendaftaran: {
|
||||
content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content,
|
||||
},
|
||||
tarifDanLayanan: {
|
||||
layanan: fasilitasKesehatan.edit.form.tarifDanLayanan.layanan,
|
||||
tarif: fasilitasKesehatan.edit.form.tarifDanLayanan.tarif,
|
||||
},
|
||||
tarifDanLayanan: fasilitasKesehatan.edit.form.tarifDanLayanan,
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
@@ -320,12 +320,26 @@ 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"),
|
||||
jadwalLibur: z.string().min(1, "Jadwal libur tidak boleh kosong"),
|
||||
jamBukaOperasional: z
|
||||
.string()
|
||||
.min(1, "Jam buka operasional tidak boleh kosong"),
|
||||
jamTutupOperasional: z
|
||||
.string()
|
||||
.min(1, "Jam tutup operasional tidak boleh kosong"),
|
||||
jamBukaLibur: z.string().min(1, "Jam buka libur tidak boleh kosong"),
|
||||
jamTutupLibur: z.string().min(1, "Jam tutup libur tidak boleh kosong"),
|
||||
});
|
||||
|
||||
const defaultDokterForm = {
|
||||
name: "",
|
||||
specialist: "",
|
||||
jadwal: "",
|
||||
jadwalLibur: "",
|
||||
jamBukaOperasional: "",
|
||||
jamTutupOperasional: "",
|
||||
jamBukaLibur: "",
|
||||
jamTutupLibur: "",
|
||||
};
|
||||
|
||||
const dokter = proxy({
|
||||
@@ -463,6 +477,11 @@ const dokter = proxy({
|
||||
name: data.name,
|
||||
specialist: data.specialist,
|
||||
jadwal: data.jadwal,
|
||||
jadwalLibur: data.jadwalLibur,
|
||||
jamBukaOperasional: data.jamBukaOperasional,
|
||||
jamTutupOperasional: data.jamTutupOperasional,
|
||||
jamBukaLibur: data.jamBukaLibur,
|
||||
jamTutupLibur: data.jamTutupLibur,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
@@ -487,6 +506,11 @@ const dokter = proxy({
|
||||
name: this.form.name,
|
||||
specialist: this.form.specialist,
|
||||
jadwal: this.form.jadwal,
|
||||
jadwalLibur: this.form.jadwalLibur,
|
||||
jamBukaOperasional: this.form.jamBukaOperasional,
|
||||
jamTutupOperasional: this.form.jamTutupOperasional,
|
||||
jamBukaLibur: this.form.jamBukaLibur,
|
||||
jamTutupLibur: this.form.jamTutupLibur,
|
||||
};
|
||||
|
||||
const cek = templateDokterForm.safeParse(formData);
|
||||
@@ -567,9 +591,255 @@ const dokter = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
const templateTarifForm = z.object({
|
||||
tarif: z.string().min(1, "Tarif tidak boleh kosong"),
|
||||
layanan: z.string().min(1, "Layanan tidak boleh kosong"),
|
||||
});
|
||||
|
||||
const defaultTarifForm = {
|
||||
tarif: "",
|
||||
layanan: "",
|
||||
};
|
||||
|
||||
const tarif = proxy({
|
||||
create: {
|
||||
form: defaultTarifForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateTarifForm.safeParse(tarif.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
tarif.create.loading = true;
|
||||
const res = await ApiFetch.api.kesehatan.tarifdanlayanan["create"].post(
|
||||
tarif.create.form
|
||||
);
|
||||
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data;
|
||||
if (id) {
|
||||
toast.success("Sukses menambahkan");
|
||||
tarif.create.form = { ...defaultTarifForm };
|
||||
tarif.findMany.load();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
toast.error("failed create");
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
return null;
|
||||
} finally {
|
||||
tarif.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.TarifDanLayananGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
tarif.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
tarif.findMany.page = page;
|
||||
tarif.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.kesehatan.tarifdanlayanan[
|
||||
"findMany"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
tarif.findMany.data = res.data.data ?? [];
|
||||
tarif.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
tarif.findMany.data = [];
|
||||
tarif.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch tarif dan layanan paginated:", err);
|
||||
tarif.findMany.data = [];
|
||||
tarif.findMany.totalPages = 1;
|
||||
} finally {
|
||||
tarif.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.TarifDanLayananGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/kesehatan/tarifdanlayanan/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
tarif.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch tarif dan layanan",
|
||||
res.statusText
|
||||
);
|
||||
tarif.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching tarif dan layanan", error);
|
||||
tarif.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultTarifForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/kesehatan/tarifdanlayanan/${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 = {
|
||||
tarif: data.tarif,
|
||||
layanan: data.layanan
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading tarif dan layanan:", 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 = {
|
||||
tarif: this.form.tarif,
|
||||
layanan: this.form.layanan
|
||||
};
|
||||
|
||||
const cek = templateTarifForm.safeParse(formData);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v: any) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await fetch(`/api/kesehatan/tarifdanlayanan/${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 tarif.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
toast.error("Gagal update data tarif dan layanan");
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) {
|
||||
return toast.warn("ID tidak valid");
|
||||
}
|
||||
try {
|
||||
tarif.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/kesehatan/tarifdanlayanan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "tarif dan layanan berhasil dihapus"
|
||||
);
|
||||
await tarif.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus tarif dan layanan"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus tarif dan layanan");
|
||||
} finally {
|
||||
tarif.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const fasilitasKesehatanState = proxy({
|
||||
fasilitasKesehatan,
|
||||
dokter,
|
||||
tarif
|
||||
});
|
||||
|
||||
export default fasilitasKesehatanState;
|
||||
|
||||
@@ -111,7 +111,7 @@ function EditKategoriBerita() {
|
||||
{/* Form Wrapper */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client';
|
||||
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
@@ -10,19 +8,22 @@ import {
|
||||
Button,
|
||||
Group,
|
||||
Loader,
|
||||
MultiSelect,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
interface FasilitasKesehatanFormBase {
|
||||
// Tipe form yang SESUAI dengan logika relasi (array ID)
|
||||
interface EditFasilitasKesehatanForm {
|
||||
name: string;
|
||||
informasiUmum: {
|
||||
fasilitas: string;
|
||||
@@ -30,128 +31,92 @@ interface FasilitasKesehatanFormBase {
|
||||
jamOperasional: string;
|
||||
};
|
||||
layananUnggulan: { content: string };
|
||||
dokterdanTenagaMedis: {
|
||||
name: string;
|
||||
specialist: string;
|
||||
jadwal: string;
|
||||
};
|
||||
dokterdanTenagaMedis: string[]; // ← ARRAY ID
|
||||
fasilitasPendukung: { content: string };
|
||||
prosedurPendaftaran: { content: string };
|
||||
tarifDanLayanan: {
|
||||
layanan: string;
|
||||
tarif: string;
|
||||
};
|
||||
tarifDanLayanan: string[]; // ← ARRAY ID
|
||||
}
|
||||
|
||||
function EditFasilitasKesehatan() {
|
||||
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||
const dokterState = useProxy(fasilitasKesehatanState.dokter);
|
||||
const tarifState = useProxy(fasilitasKesehatanState.tarif);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const params = useParams<{ id: string }>();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState<FasilitasKesehatanFormBase>({
|
||||
const [formData, setFormData] = useState<EditFasilitasKesehatanForm>({
|
||||
name: '',
|
||||
informasiUmum: { fasilitas: '', alamat: '', jamOperasional: '' },
|
||||
layananUnggulan: { content: '' },
|
||||
dokterdanTenagaMedis: { name: '', specialist: '', jadwal: '' },
|
||||
dokterdanTenagaMedis: [],
|
||||
fasilitasPendukung: { content: '' },
|
||||
prosedurPendaftaran: { content: '' },
|
||||
tarifDanLayanan: { layanan: '', tarif: '' },
|
||||
tarifDanLayanan: [],
|
||||
});
|
||||
|
||||
const [originalData, setOriginalData] = useState<FasilitasKesehatanFormBase>({
|
||||
name: '',
|
||||
informasiUmum: { fasilitas: '', alamat: '', jamOperasional: '' },
|
||||
layananUnggulan: { content: '' },
|
||||
dokterdanTenagaMedis: { name: '', specialist: '', jadwal: '' },
|
||||
fasilitasPendukung: { content: '' },
|
||||
prosedurPendaftaran: { content: '' },
|
||||
tarifDanLayanan: { layanan: '', tarif: '' },
|
||||
});
|
||||
// Load data fasilitas & daftar dokter/tarif
|
||||
useShallowEffect(() => {
|
||||
const loadAll = async () => {
|
||||
const id = params?.id;
|
||||
if (!id) return;
|
||||
|
||||
// Helper untuk update nested state
|
||||
const updateForm = <K extends keyof FasilitasKesehatanFormBase>(
|
||||
key: K,
|
||||
value: FasilitasKesehatanFormBase[K]
|
||||
) => setFormData(prev => ({ ...prev, [key]: value }));
|
||||
// Load dokter & tarif (untuk opsi MultiSelect)
|
||||
await Promise.all([
|
||||
dokterState.findMany.load(),
|
||||
tarifState.findMany.load(),
|
||||
]);
|
||||
|
||||
const updateNested = <
|
||||
K extends keyof FasilitasKesehatanFormBase,
|
||||
N extends keyof FasilitasKesehatanFormBase[K]
|
||||
>(key: K, nestedKey: N, value: FasilitasKesehatanFormBase[K][N]) =>
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[key]: { ...prev[key] as object, [nestedKey]: value },
|
||||
}));
|
||||
// Load data fasilitas
|
||||
await state.edit.load(id);
|
||||
const loaded = state.edit.form;
|
||||
if (loaded) {
|
||||
setFormData({
|
||||
name: loaded.name,
|
||||
informasiUmum: loaded.informasiUmum,
|
||||
layananUnggulan: loaded.layananUnggulan,
|
||||
dokterdanTenagaMedis: loaded.dokterdanTenagaMedis || [],
|
||||
fasilitasPendukung: loaded.fasilitasPendukung,
|
||||
prosedurPendaftaran: loaded.prosedurPendaftaran,
|
||||
tarifDanLayanan: loaded.tarifDanLayanan || [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deepClone = (obj: any): any => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
} catch (error) {
|
||||
console.warn('Gagal deep clone dengan JSON fallback:', error);
|
||||
return obj; // fallback (berisiko shared reference)
|
||||
loadAll();
|
||||
}, [params?.id]);
|
||||
|
||||
const updateForm = <K extends keyof EditFasilitasKesehatanForm>(
|
||||
field: K,
|
||||
value: EditFasilitasKesehatanForm[K]
|
||||
) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
const loaded = state.edit.form;
|
||||
if (loaded) {
|
||||
setFormData({
|
||||
name: loaded.name,
|
||||
informasiUmum: loaded.informasiUmum,
|
||||
layananUnggulan: loaded.layananUnggulan,
|
||||
dokterdanTenagaMedis: loaded.dokterdanTenagaMedis || [],
|
||||
fasilitasPendukung: loaded.fasilitasPendukung,
|
||||
prosedurPendaftaran: loaded.prosedurPendaftaran,
|
||||
tarifDanLayanan: loaded.tarifDanLayanan || [],
|
||||
});
|
||||
toast.info('Form dikembalikan ke data awal');
|
||||
}
|
||||
};
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
await state.edit.load(id);
|
||||
const loadedData = state.edit.form;
|
||||
|
||||
if (!loadedData) {
|
||||
toast.error('Data tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
|
||||
// Gunakan JSON fallback untuk deep clone
|
||||
const clonedData = deepClone(loadedData) as FasilitasKesehatanFormBase;
|
||||
|
||||
setFormData(clonedData);
|
||||
setOriginalData(clonedData);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('Gagal memuat data fasilitas kesehatan');
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleResetForm = () => {
|
||||
setFormData({
|
||||
name: originalData.name,
|
||||
informasiUmum:
|
||||
{
|
||||
fasilitas: originalData.informasiUmum.fasilitas,
|
||||
alamat: originalData.informasiUmum.alamat,
|
||||
jamOperasional: originalData.informasiUmum.jamOperasional
|
||||
},
|
||||
layananUnggulan: { content: originalData.layananUnggulan.content },
|
||||
dokterdanTenagaMedis: {
|
||||
name: originalData.dokterdanTenagaMedis.name,
|
||||
specialist: originalData.dokterdanTenagaMedis.specialist,
|
||||
jadwal: originalData.dokterdanTenagaMedis.jadwal
|
||||
},
|
||||
fasilitasPendukung: { content: originalData.fasilitasPendukung.content },
|
||||
prosedurPendaftaran: { content: originalData.prosedurPendaftaran.content },
|
||||
tarifDanLayanan: {
|
||||
layanan: originalData.tarifDanLayanan.layanan,
|
||||
tarif: originalData.tarifDanLayanan.tarif
|
||||
},
|
||||
});
|
||||
toast.info("Form dikembalikan ke data awal");
|
||||
};
|
||||
|
||||
// Submit
|
||||
const handleSubmit = async () => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
state.edit.form = { ...state.edit.form, ...formData };
|
||||
|
||||
// Update state Valtio
|
||||
state.edit.form = { ...formData };
|
||||
|
||||
const success = await state.edit.submit();
|
||||
if (success) {
|
||||
toast.success('Fasilitas kesehatan berhasil diperbarui!');
|
||||
@@ -159,14 +124,14 @@ function EditFasilitasKesehatan() {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('Terjadi kesalahan saat memperbarui data fasilitas kesehatan');
|
||||
toast.error('Gagal memperbarui data');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
@@ -189,7 +154,7 @@ function EditFasilitasKesehatan() {
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Fasilitas Kesehatan"
|
||||
placeholder="Masukkan nama fasilitas kesehatan"
|
||||
placeholder="Masukkan nama"
|
||||
value={formData.name}
|
||||
onChange={(e) => updateForm('name', e.target.value)}
|
||||
required
|
||||
@@ -197,118 +162,108 @@ function EditFasilitasKesehatan() {
|
||||
|
||||
{/* Informasi Umum */}
|
||||
<Box>
|
||||
<Text fw="bold" mb={5}>
|
||||
Informasi Umum
|
||||
</Text>
|
||||
<Text fw="bold" mb={5}>Informasi Umum</Text>
|
||||
<TextInput
|
||||
label="Fasilitas"
|
||||
value={formData.informasiUmum.fasilitas}
|
||||
onChange={(e) => updateNested('informasiUmum', 'fasilitas', e.target.value)}
|
||||
onChange={(e) =>
|
||||
updateForm('informasiUmum', {
|
||||
...formData.informasiUmum,
|
||||
fasilitas: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Alamat"
|
||||
value={formData.informasiUmum.alamat}
|
||||
onChange={(e) => updateNested('informasiUmum', 'alamat', e.target.value)}
|
||||
onChange={(e) =>
|
||||
updateForm('informasiUmum', {
|
||||
...formData.informasiUmum,
|
||||
alamat: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jam Operasional"
|
||||
value={formData.informasiUmum.jamOperasional}
|
||||
onChange={(e) => updateNested('informasiUmum', 'jamOperasional', e.target.value)}
|
||||
onChange={(e) =>
|
||||
updateForm('informasiUmum', {
|
||||
...formData.informasiUmum,
|
||||
jamOperasional: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Layanan Unggulan */}
|
||||
<Box>
|
||||
<Text fw="bold" mb={5}>
|
||||
Layanan Unggulan
|
||||
</Text>
|
||||
<Text fw="bold" mb={5}>Layanan Unggulan</Text>
|
||||
<EditEditor
|
||||
value={formData.layananUnggulan.content}
|
||||
onChange={(v) => updateNested('layananUnggulan', 'content', v)}
|
||||
onChange={(v) => updateForm('layananUnggulan', { content: v })}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Dokter dan Tenaga Medis */}
|
||||
<Box>
|
||||
<Text fw="bold" mb={5}>
|
||||
Dokter dan Tenaga Medis
|
||||
</Text>
|
||||
<TextInput
|
||||
label="Nama Dokter"
|
||||
value={formData.dokterdanTenagaMedis.name}
|
||||
onChange={(e) => updateNested('dokterdanTenagaMedis', 'name', e.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Specialist"
|
||||
value={formData.dokterdanTenagaMedis.specialist}
|
||||
onChange={(e) =>
|
||||
updateNested('dokterdanTenagaMedis', 'specialist', e.target.value)
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jadwal"
|
||||
value={formData.dokterdanTenagaMedis.jadwal}
|
||||
onChange={(e) => updateNested('dokterdanTenagaMedis', 'jadwal', e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{/* Dokter & Tenaga Medis — MultiSelect */}
|
||||
<MultiSelect
|
||||
label="Dokter & Tenaga Medis"
|
||||
placeholder="Pilih dokter/tenaga medis"
|
||||
data={
|
||||
dokterState.findMany.data?.map((d) => ({
|
||||
value: d.id,
|
||||
label: `${d.name} (${d.specialist})`,
|
||||
})) || []
|
||||
}
|
||||
value={formData.dokterdanTenagaMedis}
|
||||
onChange={(val) => updateForm('dokterdanTenagaMedis', val)}
|
||||
searchable
|
||||
clearable
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Fasilitas Pendukung */}
|
||||
<Box>
|
||||
<Text fw="bold" mb={5}>
|
||||
Fasilitas Pendukung
|
||||
</Text>
|
||||
<Text fw="bold" mb={5}>Fasilitas Pendukung</Text>
|
||||
<EditEditor
|
||||
value={formData.fasilitasPendukung.content}
|
||||
onChange={(v) => updateNested('fasilitasPendukung', 'content', v)}
|
||||
onChange={(v) => updateForm('fasilitasPendukung', { content: v })}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Prosedur Pendaftaran */}
|
||||
<Box>
|
||||
<Text fw="bold" mb={5}>
|
||||
Prosedur Pendaftaran
|
||||
</Text>
|
||||
<Text fw="bold" mb={5}>Prosedur Pendaftaran</Text>
|
||||
<EditEditor
|
||||
value={formData.prosedurPendaftaran.content}
|
||||
onChange={(v) => updateNested('prosedurPendaftaran', 'content', v)}
|
||||
onChange={(v) => updateForm('prosedurPendaftaran', { content: v })}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Tarif dan Layanan */}
|
||||
<Box>
|
||||
<Text fw="bold" mb={5}>
|
||||
Tarif dan Layanan
|
||||
</Text>
|
||||
<TextInput
|
||||
label="Tarif"
|
||||
value={formData.tarifDanLayanan.tarif}
|
||||
onChange={(e) => updateNested('tarifDanLayanan', 'tarif', e.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Layanan"
|
||||
value={formData.tarifDanLayanan.layanan}
|
||||
onChange={(e) => updateNested('tarifDanLayanan', 'layanan', e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{/* Tarif & Layanan — MultiSelect */}
|
||||
<MultiSelect
|
||||
label="Tarif & Layanan"
|
||||
placeholder="Pilih layanan"
|
||||
data={
|
||||
tarifState.findMany.data?.map((t) => ({
|
||||
value: t.id,
|
||||
label: `${t.layanan} - ${t.tarif}`,
|
||||
})) || []
|
||||
}
|
||||
value={formData.tarifDanLayanan}
|
||||
onChange={(val) => updateForm('tarifDanLayanan', val)}
|
||||
searchable
|
||||
clearable
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
{/* Aksi */}
|
||||
<Group justify="right">
|
||||
{/* Tombol Batal */}
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
>
|
||||
<Button variant="outline" color="gray" radius="md" onClick={handleReset}>
|
||||
Batal
|
||||
</Button>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
type="submit"
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
@@ -324,4 +279,4 @@ function EditFasilitasKesehatan() {
|
||||
);
|
||||
}
|
||||
|
||||
export default EditFasilitasKesehatan;
|
||||
export default EditFasilitasKesehatan;
|
||||
@@ -9,6 +9,12 @@ import {
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
@@ -108,21 +114,79 @@ function DetailFasilitasKesehatan() {
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Dokter & Tenaga Medis</Text>
|
||||
<Text fz="md" fw="bold">Nama</Text>
|
||||
<Text fz="md" c="dimmed">{data.dokterdantenagamedis?.name || '-'}</Text>
|
||||
<Text fz="md" fw="bold">Spesialis</Text>
|
||||
<Text fz="md" c="dimmed">{data.dokterdantenagamedis?.specialist || '-'}</Text>
|
||||
<Text fz="md" fw="bold">Jadwal</Text>
|
||||
<Text fz="md" c="dimmed">{data.dokterdantenagamedis?.jadwal || '-'}</Text>
|
||||
<Text fz="lg" fw="bold" mb="sm">Dokter & Tenaga Medis</Text>
|
||||
{data.dokterdantenagamedis && data.dokterdantenagamedis.length > 0 ? (
|
||||
<Box style={{ overflowX: 'auto', width: '100%' }}>
|
||||
<Table striped highlightOnHover withTableBorder>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ whiteSpace: 'nowrap' }}>Nama</TableTh>
|
||||
<TableTh style={{ whiteSpace: 'nowrap' }}>Spesialis</TableTh>
|
||||
<TableTh style={{ whiteSpace: 'nowrap' }}>Jadwal</TableTh>
|
||||
<TableTh style={{ whiteSpace: 'nowrap' }}>Jam Operasional</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.dokterdantenagamedis.map((dokter) => (
|
||||
<TableTr key={dokter.id}>
|
||||
<TableTd style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
{dokter.name || '-'}
|
||||
</TableTd>
|
||||
<TableTd style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
{dokter.specialist || '-'}
|
||||
</TableTd>
|
||||
<TableTd style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
{dokter.jadwal || '-'}
|
||||
</TableTd>
|
||||
<TableTd style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
{dokter.jamBukaOperasional} – {dokter.jamTutupOperasional}
|
||||
{dokter.jadwalLibur && (
|
||||
<>
|
||||
<br />
|
||||
<Text span c="dimmed" fz="xs">
|
||||
Libur: {dokter.jadwalLibur} ({dokter.jamBukaLibur}–{dokter.jamTutupLibur})
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c="dimmed">Tidak ada dokter atau tenaga medis terdaftar.</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Tarif & Layanan</Text>
|
||||
<Text fz="md" fw="bold">Layanan</Text>
|
||||
<Text fz="md" c="dimmed">{data.tarifdanlayanan?.layanan || '-'}</Text>
|
||||
<Text fz="md" fw="bold">Tarif</Text>
|
||||
<Text fz="md" c="dimmed">{data.tarifdanlayanan?.tarif || '-'}</Text>
|
||||
<Box mt="xl">
|
||||
<Text fz="lg" fw="bold" mb="sm">Tarif & Layanan</Text>
|
||||
{data.tarifdanlayanan && data.tarifdanlayanan.length > 0 ? (
|
||||
<Box style={{ overflowX: 'auto', width: '100%' }}>
|
||||
<Table striped highlightOnHover withTableBorder>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ whiteSpace: 'nowrap' }}>Layanan</TableTh>
|
||||
<TableTh style={{ whiteSpace: 'nowrap' }}>Tarif</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.tarifdanlayanan.map((tarif) => (
|
||||
<TableTr key={tarif.id}>
|
||||
<TableTd style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
{tarif.layanan || '-'}
|
||||
</TableTd>
|
||||
<TableTd style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
{tarif.tarif || '-'}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c="dimmed">Tidak ada tarif atau layanan terdaftar.</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Aksi */}
|
||||
|
||||
@@ -7,19 +7,20 @@ import {
|
||||
Button,
|
||||
Group,
|
||||
Loader,
|
||||
MultiSelect,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function CreateFasilitasKesehatan() {
|
||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||
const router = useRouter();
|
||||
@@ -34,23 +35,16 @@ function CreateFasilitasKesehatan() {
|
||||
jamOperasional: '',
|
||||
},
|
||||
layananUnggulan: {
|
||||
content: '',
|
||||
},
|
||||
dokterdanTenagaMedis: {
|
||||
name: '',
|
||||
specialist: '',
|
||||
jadwal: '',
|
||||
content: ''
|
||||
},
|
||||
dokterdanTenagaMedis: [] as string[],
|
||||
fasilitasPendukung: {
|
||||
content: '',
|
||||
},
|
||||
prosedurPendaftaran: {
|
||||
content: '',
|
||||
},
|
||||
tarifDanLayanan: {
|
||||
layanan: '',
|
||||
tarif: '',
|
||||
},
|
||||
tarifDanLayanan: [] as string[],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -70,6 +64,11 @@ function CreateFasilitasKesehatan() {
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fasilitasKesehatanState.dokter.findMany.load();
|
||||
fasilitasKesehatanState.tarif.findMany.load();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||
{/* Header */}
|
||||
@@ -140,31 +139,25 @@ function CreateFasilitasKesehatan() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Dokter dan Tenaga Medis */}
|
||||
<Box>
|
||||
<Text fz="md" fw="bold" mb={5}>Dokter dan Tenaga Medis</Text>
|
||||
<TextInput
|
||||
label="Nama Dokter"
|
||||
placeholder="Masukkan nama dokter"
|
||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name}
|
||||
onChange={(e) => (stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Spesialis"
|
||||
placeholder="Masukkan spesialis"
|
||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist}
|
||||
onChange={(e) => (stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Jadwal"
|
||||
placeholder="Masukkan jadwal"
|
||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal}
|
||||
onChange={(e) => (stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal = e.target.value)}
|
||||
required
|
||||
/>
|
||||
</Box>
|
||||
<MultiSelect
|
||||
label="Dokter & Tenaga Medis"
|
||||
placeholder="Pilih dokter / tenaga medis"
|
||||
data={
|
||||
fasilitasKesehatanState.dokter.findMany.data?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})) || []
|
||||
}
|
||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis}
|
||||
onChange={(val: string[]) => {
|
||||
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis = val;
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Fasilitas Pendukung */}
|
||||
<Box>
|
||||
@@ -175,6 +168,24 @@ function CreateFasilitasKesehatan() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<MultiSelect
|
||||
label="Layanan"
|
||||
placeholder="Pilih layanan"
|
||||
data={
|
||||
fasilitasKesehatanState.tarif.findMany.data?.map((item) => ({
|
||||
label: item.layanan,
|
||||
value: item.id,
|
||||
})) || []
|
||||
}
|
||||
value={stateFasilitasKesehatan.create.form.tarifDanLayanan} // string[]
|
||||
onChange={(val: string[]) => {
|
||||
stateFasilitasKesehatan.create.form.tarifDanLayanan = val;
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Prosedur Pendaftaran */}
|
||||
<Box>
|
||||
<Text fz="md" fw="bold" mb={5}>Prosedur Pendaftaran</Text>
|
||||
@@ -184,24 +195,6 @@ function CreateFasilitasKesehatan() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Tarif dan Layanan */}
|
||||
<Box>
|
||||
<Text fz="md" fw="bold" mb={5}>Tarif dan Layanan</Text>
|
||||
<TextInput
|
||||
label="Tarif"
|
||||
placeholder="Masukkan tarif"
|
||||
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif}
|
||||
onChange={(e) => (stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Layanan"
|
||||
placeholder="Masukkan layanan"
|
||||
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan}
|
||||
onChange={(e) => (stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan = e.target.value)}
|
||||
required
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Submit */}
|
||||
<Group justify="right">
|
||||
|
||||
@@ -1,11 +1,241 @@
|
||||
import React from 'react';
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client';
|
||||
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { TimeInput } from '@mantine/dates';
|
||||
import { IconArrowBack, IconClock } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function EditDokterTenagaMedis() {
|
||||
const state = useProxy(fasilitasKesehatanState.dokter);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
specialist: '',
|
||||
jadwal: '',
|
||||
jadwalLibur: '',
|
||||
jamBukaOperasional: '',
|
||||
jamTutupOperasional: '',
|
||||
jamBukaLibur: '',
|
||||
jamTutupLibur: '',
|
||||
});
|
||||
|
||||
const [originalData, setOriginalData] = useState({
|
||||
name: '',
|
||||
specialist: '',
|
||||
jadwal: '',
|
||||
jadwalLibur: '',
|
||||
jamBukaOperasional: '',
|
||||
jamTutupOperasional: '',
|
||||
jamBukaLibur: '',
|
||||
jamTutupLibur: '',
|
||||
});
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
await state.update.load(id);
|
||||
const loadedData = state.update.form;
|
||||
|
||||
if (!loadedData) {
|
||||
toast.error('Data tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
|
||||
setFormData(loadedData);
|
||||
setOriginalData(loadedData);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('Gagal memuat data fasilitas kesehatan');
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleResetForm = () => {
|
||||
setFormData({
|
||||
name: originalData.name,
|
||||
specialist: originalData.specialist,
|
||||
jadwal: originalData.jadwal,
|
||||
jadwalLibur: originalData.jadwalLibur,
|
||||
jamBukaOperasional: originalData.jamBukaOperasional,
|
||||
jamTutupOperasional: originalData.jamTutupOperasional,
|
||||
jamBukaLibur: originalData.jamBukaLibur,
|
||||
jamTutupLibur: originalData.jamTutupLibur,
|
||||
});
|
||||
toast.info("Form dikembalikan ke data awal");
|
||||
};
|
||||
|
||||
const refBuka = useRef<HTMLInputElement>(null);
|
||||
const refTutup = useRef<HTMLInputElement>(null);
|
||||
const refBukaLibur = useRef<HTMLInputElement>(null);
|
||||
const refTutupLibur = useRef<HTMLInputElement>(null);
|
||||
|
||||
const picker = (ref: any) => (
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => ref.current?.showPicker()}>
|
||||
<IconClock size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
);
|
||||
|
||||
const handleChange = (field: string, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
// Submit
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
state.update.form = { ...state.update.form, ...formData };
|
||||
const success = await state.update.submit();
|
||||
if (success) {
|
||||
toast.success('Data berhasil diperbarui!');
|
||||
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<div>
|
||||
Page
|
||||
</div>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Fasilitas Kesehatan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Dokter"
|
||||
placeholder="Masukkan nama dokter"
|
||||
value={formData.name}
|
||||
onChange={(e) => handleChange("name", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jadwal"
|
||||
placeholder="Masukkan jadwal"
|
||||
value={formData.jadwal}
|
||||
onChange={(e) => handleChange("jadwal", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jadwal Libur"
|
||||
placeholder="Masukkan jadwal libur"
|
||||
value={formData.jadwalLibur}
|
||||
onChange={(e) => handleChange("jadwalLibur", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Buka Operasional"
|
||||
ref={refBuka}
|
||||
rightSection={picker(refBuka)}
|
||||
value={formData.jamBukaOperasional}
|
||||
onChange={(e) => handleChange("jamBukaOperasional", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Tutup Operasional"
|
||||
ref={refTutup}
|
||||
rightSection={picker(refTutup)}
|
||||
value={formData.jamTutupOperasional}
|
||||
onChange={(e) => handleChange("jamTutupOperasional", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Buka Hari Libur"
|
||||
ref={refBukaLibur}
|
||||
rightSection={picker(refBukaLibur)}
|
||||
value={formData.jamBukaLibur}
|
||||
onChange={(e) => handleChange("jamBukaLibur", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Tutup Hari Libur"
|
||||
ref={refTutupLibur}
|
||||
rightSection={picker(refTutupLibur)}
|
||||
value={formData.jamTutupLibur}
|
||||
onChange={(e) => handleChange("jamTutupLibur", e.target.value)}
|
||||
required
|
||||
/>
|
||||
{/* Tombol Simpan */}
|
||||
<Group justify="right">
|
||||
{/* Tombol Batal */}
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default EditDokterTenagaMedis;
|
||||
|
||||
@@ -1,11 +1,165 @@
|
||||
import React from 'react';
|
||||
'use client'
|
||||
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,
|
||||
Group,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailDokterTenagaMedis() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const state = useProxy(fasilitasKesehatanState.dokter);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
state.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push(
|
||||
'/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = state.findUnique.data;
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<div>
|
||||
Page
|
||||
</div>
|
||||
<Box py={10}>
|
||||
{/* Tombol Back */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Wrapper Detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
Detail Dokter & Tenaga Medis
|
||||
</Text>
|
||||
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Nama Dokter</Text>
|
||||
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="md" fw="bold">Specialist</Text>
|
||||
<Text fz="md" c="dimmed">{data.specialist || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Jadwal</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.jadwal || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Jadwal Libur</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.jadwalLibur || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Jam Buka Operasional</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.jamBukaOperasional || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Jam Tutup Operasional</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.jamTutupOperasional || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Jam Buka Libur</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.jamBukaLibur || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Jam Tutup Libur</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.jamTutupLibur || '-' }} />
|
||||
</Box>
|
||||
|
||||
{/* Aksi */}
|
||||
<Group gap="sm">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/${data.id}/edit`
|
||||
)
|
||||
}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus dokter & tenaga medis ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default DetailDokterTenagaMedis;
|
||||
|
||||
@@ -1,71 +1,184 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'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 { ActionIcon, Box, Button, Group, Loader, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { TimeInput } from '@mantine/dates';
|
||||
import { IconArrowBack, IconClock } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function CreateDokter() {
|
||||
const params = useParams()
|
||||
const createState = useProxy(fasilitasKesehatanState.dokter)
|
||||
const router = useRouter();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.create.form = {
|
||||
name: "",
|
||||
specialist: "",
|
||||
jadwal: "",
|
||||
jadwalLibur: "",
|
||||
jamBukaOperasional: "",
|
||||
jamTutupOperasional: "",
|
||||
jamBukaLibur: "",
|
||||
jamTutupLibur: "",
|
||||
};
|
||||
};
|
||||
|
||||
const refBuka = useRef<HTMLInputElement>(null);
|
||||
const refTutup = useRef<HTMLInputElement>(null);
|
||||
const refBukaLibur = useRef<HTMLInputElement>(null);
|
||||
const refTutupLibur = useRef<HTMLInputElement>(null);
|
||||
|
||||
const picker = (ref: any) => (
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => ref.current?.showPicker()}>
|
||||
<IconClock size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
);
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await createState.create.create.create();
|
||||
resetForm();
|
||||
router.push(`/admin/kesehatan/fasilitas-kesehatan/${params?.id}/dokter-tenaga-medis`)
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
await createState.create.create.create();
|
||||
toast.success('Data berhasil disimpan');
|
||||
resetForm();
|
||||
router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis`)
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('Gagal menyimpan data');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Box component="form" onSubmit={handleSubmit}>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Tambah Data Dokter & Tenaga Medis
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Create Dokter</Title>
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
|
||||
placeholder="masukkan nama dokter"
|
||||
label={"Nama Dokter"}
|
||||
placeholder="Masukkan nama dokter"
|
||||
value={createState.create.create.form.name}
|
||||
onChange={(e) => {
|
||||
createState.create.create.form.name = e.target.value;
|
||||
}}
|
||||
onChange={(e) => (createState.create.create.form.name = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Text fz="md" fw="bold">Specialist</Text>
|
||||
|
||||
{/* Informasi Umum */}
|
||||
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Specialist</Text>}
|
||||
placeholder="masukkan specialist"
|
||||
label="Specialist"
|
||||
placeholder="Masukkan specialist"
|
||||
value={createState.create.create.form.specialist}
|
||||
onChange={(e) => {
|
||||
createState.create.create.form.specialist = e.target.value;
|
||||
}}
|
||||
onChange={(e) => (createState.create.create.form.specialist = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Text fz="md" fw="bold">Jadwal</Text>
|
||||
<CreateEditor
|
||||
value={createState.create.create.form.jadwal}
|
||||
onChange={(htmlContent) => {
|
||||
createState.create.create.form.jadwal = htmlContent;
|
||||
|
||||
<TextInput
|
||||
label="Jadwal"
|
||||
placeholder="Masukkan jadwal"
|
||||
value={createState.create.create.form.jadwal}
|
||||
onChange={(e) => (createState.create.create.form.jadwal = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jadwal Libur"
|
||||
placeholder="Masukkan jadwal libur"
|
||||
value={createState.create.create.form.jadwalLibur}
|
||||
onChange={(e) => (createState.create.create.form.jadwalLibur = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Buka Operasional"
|
||||
ref={refBuka}
|
||||
rightSection={picker(refBuka)}
|
||||
value={createState.create.create.form.jamBukaOperasional}
|
||||
onChange={(e) => (createState.create.create.form.jamBukaOperasional = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Tutup Operasional"
|
||||
ref={refTutup}
|
||||
rightSection={picker(refTutup)}
|
||||
value={createState.create.create.form.jamTutupOperasional}
|
||||
onChange={(e) => (createState.create.create.form.jamTutupOperasional = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Buka Hari Libur"
|
||||
ref={refBukaLibur}
|
||||
rightSection={picker(refBukaLibur)}
|
||||
value={createState.create.create.form.jamBukaLibur}
|
||||
onChange={(e) => (createState.create.create.form.jamBukaLibur = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TimeInput
|
||||
label="Jam Tutup Hari Libur"
|
||||
ref={refTutupLibur}
|
||||
rightSection={picker(refTutupLibur)}
|
||||
value={createState.create.create.form.jamTutupLibur}
|
||||
onChange={(e) => (createState.create.create.form.jamTutupLibur = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Submit */}
|
||||
<Group justify="right">
|
||||
{/* Tombol Batal */}
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={resetForm}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||
Simpan
|
||||
</Button>
|
||||
>
|
||||
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { IconArrowBack, IconDeviceImacCog, IconPlus, 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';
|
||||
|
||||
@@ -18,7 +17,7 @@ function DokterTenagaMedis() {
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan')}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
@@ -60,49 +59,101 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
|
||||
}
|
||||
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) => (
|
||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||
{/* Judul + Tombol Tambah */}
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Dokter dan Tenaga Medis</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/create'
|
||||
)
|
||||
}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{/* Tabel */}
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Dokter</TableTh>
|
||||
<TableTh>Spesialis</TableTh>
|
||||
<TableTh>Jadwal</TableTh>
|
||||
<TableTh>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{item.specialist}</TableTd>
|
||||
<TableTd>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
|
||||
<Box w={150}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
<Box w={150}>
|
||||
{item.specialist || '-'}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.jadwal || '-' }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconDeviceImacCog size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">
|
||||
Tidak ada fasilitas kesehatan yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10, search);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
@@ -16,30 +19,52 @@ import {
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { IconCoin, IconDeviceImacCog, IconPlus, IconReportMedical, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
|
||||
|
||||
function FasilitasKesehatan() {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const router = useRouter()
|
||||
return (
|
||||
<Box>
|
||||
{/* Header Search */}
|
||||
<HeaderSearch
|
||||
title='Fasilitas Kesehatan'
|
||||
placeholder='Cari nama, alamat, atau jam operasional...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<Grid mb={10}>
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Title order={3}>Fasilitas Kesehatan</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Group gap={"xs"}>
|
||||
<Tooltip label="List Dokter" withArrow>
|
||||
<ActionIcon onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis')} size="lg" radius="xl" color="green.6">
|
||||
<IconReportMedical size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="List Tarif Layanan" withArrow>
|
||||
<ActionIcon onClick={()=> router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan')} size="lg" radius="xl" color="blue.6">
|
||||
<IconCoin size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Paper radius="lg" bg={colors['white-1']}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder='Cari nama, alamat, atau jam operasional...'
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w="133%"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Paper>
|
||||
</Group>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
<ListFasilitasKesehatan search={search} />
|
||||
</Box>
|
||||
@@ -54,6 +79,7 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||
const { data, page, totalPages, loading, load } = stateFasilitasKesehatan.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
@@ -93,8 +119,8 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Fasilitas Kesehatan</TableTh>
|
||||
<TableTh>Dokter</TableTh>
|
||||
<TableTh>Layanan</TableTh>
|
||||
<TableTh>Jumlah Dokter</TableTh>
|
||||
<TableTh>Jumlah Layanan</TableTh>
|
||||
<TableTh>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
@@ -111,13 +137,17 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
{item.dokterdantenagamedis?.name || '-'}
|
||||
{item.dokterdantenagamedis?.length
|
||||
? `${item.dokterdantenagamedis.length} dokter`
|
||||
: '-'}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Text truncate="end" lineClamp={1}>
|
||||
{item.tarifdanlayanan?.layanan || '-'}
|
||||
{item.tarifdanlayanan?.length
|
||||
? `${item.tarifdanlayanan.length} layanan`
|
||||
: '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
@@ -141,7 +171,7 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">
|
||||
<Text c="dimmed">
|
||||
Tidak ada fasilitas kesehatan yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
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 EditTarifLayanan() {
|
||||
const editState = useProxy(fasilitasKesehatanState.tarif);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [originalData, setOriginalData] = useState({
|
||||
tarif: '',
|
||||
layanan: ''
|
||||
});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
tarif: '',
|
||||
layanan: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadTarifLayanan = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
tarif: data.tarif || '',
|
||||
layanan: data.layanan || '',
|
||||
});
|
||||
setOriginalData({
|
||||
tarif: data.tarif || '',
|
||||
layanan: data.layanan || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading tarif layanan:', error);
|
||||
toast.error('Gagal memuat data tarif layanan');
|
||||
}
|
||||
};
|
||||
|
||||
loadTarifLayanan();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleChange = (field: string, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleResetForm = () => {
|
||||
setFormData({
|
||||
tarif: originalData.tarif,
|
||||
layanan: originalData.layanan,
|
||||
});
|
||||
toast.info('Form dikembalikan ke data awal');
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
// update global state hanya saat submit
|
||||
editState.update.form = {
|
||||
...editState.update.form,
|
||||
tarif: formData.tarif,
|
||||
layanan: formData.layanan,
|
||||
};
|
||||
|
||||
await editState.update.submit();
|
||||
toast.success('Tarif Layanan berhasil diperbarui!');
|
||||
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan');
|
||||
} catch (error) {
|
||||
console.error('Error updating tarif layanan:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui tarif layanan');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Back Button + Title */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Tarif Layanan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Wrapper */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
name="layanan"
|
||||
label="Layanan"
|
||||
placeholder="Masukkan nama layanan"
|
||||
value={formData.layanan}
|
||||
onChange={(e) => handleChange('layanan', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
name="tarif"
|
||||
label="Tarif"
|
||||
placeholder="Masukkan tarif layanan"
|
||||
value={formData.tarif}
|
||||
onChange={(e) => handleChange('tarif', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={handleResetForm}
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditTarifLayanan;
|
||||
@@ -0,0 +1,119 @@
|
||||
'use client';
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreateTarifLayanan() {
|
||||
const createState = useProxy(fasilitasKesehatanState.tarif);
|
||||
const router = useRouter();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.form = {
|
||||
tarif: '',
|
||||
layanan: '',
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await createState.create.create();
|
||||
resetForm();
|
||||
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan');
|
||||
} catch (error) {
|
||||
console.error('Error creating tarif layanan:', error);
|
||||
toast.error('Gagal menambahkan tarif layanan');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header dengan back button */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Tambah Tarif Layanan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form utama */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Layanan"
|
||||
placeholder="Masukkan nama layanan"
|
||||
value={createState.create.form.layanan || ''}
|
||||
onChange={(e) => (createState.create.form.layanan = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Tarif"
|
||||
placeholder="Masukkan tarif"
|
||||
value={createState.create.form.tarif || ''}
|
||||
onChange={(e) => (createState.create.form.tarif = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
variant="outline"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
onClick={resetForm}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
|
||||
{/* Tombol Simpan */}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateTarifLayanan;
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { IconArrowBack, IconEdit, IconPlus, IconSearch, IconTrash } 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 { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -18,12 +18,12 @@ function TarifLayanan() {
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan')}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<HeaderSearch
|
||||
title='Dokter dan Tenaga Medis'
|
||||
title='Tarif dan Layanan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
@@ -35,8 +35,11 @@ function TarifLayanan() {
|
||||
}
|
||||
|
||||
function ListTarifLayanan({ search }: { search: string }) {
|
||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.dokter)
|
||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.tarif);
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
@@ -49,6 +52,15 @@ function ListTarifLayanan({ search }: { search: string }) {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateFasilitasKesehatan.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
load(page, 10, search);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
@@ -60,51 +72,116 @@ function ListTarifLayanan({ search }: { search: string }) {
|
||||
}
|
||||
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) => (
|
||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||
{/* Judul + Tombol Tambah */}
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Tarif dan Layanan</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan/create'
|
||||
)
|
||||
}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{/* Tabel */}
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Layanan</TableTh>
|
||||
<TableTh>Tarif</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{item.specialist}</TableTd>
|
||||
<TableTd>
|
||||
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
|
||||
<Box w={150}>
|
||||
{item.layanan || '-'}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
<Box w={150}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.tarif}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
disabled={stateFasilitasKesehatan.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">
|
||||
Tidak ada fasilitas kesehatan yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10, search);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text="Apakah anda yakin ingin menghapus tarif layanan ini?"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FasilitasKesehatanInput = {
|
||||
name: string;
|
||||
informasiUmum: { fasilitas: string; alamat: string; jamOperasional: string };
|
||||
layananUnggulan: { content: string };
|
||||
dokterdanTenagaMedis: { name: string; specialist: string; jadwal: string };
|
||||
fasilitasPendukung: { content: string };
|
||||
prosedurPendaftaran: { content: string };
|
||||
tarifDanLayanan: { layanan: string; tarif: string };
|
||||
};
|
||||
|
||||
const fasilitasKesehatanCreate = async (context: Context) => {
|
||||
const body = await context.body as FasilitasKesehatanInput;
|
||||
const body = (await context.body) as {
|
||||
name: string;
|
||||
informasiUmum: { fasilitas: string; alamat: string; jamOperasional: string };
|
||||
layananUnggulan: { content: string };
|
||||
dokterdanTenagaMedis: string[]; // ← ARRAY OF ID
|
||||
fasilitasPendukung: { content: string };
|
||||
prosedurPendaftaran: { content: string };
|
||||
tarifDanLayanan: string[]; // ← ARRAY OF ID
|
||||
};
|
||||
|
||||
const {
|
||||
name,
|
||||
@@ -24,25 +22,30 @@ const fasilitasKesehatanCreate = async (context: Context) => {
|
||||
tarifDanLayanan,
|
||||
} = body;
|
||||
|
||||
// Buat masing-masing relasi terlebih dahulu
|
||||
const [createdInformasiUmum, createdLayananUnggulan, createdDokter, createdPendukung, createdProsedur, createdTarif] = await Promise.all([
|
||||
prisma.informasiUmum.create({ data: informasiUmum }),
|
||||
prisma.layananUnggulan.create({ data: layananUnggulan }),
|
||||
prisma.dokterdanTenagaMedis.create({ data: dokterdanTenagaMedis }),
|
||||
prisma.fasilitasPendukung.create({ data: fasilitasPendukung }),
|
||||
prisma.prosedurPendaftaran.create({ data: prosedurPendaftaran }),
|
||||
prisma.tarifDanLayanan.create({ data: tarifDanLayanan }),
|
||||
]);
|
||||
// CREATE SINGLE DATA
|
||||
const [createdInformasi, createdUnggulan, createdPendukung, createdProsedur] =
|
||||
await Promise.all([
|
||||
prisma.informasiUmum.create({ data: informasiUmum }),
|
||||
prisma.layananUnggulan.create({ data: layananUnggulan }),
|
||||
prisma.fasilitasPendukung.create({ data: fasilitasPendukung }),
|
||||
prisma.prosedurPendaftaran.create({ data: prosedurPendaftaran }),
|
||||
]);
|
||||
|
||||
// ✅ CUKUP CONNECT KE ID YANG SUDAH ADA
|
||||
const fasilitas = await prisma.fasilitasKesehatan.create({
|
||||
data: {
|
||||
name,
|
||||
informasiUmumId: createdInformasiUmum.id,
|
||||
layananUnggulanId: createdLayananUnggulan.id,
|
||||
dokterdanTenagaMedisId: createdDokter.id,
|
||||
informasiUmumId: createdInformasi.id,
|
||||
layananUnggulanId: createdUnggulan.id,
|
||||
fasilitasPendukungId: createdPendukung.id,
|
||||
prosedurPendaftaranId: createdProsedur.id,
|
||||
tarifDanLayananId: createdTarif.id,
|
||||
|
||||
dokterdantenagamedis: {
|
||||
connect: dokterdanTenagaMedis.map(id => ({ id })), // ← langsung dari input
|
||||
},
|
||||
tarifdanlayanan: {
|
||||
connect: tarifDanLayanan.map(id => ({ id })), // ← langsung dari input
|
||||
},
|
||||
},
|
||||
include: {
|
||||
informasiumum: true,
|
||||
|
||||
@@ -2,42 +2,14 @@ import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
const fasilitasKesehatanDelete = async (context: Context) => {
|
||||
const id = context.params?.id as string;
|
||||
const id = context.params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
status: 400,
|
||||
message: "ID tidak ditemukan",
|
||||
}
|
||||
}
|
||||
const data = await prisma.fasilitasKesehatan.findUnique({ where: { id } });
|
||||
if (!data) return { status: 404, message: "Data tidak ditemukan" };
|
||||
|
||||
const fasilitasKesehatan = await prisma.fasilitasKesehatan.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
informasiumum: true,
|
||||
layananunggulan: true,
|
||||
dokterdantenagamedis: true,
|
||||
fasilitaspendukung: true,
|
||||
prosedurpendaftaran: true,
|
||||
tarifdanlayanan: true,
|
||||
}
|
||||
})
|
||||
await prisma.fasilitasKesehatan.delete({ where: { id } });
|
||||
|
||||
if (!fasilitasKesehatan) {
|
||||
return {
|
||||
status: 404,
|
||||
message: "Fasilitas kesehatan tidak ditemukan",
|
||||
}
|
||||
}
|
||||
return { success: true, message: "Berhasil dihapus" };
|
||||
};
|
||||
|
||||
await prisma.fasilitasKesehatan.delete({
|
||||
where: { id },
|
||||
})
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Fasilitas kesehatan berhasil dihapus",
|
||||
}
|
||||
}
|
||||
export default fasilitasKesehatanDelete
|
||||
export default fasilitasKesehatanDelete;
|
||||
|
||||
@@ -5,6 +5,11 @@ type FormCreate = {
|
||||
name: string;
|
||||
specialist: string;
|
||||
jadwal: string;
|
||||
jadwalLibur: string;
|
||||
jamBukaOperasional: string;
|
||||
jamTutupOperasional: string;
|
||||
jamBukaLibur: string;
|
||||
jamTutupLibur: string;
|
||||
};
|
||||
|
||||
export default async function dokterTenagaMedisCreate(context: Context) {
|
||||
@@ -15,11 +20,21 @@ export default async function dokterTenagaMedisCreate(context: Context) {
|
||||
name: body.name,
|
||||
specialist: body.specialist,
|
||||
jadwal: body.jadwal,
|
||||
jadwalLibur: body.jadwalLibur,
|
||||
jamBukaOperasional: body.jamBukaOperasional,
|
||||
jamTutupOperasional: body.jamTutupOperasional,
|
||||
jamBukaLibur: body.jamBukaLibur,
|
||||
jamTutupLibur: body.jamTutupLibur,
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
specialist: true,
|
||||
jadwal: true,
|
||||
jadwalLibur: true,
|
||||
jamBukaOperasional: true,
|
||||
jamTutupOperasional: true,
|
||||
jamBukaLibur: true,
|
||||
jamTutupLibur: true,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ async function dokterTenagaMedisFindMany(context: Context) {
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ specialist: { contains: search, mode: 'insensitive' } },
|
||||
{ jadwal: { contains: search, mode: 'insensitive' } },
|
||||
{ jadwalLibur: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ const DokterTenagaMedis = new Elysia({
|
||||
name: t.String(),
|
||||
specialist: t.String(),
|
||||
jadwal: t.String(),
|
||||
jadwalLibur: t.String(),
|
||||
jamBukaOperasional: t.String(),
|
||||
jamTutupOperasional: t.String(),
|
||||
jamBukaLibur: t.String(),
|
||||
jamTutupLibur: t.String(),
|
||||
}),
|
||||
})
|
||||
.put("/:id", dokterTenagaMedisUpdate, {
|
||||
@@ -26,6 +31,11 @@ const DokterTenagaMedis = new Elysia({
|
||||
name: t.String(),
|
||||
specialist: t.String(),
|
||||
jadwal: t.String(),
|
||||
jadwalLibur: t.String(),
|
||||
jamBukaOperasional: t.String(),
|
||||
jamTutupOperasional: t.String(),
|
||||
jamBukaLibur: t.String(),
|
||||
jamTutupLibur: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", dokterTenagaMedisDelete)
|
||||
|
||||
@@ -5,6 +5,11 @@ type FormUpdate = {
|
||||
name: string;
|
||||
specialist: string;
|
||||
jadwal: string;
|
||||
jadwalLibur: string;
|
||||
jamBukaOperasional: string;
|
||||
jamTutupOperasional: string;
|
||||
jamBukaLibur: string;
|
||||
jamTutupLibur: string;
|
||||
}
|
||||
|
||||
export default async function dokterTenagaMedisUpdate(context: Context) {
|
||||
@@ -18,6 +23,12 @@ export default async function dokterTenagaMedisUpdate(context: Context) {
|
||||
name: body.name,
|
||||
specialist: body.specialist,
|
||||
jadwal: body.jadwal,
|
||||
jadwalLibur: body.jadwalLibur,
|
||||
jamBukaOperasional: body.jamBukaOperasional,
|
||||
jamTutupOperasional: body.jamTutupOperasional,
|
||||
jamBukaLibur: body.jamBukaLibur,
|
||||
jamTutupLibur: body.jamTutupLibur,
|
||||
|
||||
},
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import fasilitasKesehatanCreate from "./create";
|
||||
import findManyFasilitasKesehatan from "./findMany";
|
||||
import fasilitasKesehatanFindMany from "./findMany";
|
||||
import findUniqueFasilitasKesehatan from "./findUnique";
|
||||
import fasilitasKesehatanUpdate from "./updt";
|
||||
import fasilitasKesehatanDelete from "./del";
|
||||
@@ -9,42 +9,61 @@ const FasilitasKesehatan = new Elysia({
|
||||
prefix: "fasilitas-kesehatan",
|
||||
tags: ["Kesehatan/Fasilitas Kesehatan"],
|
||||
})
|
||||
|
||||
// ==========================
|
||||
// CREATE
|
||||
// ==========================
|
||||
.post("/create", fasilitasKesehatanCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
|
||||
informasiUmum: t.Object({
|
||||
fasilitas: t.String(),
|
||||
alamat: t.String(),
|
||||
jamOperasional: t.String(),
|
||||
}),
|
||||
|
||||
layananUnggulan: t.Object({
|
||||
content: t.String(),
|
||||
}),
|
||||
dokterdanTenagaMedis: t.Object({
|
||||
name: t.String(),
|
||||
specialist: t.String(),
|
||||
jadwal: t.String(),
|
||||
}),
|
||||
|
||||
dokterdanTenagaMedis: t.Array(t.String()), // FIX karena create pakai array of string
|
||||
|
||||
fasilitasPendukung: t.Object({
|
||||
content: t.String(),
|
||||
}),
|
||||
|
||||
prosedurPendaftaran: t.Object({
|
||||
content: t.String(),
|
||||
}),
|
||||
tarifDanLayanan: t.Object({
|
||||
layanan: t.String(),
|
||||
tarif: t.String(),
|
||||
}),
|
||||
|
||||
tarifDanLayanan: t.Array(t.String()), // FIX karena create pakai array of string
|
||||
}),
|
||||
})
|
||||
.get("/find-many", findManyFasilitasKesehatan)
|
||||
|
||||
// ==========================
|
||||
// FIND MANY
|
||||
// ==========================
|
||||
.get("/find-many", fasilitasKesehatanFindMany)
|
||||
|
||||
// ==========================
|
||||
// DELETE
|
||||
// ==========================
|
||||
.delete("/del/:id", fasilitasKesehatanDelete)
|
||||
|
||||
// ==========================
|
||||
// FIND UNIQUE
|
||||
// ==========================
|
||||
.get("/:id", async (context) => {
|
||||
const response = await findUniqueFasilitasKesehatan(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
|
||||
// ==========================
|
||||
// UPDATE
|
||||
// ==========================
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
@@ -54,29 +73,30 @@ const FasilitasKesehatan = new Elysia({
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
|
||||
informasiUmum: t.Object({
|
||||
fasilitas: t.String(),
|
||||
alamat: t.String(),
|
||||
jamOperasional: t.String(),
|
||||
}),
|
||||
|
||||
layananUnggulan: t.Object({
|
||||
content: t.String(),
|
||||
}),
|
||||
dokterdanTenagaMedis: t.Object({
|
||||
name: t.String(),
|
||||
specialist: t.String(),
|
||||
jadwal: t.String(),
|
||||
}),
|
||||
|
||||
// FIX → harus array of string (ID dokter)
|
||||
dokterdanTenagaMedis: t.Array(t.String()),
|
||||
|
||||
fasilitasPendukung: t.Object({
|
||||
content: t.String(),
|
||||
}),
|
||||
|
||||
prosedurPendaftaran: t.Object({
|
||||
content: t.String(),
|
||||
}),
|
||||
tarifDanLayanan: t.Object({
|
||||
layanan: t.String(),
|
||||
tarif: t.String(),
|
||||
}),
|
||||
|
||||
// FIX → harus array of string (ID tarif)
|
||||
tarifDanLayanan: t.Array(t.String()),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
layanan: string;
|
||||
tarif: string;
|
||||
};
|
||||
|
||||
export default async function tarifLayananCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
const created = await prisma.tarifDanLayanan.create({
|
||||
data: {
|
||||
layanan: body.layanan,
|
||||
tarif: body.tarif,
|
||||
},
|
||||
select: {
|
||||
layanan: true,
|
||||
tarif: true,
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Sukses menambahkan dokter tenaga medis",
|
||||
data: created,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function tarifLayananDelete(context: Context) {
|
||||
|
||||
const id = context.params?.id;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
const existing = await prisma.tarifDanLayanan.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
const deleted = await prisma.tarifDanLayanan.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data berhasil dihapus",
|
||||
data: deleted,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function tarifLayananFindMany(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 = [
|
||||
{ layanan: { contains: search, mode: 'insensitive' } },
|
||||
{ tarif: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.tarifDanLayanan.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { layanan: 'asc' },
|
||||
}),
|
||||
prisma.tarifDanLayanan.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil data tarif layanan 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 tarif layanan",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default tarifLayananFindMany;
|
||||
@@ -0,0 +1,47 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function tarifLayananFindUnique(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.tarifDanLayanan.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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import tarifLayananCreate from "./create";
|
||||
import tarifLayananFindMany from "./findMany";
|
||||
import tarifLayananFindUnique from "./findUnique";
|
||||
import tarifLayananDelete from "./del";
|
||||
import tarifLayananUpdate from "./updt";
|
||||
|
||||
const TarifLayanan = new Elysia({
|
||||
prefix: "/tarifdanlayanan",
|
||||
tags: ["Data Kesehatan/Fasilitas Kesehatan/Tarif Layanan"]
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
const response = await tarifLayananFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.get("/findMany", tarifLayananFindMany)
|
||||
.post("/create", tarifLayananCreate, {
|
||||
body: t.Object({
|
||||
tarif: t.String(),
|
||||
layanan: t.String()
|
||||
}),
|
||||
})
|
||||
.put("/:id", tarifLayananUpdate, {
|
||||
body: t.Object({
|
||||
tarif: t.String(),
|
||||
layanan: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", tarifLayananDelete)
|
||||
export default TarifLayanan
|
||||
@@ -0,0 +1,32 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
layanan: string;
|
||||
tarif: string;
|
||||
};
|
||||
|
||||
export default async function tarifLayananUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.tarifDanLayanan.update({
|
||||
where: { id },
|
||||
data: {
|
||||
layanan: body.layanan,
|
||||
tarif: body.tarif,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate data tarif layanan",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating data tarif layanan:", error);
|
||||
throw new Error(
|
||||
"Gagal mengupdate data tarif layanan: " + (error as Error).message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,32 +5,26 @@ type FasilitasKesehatanInput = {
|
||||
name: string;
|
||||
informasiUmum: { fasilitas: string; alamat: string; jamOperasional: string };
|
||||
layananUnggulan: { content: string };
|
||||
dokterdanTenagaMedis: { name: string; specialist: string; jadwal: string };
|
||||
dokterdanTenagaMedis: string[]; // ← ID saja
|
||||
fasilitasPendukung: { content: string };
|
||||
prosedurPendaftaran: { content: string };
|
||||
tarifDanLayanan: { layanan: string; tarif: string };
|
||||
tarifDanLayanan: string[]; // ← ID saja
|
||||
};
|
||||
|
||||
const fasilitasKesehatanUpdate = async (context: Context) => {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as FasilitasKesehatanInput;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "ID is required" }),
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
const body = (await context.body) as FasilitasKesehatanInput;
|
||||
|
||||
const existing = await prisma.fasilitasKesehatan.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
dokterdantenagamedis: true,
|
||||
tarifdanlayanan: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "Data not found" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
return { success: false, message: "Data tidak ditemukan" };
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -43,38 +37,46 @@ const fasilitasKesehatanUpdate = async (context: Context) => {
|
||||
tarifDanLayanan,
|
||||
} = body;
|
||||
|
||||
// Update data masing-masing relasi
|
||||
// update relasi 1-1
|
||||
await Promise.all([
|
||||
prisma.informasiUmum.update({
|
||||
where: { id: existing.informasiUmumId },
|
||||
data: informasiUmum,
|
||||
}),
|
||||
|
||||
prisma.layananUnggulan.update({
|
||||
where: { id: existing.layananUnggulanId },
|
||||
data: layananUnggulan,
|
||||
}),
|
||||
prisma.dokterdanTenagaMedis.update({
|
||||
where: { id: existing.dokterdanTenagaMedisId },
|
||||
data: dokterdanTenagaMedis,
|
||||
}),
|
||||
|
||||
prisma.fasilitasPendukung.update({
|
||||
where: { id: existing.fasilitasPendukungId },
|
||||
data: fasilitasPendukung,
|
||||
}),
|
||||
|
||||
prisma.prosedurPendaftaran.update({
|
||||
where: { id: existing.prosedurPendaftaranId },
|
||||
data: prosedurPendaftaran,
|
||||
}),
|
||||
prisma.tarifDanLayanan.update({
|
||||
where: { id: existing.tarifDanLayananId },
|
||||
data: tarifDanLayanan,
|
||||
}),
|
||||
]);
|
||||
|
||||
// Update main record
|
||||
// update m2m
|
||||
const updated = await prisma.fasilitasKesehatan.update({
|
||||
where: { id },
|
||||
data: { name },
|
||||
|
||||
data: {
|
||||
name,
|
||||
|
||||
// reset dokter lama → ganti baru
|
||||
dokterdantenagamedis: {
|
||||
set: dokterdanTenagaMedis.map((id) => ({ id })),
|
||||
},
|
||||
|
||||
tarifdanlayanan: {
|
||||
set: tarifDanLayanan.map((id) => ({ id })),
|
||||
},
|
||||
},
|
||||
|
||||
include: {
|
||||
informasiumum: true,
|
||||
layananunggulan: true,
|
||||
@@ -87,7 +89,7 @@ const fasilitasKesehatanUpdate = async (context: Context) => {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Fasilitas berhasil diupdate",
|
||||
message: "Fasilitas diupdate",
|
||||
data: updated,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import Kelahiran from "./data_kesehatan_warga/persentase_kelahiran_kematian/kela
|
||||
import Kematian from "./data_kesehatan_warga/persentase_kelahiran_kematian/kematian";
|
||||
import DokterTenagaMedis from "./data_kesehatan_warga/fasilitas_kesehatan/dokter-tenaga-medis";
|
||||
import PendaftaranJadwalKegiatan from "./data_kesehatan_warga/jadwal_kegiatan/pendaftaran";
|
||||
import TarifLayanan from "./data_kesehatan_warga/fasilitas_kesehatan/tarif-layanan";
|
||||
|
||||
|
||||
const Kesehatan = new Elysia({
|
||||
@@ -46,5 +47,6 @@ const Kesehatan = new Elysia({
|
||||
.use(Kelahiran)
|
||||
.use(Kematian)
|
||||
.use(DokterTenagaMedis)
|
||||
.use(TarifLayanan)
|
||||
.use(PendaftaranJadwalKegiatan)
|
||||
export default Kesehatan;
|
||||
|
||||
@@ -557,25 +557,37 @@ export default async function searchFindMany(context: Context) {
|
||||
],
|
||||
},
|
||||
layananunggulan: { content: { contains: query, mode: "insensitive" } },
|
||||
dokterdantenagamedis: {
|
||||
OR: [
|
||||
{ name: { contains: query, mode: "insensitive" } },
|
||||
{ specialist: { contains: query, mode: "insensitive" } },
|
||||
{ jadwal: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
|
||||
fasilitaspendukung: {
|
||||
content: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
prosedurpendaftaran: {
|
||||
content: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
tarifdanlayanan: {
|
||||
OR: [
|
||||
{ layanan: { contains: query, mode: "insensitive" } },
|
||||
{ tarif: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "doktertenagamedis") {
|
||||
const data = await prisma.dokterdanTenagaMedis.findMany({
|
||||
where: {
|
||||
name: { contains: query, mode: "insensitive" },
|
||||
specialist: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
skip,
|
||||
take: limitNum,
|
||||
});
|
||||
return { data, nextPage: data.length < limitNum ? null : pageNum + 1 };
|
||||
}
|
||||
|
||||
if (type === "tarifdanlayanan") {
|
||||
const data = await prisma.tarifDanLayanan.findMany({
|
||||
where: {
|
||||
layanan: { contains: query, mode: "insensitive" },
|
||||
tarif: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
skip,
|
||||
take: limitNum,
|
||||
@@ -1567,6 +1579,8 @@ export default async function searchFindMany(context: Context) {
|
||||
jenisProgramYangDiselenggarakan,
|
||||
dataPerpustakaan,
|
||||
dataPendidikan,
|
||||
dokterDanTenagaMedis,
|
||||
tarifDanLayanan
|
||||
] = await Promise.all([
|
||||
prisma.pejabatDesa.findMany({
|
||||
where: { name: { contains: query, mode: "insensitive" } },
|
||||
@@ -1894,25 +1908,27 @@ export default async function searchFindMany(context: Context) {
|
||||
],
|
||||
},
|
||||
layananunggulan: { content: { contains: query, mode: "insensitive" } },
|
||||
dokterdantenagamedis: {
|
||||
OR: [
|
||||
{ name: { contains: query, mode: "insensitive" } },
|
||||
{ specialist: { contains: query, mode: "insensitive" } },
|
||||
{ jadwal: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
|
||||
fasilitaspendukung: {
|
||||
content: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
prosedurpendaftaran: {
|
||||
content: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
tarifdanlayanan: {
|
||||
OR: [
|
||||
{ layanan: { contains: query, mode: "insensitive" } },
|
||||
{ tarif: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.dokterdanTenagaMedis.findMany({
|
||||
where: {
|
||||
name: { contains: query, mode: "insensitive" },
|
||||
specialist: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
take: limitNum,
|
||||
}),
|
||||
prisma.tarifDanLayanan.findMany({
|
||||
where: {
|
||||
tarif: { contains: query, mode: "insensitive" },
|
||||
layanan: { contains: query, mode: "insensitive" },
|
||||
},
|
||||
take: limitNum,
|
||||
}),
|
||||
@@ -2316,7 +2332,7 @@ export default async function searchFindMany(context: Context) {
|
||||
{ judul: { contains: query, mode: "insensitive" } },
|
||||
{ deskripsiSingkat: { contains: query, mode: "insensitive" } },
|
||||
{ deskripsiLengkap: { contains: query, mode: "insensitive" } },
|
||||
{ lokasi: { contains: query, mode: "insensitive" } },
|
||||
{ lokasi: { contains: query, mode: "insensitive" } },
|
||||
{
|
||||
kategoriKegiatan: {
|
||||
nama: { contains: query, mode: "insensitive" },
|
||||
@@ -2559,6 +2575,8 @@ export default async function searchFindMany(context: Context) {
|
||||
...penghargaan.map((b) => ({ type: "penghargaan", ...b })),
|
||||
...posyandu.map((b) => ({ type: "posyandu", ...b })),
|
||||
...fasilitasKesehatan.map((b) => ({ type: "fasilitasKesehatan", ...b })),
|
||||
...dokterDanTenagaMedis.map((b) => ({ type: "dokterdanTenagaMedis", ...b })),
|
||||
...tarifDanLayanan.map((b) => ({ type: "tarifDanLayanan", ...b })),
|
||||
...jadwalKegiatan.map((b) => ({ type: "jadwalKegiatan", ...b })),
|
||||
...artikelKesehatan.map((b) => ({ type: "artikelKesehatan", ...b })),
|
||||
...puskesmas.map((b) => ({ type: "puskesmas", ...b })),
|
||||
|
||||
@@ -42,9 +42,7 @@ function Page() {
|
||||
const alamat = data?.informasiumum?.alamat || '-';
|
||||
const jam = data?.informasiumum?.jamOperasional || '-';
|
||||
const layananUnggulan = data?.layananunggulan?.content || '';
|
||||
const tenaga = data?.dokterdantenagamedis || null;
|
||||
const fasilitasPendukungHtml = data?.fasilitaspendukung?.content || '';
|
||||
const tarif = (data?.tarifdanlayanan as TarifDanLayanan) || null;
|
||||
const kontak = (data?.kontak as Kontak) || {
|
||||
telepon: '(0361) 123456',
|
||||
whatsapp: '6289647037426',
|
||||
@@ -211,7 +209,7 @@ function Page() {
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={4}>Dokter & Tenaga Medis</Title>
|
||||
<Table highlightOnHover withTableBorder withColumnBorders stickyHeader stickyHeaderOffset={0} aria-label="Tabel Dokter">
|
||||
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Dokter">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
@@ -220,12 +218,19 @@ function Page() {
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{tenaga ? (
|
||||
<TableTr>
|
||||
<TableTd><Group gap="xs"><IconUser size={16} /><Text>{tenaga?.name || '-'}</Text></Group></TableTd>
|
||||
<TableTd>{tenaga?.specialist || '-'}</TableTd>
|
||||
<TableTd>{tenaga?.jadwal || '-'}</TableTd>
|
||||
</TableTr>
|
||||
{Array.isArray(data?.dokterdantenagamedis) && data.dokterdantenagamedis.length > 0 ? (
|
||||
data.dokterdantenagamedis.map((dokter: any) => (
|
||||
<TableTr key={dokter.id}>
|
||||
<TableTd>
|
||||
<Group gap="xs">
|
||||
<IconUser size={16} />
|
||||
<Text>{dokter.name || '-'}</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>{dokter.specialist || '-'}</TableTd>
|
||||
<TableTd>{dokter.jadwal || '-'}</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={3}>
|
||||
@@ -241,72 +246,74 @@ function Page() {
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Fasilitas Pendukung</Title>
|
||||
<Divider />
|
||||
{fasilitasPendukungHtml ? (
|
||||
<Text fz="md" style={{wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} />
|
||||
) : (
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group gap="sm">
|
||||
<IconMoodEmpty />
|
||||
<Text>Belum ada informasi fasilitas pendukung.</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Fasilitas Pendukung</Title>
|
||||
<Divider />
|
||||
{fasilitasPendukungHtml ? (
|
||||
<Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} />
|
||||
) : (
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group gap="sm">
|
||||
<IconMoodEmpty />
|
||||
<Text>Belum ada informasi fasilitas pendukung.</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Layanan & Tarif</Title>
|
||||
<Divider />
|
||||
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Layanan</TableTh>
|
||||
<TableTh>Tarif</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{tarif ? (
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Layanan & Tarif</Title>
|
||||
<Divider />
|
||||
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTd>{tarif?.layanan || '-'}</TableTd>
|
||||
<TableTd>{formatRupiah(tarif?.tarif)}</TableTd>
|
||||
<TableTh>Layanan</TableTh>
|
||||
<TableTh>Tarif</TableTh>
|
||||
</TableTr>
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={2}>
|
||||
<Group justify="center" gap="xs" c="dimmed">
|
||||
<IconSearch size={18} />
|
||||
<Text>Tidak ada data tarif.</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
{gratisBpjs && (
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
|
||||
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{Array.isArray(data?.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? (
|
||||
data.tarifdanlayanan.map((item: any) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.layanan || '-'}</TableTd>
|
||||
<TableTd>{formatRupiah(item.tarif)}</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={2}>
|
||||
<Group justify="center" gap="xs" c="dimmed">
|
||||
<IconSearch size={18} />
|
||||
<Text>Tidak ada data tarif.</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
{gratisBpjs && (
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
|
||||
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }} pb="xl">
|
||||
<Paper radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Prosedur Pendaftaran</Title>
|
||||
<Divider />
|
||||
{prosedur ? (
|
||||
<Text fz="md" style={{wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: prosedur }} />
|
||||
<Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: prosedur }} />
|
||||
) : (
|
||||
<Text fz="md" c="dimmed">Belum ada prosedur pendaftaran</Text>
|
||||
)}
|
||||
|
||||
@@ -50,7 +50,9 @@ function SosmedView({
|
||||
loading="lazy"
|
||||
src={src}
|
||||
alt={item.name}
|
||||
fit={item.image?.link ? "cover" : "contain"}
|
||||
w={24}
|
||||
h={24}
|
||||
fit="contain"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const getDetailUrl = (item: { type?: string; id: string | number; [key: string]: unknown }) => {
|
||||
const getDetailUrl = (item: { type?: string; id: string | number;[key: string]: unknown }) => {
|
||||
const { type, id, kategori } = item;
|
||||
const map: Record<string, (id: string | number, kategori?: string) => string> = {
|
||||
programinovasi: (id) => `/darmasaba/program-inovasi/${id}`,
|
||||
@@ -30,6 +30,8 @@ const getDetailUrl = (item: { type?: string; id: string | number; [key: string]:
|
||||
penghargaan: () => '/darmasaba/desa/penghargaan',
|
||||
posyandu: (id) => `/darmasaba/kesehatan/posyandu/${id}`,
|
||||
fasilitasKesehatan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||
dokterDanTenagaMedis: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||
tarifDanLayanan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||
jadwalKegiatan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||
artikelKesehatan: () => '/darmasaba/kesehatan/data-kesehatan-warga',
|
||||
puskesmas: () => '/darmasaba/kesehatan/puskesmas',
|
||||
@@ -82,7 +84,7 @@ const getDetailUrl = (item: { type?: string; id: string | number; [key: string]:
|
||||
jenisProgramYangDiselenggarakan: () => '/darmasaba/pendidikan/pendidikan-non-formal',
|
||||
dataPerpustakaan: () => '/darmasaba/pendidikan/perpustakaan-digital/semua',
|
||||
dataPendidikan: () => '/darmasaba/pendidikan/data-pendidikan',
|
||||
|
||||
|
||||
};
|
||||
|
||||
if (type && map[type]) return map[type](id, kategori as string | undefined);
|
||||
|
||||
Reference in New Issue
Block a user