Fix UI Admin Menu Kesehatan, Login Admin, OTP
This commit is contained in:
@@ -75,6 +75,7 @@
|
|||||||
"prisma": "^6.3.1",
|
"prisma": "^6.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-international-phone": "^4.6.0",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-simple-toasts": "^6.1.0",
|
"react-simple-toasts": "^6.1.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
|||||||
@@ -115,27 +115,38 @@ const artikelKesehatanState = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
artikelKesehatanState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
artikelKesehatanState.findMany.page = page;
|
||||||
|
artikelKesehatanState.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
const query: any = { page, limit };
|
||||||
const res = await (ApiFetch.api.kesehatan as any)["artikel-kesehatan"][
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.kesehatan["artikel-kesehatan"][
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({ query });
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
this.data = res.data?.data ?? [];
|
artikelKesehatanState.findMany.data =
|
||||||
|
res.data.data ?? [];
|
||||||
|
artikelKesehatanState.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
toast.error("Gagal memuat data artikel kesehatan");
|
artikelKesehatanState.findMany.data = [];
|
||||||
|
artikelKesehatanState.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("Terjadi error saat load data");
|
console.error("Gagal fetch artikel kesehatan paginated:", err);
|
||||||
console.error("LOAD ERROR:", err);
|
artikelKesehatanState.findMany.data = [];
|
||||||
throw err;
|
artikelKesehatanState.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
artikelKesehatanState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -280,12 +291,9 @@ const artikelKesehatanState = proxy({
|
|||||||
async byId(id: string) {
|
async byId(id: string) {
|
||||||
try {
|
try {
|
||||||
artikelKesehatanState.delete.loading = true;
|
artikelKesehatanState.delete.loading = true;
|
||||||
const res = await fetch(
|
const res = await fetch(`/api/kesehatan/artikel-kesehatan/del/${id}`, {
|
||||||
`/api/kesehatan/artikel-kesehatan/del/${id}`,
|
method: "DELETE",
|
||||||
{
|
});
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
if (res.ok && result.success) {
|
if (res.ok && result.success) {
|
||||||
|
|||||||
@@ -116,27 +116,38 @@ const fasilitasKesehatan = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.page = page;
|
||||||
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
const query: any = { page, limit };
|
||||||
const res = await (ApiFetch.api.kesehatan as any)[
|
if (search) query.search = search;
|
||||||
"fasilitas-kesehatan"
|
|
||||||
]["find-many"].get();
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
const res = await ApiFetch.api.kesehatan["fasilitas-kesehatan"][
|
||||||
this.data = res.data?.data ?? [];
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.data =
|
||||||
|
res.data.data ?? [];
|
||||||
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
toast.error("Gagal memuat data fasilitas kesehatan");
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.data = [];
|
||||||
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("Terjadi error saat load data");
|
console.error("Gagal fetch fasilitas kesehatan paginated:", err);
|
||||||
console.error("LOAD ERROR:", err);
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.data = [];
|
||||||
throw err;
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
fasilitasKesehatanState.fasilitasKesehatan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -558,7 +569,7 @@ const dokter = proxy({
|
|||||||
|
|
||||||
const fasilitasKesehatanState = proxy({
|
const fasilitasKesehatanState = proxy({
|
||||||
fasilitasKesehatan,
|
fasilitasKesehatan,
|
||||||
dokter
|
dokter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default fasilitasKesehatanState;
|
export default fasilitasKesehatanState;
|
||||||
|
|||||||
@@ -120,27 +120,36 @@ const jadwalkegiatanState = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
jadwalkegiatanState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
jadwalkegiatanState.findMany.page = page;
|
||||||
|
jadwalkegiatanState.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
const query: any = { page, limit };
|
||||||
const res = await (ApiFetch.api.kesehatan as any)[
|
if (search) query.search = search;
|
||||||
"jadwal-kegiatan"
|
|
||||||
]["find-many"].get();
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
const res = await ApiFetch.api.kesehatan["jadwal-kegiatan"][
|
||||||
this.data = res.data?.data ?? [];
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
jadwalkegiatanState.findMany.data = res.data.data ?? [];
|
||||||
|
jadwalkegiatanState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
toast.error("Gagal memuat data jadwal kegiatan");
|
jadwalkegiatanState.findMany.data = [];
|
||||||
|
jadwalkegiatanState.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("Terjadi error saat load data");
|
console.error("Gagal fetch jadwal kegiatan paginated:", err);
|
||||||
console.error("LOAD ERROR:", err);
|
jadwalkegiatanState.findMany.data = [];
|
||||||
throw err;
|
jadwalkegiatanState.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
jadwalkegiatanState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -227,29 +236,42 @@ const jadwalkegiatanState = proxy({
|
|||||||
content: jadwalkegiatanState.edit.form.content,
|
content: jadwalkegiatanState.edit.form.content,
|
||||||
informasiJadwalKegiatan: {
|
informasiJadwalKegiatan: {
|
||||||
name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name,
|
name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name,
|
||||||
tanggal: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal,
|
tanggal:
|
||||||
|
jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal,
|
||||||
waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu,
|
waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu,
|
||||||
lokasi: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi,
|
lokasi:
|
||||||
|
jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi,
|
||||||
},
|
},
|
||||||
layananJadwalKegiatan: {
|
layananJadwalKegiatan: {
|
||||||
content: jadwalkegiatanState.edit.form.layananJadwalKegiatan.content,
|
content:
|
||||||
|
jadwalkegiatanState.edit.form.layananJadwalKegiatan.content,
|
||||||
},
|
},
|
||||||
deskripsiJadwalKegiatan: {
|
deskripsiJadwalKegiatan: {
|
||||||
deskripsi: jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi,
|
deskripsi:
|
||||||
|
jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi,
|
||||||
},
|
},
|
||||||
syaratKetentuanJadwalKegiatan: {
|
syaratKetentuanJadwalKegiatan: {
|
||||||
content: jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan.content,
|
content:
|
||||||
|
jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan
|
||||||
|
.content,
|
||||||
},
|
},
|
||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
|
content:
|
||||||
|
jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
|
||||||
},
|
},
|
||||||
pendaftaranJadwalKegiatan: {
|
pendaftaranJadwalKegiatan: {
|
||||||
name: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.name,
|
name: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.name,
|
||||||
tanggal: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.tanggal,
|
tanggal:
|
||||||
namaOrangtua: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.namaOrangtua,
|
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.tanggal,
|
||||||
nomor: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.nomor,
|
namaOrangtua:
|
||||||
alamat: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.alamat,
|
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan
|
||||||
catatan: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.catatan,
|
.namaOrangtua,
|
||||||
|
nomor:
|
||||||
|
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.nomor,
|
||||||
|
alamat:
|
||||||
|
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.alamat,
|
||||||
|
catatan:
|
||||||
|
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.catatan,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,7 +308,7 @@ const jadwalkegiatanState = proxy({
|
|||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
loading: false,
|
loading: false,
|
||||||
async byId(id: string){
|
async byId(id: string) {
|
||||||
try {
|
try {
|
||||||
jadwalkegiatanState.delete.loading = true;
|
jadwalkegiatanState.delete.loading = true;
|
||||||
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, {
|
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, {
|
||||||
@@ -305,7 +327,7 @@ const jadwalkegiatanState = proxy({
|
|||||||
} finally {
|
} finally {
|
||||||
jadwalkegiatanState.delete.loading = false;
|
jadwalkegiatanState.delete.loading = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,32 @@ const userState = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
updateActive: {
|
||||||
|
loading: false,
|
||||||
|
async submit(id: string, isActive: boolean) {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/user/updt`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id, isActive }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 200 && data.success) {
|
||||||
|
toast.success(data.message);
|
||||||
|
userState.findMany.load(userState.findMany.page, 10, userState.findMany.search);
|
||||||
|
} else {
|
||||||
|
toast.error(data.message || "Gagal update status user");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Gagal update status user");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const templateRole = z.object({
|
const templateRole = z.object({
|
||||||
|
|||||||
111
src/app/admin/(dashboard)/auth/login-admin/page.tsx
Normal file
111
src/app/admin/(dashboard)/auth/login-admin/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import { apiFetchLogin } from '@/app/admin/auth/_lib/api_fetch_auth';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Flex, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { PhoneInput } from "react-international-phone";
|
||||||
|
import "react-international-phone/style.css";
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
const router = useRouter()
|
||||||
|
const [phone, setPhone] = useState("")
|
||||||
|
const [isError, setError] = useState(false)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
async function onLogin() {
|
||||||
|
const nomor = phone.substring(1);
|
||||||
|
if (nomor.length <= 4) return setError(true)
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await apiFetchLogin({ nomor: nomor })
|
||||||
|
if (response && response.success) {
|
||||||
|
localStorage.setItem("hipmi_auth_code_id", response.kodeId);
|
||||||
|
toast.success(response.message);
|
||||||
|
router.push("/validasi", { scroll: false });
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error(response?.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false)
|
||||||
|
console.log("Error Login", error)
|
||||||
|
toast.error("Terjadi kesalahan saat login")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack pos={"relative"} bg={colors.Bg}>
|
||||||
|
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||||
|
<Stack align='center' justify='center' h={"100vh"}>
|
||||||
|
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
||||||
|
<Stack align='center' gap={"lg"}>
|
||||||
|
<Box>
|
||||||
|
<Title ta={"center"} order={2} fw={'bold'} c={colors['blue-button']}>
|
||||||
|
Login
|
||||||
|
</Title>
|
||||||
|
<Center>
|
||||||
|
<Image src={"/darmasaba-icon.png"} alt="" w={80} />
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{/* <Box mb={10}>
|
||||||
|
<Text c={colors['blue-button']} ta={"center"} fz={"sm"} fw={'bold'}>Masuk Untuk Akses Admin</Text>
|
||||||
|
<TextInput
|
||||||
|
label='Username'
|
||||||
|
placeholder='Username'
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Box> */}
|
||||||
|
<PhoneInput
|
||||||
|
countrySelectorStyleProps={{
|
||||||
|
buttonStyle: {
|
||||||
|
backgroundColor: colors['blue-button'],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputStyle={{ width: "100%"}}
|
||||||
|
defaultCountry="id"
|
||||||
|
onChange={(val) => {
|
||||||
|
setPhone(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isError ? (
|
||||||
|
toast.error("Masukan nomor telepon anda")
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<Box py={20} >
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
radius={'xl'}
|
||||||
|
onClick={onLogin}
|
||||||
|
loading={loading ? true : false}
|
||||||
|
>Masuk
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Flex justify={'center'} align={'center'}>
|
||||||
|
<Text>Belum punya akun? </Text>
|
||||||
|
<Button variant='transparent' component={Link} href={'/registrasi'}>
|
||||||
|
<Text c={colors['blue-button']} fw={'bold'}>Registrasi</Text>
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
||||||
121
src/app/admin/(dashboard)/auth/registrasi-admin/page.tsx
Normal file
121
src/app/admin/(dashboard)/auth/registrasi-admin/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||||
|
'use client'
|
||||||
|
import { apiFetchRegister } from '@/app/admin/auth/_lib/api_fetch_auth';
|
||||||
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Checkbox, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { PhoneInput } from "react-international-phone";
|
||||||
|
import "react-international-phone/style.css";
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
function Registrasi() {
|
||||||
|
const [phone, setPhone] = useState("")
|
||||||
|
const router = useRouter()
|
||||||
|
const [value, setValue] = useState("")
|
||||||
|
const [isValue, setIsValue] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
async function onRegistarsi() {
|
||||||
|
if (value.length < 5) {
|
||||||
|
toast.error("Username minimal 5 karakter!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.includes(" ")) {
|
||||||
|
toast.error("Username tidak boleh ada spasi!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!phone) {
|
||||||
|
toast.error("Nomor telepon wajib diisi!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const respone = await apiFetchRegister({ nomor: phone, username: value });
|
||||||
|
|
||||||
|
if (respone.success) {
|
||||||
|
router.push("/login", { scroll: false });
|
||||||
|
toast.success(respone.message);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error(respone.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log("Error Registrasi", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Stack pos={"relative"} bg={colors.Bg} gap={"22"} py={"xl"} h={"100vh"}>
|
||||||
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<BackButton />
|
||||||
|
</Box>
|
||||||
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<Stack justify='center' align='center' h={"80vh"}>
|
||||||
|
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
||||||
|
<Stack align='center'>
|
||||||
|
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
||||||
|
Registrasi
|
||||||
|
</Title>
|
||||||
|
<Center>
|
||||||
|
<Image src={"/darmasaba-icon.png"} alt="" w={80} />
|
||||||
|
</Center>
|
||||||
|
<Box>
|
||||||
|
<TextInput placeholder='Username'
|
||||||
|
label='Username'
|
||||||
|
maxLength={50}
|
||||||
|
|
||||||
|
error={
|
||||||
|
value.length > 0 && value.length < 5
|
||||||
|
? "Minimal 5 karakter !"
|
||||||
|
: value.includes(" ")
|
||||||
|
? "Tidak boleh ada spasi"
|
||||||
|
: isValue
|
||||||
|
? "Masukan username anda"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
val.currentTarget.value.length > 0 ? setIsValue(false) : "";
|
||||||
|
setValue(val.currentTarget.value);
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
|
||||||
|
/>
|
||||||
|
<Box py={10}>
|
||||||
|
<Text fz={"sm"} >Nomor Telepon</Text>
|
||||||
|
<PhoneInput
|
||||||
|
countrySelectorStyleProps={{
|
||||||
|
buttonStyle: {
|
||||||
|
backgroundColor: colors['blue-button'],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputStyle={{ width: "100%" }}
|
||||||
|
defaultCountry="id"
|
||||||
|
onChange={(val) => {
|
||||||
|
setPhone(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box pb={10}>
|
||||||
|
<Checkbox
|
||||||
|
label="Saya menyetujui syarat dan ketentuan yang berlaku"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box pb={20} >
|
||||||
|
<Button fullWidth bg={colors['blue-button']} radius={'xl'} onClick={onRegistarsi} loading={loading ? true : false}>Daftar</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Registrasi;
|
||||||
38
src/app/admin/(dashboard)/auth/validasi-admin/page.tsx
Normal file
38
src/app/admin/(dashboard)/auth/validasi-admin/page.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, PinInput, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
function Validasi() {
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<Stack pos={"relative"} bg={colors.Bg}>
|
||||||
|
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
||||||
|
<Stack align='center' justify='center' h={"100vh"}>
|
||||||
|
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
||||||
|
<Stack align='center' gap={"lg"}>
|
||||||
|
<Box>
|
||||||
|
<Title ta={"center"} order={2} fw={'bold'} c={colors['blue-button']}>
|
||||||
|
Kode Verifikasi
|
||||||
|
</Title>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Text c={colors['blue-button']} ta={"center"} fz={"sm"} fw={'bold'}>Masukkan Kode Verifikasi</Text>
|
||||||
|
<PinInput type={/^[0-9]*$/} inputType="tel" inputMode="numeric" />
|
||||||
|
</Box>
|
||||||
|
<Box py={20} >
|
||||||
|
<Button onClick={() => router.push("/admin/landing-page/profile/program-inovasi")}>
|
||||||
|
Page
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Validasi;
|
||||||
@@ -1,77 +1,140 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconActivity, IconBuildingHospital, IconCalendarEvent, IconGauge, IconNotes } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
label: "Presentase Kelahiran & Kematian",
|
|
||||||
value: "presentasekelahiran&kematian",
|
|
||||||
href: "/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik",
|
|
||||||
value: "grafikhasilkepuasan",
|
|
||||||
href: "/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Fasilitas Kesehatan",
|
|
||||||
value: "fasilitaskesehatan",
|
|
||||||
href: "/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Jadwal Kegiatan",
|
|
||||||
value: "jadwalkegiatan",
|
|
||||||
href: "/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Artikel Kesehatan",
|
|
||||||
value: "artikelkesehatan",
|
|
||||||
href: "/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
|
||||||
const tab = tabs.find(t => t.value === value)
|
|
||||||
if (tab) {
|
|
||||||
router.push(tab.href)
|
|
||||||
}
|
|
||||||
setActiveTab(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
const tabs = [
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
{
|
||||||
if (match) {
|
label: "Presentase Kelahiran & Kematian",
|
||||||
setActiveTab(match.value)
|
value: "presentasekelahiran&kematian",
|
||||||
}
|
href: "/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian",
|
||||||
}, [pathname])
|
icon: <IconActivity size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Lihat data kelahiran dan kematian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Grafik Hasil Kepuasan Masyarakat",
|
||||||
|
value: "grafikhasilkepuasan",
|
||||||
|
href: "/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan",
|
||||||
|
icon: <IconGauge size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Grafik kepuasan masyarakat terhadap pelayanan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Fasilitas Kesehatan",
|
||||||
|
value: "fasilitaskesehatan",
|
||||||
|
href: "/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan",
|
||||||
|
icon: <IconBuildingHospital size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Data fasilitas kesehatan desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Jadwal Kegiatan",
|
||||||
|
value: "jadwalkegiatan",
|
||||||
|
href: "/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan",
|
||||||
|
icon: <IconCalendarEvent size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Atur jadwal kegiatan kesehatan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Artikel Kesehatan",
|
||||||
|
value: "artikelkesehatan",
|
||||||
|
href: "/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan",
|
||||||
|
icon: <IconNotes size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Artikel & informasi seputar kesehatan"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack>
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
<Title order={3}>Data Kesehatan Warga</Title>
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
|
||||||
{tabs.map((e, i) => (
|
const handleTabChange = (value: string | null) => {
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
const tab = tabs.find(t => t.value === value);
|
||||||
))}
|
if (tab) {
|
||||||
</TabsList>
|
router.push(tab.href);
|
||||||
{tabs.map((e, i) => (
|
}
|
||||||
<TabsPanel key={i} value={e.value}>
|
setActiveTab(value);
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
};
|
||||||
<></>
|
|
||||||
</TabsPanel>
|
|
||||||
))}
|
useEffect(() => {
|
||||||
</Tabs>
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
{children}
|
if (match) {
|
||||||
</Stack>
|
setActiveTab(match.value);
|
||||||
);
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
|
Data Kesehatan Warga
|
||||||
|
</Title>
|
||||||
|
<Tabs
|
||||||
|
color={colors['blue-button']}
|
||||||
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip
|
||||||
|
key={i}
|
||||||
|
label={tab.tooltip}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
|
>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
@@ -4,7 +4,17 @@
|
|||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -14,29 +24,12 @@ import { useProxy } from 'valtio/utils';
|
|||||||
interface ArtikelKesehatanFormBase {
|
interface ArtikelKesehatanFormBase {
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
introduction: {
|
introduction: { content: string };
|
||||||
content: string;
|
symptom: { title: string; content: string };
|
||||||
};
|
prevention: { title: string; content: string };
|
||||||
symptom: {
|
firstAid: { title: string; content: string };
|
||||||
title: string;
|
mythVsFact: { title: string; mitos: string; fakta: string };
|
||||||
content: string;
|
doctorSign: { content: string };
|
||||||
};
|
|
||||||
prevention: {
|
|
||||||
title: string;
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
firstAid: {
|
|
||||||
title: string;
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
mythVsFact: {
|
|
||||||
title: string;
|
|
||||||
mitos: string;
|
|
||||||
fakta: string;
|
|
||||||
};
|
|
||||||
doctorSign: {
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditArtikelKesehatan() {
|
function EditArtikelKesehatan() {
|
||||||
@@ -47,29 +40,27 @@ function EditArtikelKesehatan() {
|
|||||||
const [formData, setFormData] = useState<ArtikelKesehatanFormBase>({
|
const [formData, setFormData] = useState<ArtikelKesehatanFormBase>({
|
||||||
title: stateArtikelKesehatan.edit.form.title || '',
|
title: stateArtikelKesehatan.edit.form.title || '',
|
||||||
content: stateArtikelKesehatan.edit.form.content || '',
|
content: stateArtikelKesehatan.edit.form.content || '',
|
||||||
introduction: {
|
introduction: { content: stateArtikelKesehatan.edit.form.introduction?.content || '' },
|
||||||
content: stateArtikelKesehatan.edit.form.introduction?.content || '',
|
|
||||||
},
|
|
||||||
symptom: {
|
symptom: {
|
||||||
title: stateArtikelKesehatan.edit.form.symptom?.title || '',
|
title: stateArtikelKesehatan.edit.form.symptom?.title || '',
|
||||||
content: stateArtikelKesehatan.edit.form.symptom?.content || '',
|
content: stateArtikelKesehatan.edit.form.symptom?.content || ''
|
||||||
},
|
},
|
||||||
prevention: {
|
prevention: {
|
||||||
title: stateArtikelKesehatan.edit.form.prevention?.title || '',
|
title: stateArtikelKesehatan.edit.form.prevention?.title || '',
|
||||||
content: stateArtikelKesehatan.edit.form.prevention?.content || '',
|
content: stateArtikelKesehatan.edit.form.prevention?.content || ''
|
||||||
},
|
},
|
||||||
firstAid: {
|
firstAid: {
|
||||||
title: stateArtikelKesehatan.edit.form.firstAid?.title || '',
|
title: stateArtikelKesehatan.edit.form.firstAid?.title || '',
|
||||||
content: stateArtikelKesehatan.edit.form.firstAid?.content || '',
|
content: stateArtikelKesehatan.edit.form.firstAid?.content || ''
|
||||||
},
|
},
|
||||||
mythVsFact: {
|
mythVsFact: {
|
||||||
title: stateArtikelKesehatan.edit.form.mythVsFact?.title || '',
|
title: stateArtikelKesehatan.edit.form.mythVsFact?.title || '',
|
||||||
mitos: stateArtikelKesehatan.edit.form.mythVsFact?.mitos || '',
|
mitos: stateArtikelKesehatan.edit.form.mythVsFact?.mitos || '',
|
||||||
fakta: stateArtikelKesehatan.edit.form.mythVsFact?.fakta || '',
|
fakta: stateArtikelKesehatan.edit.form.mythVsFact?.fakta || ''
|
||||||
},
|
},
|
||||||
doctorSign: {
|
doctorSign: {
|
||||||
content: stateArtikelKesehatan.edit.form.doctorSign?.content || '',
|
content: stateArtikelKesehatan.edit.form.doctorSign?.content || ''
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -84,29 +75,27 @@ function EditArtikelKesehatan() {
|
|||||||
setFormData({
|
setFormData({
|
||||||
title: form.title,
|
title: form.title,
|
||||||
content: form.content,
|
content: form.content,
|
||||||
introduction: {
|
introduction: { content: form.introduction?.content || '' },
|
||||||
content: form.introduction?.content || '',
|
|
||||||
},
|
|
||||||
symptom: {
|
symptom: {
|
||||||
title: form.symptom?.title || '',
|
title: form.symptom?.title || '',
|
||||||
content: form.symptom?.content || '',
|
content: form.symptom?.content || ''
|
||||||
},
|
},
|
||||||
prevention: {
|
prevention: {
|
||||||
title: form.prevention?.title || '',
|
title: form.prevention?.title || '',
|
||||||
content: form.prevention?.content || '',
|
content: form.prevention?.content || ''
|
||||||
},
|
},
|
||||||
firstAid: {
|
firstAid: {
|
||||||
title: form.firstAid?.title || '',
|
title: form.firstAid?.title || '',
|
||||||
content: form.firstAid?.content || '',
|
content: form.firstAid?.content || ''
|
||||||
},
|
},
|
||||||
mythVsFact: {
|
mythVsFact: {
|
||||||
title: form.mythVsFact?.title || '',
|
title: form.mythVsFact?.title || '',
|
||||||
mitos: form.mythVsFact?.mitos || '',
|
mitos: form.mythVsFact?.mitos || '',
|
||||||
fakta: form.mythVsFact?.fakta || '',
|
fakta: form.mythVsFact?.fakta || ''
|
||||||
},
|
},
|
||||||
doctorSign: {
|
doctorSign: {
|
||||||
content: form.doctorSign?.content || '',
|
content: form.doctorSign?.content || ''
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -119,34 +108,7 @@ function EditArtikelKesehatan() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateArtikelKesehatan.edit.form = {
|
stateArtikelKesehatan.edit.form = { ...formData };
|
||||||
...stateArtikelKesehatan.edit.form,
|
|
||||||
title: formData.title,
|
|
||||||
content: formData.content,
|
|
||||||
introduction: {
|
|
||||||
content: formData.introduction.content,
|
|
||||||
},
|
|
||||||
symptom: {
|
|
||||||
title: formData.symptom.title,
|
|
||||||
content: formData.symptom.content,
|
|
||||||
},
|
|
||||||
prevention: {
|
|
||||||
title: formData.prevention.title,
|
|
||||||
content: formData.prevention.content,
|
|
||||||
},
|
|
||||||
firstAid: {
|
|
||||||
title: formData.firstAid.title,
|
|
||||||
content: formData.firstAid.content,
|
|
||||||
},
|
|
||||||
mythVsFact: {
|
|
||||||
title: formData.mythVsFact.title,
|
|
||||||
mitos: formData.mythVsFact.mitos,
|
|
||||||
fakta: formData.mythVsFact.fakta,
|
|
||||||
},
|
|
||||||
doctorSign: {
|
|
||||||
content: formData.doctorSign.content,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const success = await stateArtikelKesehatan.edit.submit();
|
const success = await stateArtikelKesehatan.edit.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success("Artikel kesehatan berhasil diperbarui!");
|
toast.success("Artikel kesehatan berhasil diperbarui!");
|
||||||
@@ -157,214 +119,196 @@ function EditArtikelKesehatan() {
|
|||||||
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data artikel kesehatan");
|
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data artikel kesehatan");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button onClick={() => router.back()} variant="subtle" color="blue">
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Button>
|
||||||
<Stack gap="xs">
|
</Tooltip>
|
||||||
<Title order={3}>Edit Artikel Kesehatan</Title>
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Artikel 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
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label="Judul"
|
||||||
placeholder="masukkan judul"
|
placeholder="Masukkan judul artikel"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChange={(e) => {
|
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
|
||||||
setFormData(prev => ({
|
required
|
||||||
...prev,
|
|
||||||
title: e.target.value
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
label="Deskripsi"
|
||||||
placeholder="masukkan deskripsi"
|
placeholder="Masukkan deskripsi artikel"
|
||||||
value={formData.content}
|
value={formData.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
|
||||||
setFormData(prev => ({
|
required
|
||||||
...prev,
|
|
||||||
content: e.target.value
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Pendahuluan</Text>}
|
label="Pendahuluan"
|
||||||
placeholder="masukkan pendahuluan"
|
placeholder="Masukkan pendahuluan"
|
||||||
value={formData.introduction.content}
|
value={formData.introduction.content}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
introduction: {
|
introduction: { ...prev.introduction, content: e.target.value }
|
||||||
...prev.introduction,
|
}))
|
||||||
content: e.target.value
|
}
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Gejala */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Gejala</Text>
|
<Text fw="bold">Gejala</Text>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label="Judul Gejala"
|
||||||
placeholder="masukkan judul gejala penyakit"
|
placeholder="Masukkan judul gejala"
|
||||||
value={formData.symptom.title}
|
value={formData.symptom.title}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
symptom: {
|
symptom: { ...prev.symptom, title: e.target.value }
|
||||||
...prev.symptom,
|
}))
|
||||||
title: e.target.value
|
}
|
||||||
}
|
/>
|
||||||
}));
|
<EditEditor
|
||||||
}}
|
value={formData.symptom.content}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
symptom: { ...prev.symptom, content: e }
|
||||||
|
}))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi Gejala</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.symptom.content}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
symptom: {
|
|
||||||
...prev.symptom,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Pencegahan */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Pencegahan</Text>
|
<Text fw="bold">Pencegahan</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label="Judul"
|
||||||
placeholder="masukkan judul"
|
|
||||||
value={formData.prevention.title}
|
value={formData.prevention.title}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
prevention: {
|
prevention: { ...prev.prevention, title: e.target.value }
|
||||||
...prev.prevention,
|
}))
|
||||||
title: e.target.value
|
}
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.prevention.content}
|
value={formData.prevention.content}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
prevention: {
|
prevention: { ...prev.prevention, content: e }
|
||||||
...prev.prevention,
|
}))
|
||||||
content: e
|
}
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Pertolongan Pertama */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Pertolongan Pertama</Text>
|
<Text fw="bold">Pertolongan Pertama</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label="Judul"
|
||||||
placeholder="masukkan judul"
|
|
||||||
value={formData.firstAid.title}
|
value={formData.firstAid.title}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
firstAid: {
|
firstAid: { ...prev.firstAid, title: e.target.value }
|
||||||
...prev.firstAid,
|
}))
|
||||||
title: e.target.value
|
}
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.firstAid.content}
|
value={formData.firstAid.content}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
firstAid: {
|
firstAid: { ...prev.firstAid, content: e }
|
||||||
...prev.firstAid,
|
}))
|
||||||
content: e
|
}
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Mitos vs Fakta */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Mitos dan Fakta</Text>
|
<Text fw="bold">Mitos vs Fakta</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label="Judul"
|
||||||
placeholder="masukkan judul"
|
|
||||||
value={formData.mythVsFact.title}
|
value={formData.mythVsFact.title}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
mythVsFact: {
|
mythVsFact: { ...prev.mythVsFact, title: e.target.value }
|
||||||
...prev.mythVsFact,
|
}))
|
||||||
title: e.target.value
|
}
|
||||||
}
|
/>
|
||||||
}));
|
<Text fw="500">Mitos</Text>
|
||||||
}}
|
<EditEditor
|
||||||
|
value={formData.mythVsFact.mitos}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
mythVsFact: { ...prev.mythVsFact, mitos: e }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text fw="500">Fakta</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.mythVsFact.fakta}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
mythVsFact: { ...prev.mythVsFact, fakta: e }
|
||||||
|
}))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Box>
|
|
||||||
<Text>
|
|
||||||
Mitos
|
|
||||||
</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.mythVsFact.mitos}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
mythVsFact: {
|
|
||||||
...prev.mythVsFact,
|
|
||||||
mitos: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text>
|
|
||||||
Fakta
|
|
||||||
</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.mythVsFact.fakta}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
mythVsFact: {
|
|
||||||
...prev.mythVsFact,
|
|
||||||
fakta: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Kapan harus ke dokter */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Kapan Harus Ke Dokter</Text>
|
<Text fw="bold">Kapan Harus Ke Dokter</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.doctorSign.content}
|
value={formData.doctorSign.content}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
doctorSign: {
|
doctorSign: { ...prev.doctorSign, content: e }
|
||||||
...prev.doctorSign,
|
}))
|
||||||
content: e
|
}
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
{/* Save button */}
|
||||||
</Button>
|
<Group justify="right">
|
||||||
|
<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)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -2,134 +2,181 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function DetailArtikelKesehatan() {
|
function DetailArtikelKesehatan() {
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateArtikelKesehatan = useProxy(artikelKesehatanState)
|
const state = useProxy(artikelKesehatanState);
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateArtikelKesehatan.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateArtikelKesehatan.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan")
|
router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!stateArtikelKesehatan.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Artikel Kesehatan</Text>
|
Kembali
|
||||||
{stateArtikelKesehatan.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Wrapper Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
withBorder
|
||||||
<Text fz={"md"}>{stateArtikelKesehatan.findUnique.data.title}</Text>
|
w={{ base: '100%', md: '50%' }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
radius="md"
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.content }} />
|
shadow="sm"
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Stack gap="md">
|
||||||
<Text fz={"lg"} fw={"bold"}>Pendahuluan</Text>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.introduction.content }} />
|
Detail Artikel Kesehatan
|
||||||
</Box>
|
</Text>
|
||||||
<Box>
|
|
||||||
<Stack gap={"xs"}>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Text fz={"lg"} fw={"bold"}>Gejala</Text>
|
<Stack gap="sm">
|
||||||
<Text fz={"md"} fw={"bold"}>Judul Gejala</Text>
|
{/* Judul */}
|
||||||
<Text fz={"md"}>{stateArtikelKesehatan.findUnique.data.symptom.title}</Text>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Deskripsi Gejala</Text>
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.symptom.content }} />
|
<Text fz="md" c="dimmed">{data.title}</Text>
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
|
||||||
<Box>
|
{/* Deskripsi */}
|
||||||
<Stack gap={"xs"}>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Pencegahan</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text fz={"md"} fw={"bold"}>Judul Pencegahan</Text>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.content }} />
|
||||||
<Text fz={"md"}>{stateArtikelKesehatan.findUnique.data.prevention.title}</Text>
|
</Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Deskripsi Pencegahan</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.prevention.content }} />
|
{/* Pendahuluan */}
|
||||||
</Stack>
|
<Box>
|
||||||
</Box>
|
<Text fz="lg" fw="bold">Pendahuluan</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.introduction?.content }} />
|
||||||
<Stack gap={"xs"}>
|
</Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Pertolongan Pertama</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Judul Pertolongan Pertama</Text>
|
{/* Gejala */}
|
||||||
<Text fz={"md"}>{stateArtikelKesehatan.findUnique.data.firstaid.title}</Text>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Deskripsi Pertolongan Pertama</Text>
|
<Text fz="lg" fw="bold">Gejala</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.firstaid.content }} />
|
<Text fz="md" fw="bold">Judul</Text>
|
||||||
</Stack>
|
<Text fz="md" c="dimmed">{data.symptom?.title}</Text>
|
||||||
</Box>
|
<Text fz="md" fw="bold">Deskripsi</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.symptom?.content }} />
|
||||||
<Stack gap={"xs"}>
|
</Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Mitos dan Fakta</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Judul Mitos dan Fakta</Text>
|
{/* Pencegahan */}
|
||||||
<Text fz={"md"}>{stateArtikelKesehatan.findUnique.data.mythvsfact.title}</Text>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Deskripsi Mitos</Text>
|
<Text fz="lg" fw="bold">Pencegahan</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.mythvsfact.mitos }} />
|
<Text fz="md" fw="bold">Judul</Text>
|
||||||
<Text fz={"md"} fw={"bold"}>Deskripsi Fakta</Text>
|
<Text fz="md" c="dimmed">{data.prevention?.title}</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.mythvsfact.fakta }} />
|
<Text fz="md" fw="bold">Deskripsi</Text>
|
||||||
</Stack>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.prevention?.content }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Pertolongan Pertama */}
|
||||||
<Text fz={"lg"} fw={"bold"}>Kapan Harus ke Dokter</Text>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Deskripsi Kapan Harus ke Dokter</Text>
|
<Text fz="lg" fw="bold">Pertolongan Pertama</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateArtikelKesehatan.findUnique.data.doctorsign.content }} />
|
<Text fz="md" fw="bold">Judul</Text>
|
||||||
</Stack>
|
<Text fz="md" c="dimmed">{data.firstaid?.title}</Text>
|
||||||
</Box>
|
<Text fz="md" fw="bold">Deskripsi</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.firstaid?.content }} />
|
||||||
<Flex gap={"xs"}>
|
</Box>
|
||||||
<Button color="red" onClick={() => {
|
|
||||||
if (stateArtikelKesehatan.findUnique.data) {
|
{/* Mitos vs Fakta */}
|
||||||
setSelectedId(stateArtikelKesehatan.findUnique.data.id)
|
<Box>
|
||||||
setModalHapus(true)
|
<Text fz="lg" fw="bold">Mitos dan Fakta</Text>
|
||||||
}
|
<Text fz="md" fw="bold">Judul</Text>
|
||||||
}}>
|
<Text fz="md" c="dimmed">{data.mythvsfact?.title}</Text>
|
||||||
<IconX size={20} />
|
<Text fz="md" fw="bold">Mitos</Text>
|
||||||
</Button>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.mythvsfact?.mitos }} />
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${stateArtikelKesehatan.findUnique.data?.id}/edit`)} color="green">
|
<Text fz="md" fw="bold">Fakta</Text>
|
||||||
<IconEdit size={20} />
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.mythvsfact?.fakta }} />
|
||||||
</Button>
|
</Box>
|
||||||
</Flex>
|
|
||||||
</Box>
|
{/* Kapan ke Dokter */}
|
||||||
</Stack>
|
<Box>
|
||||||
</Paper>
|
<Text fz="lg" fw="bold">Kapan Harus ke Dokter</Text>
|
||||||
) : null}
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.doctorsign?.content }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Artikel" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Artikel" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${data.id}/edit`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
|
|||||||
@@ -2,101 +2,129 @@
|
|||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function CreateArtikelKesehatan() {
|
function CreateArtikelKesehatan() {
|
||||||
const stateArtikelKesehatan = useProxy(artikelKesehatanState)
|
const stateArtikelKesehatan = useProxy(artikelKesehatanState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateArtikelKesehatan.create.form = {
|
stateArtikelKesehatan.create.form = {
|
||||||
title: "",
|
title: '',
|
||||||
content: "",
|
content: '',
|
||||||
introduction: {
|
introduction: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
symptom: {
|
symptom: {
|
||||||
title: "",
|
title: '',
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
prevention: {
|
prevention: {
|
||||||
title: "",
|
title: '',
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
firstAid: {
|
firstAid: {
|
||||||
title: "",
|
title: '',
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
mythVsFact: {
|
mythVsFact: {
|
||||||
title: "",
|
title: '',
|
||||||
mitos: "",
|
mitos: '',
|
||||||
fakta: "",
|
fakta: '',
|
||||||
},
|
},
|
||||||
doctorSign: {
|
doctorSign: {
|
||||||
content: ""
|
content: '',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e?: React.FormEvent) => {
|
||||||
|
e?.preventDefault();
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
await stateArtikelKesehatan.create.submit();
|
await stateArtikelKesehatan.create.submit();
|
||||||
|
toast.success('Data berhasil disimpan');
|
||||||
toast.success("Data berhasil disimpan");
|
|
||||||
resetForm();
|
resetForm();
|
||||||
// After successful submission, redirect to the list page
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan');
|
router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="form" onSubmit={handleSubmit}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Artikel Kesehatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
{/* Form */}
|
||||||
<Stack gap="xs">
|
<Paper
|
||||||
<Title order={3}>Create Artikel Kesehatan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={"Judul"}
|
||||||
placeholder="masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
value={stateArtikelKesehatan.create.form.title}
|
value={stateArtikelKesehatan.create.form.title}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.title = e.target.value;
|
stateArtikelKesehatan.create.form.title = e.target.value;
|
||||||
}}
|
}}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
label={"Deskripsi"}
|
||||||
placeholder="masukkan deskripsi"
|
placeholder="Masukkan deskripsi"
|
||||||
value={stateArtikelKesehatan.create.form.content}
|
value={stateArtikelKesehatan.create.form.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.content = e.target.value;
|
stateArtikelKesehatan.create.form.content = e.target.value;
|
||||||
}}
|
}}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Pendahuluan</Text>}
|
label={"Pendahuluan"}
|
||||||
placeholder="masukkan pendahuluan"
|
placeholder="Masukkan pendahuluan"
|
||||||
|
required
|
||||||
value={stateArtikelKesehatan.create.form.introduction.content}
|
value={stateArtikelKesehatan.create.form.introduction.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.introduction.content = e.target.value;
|
stateArtikelKesehatan.create.form.introduction.content = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Gejala */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Gejala</Text>
|
<Text fz="md" fw="bold">Gejala</Text>
|
||||||
<Stack gap="xs">
|
<Stack gap="sm">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={"Judul Gejala"}
|
||||||
placeholder="masukkan judul gejala penyakit"
|
required
|
||||||
|
placeholder="Masukkan judul gejala penyakit"
|
||||||
value={stateArtikelKesehatan.create.form.symptom.title}
|
value={stateArtikelKesehatan.create.form.symptom.title}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.symptom.title = e.target.value;
|
stateArtikelKesehatan.create.form.symptom.title = e.target.value;
|
||||||
@@ -114,54 +142,62 @@ function CreateArtikelKesehatan() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
{/* Pencegahan */}
|
||||||
|
<Stack gap="xs">
|
||||||
<Text fz="md" fw="bold">Pencegahan</Text>
|
<Text fz="md" fw="bold">Pencegahan</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={"Judul Pencegahan"}
|
||||||
placeholder="masukkan judul"
|
required
|
||||||
|
placeholder="Masukkan judul"
|
||||||
value={stateArtikelKesehatan.create.form.prevention.title}
|
value={stateArtikelKesehatan.create.form.prevention.title}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.prevention.title = e.target.value;
|
stateArtikelKesehatan.create.form.prevention.title = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Text fz="sm">Deskripsi Pencegahan</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateArtikelKesehatan.create.form.prevention.content}
|
value={stateArtikelKesehatan.create.form.prevention.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.prevention.content = e;
|
stateArtikelKesehatan.create.form.prevention.content = e;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Stack>
|
||||||
<Box>
|
|
||||||
|
{/* Pertolongan Pertama */}
|
||||||
|
<Stack gap={"xs"}>
|
||||||
<Text fz="md" fw="bold">Pertolongan Pertama</Text>
|
<Text fz="md" fw="bold">Pertolongan Pertama</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={"Judul Pertolongan Pertama"}
|
||||||
placeholder="masukkan judul"
|
required
|
||||||
|
placeholder="Masukkan judul"
|
||||||
value={stateArtikelKesehatan.create.form.firstAid.title}
|
value={stateArtikelKesehatan.create.form.firstAid.title}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.firstAid.title = e.target.value;
|
stateArtikelKesehatan.create.form.firstAid.title = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Text fz="sm">Deskripsi Pertolongan Pertama</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateArtikelKesehatan.create.form.firstAid.content}
|
value={stateArtikelKesehatan.create.form.firstAid.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.firstAid.content = e;
|
stateArtikelKesehatan.create.form.firstAid.content = e;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Stack>
|
||||||
|
|
||||||
|
{/* Mitos vs Fakta */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Mitos dan Fakta</Text>
|
<Text fz="md" fw="bold">Mitos dan Fakta</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={"Judul Mitos dan Fakta"}
|
||||||
placeholder="masukkan judul"
|
required
|
||||||
|
placeholder="Masukkan judul"
|
||||||
value={stateArtikelKesehatan.create.form.mythVsFact.title}
|
value={stateArtikelKesehatan.create.form.mythVsFact.title}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateArtikelKesehatan.create.form.mythVsFact.title = e.target.value;
|
stateArtikelKesehatan.create.form.mythVsFact.title = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box>
|
<Box mt="sm">
|
||||||
<Text>
|
<Text fz="sm" fw="bold">Mitos</Text>
|
||||||
Mitos
|
|
||||||
</Text>
|
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateArtikelKesehatan.create.form.mythVsFact.mitos}
|
value={stateArtikelKesehatan.create.form.mythVsFact.mitos}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -169,10 +205,8 @@ function CreateArtikelKesehatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box mt="sm">
|
||||||
<Text>
|
<Text fz="sm" fw="bold">Fakta</Text>
|
||||||
Fakta
|
|
||||||
</Text>
|
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateArtikelKesehatan.create.form.mythVsFact.fakta}
|
value={stateArtikelKesehatan.create.form.mythVsFact.fakta}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -181,8 +215,10 @@ function CreateArtikelKesehatan() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Kapan Harus ke Dokter */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Kapan Harus Ke Dokter</Text>
|
<Text fz="md" fw="bold">Kapan Harus ke Dokter</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateArtikelKesehatan.create.form.doctorSign.content}
|
value={stateArtikelKesehatan.create.form.doctorSign.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -191,9 +227,21 @@ function CreateArtikelKesehatan() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
{/* Submit Button */}
|
||||||
Simpan
|
<Group justify="right">
|
||||||
</Button>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,99 +1,164 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Pagination,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconArrowBack, IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import artikelKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
function ArtikelKesehatan() {
|
function ArtikelKesehatan() {
|
||||||
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Tombol Back */}
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Artikel Kesehatan'
|
title='Artikel Kesehatan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari judul atau konten...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListArtikelKesehatan search={search} />
|
<ListArtikelKesehatan search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListArtikelKesehatan({ search }: { search: string }) {
|
function ListArtikelKesehatan({ search }: { search: string }) {
|
||||||
const stateArtikelKesehatan = useProxy(artikelKesehatanState)
|
const stateArtikel = useProxy(artikelKesehatanState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { data, page, totalPages, loading, load } = stateArtikel.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateArtikelKesehatan.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (stateArtikelKesehatan.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
item.title.toLowerCase().includes(keyword) ||
|
<Stack py={10}>
|
||||||
item.content.toLowerCase().includes(keyword)
|
<Skeleton height={600} radius="md" />
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateArtikelKesehatan.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Judul + Tombol Tambah */}
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Artikel Kesehatan'
|
<Title order={4}>Daftar Artikel Kesehatan</Title>
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
|
<Tooltip label="Tambah Artikel Kesehatan" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowX: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
color="blue"
|
||||||
<TableThead>
|
variant="light"
|
||||||
<TableTr>
|
onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create')}
|
||||||
<TableTh>Judul</TableTh>
|
>
|
||||||
<TableTh>Content</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Detail</TableTh>
|
</Button>
|
||||||
</TableTr>
|
</Tooltip>
|
||||||
</TableThead>
|
</Group>
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
{/* Tabel */}
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Judul</TableTh>
|
||||||
|
<TableTh>Konten</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.title}</Text>
|
{item.title}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text truncate fz="sm" c="dimmed" lineClamp={1}>
|
||||||
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.content}</Text>
|
{item.content}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={3}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada artikel yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ArtikelKesehatan;
|
export default ArtikelKesehatan;
|
||||||
|
|||||||
@@ -4,7 +4,17 @@
|
|||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -18,20 +28,14 @@ interface FasilitasKesehatanFormBase {
|
|||||||
alamat: string;
|
alamat: string;
|
||||||
jamOperasional: string;
|
jamOperasional: string;
|
||||||
};
|
};
|
||||||
layananUnggulan: {
|
layananUnggulan: { content: string };
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
dokterdanTenagaMedis: {
|
dokterdanTenagaMedis: {
|
||||||
name: string;
|
name: string;
|
||||||
specialist: string;
|
specialist: string;
|
||||||
jadwal: string;
|
jadwal: string;
|
||||||
};
|
};
|
||||||
fasilitasPendukung: {
|
fasilitasPendukung: { content: string };
|
||||||
content: string;
|
prosedurPendaftaran: { content: string };
|
||||||
};
|
|
||||||
prosedurPendaftaran: {
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
tarifDanLayanan: {
|
tarifDanLayanan: {
|
||||||
layanan: string;
|
layanan: string;
|
||||||
tarif: string;
|
tarif: string;
|
||||||
@@ -86,20 +90,14 @@ function EditFasilitasKesehatan() {
|
|||||||
alamat: form.informasiUmum?.alamat || '',
|
alamat: form.informasiUmum?.alamat || '',
|
||||||
jamOperasional: form.informasiUmum?.jamOperasional || '',
|
jamOperasional: form.informasiUmum?.jamOperasional || '',
|
||||||
},
|
},
|
||||||
layananUnggulan: {
|
layananUnggulan: { content: form.layananUnggulan?.content || '' },
|
||||||
content: form.layananUnggulan?.content || '',
|
|
||||||
},
|
|
||||||
dokterdanTenagaMedis: {
|
dokterdanTenagaMedis: {
|
||||||
name: form.dokterdanTenagaMedis?.name || '',
|
name: form.dokterdanTenagaMedis?.name || '',
|
||||||
specialist: form.dokterdanTenagaMedis?.specialist || '',
|
specialist: form.dokterdanTenagaMedis?.specialist || '',
|
||||||
jadwal: form.dokterdanTenagaMedis?.jadwal || '',
|
jadwal: form.dokterdanTenagaMedis?.jadwal || '',
|
||||||
},
|
},
|
||||||
fasilitasPendukung: {
|
fasilitasPendukung: { content: form.fasilitasPendukung?.content || '' },
|
||||||
content: form.fasilitasPendukung?.content || '',
|
prosedurPendaftaran: { content: form.prosedurPendaftaran?.content || '' },
|
||||||
},
|
|
||||||
prosedurPendaftaran: {
|
|
||||||
content: form.prosedurPendaftaran?.content || '',
|
|
||||||
},
|
|
||||||
tarifDanLayanan: {
|
tarifDanLayanan: {
|
||||||
layanan: form.tarifDanLayanan?.layanan || '',
|
layanan: form.tarifDanLayanan?.layanan || '',
|
||||||
tarif: form.tarifDanLayanan?.tarif || '',
|
tarif: form.tarifDanLayanan?.tarif || '',
|
||||||
@@ -118,30 +116,7 @@ function EditFasilitasKesehatan() {
|
|||||||
try {
|
try {
|
||||||
stateFasilitasKesehatan.edit.form = {
|
stateFasilitasKesehatan.edit.form = {
|
||||||
...stateFasilitasKesehatan.edit.form,
|
...stateFasilitasKesehatan.edit.form,
|
||||||
name: formData.name,
|
...formData,
|
||||||
informasiUmum: {
|
|
||||||
fasilitas: formData.informasiUmum.fasilitas,
|
|
||||||
alamat: formData.informasiUmum.alamat,
|
|
||||||
jamOperasional: formData.informasiUmum.jamOperasional,
|
|
||||||
},
|
|
||||||
layananUnggulan: {
|
|
||||||
content: formData.layananUnggulan.content,
|
|
||||||
},
|
|
||||||
dokterdanTenagaMedis: {
|
|
||||||
name: formData.dokterdanTenagaMedis.name,
|
|
||||||
specialist: formData.dokterdanTenagaMedis.specialist,
|
|
||||||
jadwal: formData.dokterdanTenagaMedis.jadwal,
|
|
||||||
},
|
|
||||||
fasilitasPendukung: {
|
|
||||||
content: formData.fasilitasPendukung.content,
|
|
||||||
},
|
|
||||||
prosedurPendaftaran: {
|
|
||||||
content: formData.prosedurPendaftaran.content,
|
|
||||||
},
|
|
||||||
tarifDanLayanan: {
|
|
||||||
layanan: formData.tarifDanLayanan.layanan,
|
|
||||||
tarif: formData.tarifDanLayanan.tarif,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const success = await stateFasilitasKesehatan.edit.submit();
|
const success = await stateFasilitasKesehatan.edit.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -150,208 +125,197 @@ function EditFasilitasKesehatan() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating fasilitas kesehatan:", error);
|
console.error("Error updating fasilitas kesehatan:", error);
|
||||||
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data fasilitas kesehatan");
|
toast.error("Terjadi kesalahan saat memperbarui data fasilitas kesehatan");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button onClick={() => router.back()} variant="subtle" color="blue">
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap="xs">
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Fasilitas Kesehatan</Title>
|
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 Fasilitas Kesehatan"
|
||||||
|
placeholder="Masukkan nama fasilitas kesehatan"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Informasi Umum */}
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" mb={5}>Informasi Umum</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Fasilitas Kesehatan</Text>}
|
label="Fasilitas"
|
||||||
placeholder="masukkan nama fasilitas kesehatan"
|
value={formData.informasiUmum.fasilitas}
|
||||||
value={formData.name}
|
onChange={(e) =>
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
name: e.target.value
|
informasiUmum: { ...prev.informasiUmum, fasilitas: e.target.value },
|
||||||
}));
|
}))
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
<Box>
|
<TextInput
|
||||||
<Text fz="md" fw="bold">Informasi Umum</Text>
|
label="Alamat"
|
||||||
<TextInput
|
value={formData.informasiUmum.alamat}
|
||||||
label={<Text fz="sm" fw="bold">Fasilitas</Text>}
|
onChange={(e) =>
|
||||||
placeholder="masukkan fasilitas"
|
setFormData(prev => ({
|
||||||
value={formData.informasiUmum.fasilitas}
|
...prev,
|
||||||
onChange={(e) => {
|
informasiUmum: { ...prev.informasiUmum, alamat: e.target.value },
|
||||||
setFormData(prev => ({
|
}))
|
||||||
...prev,
|
}
|
||||||
informasiUmum: {
|
/>
|
||||||
...prev.informasiUmum,
|
<TextInput
|
||||||
fasilitas: e.target.value
|
label="Jam Operasional"
|
||||||
}
|
value={formData.informasiUmum.jamOperasional}
|
||||||
}));
|
onChange={(e) =>
|
||||||
}}
|
setFormData(prev => ({
|
||||||
/>
|
...prev,
|
||||||
<TextInput
|
informasiUmum: { ...prev.informasiUmum, jamOperasional: e.target.value },
|
||||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
}))
|
||||||
placeholder="masukkan alamat"
|
}
|
||||||
value={formData.informasiUmum.alamat}
|
/>
|
||||||
onChange={(e) => {
|
</Box>
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
informasiUmum: {
|
|
||||||
...prev.informasiUmum,
|
|
||||||
alamat: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Jam Operasional</Text>}
|
|
||||||
placeholder="masukkan jam operasional"
|
|
||||||
value={formData.informasiUmum.jamOperasional}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
informasiUmum: {
|
|
||||||
...prev.informasiUmum,
|
|
||||||
jamOperasional: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Layanan Unggulan</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.layananUnggulan.content}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
layananUnggulan: {
|
|
||||||
...prev.layananUnggulan,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Dokter dan Tenaga Medis</Text>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
|
|
||||||
placeholder="masukkan nama dokter"
|
|
||||||
value={formData.dokterdanTenagaMedis.name}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
dokterdanTenagaMedis: {
|
|
||||||
...prev.dokterdanTenagaMedis,
|
|
||||||
name: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Specialist</Text>}
|
|
||||||
placeholder="masukkan specialist"
|
|
||||||
value={formData.dokterdanTenagaMedis.specialist}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
dokterdanTenagaMedis: {
|
|
||||||
...prev.dokterdanTenagaMedis,
|
|
||||||
specialist: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Jadwal</Text>}
|
|
||||||
placeholder="masukkan jadwal"
|
|
||||||
value={formData.dokterdanTenagaMedis.jadwal}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
dokterdanTenagaMedis: {
|
|
||||||
...prev.dokterdanTenagaMedis,
|
|
||||||
jadwal: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Fasilitas Pendukung</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.fasilitasPendukung.content}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
fasilitasPendukung: {
|
|
||||||
...prev.fasilitasPendukung,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Prosedur Pendaftaran</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.prosedurPendaftaran.content}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
prosedurPendaftaran: {
|
|
||||||
...prev.prosedurPendaftaran,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Tarif dan Layanan</Text>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Tarif</Text>}
|
|
||||||
placeholder="masukkan tarif"
|
|
||||||
value={formData.tarifDanLayanan.tarif}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
tarifDanLayanan: {
|
|
||||||
...prev.tarifDanLayanan,
|
|
||||||
tarif: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Layanan</Text>}
|
|
||||||
placeholder="masukkan layanan"
|
|
||||||
value={formData.tarifDanLayanan.layanan}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
tarifDanLayanan: {
|
|
||||||
...prev.tarifDanLayanan,
|
|
||||||
layanan: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button
|
{/* Layanan Unggulan */}
|
||||||
onClick={handleSubmit}
|
<Box>
|
||||||
bg={colors['blue-button']}
|
<Text fw="bold" mb={5}>Layanan Unggulan</Text>
|
||||||
loading={stateFasilitasKesehatan.edit.loading}
|
<EditEditor
|
||||||
>
|
value={formData.layananUnggulan.content}
|
||||||
Simpan
|
onChange={(e) =>
|
||||||
</Button>
|
setFormData(prev => ({
|
||||||
</Stack>
|
...prev,
|
||||||
</Paper>
|
layananUnggulan: { content: e },
|
||||||
</Stack>
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</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) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
dokterdanTenagaMedis: { ...prev.dokterdanTenagaMedis, name: e.target.value },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Specialist"
|
||||||
|
value={formData.dokterdanTenagaMedis.specialist}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
dokterdanTenagaMedis: { ...prev.dokterdanTenagaMedis, specialist: e.target.value },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jadwal"
|
||||||
|
value={formData.dokterdanTenagaMedis.jadwal}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
dokterdanTenagaMedis: { ...prev.dokterdanTenagaMedis, jadwal: e.target.value },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Fasilitas Pendukung */}
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" mb={5}>Fasilitas Pendukung</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.fasilitasPendukung.content}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
fasilitasPendukung: { content: e },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Prosedur Pendaftaran */}
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" mb={5}>Prosedur Pendaftaran</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.prosedurPendaftaran.content}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
prosedurPendaftaran: { content: e },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Tarif dan Layanan */}
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" mb={5}>Tarif dan Layanan</Text>
|
||||||
|
<TextInput
|
||||||
|
label="Tarif"
|
||||||
|
value={formData.tarifDanLayanan.tarif}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
tarifDanLayanan: { ...prev.tarifDanLayanan, tarif: e.target.value },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Layanan"
|
||||||
|
value={formData.tarifDanLayanan.layanan}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
tarifDanLayanan: { ...prev.tarifDanLayanan, layanan: e.target.value },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Tombol Simpan */}
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
loading={stateFasilitasKesehatan.edit.loading}
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,133 +2,169 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function DetailFasilitasKesehatan() {
|
function DetailFasilitasKesehatan() {
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateFasilitasKesehatan.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateFasilitasKesehatan.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan")
|
router.push(
|
||||||
|
'/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!stateFasilitasKesehatan.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Grid>
|
Kembali
|
||||||
<GridCol span={12}>
|
</Button>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
|
|
||||||
</GridCol>
|
|
||||||
{/* <GridCol span={12}>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/dokter-tenaga-medis`)}>
|
|
||||||
Tambah Dokter
|
|
||||||
</Button>
|
|
||||||
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/layanan-unggulan/create`)}>
|
|
||||||
Tambah Layanan
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</GridCol> */}
|
|
||||||
</Grid>
|
|
||||||
{stateFasilitasKesehatan.findUnique.data ? (
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Fasilitas Kesehatan</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.name}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Informasi Umum</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Fasilitas</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.informasiumum.fasilitas}</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Alamat</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.informasiumum.alamat}</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Jam Operasional</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.informasiumum.jamOperasional}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Layanan Unggulan</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.layananunggulan.content }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Dokter dan Tenaga Medis</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Nama Dokter</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.name}</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Specialist</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.specialist}</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Jadwal</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.jadwal}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Tarif dan Layanan</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Layanan</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.tarifdanlayanan.layanan}</Text>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Tarif</Text>
|
|
||||||
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.tarifdanlayanan.tarif}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red" onClick={() => {
|
|
||||||
if (stateFasilitasKesehatan.findUnique.data) {
|
|
||||||
setSelectedId(stateFasilitasKesehatan.findUnique.data.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${stateFasilitasKesehatan.findUnique.data?.id}/edit`)} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
|
{/* 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 Fasilitas Kesehatan
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nama Fasilitas</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Informasi Umum</Text>
|
||||||
|
<Text fz="md" fw="bold">Fasilitas</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.informasiumum?.fasilitas || '-'}</Text>
|
||||||
|
<Text fz="md" fw="bold">Alamat</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.informasiumum?.alamat || '-'}</Text>
|
||||||
|
<Text fz="md" fw="bold">Jam Operasional</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.informasiumum?.jamOperasional || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Layanan Unggulan</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.layananunggulan?.content || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Fasilitas Pendukung</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.fasilitaspendukung?.content || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Prosedur Pendaftaran</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.prosedurpendaftaran?.content || '-' }} />
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${data.id}/edit`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
|
|||||||
@@ -2,7 +2,17 @@
|
|||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -10,174 +20,196 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
|
|
||||||
function CreateFasilitasKesehatan() {
|
function CreateFasilitasKesehatan() {
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateFasilitasKesehatan.create.form = {
|
stateFasilitasKesehatan.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
informasiUmum: {
|
informasiUmum: {
|
||||||
fasilitas: "",
|
fasilitas: '',
|
||||||
alamat: "",
|
alamat: '',
|
||||||
jamOperasional: "",
|
jamOperasional: '',
|
||||||
},
|
},
|
||||||
layananUnggulan: {
|
layananUnggulan: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
dokterdanTenagaMedis: {
|
dokterdanTenagaMedis: {
|
||||||
name: "",
|
name: '',
|
||||||
specialist: "",
|
specialist: '',
|
||||||
jadwal: "",
|
jadwal: '',
|
||||||
},
|
},
|
||||||
fasilitasPendukung: {
|
fasilitasPendukung: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
prosedurPendaftaran: {
|
prosedurPendaftaran: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
tarifDanLayanan: {
|
tarifDanLayanan: {
|
||||||
layanan: "",
|
layanan: '',
|
||||||
tarif: "",
|
tarif: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await stateFasilitasKesehatan.create.submit();
|
await stateFasilitasKesehatan.create.submit();
|
||||||
|
toast.success('Data berhasil disimpan');
|
||||||
toast.success("Data berhasil disimpan");
|
|
||||||
resetForm();
|
resetForm();
|
||||||
// After successful submission, redirect to the list page
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan');
|
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="form" onSubmit={handleSubmit}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Data Fasilitas Kesehatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
{/* Form */}
|
||||||
<Stack gap="xs">
|
<Paper
|
||||||
<Title order={3}>Create Fasilitas Kesehatan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Fasilitas Kesehatan</Text>}
|
label={"Nama Fasilitas Kesehatan"}
|
||||||
placeholder="masukkan nama fasilitas kesehatan"
|
placeholder="Masukkan nama fasilitas kesehatan"
|
||||||
value={stateFasilitasKesehatan.create.form.name}
|
value={stateFasilitasKesehatan.create.form.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => (stateFasilitasKesehatan.create.form.name = e.target.value)}
|
||||||
stateFasilitasKesehatan.create.form.name = e.target.value;
|
required
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Informasi Umum */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Informasi Umum</Text>
|
<Text fz="md" fw="bold" mb={5}>Informasi Umum</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Fasilitas</Text>}
|
label="Fasilitas"
|
||||||
placeholder="masukkan fasilitas"
|
placeholder="Masukkan fasilitas"
|
||||||
value={stateFasilitasKesehatan.create.form.informasiUmum.fasilitas}
|
value={stateFasilitasKesehatan.create.form.informasiUmum.fasilitas}
|
||||||
onChange={(e) => {
|
onChange={(e) => (stateFasilitasKesehatan.create.form.informasiUmum.fasilitas = e.target.value)}
|
||||||
stateFasilitasKesehatan.create.form.informasiUmum.fasilitas = e.target.value;
|
required
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
label="Alamat"
|
||||||
placeholder="masukkan alamat"
|
placeholder="Masukkan alamat"
|
||||||
value={stateFasilitasKesehatan.create.form.informasiUmum.alamat}
|
value={stateFasilitasKesehatan.create.form.informasiUmum.alamat}
|
||||||
onChange={(e) => {
|
onChange={(e) => (stateFasilitasKesehatan.create.form.informasiUmum.alamat = e.target.value)}
|
||||||
stateFasilitasKesehatan.create.form.informasiUmum.alamat = e.target.value;
|
required
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Jam Operasional</Text>}
|
label="Jam Operasional"
|
||||||
placeholder="masukkan jam operasional"
|
placeholder="Masukkan jam operasional"
|
||||||
value={stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional}
|
value={stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional}
|
||||||
onChange={(e) => {
|
onChange={(e) => (stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional = e.target.value)}
|
||||||
stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional = e.target.value;
|
required
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Layanan Unggulan */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Layanan Unggulan</Text>
|
<Text fz="md" fw="bold" mb={5}>Layanan Unggulan</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateFasilitasKesehatan.create.form.layananUnggulan.content}
|
value={stateFasilitasKesehatan.create.form.layananUnggulan.content}
|
||||||
onChange={(e) => {
|
onChange={(val) => (stateFasilitasKesehatan.create.form.layananUnggulan.content = val)}
|
||||||
stateFasilitasKesehatan.create.form.layananUnggulan.content = e;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Dokter dan Tenaga Medis</Text>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
|
|
||||||
placeholder="masukkan nama dokter"
|
|
||||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Specialist</Text>}
|
|
||||||
placeholder="masukkan specialist"
|
|
||||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Jadwal</Text>}
|
|
||||||
placeholder="masukkan jadwal"
|
|
||||||
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Fasilitas Pendukung</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateFasilitasKesehatan.create.form.fasilitasPendukung.content}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.fasilitasPendukung.content = e;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Prosedur Pendaftaran</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateFasilitasKesehatan.create.form.prosedurPendaftaran.content}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.prosedurPendaftaran.content = e;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Tarif dan Layanan</Text>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Tarif</Text>}
|
|
||||||
placeholder="masukkan tarif"
|
|
||||||
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Layanan</Text>}
|
|
||||||
placeholder="masukkan layanan"
|
|
||||||
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
{/* Dokter dan Tenaga Medis */}
|
||||||
Simpan
|
<Box>
|
||||||
</Button>
|
<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>
|
||||||
|
|
||||||
|
{/* Fasilitas Pendukung */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="md" fw="bold" mb={5}>Fasilitas Pendukung</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateFasilitasKesehatan.create.form.fasilitasPendukung.content}
|
||||||
|
onChange={(val) => (stateFasilitasKesehatan.create.form.fasilitasPendukung.content = val)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Prosedur Pendaftaran */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="md" fw="bold" mb={5}>Prosedur Pendaftaran</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateFasilitasKesehatan.create.form.prosedurPendaftaran.content}
|
||||||
|
onChange={(val) => (stateFasilitasKesehatan.create.form.prosedurPendaftaran.content = val)}
|
||||||
|
/>
|
||||||
|
</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" mt="md">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,114 +1,172 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Pagination,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconArrowBack, IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||||
|
|
||||||
|
|
||||||
function FasilitasKesehatan() {
|
function FasilitasKesehatan() {
|
||||||
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
// const router = useRouter();
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{/* <Grid>
|
{/* Tombol Back */}
|
||||||
<GridCol span={12}>
|
<Box mb={10}>
|
||||||
<HeaderSearch
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
title='Fasilitas Kesehatan'
|
<IconArrowBack color={colors["blue-button"]} size={25} />
|
||||||
placeholder='pencarian'
|
</Button>
|
||||||
searchIcon={<IconSearch size={20} />}
|
</Box>
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
{/* Header Search */}
|
||||||
/>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={12}>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis`)}>
|
|
||||||
<IconList size={20} /> List Dokter
|
|
||||||
</Button>
|
|
||||||
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan`)}>
|
|
||||||
<IconList size={20} /> List Layanan
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</GridCol>
|
|
||||||
</Grid> */}
|
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Fasilitas Kesehatan'
|
title='Fasilitas Kesehatan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama, alamat, atau jam operasional...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListFasilitasKesehatan search={search} />
|
<ListFasilitasKesehatan search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ListFasilitasKesehatan({ search }: { search: string }) {
|
function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||||
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { data, page, totalPages, loading, load } = stateFasilitasKesehatan.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateFasilitasKesehatan.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (stateFasilitasKesehatan.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.informasiumum.alamat.toLowerCase().includes(keyword) ||
|
|
||||||
item.informasiumum.jamOperasional.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateFasilitasKesehatan.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Judul + Tombol Tambah */}
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Fasilitas Kesehatan'
|
<Title order={4}>Daftar Fasilitas Kesehatan</Title>
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
|
<Tooltip label="Tambah Fasilitas Kesehatan" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowX: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
color="blue"
|
||||||
<TableThead>
|
variant="light"
|
||||||
<TableTr>
|
onClick={() =>
|
||||||
<TableTh>Fasilitas Kesehatan</TableTh>
|
router.push(
|
||||||
<TableTh>Dokter</TableTh>
|
'/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
|
||||||
<TableTh>Layanan</TableTh>
|
)
|
||||||
<TableTh>Detail</TableTh>
|
}
|
||||||
</TableTr>
|
>
|
||||||
</TableThead>
|
Tambah Baru
|
||||||
<TableTbody>
|
</Button>
|
||||||
{filteredData.map((item) => (
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Tabel */}
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Fasilitas Kesehatan</TableTh>
|
||||||
|
<TableTh>Dokter</TableTh>
|
||||||
|
<TableTh>Layanan</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>{item.dokterdantenagamedis.name}</TableTd>
|
|
||||||
<TableTd>{item.tarifdanlayanan.layanan}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<IconDeviceImacCog size={25} />
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.dokterdantenagamedis?.name || '-'}</TableTd>
|
||||||
|
<TableTd>{item.tarifdanlayanan?.layanan || '-'}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada fasilitas kesehatan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -10,9 +19,10 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditGrafikHasilKepuasan() {
|
function EditGrafikHasilKepuasan() {
|
||||||
const editState = useProxy(grafikkepuasan)
|
const editState = useProxy(grafikkepuasan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
nama: editState.update.form.nama || '',
|
nama: editState.update.form.nama || '',
|
||||||
tanggal: editState.update.form.tanggal || '',
|
tanggal: editState.update.form.tanggal || '',
|
||||||
@@ -22,12 +32,12 @@ function EditGrafikHasilKepuasan() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadKelahiran = async () => {
|
const loadData = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
const data = await editState.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
nama: data.nama || '',
|
nama: data.nama || '',
|
||||||
@@ -43,21 +53,17 @@ function EditGrafikHasilKepuasan() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadKelahiran();
|
loadData();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
editState.update.form = {
|
editState.update.form = {
|
||||||
...editState.update.form,
|
...editState.update.form,
|
||||||
nama: formData.nama,
|
...formData,
|
||||||
tanggal: formData.tanggal,
|
|
||||||
jenisKelamin: formData.jenisKelamin,
|
|
||||||
alamat: formData.alamat,
|
|
||||||
penyakit: formData.penyakit,
|
|
||||||
};
|
};
|
||||||
await editState.update.submit();
|
await editState.update.submit();
|
||||||
toast.success('grafik hasil kepuasan berhasil diperbarui!');
|
toast.success('Grafik hasil kepuasan berhasil diperbarui!');
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
|
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating grafik hasil kepuasan:', error);
|
console.error('Error updating grafik hasil kepuasan:', error);
|
||||||
@@ -66,47 +72,87 @@ function EditGrafikHasilKepuasan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
onClick={() => router.back()}
|
||||||
<Stack gap={"xs"}>
|
p="xs"
|
||||||
<Title order={3}>Edit grafik hasil kepuasan</Title>
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Grafik Hasil Kepuasan
|
||||||
|
</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
|
<TextInput
|
||||||
value={formData.nama}
|
value={formData.nama}
|
||||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
label="Nama"
|
||||||
placeholder="masukkan nama"
|
placeholder="Masukkan nama"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
type='date'
|
type="date"
|
||||||
value={formData.tanggal}
|
value={formData.tanggal}
|
||||||
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
label="Tanggal"
|
||||||
placeholder="masukkan tanggal"
|
placeholder="Masukkan tanggal"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.jenisKelamin}
|
value={formData.jenisKelamin}
|
||||||
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
onChange={(e) =>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
setFormData({ ...formData, jenisKelamin: e.target.value })
|
||||||
placeholder="masukkan jenis kelamin"
|
}
|
||||||
|
label="Jenis Kelamin"
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.alamat}
|
value={formData.alamat}
|
||||||
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
label="Alamat"
|
||||||
placeholder="masukkan alamat"
|
placeholder="Masukkan alamat"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.penyakit}
|
value={formData.penyakit}
|
||||||
onChange={(e) => setFormData({ ...formData, penyakit: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, penyakit: e.target.value })}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Penyakit</Text>}
|
label="Penyakit"
|
||||||
placeholder="masukkan penyakit"
|
placeholder="Masukkan penyakit"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleSubmit}>Simpan</Button>
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
@@ -11,103 +10,130 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirma
|
|||||||
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
function DetailGrafikHasilKepuasan() {
|
function DetailGrafikHasilKepuasan() {
|
||||||
const state = useProxy(grafikkepuasan)
|
const state = useProxy(grafikkepuasan);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
state.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan")
|
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Data Grafik Hasil Kepuasan</Text>
|
Kembali
|
||||||
{state.findUnique.data ? (
|
</Button>
|
||||||
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Wrapper Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
withBorder
|
||||||
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
w={{ base: "100%", md: "50%" }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
radius="md"
|
||||||
<Text fz={"lg"}>
|
shadow="sm"
|
||||||
{new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
|
>
|
||||||
day: '2-digit',
|
<Stack gap="md">
|
||||||
month: 'long',
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
year: 'numeric'
|
Detail Data Grafik Hasil Kepuasan
|
||||||
})}
|
</Text>
|
||||||
</Text>
|
|
||||||
</Box>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Box>
|
<Stack gap="sm">
|
||||||
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
<Box>
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
|
<Text fz="lg" fw="bold">Nama</Text>
|
||||||
</Box>
|
<Text fz="md" c="dimmed">{data.nama || '-'}</Text>
|
||||||
<Box>
|
</Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
|
<Box>
|
||||||
</Box>
|
<Text fz="lg" fw="bold">Tanggal</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed">
|
||||||
<Text fw={"bold"} fz={"lg"}>Penyakit</Text>
|
{new Date(data.tanggal).toLocaleDateString("id-ID", {
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.penyakit}</Text>
|
day: "2-digit",
|
||||||
</Box>
|
month: "long",
|
||||||
<Flex gap={"xs"} mt={10}>
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Jenis Kelamin</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.jenisKelamin || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Alamat</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.alamat || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Penyakit</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.penyakit || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (state.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(state.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={state.delete.loading || !state.findUnique.data}
|
variant="light"
|
||||||
color={"red"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (state.findUnique.data) {
|
onClick={() =>
|
||||||
router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${state.findUnique.data.id}/edit`);
|
router.push(
|
||||||
}
|
`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${data.id}/edit`
|
||||||
}}
|
)
|
||||||
disabled={!state.findUnique.data}
|
}
|
||||||
color={"green"}
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
) : null}
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -116,10 +142,10 @@ function DetailGrafikHasilKepuasan() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus data ini?'
|
text="Apakah anda yakin ingin menghapus data ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailGrafikHasilKepuasan;
|
export default DetailGrafikHasilKepuasan;
|
||||||
|
|||||||
@@ -3,7 +3,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -12,7 +22,7 @@ import { useProxy } from 'valtio/utils';
|
|||||||
function CreateGrafikHasilKepuasanMasyarakat() {
|
function CreateGrafikHasilKepuasanMasyarakat() {
|
||||||
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateGrafikKepuasan.create.form = {
|
stateGrafikKepuasan.create.form = {
|
||||||
@@ -21,82 +31,97 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
jenisKelamin: "",
|
jenisKelamin: "",
|
||||||
alamat: "",
|
alamat: "",
|
||||||
penyakit: "",
|
penyakit: "",
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await stateGrafikKepuasan.create.create();
|
await stateGrafikKepuasan.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack size={20} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Box>
|
onClick={() => router.back()}
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
p="xs"
|
||||||
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
|
radius="md"
|
||||||
<Stack gap={"xs"}>
|
>
|
||||||
<TextInput
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
label="Nama"
|
</Button>
|
||||||
type="text"
|
</Tooltip>
|
||||||
value={stateGrafikKepuasan.create.form.nama}
|
<Title order={4} ml="sm" c="dark">
|
||||||
placeholder="Masukkan nama"
|
Tambah Grafik Hasil Kepuasan Masyarakat
|
||||||
onChange={(val) => {
|
</Title>
|
||||||
stateGrafikKepuasan.create.form.nama = val.currentTarget.value;
|
</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"
|
||||||
|
placeholder="Masukkan nama"
|
||||||
|
value={stateGrafikKepuasan.create.form.nama}
|
||||||
|
onChange={(e) => (stateGrafikKepuasan.create.form.nama = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="date"
|
||||||
|
label="Tanggal"
|
||||||
|
placeholder="Masukkan tanggal"
|
||||||
|
value={stateGrafikKepuasan.create.form.tanggal}
|
||||||
|
onChange={(e) => (stateGrafikKepuasan.create.form.tanggal = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jenis Kelamin"
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
value={stateGrafikKepuasan.create.form.jenisKelamin}
|
||||||
|
onChange={(e) => (stateGrafikKepuasan.create.form.jenisKelamin = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
value={stateGrafikKepuasan.create.form.alamat}
|
||||||
|
onChange={(e) => (stateGrafikKepuasan.create.form.alamat = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Penyakit"
|
||||||
|
placeholder="Masukkan penyakit"
|
||||||
|
value={stateGrafikKepuasan.create.form.penyakit}
|
||||||
|
onChange={(e) => (stateGrafikKepuasan.create.form.penyakit = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<TextInput
|
Simpan
|
||||||
label="Tanggal"
|
</Button>
|
||||||
type="date"
|
</Group>
|
||||||
value={stateGrafikKepuasan.create.form.tanggal}
|
</Stack>
|
||||||
placeholder="Masukkan tanggal"
|
</Paper>
|
||||||
onChange={(val) => {
|
|
||||||
stateGrafikKepuasan.create.form.tanggal = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Jenis Kelamin"
|
|
||||||
type="text"
|
|
||||||
value={stateGrafikKepuasan.create.form.jenisKelamin}
|
|
||||||
placeholder="Masukkan jenis kelamin"
|
|
||||||
onChange={(val) => {
|
|
||||||
stateGrafikKepuasan.create.form.jenisKelamin = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Alamat"
|
|
||||||
type="text"
|
|
||||||
value={stateGrafikKepuasan.create.form.alamat}
|
|
||||||
placeholder="Masukkan alamat"
|
|
||||||
onChange={(val) => {
|
|
||||||
stateGrafikKepuasan.create.form.alamat = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Penyakit"
|
|
||||||
type="text"
|
|
||||||
value={stateGrafikKepuasan.create.form.penyakit}
|
|
||||||
placeholder="Masukkan penyakit"
|
|
||||||
onChange={(val) => {
|
|
||||||
stateGrafikKepuasan.create.form.penyakit = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
mt={10}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +1,108 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconArrowBack, IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
import { Bar, BarChart, Tooltip as ChartTooltip, Legend, XAxis, YAxis } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||||
|
|
||||||
function GrafikHasilKepuasanMasyarakat() {
|
function GrafikHasilKepuasanMasyarakat() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Tombol Back */}
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Grafik Hasil Kepuasan Masyarakat'
|
title='Grafik Hasil Kepuasan Masyarakat'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama atau alamat...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListGrafikHasilKepuasanMasyarakat search={search} />
|
<ListGrafikHasilKepuasanMasyarakat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
||||||
type PDKMGrafik = {
|
type PDKMGrafik = {
|
||||||
id: string;
|
id: string;
|
||||||
nama: string;
|
nama: string;
|
||||||
tanggal: string | Date; // Allow both string and Date types
|
tanggal: string | Date;
|
||||||
jenisKelamin: string;
|
jenisKelamin: string;
|
||||||
alamat: string;
|
alamat: string;
|
||||||
penyakit: string;
|
penyakit: string;
|
||||||
createdAt?: Date; // Add optional fields that might come from the API
|
};
|
||||||
updatedAt?: Date;
|
|
||||||
deletedAt?: Date | null;
|
|
||||||
}
|
|
||||||
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
const stateGrafikKepuasan = useProxy(grafikkepuasan);
|
||||||
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
const [mounted, setMounted] = useState(false);
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
const isTablet = useMediaQuery('(max-width: 1024px)');
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stateGrafikKepuasan.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load
|
|
||||||
} = stateGrafikKepuasan.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true);
|
||||||
load(page, 10, search)
|
load(page, 10, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setChartData(data.map((item) => ({
|
setChartData(data.map((item) => ({
|
||||||
id: item.id,
|
...item,
|
||||||
nama: item.nama,
|
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal
|
||||||
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal,
|
|
||||||
jenisKelamin: item.jenisKelamin,
|
|
||||||
alamat: item.alamat,
|
|
||||||
penyakit: item.penyakit,
|
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
// Add this function to process the data
|
|
||||||
const processDiseaseData = (data: PDKMGrafik[]) => {
|
const processDiseaseData = (data: PDKMGrafik[]) => {
|
||||||
const diseaseCount: Record<string, number> = {};
|
const diseaseCount: Record<string, number> = {};
|
||||||
|
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
const penyakit = item.penyakit.trim();
|
const penyakit = item.penyakit.trim();
|
||||||
if (penyakit) {
|
if (penyakit) {
|
||||||
diseaseCount[penyakit] = (diseaseCount[penyakit] || 0) + 1;
|
diseaseCount[penyakit] = (diseaseCount[penyakit] || 0) + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return Object.entries(diseaseCount).map(([name, count]) => ({ name, count }));
|
||||||
return Object.entries(diseaseCount).map(([name, count]) => ({
|
|
||||||
name,
|
|
||||||
count
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add this state to store the processed chart data
|
|
||||||
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]);
|
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]);
|
||||||
|
|
||||||
// Update the chart data when data changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
setDiseaseChartData(processDiseaseData(data));
|
setDiseaseChartData(processDiseaseData(data));
|
||||||
@@ -104,97 +113,136 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Stack gap={"xs"}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
{/* Form Input */}
|
{/* Judul + Tombol Tambah */}
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Group justify="space-between" mb="md">
|
||||||
<JudulList
|
<Title order={4}>Daftar Grafik Hasil Kepuasan Masyarakat</Title>
|
||||||
title='List Grafik Hasil Kepuasan Masyarakat'
|
<Tooltip label="Tambah Data" withArrow>
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
|
<Button
|
||||||
/>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withTableBorder withRowBorders>
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Tabel */}
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Tanggal</TableTh>
|
<TableTh>Tanggal</TableTh>
|
||||||
<TableTh>Jenis Kelamin</TableTh>
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
<TableTh>Penyakit</TableTh>
|
<TableTh>Penyakit</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.nama}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>{item.nama}</TableTd>
|
||||||
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
<TableTd>
|
||||||
day: '2-digit',
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
month: 'long',
|
day: '2-digit',
|
||||||
year: 'numeric'
|
month: 'long',
|
||||||
})}
|
year: 'numeric',
|
||||||
</TableTd>
|
})}
|
||||||
<TableTd>{item.jenisKelamin}</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.penyakit}</TableTd>
|
<TableTd>{item.jenisKelamin}</TableTd>
|
||||||
<TableTd>
|
<TableTd>{item.penyakit}</TableTd>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`)}>
|
<TableTd>
|
||||||
<IconDeviceImacCog size={20} />
|
<Button
|
||||||
</Button>
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada data kepuasan masyarakat yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<Box mt="lg" style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
||||||
|
{mounted && diseaseChartData.length > 0 ? (
|
||||||
|
<BarChart
|
||||||
|
width={isMobile ? 450 : isTablet ? 500 : 550}
|
||||||
|
height={350}
|
||||||
|
data={diseaseChartData}
|
||||||
|
>
|
||||||
|
<XAxis
|
||||||
|
dataKey="name"
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
interval={0}
|
||||||
|
angle={-45}
|
||||||
|
textAnchor="end"
|
||||||
|
height={70}
|
||||||
|
/>
|
||||||
|
<YAxis />
|
||||||
|
<ChartTooltip />
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
|
||||||
|
</BarChart>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
</Box>
|
||||||
<Pagination
|
|
||||||
value={page}
|
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
|
||||||
total={totalPages}
|
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
|
|
||||||
|
|
||||||
{/* Chart */}
|
|
||||||
{!mounted && !chartData ? (
|
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
|
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
|
||||||
{mounted && diseaseChartData.length > 0 && (
|
|
||||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
|
|
||||||
<XAxis
|
|
||||||
dataKey="name"
|
|
||||||
tick={{ fontSize: 12 }}
|
|
||||||
interval={0}
|
|
||||||
angle={-45}
|
|
||||||
textAnchor="end"
|
|
||||||
height={70}
|
|
||||||
/>
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
|
|
||||||
</BarChart>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,17 @@
|
|||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -128,33 +138,14 @@ function EditJadwalKegiatan() {
|
|||||||
stateJadwalKegiatan.edit.form = {
|
stateJadwalKegiatan.edit.form = {
|
||||||
...stateJadwalKegiatan.edit.form,
|
...stateJadwalKegiatan.edit.form,
|
||||||
content: formData.content,
|
content: formData.content,
|
||||||
informasiJadwalKegiatan: {
|
informasiJadwalKegiatan: { ...formData.informasiJadwalKegiatan },
|
||||||
name: formData.informasiJadwalKegiatan.name,
|
deskripsiJadwalKegiatan: { ...formData.deskripsiJadwalKegiatan },
|
||||||
tanggal: formData.informasiJadwalKegiatan.tanggal,
|
layananJadwalKegiatan: { ...formData.layananJadwalKegiatan },
|
||||||
waktu: formData.informasiJadwalKegiatan.waktu,
|
syaratKetentuanJadwalKegiatan: { ...formData.syaratKetentuanJadwalKegiatan },
|
||||||
lokasi: formData.informasiJadwalKegiatan.lokasi,
|
dokumenJadwalKegiatan: { ...formData.dokumenJadwalKegiatan },
|
||||||
},
|
pendaftaranJadwalKegiatan: { ...formData.pendaftaranJadwalKegiatan },
|
||||||
deskripsiJadwalKegiatan: {
|
|
||||||
deskripsi: formData.deskripsiJadwalKegiatan.deskripsi,
|
|
||||||
},
|
|
||||||
layananJadwalKegiatan: {
|
|
||||||
content: formData.layananJadwalKegiatan.content,
|
|
||||||
},
|
|
||||||
syaratKetentuanJadwalKegiatan: {
|
|
||||||
content: formData.syaratKetentuanJadwalKegiatan.content,
|
|
||||||
},
|
|
||||||
dokumenJadwalKegiatan: {
|
|
||||||
content: formData.dokumenJadwalKegiatan.content,
|
|
||||||
},
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: formData.pendaftaranJadwalKegiatan.name,
|
|
||||||
tanggal: formData.pendaftaranJadwalKegiatan.tanggal,
|
|
||||||
namaOrangtua: formData.pendaftaranJadwalKegiatan.namaOrangtua,
|
|
||||||
nomor: formData.pendaftaranJadwalKegiatan.nomor,
|
|
||||||
alamat: formData.pendaftaranJadwalKegiatan.alamat,
|
|
||||||
catatan: formData.pendaftaranJadwalKegiatan.catatan,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const success = await stateJadwalKegiatan.edit.submit();
|
const success = await stateJadwalKegiatan.edit.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success("Jadwal kegiatan berhasil diperbarui!");
|
toast.success("Jadwal kegiatan berhasil diperbarui!");
|
||||||
@@ -165,241 +156,164 @@ function EditJadwalKegiatan() {
|
|||||||
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data jadwal kegiatan");
|
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data jadwal kegiatan");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button onClick={() => router.back()} variant="subtle" color="blue">
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap="xs">
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Jadwal Kegiatan</Title>
|
Edit Jadwal Kegiatan
|
||||||
|
</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">
|
||||||
|
{/* Nama Jadwal */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Jadwal Kegiatan</Text>}
|
label="Nama Jadwal Kegiatan"
|
||||||
placeholder="masukkan nama jadwal kegiatan"
|
placeholder="Masukkan nama jadwal kegiatan"
|
||||||
value={formData.content}
|
value={formData.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => setFormData((prev) => ({ ...prev, content: e.target.value }))}
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
content: e.target.value
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi Jadwal Kegiatan</Text>
|
{/* Deskripsi */}
|
||||||
<EditEditor
|
|
||||||
value={formData.deskripsiJadwalKegiatan.deskripsi}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
deskripsiJadwalKegiatan: {
|
|
||||||
...prev.deskripsiJadwalKegiatan,
|
|
||||||
deskripsi: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Informasi Jadwal Kegiatan</Text>
|
<Text fz="sm" fw="bold">Deskripsi Jadwal Kegiatan</Text>
|
||||||
<TextInput
|
<EditEditor
|
||||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
value={formData.deskripsiJadwalKegiatan.deskripsi}
|
||||||
placeholder="masukkan nama"
|
onChange={(val) => setFormData((prev) => ({
|
||||||
value={formData.informasiJadwalKegiatan.name}
|
...prev,
|
||||||
onChange={(e) => {
|
deskripsiJadwalKegiatan: { deskripsi: val }
|
||||||
setFormData(prev => ({
|
}))}
|
||||||
...prev,
|
|
||||||
informasiJadwalKegiatan: {
|
|
||||||
...prev.informasiJadwalKegiatan,
|
|
||||||
name: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Tanggal</Text>}
|
|
||||||
placeholder="masukkan tanggal"
|
|
||||||
value={formData.informasiJadwalKegiatan.tanggal}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
informasiJadwalKegiatan: {
|
|
||||||
...prev.informasiJadwalKegiatan,
|
|
||||||
tanggal: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Waktu</Text>}
|
|
||||||
placeholder="masukkan waktu"
|
|
||||||
value={formData.informasiJadwalKegiatan.waktu}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
informasiJadwalKegiatan: {
|
|
||||||
...prev.informasiJadwalKegiatan,
|
|
||||||
waktu: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Lokasi</Text>}
|
|
||||||
placeholder="masukkan lokasi"
|
|
||||||
value={formData.informasiJadwalKegiatan.lokasi}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
informasiJadwalKegiatan: {
|
|
||||||
...prev.informasiJadwalKegiatan,
|
|
||||||
lokasi: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Informasi Jadwal */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="md" fw="bold">Informasi Jadwal Kegiatan</Text>
|
||||||
|
<TextInput label="Nama" value={formData.informasiJadwalKegiatan.name}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, name: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput type="date" label="Tanggal" value={formData.informasiJadwalKegiatan.tanggal}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, tanggal: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput label="Waktu" value={formData.informasiJadwalKegiatan.waktu}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, waktu: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput label="Lokasi" value={formData.informasiJadwalKegiatan.lokasi}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, informasiJadwalKegiatan: { ...prev.informasiJadwalKegiatan, lokasi: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Layanan */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Layanan Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold">Layanan Jadwal Kegiatan</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.layananJadwalKegiatan.content}
|
value={formData.layananJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(val) => setFormData((prev) => ({
|
||||||
setFormData(prev => ({
|
...prev,
|
||||||
...prev,
|
layananJadwalKegiatan: { content: val }
|
||||||
layananJadwalKegiatan: {
|
}))}
|
||||||
...prev.layananJadwalKegiatan,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Syarat */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Syarat dan Ketentuan Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold">Syarat dan Ketentuan</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.syaratKetentuanJadwalKegiatan.content}
|
value={formData.syaratKetentuanJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(val) => setFormData((prev) => ({
|
||||||
setFormData(prev => ({
|
...prev,
|
||||||
...prev,
|
syaratKetentuanJadwalKegiatan: { content: val }
|
||||||
syaratKetentuanJadwalKegiatan: {
|
}))}
|
||||||
...prev.syaratKetentuanJadwalKegiatan,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Dokumen */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Dokumen Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold">Dokumen Jadwal Kegiatan</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.dokumenJadwalKegiatan.content}
|
value={formData.dokumenJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(val) => setFormData((prev) => ({
|
||||||
setFormData(prev => ({
|
...prev,
|
||||||
...prev,
|
dokumenJadwalKegiatan: { content: val }
|
||||||
dokumenJadwalKegiatan: {
|
}))}
|
||||||
...prev.dokumenJadwalKegiatan,
|
|
||||||
content: e
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Pendaftaran Jadwal Kegiatan</Text>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
|
||||||
placeholder="masukkan nama"
|
|
||||||
value={formData.pendaftaranJadwalKegiatan.name}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
...prev.pendaftaranJadwalKegiatan,
|
|
||||||
name: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Tanggal</Text>}
|
|
||||||
placeholder="masukkan tanggal"
|
|
||||||
value={formData.pendaftaranJadwalKegiatan.tanggal}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
...prev.pendaftaranJadwalKegiatan,
|
|
||||||
tanggal: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Nama Orangtua</Text>}
|
|
||||||
placeholder="masukkan nama orangtua"
|
|
||||||
value={formData.pendaftaranJadwalKegiatan.namaOrangtua}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
...prev.pendaftaranJadwalKegiatan,
|
|
||||||
namaOrangtua: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Nomor</Text>}
|
|
||||||
placeholder="masukkan nomor"
|
|
||||||
value={formData.pendaftaranJadwalKegiatan.nomor}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
...prev.pendaftaranJadwalKegiatan,
|
|
||||||
nomor: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
|
||||||
placeholder="masukkan alamat"
|
|
||||||
value={formData.pendaftaranJadwalKegiatan.alamat}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
...prev.pendaftaranJadwalKegiatan,
|
|
||||||
alamat: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz="sm" fw="bold">Catatan</Text>}
|
|
||||||
placeholder="masukkan catatan"
|
|
||||||
value={formData.pendaftaranJadwalKegiatan.catatan}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
...prev.pendaftaranJadwalKegiatan,
|
|
||||||
catatan: e.target.value
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
{/* Pendaftaran */}
|
||||||
Simpan
|
<Box>
|
||||||
</Button>
|
<Text fz="md" fw="bold">Pendaftaran Jadwal Kegiatan</Text>
|
||||||
|
<TextInput label="Nama" value={formData.pendaftaranJadwalKegiatan.name}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, name: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput type="date" label="Tanggal" value={formData.pendaftaranJadwalKegiatan.tanggal}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, tanggal: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput label="Nama Orangtua" value={formData.pendaftaranJadwalKegiatan.namaOrangtua}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, namaOrangtua: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput label="Nomor" value={formData.pendaftaranJadwalKegiatan.nomor}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, nomor: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput label="Alamat" value={formData.pendaftaranJadwalKegiatan.alamat}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, alamat: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<TextInput label="Catatan" value={formData.pendaftaranJadwalKegiatan.catatan}
|
||||||
|
onChange={(e) => setFormData((prev) => ({
|
||||||
|
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, catatan: e.target.value }
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Submit */}
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -32,83 +32,128 @@ function DetailJadwalKegiatan() {
|
|||||||
if (!stateJadwalKegiatan.findUnique.data) {
|
if (!stateJadwalKegiatan.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateJadwalKegiatan.findUnique.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Jadwal Kegiatan</Text>
|
Kembali
|
||||||
{stateJadwalKegiatan.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Wrapper Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Kegiatan</Text>
|
withBorder
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.content}</Text>
|
w={{ base: "100%", md: "50%" }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fz={"lg"} fw={"bold"}>Informasi</Text>
|
radius="md"
|
||||||
<Text fz={"md"} fw={"bold"}>Nama</Text>
|
shadow="sm"
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.informasijadwalkegiatan.name}</Text>
|
>
|
||||||
<Text fz={"md"} fw={"bold"}>Tanggal</Text>
|
<Stack gap="md">
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.informasijadwalkegiatan.tanggal}</Text>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"md"} fw={"bold"}>Waktu</Text>
|
Detail Jadwal Kegiatan
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.informasijadwalkegiatan.waktu}</Text>
|
</Text>
|
||||||
<Text fz={"md"} fw={"bold"}>Lokasi</Text>
|
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.informasijadwalkegiatan.lokasi}</Text>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
</Box>
|
<Stack gap="sm">
|
||||||
<Box>
|
{/* Nama Kegiatan */}
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
<Box>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateJadwalKegiatan.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
<Text fz="lg" fw="bold">Nama Kegiatan</Text>
|
||||||
</Box>
|
<Text fz="md" c="dimmed">{data.content || '-'}</Text>
|
||||||
<Box>
|
</Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Layanan</Text>
|
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateJadwalKegiatan.findUnique.data.layananjadwalkegiatan.content }} />
|
{/* Informasi */}
|
||||||
</Box>
|
<Box>
|
||||||
<Box>
|
<Text fz="lg" fw="bold">Informasi</Text>
|
||||||
<Text fz={"lg"} fw={"bold"}>Syarat Ketentuan</Text>
|
<Text fz="md" fw="bold">Nama</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateJadwalKegiatan.findUnique.data.syaratketentuanjadwalkegiatan.content}} />
|
<Text fz="md" c="dimmed">{data.informasijadwalkegiatan.name || '-'}</Text>
|
||||||
</Box>
|
<Text fz="md" fw="bold">Tanggal</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed">{data.informasijadwalkegiatan.tanggal || '-'}</Text>
|
||||||
<Text fz={"lg"} fw={"bold"}>Dokumen</Text>
|
<Text fz="md" fw="bold">Waktu</Text>
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateJadwalKegiatan.findUnique.data.dokumenjadwalkegiatan.content }} />
|
<Text fz="md" c="dimmed">{data.informasijadwalkegiatan.waktu || '-'}</Text>
|
||||||
</Box>
|
<Text fz="md" fw="bold">Lokasi</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed">{data.informasijadwalkegiatan.lokasi || '-'}</Text>
|
||||||
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
|
</Box>
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.pendaftaranjadwalkegiatan.name}</Text>
|
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.pendaftaranjadwalkegiatan.tanggal}</Text>
|
{/* Deskripsi */}
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.pendaftaranjadwalkegiatan.namaOrangtua}</Text>
|
<Box>
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.pendaftaranjadwalkegiatan.nomor}</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text fz={"md"}>{stateJadwalKegiatan.findUnique.data.pendaftaranjadwalkegiatan.alamat}</Text>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.deskripsijadwalkegiatan.deskripsi }} />
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateJadwalKegiatan.findUnique.data.pendaftaranjadwalkegiatan.catatan }} />
|
</Box>
|
||||||
</Box>
|
|
||||||
<Box>
|
{/* Layanan */}
|
||||||
<Flex gap={"xs"}>
|
<Box>
|
||||||
<Button color="red" onClick={() => {
|
<Text fz="lg" fw="bold">Layanan</Text>
|
||||||
if (stateJadwalKegiatan.findUnique.data) {
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.layananjadwalkegiatan.content }} />
|
||||||
setSelectedId(stateJadwalKegiatan.findUnique.data.id)
|
</Box>
|
||||||
setModalHapus(true)
|
|
||||||
}
|
{/* Syarat Ketentuan */}
|
||||||
}}>
|
<Box>
|
||||||
<IconX size={20} />
|
<Text fz="lg" fw="bold">Syarat Ketentuan</Text>
|
||||||
</Button>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.syaratketentuanjadwalkegiatan.content }} />
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/${stateJadwalKegiatan.findUnique.data?.id}/edit`)} color="green">
|
</Box>
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
{/* Dokumen */}
|
||||||
</Flex>
|
<Box>
|
||||||
</Box>
|
<Text fz="lg" fw="bold">Dokumen</Text>
|
||||||
</Stack>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.dokumenjadwalkegiatan.content }} />
|
||||||
</Paper>
|
</Box>
|
||||||
) : null}
|
|
||||||
|
{/* Prosedur Pendaftaran */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Prosedur Pendaftaran</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.name}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.tanggal}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.namaOrangtua}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.nomor}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.alamat}</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.pendaftaranjadwalkegiatan.catatan }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/${data.id}/edit`)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
|||||||
@@ -2,127 +2,158 @@
|
|||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
import jadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function CreateJadwalKegiatan() {
|
function CreateJadwalKegiatan() {
|
||||||
const stateJadwalKegiatan = useProxy(jadwalKegiatanState)
|
const stateJadwalKegiatan = useProxy(jadwalKegiatanState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateJadwalKegiatan.edit.form = {
|
stateJadwalKegiatan.create.form = {
|
||||||
content: "",
|
content: '',
|
||||||
informasiJadwalKegiatan: {
|
informasiJadwalKegiatan: {
|
||||||
name: "",
|
name: '',
|
||||||
tanggal: "",
|
tanggal: '',
|
||||||
waktu: "",
|
waktu: '',
|
||||||
lokasi: "",
|
lokasi: '',
|
||||||
},
|
},
|
||||||
deskripsiJadwalKegiatan: {
|
deskripsiJadwalKegiatan: {
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
},
|
},
|
||||||
layananJadwalKegiatan: {
|
layananJadwalKegiatan: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
syaratKetentuanJadwalKegiatan: {
|
syaratKetentuanJadwalKegiatan: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: "",
|
content: '',
|
||||||
},
|
},
|
||||||
pendaftaranJadwalKegiatan: {
|
pendaftaranJadwalKegiatan: {
|
||||||
name: "",
|
name: '',
|
||||||
tanggal: "",
|
tanggal: '',
|
||||||
namaOrangtua: "",
|
namaOrangtua: '',
|
||||||
nomor: "",
|
nomor: '',
|
||||||
alamat: "",
|
alamat: '',
|
||||||
catatan: "",
|
catatan: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await stateJadwalKegiatan.create.submit();
|
await stateJadwalKegiatan.create.submit();
|
||||||
|
|
||||||
toast.success("Data berhasil disimpan");
|
toast.success('Data berhasil disimpan');
|
||||||
resetForm();
|
resetForm();
|
||||||
// After successful submission, redirect to the list page
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan');
|
router.push('/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="form" onSubmit={handleSubmit}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Jadwal Kegiatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
{/* Form */}
|
||||||
<Stack gap="xs">
|
<Paper
|
||||||
<Title order={3}>Create Jadwal Kegiatan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Jadwal Kegiatan</Text>}
|
label="Nama Jadwal Kegiatan"
|
||||||
placeholder="masukkan nama jadwal kegiatan"
|
placeholder="Masukkan nama jadwal kegiatan"
|
||||||
value={stateJadwalKegiatan.create.form.content}
|
value={stateJadwalKegiatan.create.form.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.content = e.target.value;
|
stateJadwalKegiatan.create.form.content = e.target.value;
|
||||||
}}
|
}}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi Jadwal Kegiatan</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateJadwalKegiatan.create.form.deskripsiJadwalKegiatan.deskripsi}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.deskripsiJadwalKegiatan.deskripsi = e;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Informasi Jadwal Kegiatan</Text>
|
<Text fz="sm" fw="bold" mb={4}>Deskripsi Jadwal Kegiatan</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateJadwalKegiatan.create.form.deskripsiJadwalKegiatan.deskripsi}
|
||||||
|
onChange={(e) => {
|
||||||
|
stateJadwalKegiatan.create.form.deskripsiJadwalKegiatan.deskripsi = e;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="md" fw="bold" mb="sm">Informasi Jadwal Kegiatan</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
label="Nama"
|
||||||
placeholder="masukkan nama"
|
required
|
||||||
|
placeholder="Masukkan nama"
|
||||||
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name}
|
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name = e.target.value;
|
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.name = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Tanggal</Text>}
|
type="date"
|
||||||
placeholder="masukkan tanggal"
|
required
|
||||||
|
label="Tanggal"
|
||||||
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal}
|
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal = e.target.value;
|
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.tanggal = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Waktu</Text>}
|
label="Waktu"
|
||||||
placeholder="masukkan waktu"
|
required
|
||||||
|
placeholder="Masukkan waktu"
|
||||||
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu}
|
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu = e.target.value;
|
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.waktu = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Lokasi</Text>}
|
label="Lokasi"
|
||||||
placeholder="masukkan lokasi"
|
required
|
||||||
|
placeholder="Masukkan lokasi"
|
||||||
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi}
|
value={stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi = e.target.value;
|
stateJadwalKegiatan.create.form.informasiJadwalKegiatan.lokasi = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Layanan Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold" mb="sm">Layanan Jadwal Kegiatan</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateJadwalKegiatan.create.form.layananJadwalKegiatan.content}
|
value={stateJadwalKegiatan.create.form.layananJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -130,8 +161,9 @@ function CreateJadwalKegiatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Syarat dan Ketentuan Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold" mb="sm">Syarat & Ketentuan</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateJadwalKegiatan.create.form.syaratKetentuanJadwalKegiatan.content}
|
value={stateJadwalKegiatan.create.form.syaratKetentuanJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -139,8 +171,9 @@ function CreateJadwalKegiatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Dokumen Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold" mb="sm">Dokumen</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateJadwalKegiatan.create.form.dokumenJadwalKegiatan.content}
|
value={stateJadwalKegiatan.create.form.dokumenJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -148,51 +181,58 @@ function CreateJadwalKegiatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Pendaftaran Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold" mb="sm">Pendaftaran Jadwal Kegiatan</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
label="Nama"
|
||||||
placeholder="masukkan nama"
|
required
|
||||||
|
placeholder="Masukkan nama"
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.name}
|
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.name = e.target.value;
|
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.name = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Tanggal</Text>}
|
type="date"
|
||||||
placeholder="masukkan tanggal"
|
required
|
||||||
|
label="Tanggal"
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.tanggal}
|
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.tanggal}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.tanggal = e.target.value;
|
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.tanggal = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Orangtua</Text>}
|
label="Nama Orangtua"
|
||||||
placeholder="masukkan nama orangtua"
|
required
|
||||||
|
placeholder="Masukkan nama orangtua"
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.namaOrangtua}
|
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.namaOrangtua}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.namaOrangtua = e.target.value;
|
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.namaOrangtua = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nomor</Text>}
|
label="Nomor"
|
||||||
placeholder="masukkan nomor"
|
required
|
||||||
|
placeholder="Masukkan nomor"
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.nomor}
|
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.nomor}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.nomor = e.target.value;
|
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.nomor = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
label="Alamat"
|
||||||
placeholder="masukkan alamat"
|
required
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.alamat}
|
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.alamat}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.alamat = e.target.value;
|
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.alamat = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Catatan</Text>}
|
label="Catatan"
|
||||||
placeholder="masukkan catatan"
|
required
|
||||||
|
placeholder="Masukkan catatan"
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.catatan}
|
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.catatan}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.catatan = e.target.value;
|
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.catatan = e.target.value;
|
||||||
@@ -200,9 +240,21 @@ function CreateJadwalKegiatan() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
{/* Save Button */}
|
||||||
Simpan
|
<Group justify="right">
|
||||||
</Button>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,96 +1,185 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Pagination,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconArrowBack, IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import jadwalKegiatanState from '../../../_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
import jadwalKegiatanState from '../../../_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
function JadwalKegiatan() {
|
function JadwalKegiatan() {
|
||||||
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Tombol Back */}
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Jadwal Kegiatan'
|
title="Jadwal Kegiatan"
|
||||||
placeholder='pencarian'
|
placeholder="Cari nama, tanggal, lokasi..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListJadwalKegiatan search={search}/>
|
|
||||||
|
<ListJadwalKegiatan search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListJadwalKegiatan({ search }: { search: string }) {
|
function ListJadwalKegiatan({ search }: { search: string }) {
|
||||||
const stateJadwalKegiatan = useProxy(jadwalKegiatanState)
|
const state = useProxy(jadwalKegiatanState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateJadwalKegiatan.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (stateJadwalKegiatan.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
item.informasijadwalkegiatan.name.toLowerCase().includes(keyword) ||
|
<Stack py={10}>
|
||||||
item.informasijadwalkegiatan.tanggal.toLowerCase().includes(keyword) ||
|
<Skeleton height={600} radius="md" />
|
||||||
item.informasijadwalkegiatan.waktu.toLowerCase().includes(keyword) ||
|
</Stack>
|
||||||
item.informasijadwalkegiatan.lokasi.toLowerCase().includes(keyword)
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateJadwalKegiatan.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Skeleton h={500}/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Judul + Tombol Tambah */}
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Jadwal Kegiatan'
|
<Title order={4}>Daftar Jadwal Kegiatan</Title>
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/create'
|
<Tooltip label="Tambah Jadwal Kegiatan" withArrow>
|
||||||
/>
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/create')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Tabel */}
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Tanggal</TableTh>
|
<TableTh>Tanggal</TableTh>
|
||||||
<TableTh>Waktu</TableTh>
|
<TableTh>Waktu</TableTh>
|
||||||
<TableTh>Lokasi</TableTh>
|
<TableTh>Lokasi</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.informasijadwalkegiatan.name}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.informasijadwalkegiatan.tanggal}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.informasijadwalkegiatan.waktu}</TableTd>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<TableTd>{item.informasijadwalkegiatan.lokasi}</TableTd>
|
{item.informasijadwalkegiatan.name}
|
||||||
<TableTd>
|
</Text>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/${item.id}`)}>
|
</TableTd>
|
||||||
<IconDeviceImacCog size={25} />
|
<TableTd>
|
||||||
</Button>
|
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString(
|
||||||
|
'id-ID',
|
||||||
|
{
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.informasijadwalkegiatan.waktu}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate fz="sm" c="dimmed">
|
||||||
|
{item.informasijadwalkegiatan.lokasi}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada jadwal kegiatan yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Paper>
|
|
||||||
</Box>
|
{/* Pagination */}
|
||||||
)
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default JadwalKegiatan;
|
export default JadwalKegiatan;
|
||||||
|
|||||||
@@ -2,106 +2,162 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function EditKelahiran() {
|
function EditKelahiran() {
|
||||||
const editState = useProxy(persentaseKelahiranKematian.kelahiran)
|
const editState = useProxy(persentaseKelahiranKematian.kelahiran);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
nama: editState.edit.form.nama || '',
|
|
||||||
tanggal: editState.edit.form.tanggal || '',
|
|
||||||
jenisKelamin: editState.edit.form.jenisKelamin || '',
|
|
||||||
alamat: editState.edit.form.alamat || '',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadKelahiran = async () => {
|
|
||||||
const id = params?.id as string;
|
|
||||||
if (!id) return;
|
|
||||||
|
|
||||||
try {
|
const [formData, setFormData] = useState({
|
||||||
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
nama: editState.edit.form.nama || '',
|
||||||
if (data) {
|
tanggal: editState.edit.form.tanggal || '',
|
||||||
setFormData({
|
jenisKelamin: editState.edit.form.jenisKelamin || '',
|
||||||
nama: data.nama || '',
|
alamat: editState.edit.form.alamat || '',
|
||||||
tanggal: data.tanggal || '',
|
});
|
||||||
jenisKelamin: data.jenisKelamin || '',
|
|
||||||
alamat: data.alamat || '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error loading data kelahiran:", error);
|
|
||||||
toast.error("Gagal memuat data data kelahiran");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadKelahiran();
|
|
||||||
}, [params?.id]);
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
useEffect(() => {
|
||||||
try {
|
const loadKelahiran = async () => {
|
||||||
editState.edit.form = {
|
const id = params?.id as string;
|
||||||
...editState.edit.form,
|
if (!id) return;
|
||||||
nama: formData.nama,
|
|
||||||
tanggal: formData.tanggal,
|
|
||||||
jenisKelamin: formData.jenisKelamin,
|
|
||||||
alamat: formData.alamat,
|
|
||||||
};
|
|
||||||
await editState.edit.update();
|
|
||||||
toast.success('data kelahiran berhasil diperbarui!');
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating data kelahiran:', error);
|
|
||||||
toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
try {
|
||||||
<Box mb={10}>
|
const data = await editState.edit.load(id);
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
if (data) {
|
||||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
setFormData({
|
||||||
</Button>
|
nama: data.nama || '',
|
||||||
</Box>
|
tanggal: data.tanggal || '',
|
||||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
<Stack gap={"xs"}>
|
alamat: data.alamat || '',
|
||||||
<Title order={3}>Edit data kelahiran</Title>
|
});
|
||||||
<TextInput
|
}
|
||||||
value={formData.nama}
|
} catch (error) {
|
||||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
console.error('Error loading data kelahiran:', error);
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
toast.error('Gagal memuat data kelahiran');
|
||||||
placeholder="masukkan nama"
|
}
|
||||||
/>
|
};
|
||||||
<TextInput
|
|
||||||
type='date'
|
|
||||||
value={formData.tanggal}
|
loadKelahiran();
|
||||||
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
}, [params?.id]);
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
|
||||||
placeholder="masukkan tanggal"
|
|
||||||
/>
|
const handleSubmit = async () => {
|
||||||
<TextInput
|
try {
|
||||||
value={formData.jenisKelamin}
|
editState.edit.form = {
|
||||||
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
...editState.edit.form,
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
nama: formData.nama,
|
||||||
placeholder="masukkan jenis kelamin"
|
tanggal: formData.tanggal,
|
||||||
/>
|
jenisKelamin: formData.jenisKelamin,
|
||||||
<TextInput
|
alamat: formData.alamat,
|
||||||
value={formData.alamat}
|
};
|
||||||
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
|
||||||
placeholder="masukkan alamat"
|
await editState.edit.update();
|
||||||
/>
|
toast.success('Data kelahiran berhasil diperbarui!');
|
||||||
<Button onClick={handleSubmit}>Simpan</Button>
|
router.push(
|
||||||
</Stack>
|
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran'
|
||||||
</Paper>
|
);
|
||||||
</Box>
|
} catch (error) {
|
||||||
);
|
console.error('Error updating data kelahiran:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
|
{/* Header */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Data Kelahiran
|
||||||
|
</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
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
|
label="Nama"
|
||||||
|
placeholder="Masukkan nama"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="date"
|
||||||
|
value={formData.tanggal}
|
||||||
|
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
||||||
|
label="Tanggal"
|
||||||
|
placeholder="Masukkan tanggal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.jenisKelamin}
|
||||||
|
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
||||||
|
label="Jenis Kelamin"
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditKelahiran;
|
|
||||||
|
export default EditKelahiran;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
|
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
@@ -13,109 +13,152 @@ import colors from '@/con/colors';
|
|||||||
|
|
||||||
|
|
||||||
function DetailKelahiran() {
|
function DetailKelahiran() {
|
||||||
const state = useProxy(persentaseKelahiranKematian.kelahiran)
|
const state = useProxy(persentaseKelahiranKematian.kelahiran);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
state.findUnique.load(params?.id as string)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
useShallowEffect(() => {
|
||||||
if (selectedId) {
|
state.findUnique.load(params?.id as string);
|
||||||
state.delete.byId(selectedId)
|
}, []);
|
||||||
setModalHapus(false)
|
|
||||||
setSelectedId(null)
|
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
|
||||||
return (
|
|
||||||
<Stack py={10}>
|
|
||||||
<Skeleton h={40} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const handleHapus = () => {
|
||||||
<Box>
|
if (selectedId) {
|
||||||
<Box mb={10}>
|
state.delete.byId(selectedId);
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
setModalHapus(false);
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
setSelectedId(null);
|
||||||
</Button>
|
router.push(
|
||||||
</Box>
|
"/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran"
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
);
|
||||||
<Stack>
|
}
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Data Kelahiran</Text>
|
};
|
||||||
{state.findUnique.data ? (
|
|
||||||
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
|
||||||
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
|
||||||
<Text fz={"lg"}>
|
|
||||||
{new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric'
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
|
|
||||||
</Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (state.findUnique.data) {
|
|
||||||
setSelectedId(state.findUnique.data.id);
|
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={state.delete.loading || !state.findUnique.data}
|
|
||||||
color={"red"}
|
|
||||||
>
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (state.findUnique.data) {
|
|
||||||
router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${state.findUnique.data.id}/edit`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!state.findUnique.data}
|
|
||||||
color={"green"}
|
|
||||||
>
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
if (!state.findUnique.data) {
|
||||||
opened={modalHapus}
|
return (
|
||||||
onClose={() => setModalHapus(false)}
|
<Stack py={10}>
|
||||||
onConfirm={handleHapus}
|
<Skeleton height={500} radius="md" />
|
||||||
text='Apakah anda yakin ingin menghapus data ini?'
|
</Stack>
|
||||||
/>
|
);
|
||||||
</Box>
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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: "50%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Data Kelahiran
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nama</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.nama || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Tanggal</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{new Date(data.tanggal).toLocaleDateString("id-ID", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Jenis Kelamin</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.jenisKelamin || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Alamat</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.alamat || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${data.id}/edit`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus data ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default DetailKelahiran;
|
export default DetailKelahiran;
|
||||||
@@ -1,83 +1,126 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CreateKelahiran() {
|
function CreateKelahiran() {
|
||||||
const createState = useProxy(persentaseKelahiranKematian.kelahiran)
|
const createState = useProxy(persentaseKelahiranKematian.kelahiran);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
createState.create.form = {
|
|
||||||
nama: "",
|
|
||||||
tanggal: "",
|
|
||||||
jenisKelamin: "",
|
|
||||||
alamat: "",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const resetForm = () => {
|
||||||
await createState.create.create();
|
createState.create.form = {
|
||||||
resetForm();
|
nama: '',
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran")
|
tanggal: '',
|
||||||
};
|
jenisKelamin: '',
|
||||||
|
alamat: '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
const handleSubmit = async () => {
|
||||||
<Stack gap={"xs"}>
|
await createState.create.create();
|
||||||
<Title order={4}>Create Kelahiran</Title>
|
resetForm();
|
||||||
<TextInput
|
router.push(
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama</Text>}
|
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran'
|
||||||
placeholder='Masukkan nama'
|
);
|
||||||
value={createState.create.form.nama}
|
};
|
||||||
onChange={(val) => {
|
|
||||||
createState.create.form.nama = val.target.value;
|
|
||||||
}}
|
return (
|
||||||
/>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<TextInput
|
{/* Header */}
|
||||||
type='date'
|
<Group mb="md">
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal</Text>}
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
placeholder='Masukkan tanggal'
|
<Button
|
||||||
value={createState.create.form.tanggal}
|
variant="subtle"
|
||||||
onChange={(val) => {
|
onClick={() => router.back()}
|
||||||
createState.create.form.tanggal = val.target.value;
|
p="xs"
|
||||||
}}
|
radius="md"
|
||||||
/>
|
>
|
||||||
<TextInput
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Jenis Kelamin</Text>}
|
</Button>
|
||||||
placeholder='Masukkan jenis kelamin'
|
</Tooltip>
|
||||||
value={createState.create.form.jenisKelamin}
|
<Title order={4} ml="sm" c="dark">
|
||||||
onChange={(val) => {
|
Tambah Data Kelahiran
|
||||||
createState.create.form.jenisKelamin = val.target.value;
|
</Title>
|
||||||
}}
|
</Group>
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Alamat</Text>}
|
{/* Form */}
|
||||||
placeholder='Masukkan alamat'
|
<Paper
|
||||||
value={createState.create.form.alamat}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={(val) => {
|
bg={colors['white-1']}
|
||||||
createState.create.form.alamat = val.target.value;
|
p="lg"
|
||||||
}}
|
radius="md"
|
||||||
/>
|
shadow="sm"
|
||||||
<Group>
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
>
|
||||||
</Group>
|
<Stack gap="md">
|
||||||
</Stack>
|
<TextInput
|
||||||
</Paper>
|
label={<Text fw="bold" fz="sm">Nama</Text>}
|
||||||
</Box>
|
placeholder="Masukkan nama"
|
||||||
);
|
value={createState.create.form.nama}
|
||||||
|
onChange={(e) => (createState.create.form.nama = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="date"
|
||||||
|
label={<Text fw="bold" fz="sm">Tanggal</Text>}
|
||||||
|
placeholder="Masukkan tanggal"
|
||||||
|
value={createState.create.form.tanggal}
|
||||||
|
onChange={(e) => (createState.create.form.tanggal = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw="bold" fz="sm">Jenis Kelamin</Text>}
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
value={createState.create.form.jenisKelamin}
|
||||||
|
onChange={(e) => (createState.create.form.jenisKelamin = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw="bold" fz="sm">Alamat</Text>}
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
value={createState.create.form.alamat}
|
||||||
|
onChange={(e) => (createState.create.form.alamat = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKelahiran;
|
|
||||||
|
export default CreateKelahiran;
|
||||||
@@ -1,118 +1,197 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||||
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
|
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Pagination,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
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 { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function Kelahiran() {
|
function Kelahiran() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
return (
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Box>
|
||||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
{/* Tombol Back */}
|
||||||
</Button>
|
<Box mb={10}>
|
||||||
</Box>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<HeaderSearch
|
<IconArrowBack color={colors["blue-button"]} size={25} />
|
||||||
title='Data Kelahiran'
|
</Button>
|
||||||
placeholder='pencarian'
|
</Box>
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
{/* Header Search */}
|
||||||
/>
|
<HeaderSearch
|
||||||
<ListKelahiran search={search} />
|
title='Data Kelahiran'
|
||||||
</Box>
|
placeholder='Cari nama atau alamat...'
|
||||||
);
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<ListKelahiran search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ListKelahiran({ search }: { search: string }) {
|
function ListKelahiran({ search }: { search: string }) {
|
||||||
const statePersentase = useProxy(persentasekelahiran.kelahiran);
|
const statePersentase = useProxy(persentasekelahiran.kelahiran);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = statePersentase.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load
|
|
||||||
} = statePersentase.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
load(page, 10, search)
|
|
||||||
}, [page, search])
|
|
||||||
|
|
||||||
const filteredData = data || []
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search);
|
||||||
|
}, [page, search]);
|
||||||
|
|
||||||
if (loading || !data) {
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const filteredData = data || [];
|
||||||
<Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
{/* Form Input */}
|
if (loading || !data) {
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<JudulList
|
<Stack py={10}>
|
||||||
title='List Data Kelahiran'
|
<Skeleton height={600} radius="md" />
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/create'
|
</Stack>
|
||||||
/>
|
);
|
||||||
<Table striped withTableBorder withRowBorders>
|
}
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama</TableTh>
|
return (
|
||||||
<TableTh>Tanggal</TableTh>
|
<Box py={10}>
|
||||||
<TableTh>Jenis Kelamin</TableTh>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<TableTh>Alamat</TableTh>
|
{/* Judul + Tombol Tambah */}
|
||||||
<TableTh>Detail</TableTh>
|
<Group justify="space-between" mb="md">
|
||||||
</TableTr>
|
<Title order={4}>Daftar Data Kelahiran</Title>
|
||||||
</TableThead>
|
<Tooltip label="Tambah Data Kelahiran" withArrow>
|
||||||
<TableTbody>
|
<Button
|
||||||
{filteredData.map((item) => (
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableTr key={item.id}>
|
color="blue"
|
||||||
<TableTd>{item.nama}</TableTd>
|
variant="light"
|
||||||
<TableTd>
|
onClick={() =>
|
||||||
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
router.push(
|
||||||
day: '2-digit',
|
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/create'
|
||||||
month: 'long',
|
)
|
||||||
year: 'numeric'
|
}
|
||||||
})}
|
>
|
||||||
</TableTd>
|
Tambah Baru
|
||||||
<TableTd>{item.jenisKelamin}</TableTd>
|
</Button>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
</Tooltip>
|
||||||
<TableTd>
|
</Group>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}>
|
|
||||||
<IconDeviceImacCog size={20} />
|
|
||||||
</Button>
|
{/* Tabel */}
|
||||||
</TableTd>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
</TableTr>
|
<Table highlightOnHover>
|
||||||
))}
|
<TableThead>
|
||||||
</TableTbody>
|
<TableTr>
|
||||||
</Table>
|
<TableTh>Nama</TableTh>
|
||||||
</Paper>
|
<TableTh>Tanggal</TableTh>
|
||||||
</Stack>
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
<Center>
|
<TableTh>Alamat</TableTh>
|
||||||
<Pagination
|
<TableTh>Aksi</TableTh>
|
||||||
value={page}
|
</TableTr>
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
</TableThead>
|
||||||
total={totalPages}
|
<TableTbody>
|
||||||
mt="md"
|
{filteredData.length > 0 ? (
|
||||||
mb="md"
|
filteredData.map((item) => (
|
||||||
/>
|
<TableTr key={item.id}>
|
||||||
</Center>
|
<TableTd>
|
||||||
</Box>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
);
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.jenisKelamin}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate fz="sm" c="dimmed">
|
||||||
|
{item.alamat}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada data kelahiran yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Kelahiran;
|
|
||||||
|
export default Kelahiran;
|
||||||
@@ -3,119 +3,177 @@
|
|||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function EditKematian() {
|
function EditKematian() {
|
||||||
const editState = useProxy(persentaseKelahiranKematian.kematian)
|
const editState = useProxy(persentaseKelahiranKematian.kematian);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
nama: editState.edit.form.nama || '',
|
|
||||||
tanggal: editState.edit.form.tanggal || '',
|
|
||||||
jenisKelamin: editState.edit.form.jenisKelamin || '',
|
|
||||||
alamat: editState.edit.form.alamat || '',
|
|
||||||
penyebab: editState.edit.form.penyebab || '',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadKelahiran = async () => {
|
|
||||||
const id = params?.id as string;
|
|
||||||
if (!id) return;
|
|
||||||
|
|
||||||
try {
|
const [formData, setFormData] = useState({
|
||||||
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
nama: editState.edit.form.nama || '',
|
||||||
if (data) {
|
tanggal: editState.edit.form.tanggal || '',
|
||||||
setFormData({
|
jenisKelamin: editState.edit.form.jenisKelamin || '',
|
||||||
nama: data.nama || '',
|
alamat: editState.edit.form.alamat || '',
|
||||||
tanggal: data.tanggal || '',
|
penyebab: editState.edit.form.penyebab || '',
|
||||||
jenisKelamin: data.jenisKelamin || '',
|
});
|
||||||
alamat: data.alamat || '',
|
|
||||||
penyebab: data.penyebab || '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error loading data kelahiran:", error);
|
|
||||||
toast.error("Gagal memuat data data kelahiran");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadKelahiran();
|
|
||||||
}, [params?.id]);
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
useEffect(() => {
|
||||||
try {
|
const loadData = async () => {
|
||||||
editState.edit.form = {
|
const id = params?.id as string;
|
||||||
...editState.edit.form,
|
if (!id) return;
|
||||||
nama: formData.nama,
|
|
||||||
tanggal: formData.tanggal,
|
|
||||||
jenisKelamin: formData.jenisKelamin,
|
|
||||||
alamat: formData.alamat,
|
|
||||||
penyebab: formData.penyebab,
|
|
||||||
};
|
|
||||||
await editState.edit.update();
|
|
||||||
toast.success('data kelahiran berhasil diperbarui!');
|
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating data kelahiran:', error);
|
|
||||||
toast.error('Terjadi kesalahan saat memperbarui data kelahiran');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
try {
|
||||||
<Box mb={10}>
|
const data = await editState.edit.load(id);
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
if (data) {
|
||||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
setFormData({
|
||||||
</Button>
|
nama: data.nama || '',
|
||||||
</Box>
|
tanggal: data.tanggal || '',
|
||||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
jenisKelamin: data.jenisKelamin || '',
|
||||||
<Stack gap={"xs"}>
|
alamat: data.alamat || '',
|
||||||
<Title order={3}>Edit data kelahiran</Title>
|
penyebab: data.penyebab || '',
|
||||||
<TextInput
|
});
|
||||||
value={formData.nama}
|
}
|
||||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
} catch (error) {
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
console.error('Error loading data kematian:', error);
|
||||||
placeholder="masukkan nama"
|
toast.error('Gagal memuat data kematian');
|
||||||
/>
|
}
|
||||||
<TextInput
|
};
|
||||||
type='date'
|
|
||||||
value={formData.tanggal}
|
|
||||||
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
loadData();
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
}, [params?.id]);
|
||||||
placeholder="masukkan tanggal"
|
|
||||||
/>
|
|
||||||
<TextInput
|
const handleSubmit = async () => {
|
||||||
value={formData.jenisKelamin}
|
try {
|
||||||
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
editState.edit.form = { ...editState.edit.form, ...formData };
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
|
await editState.edit.update();
|
||||||
placeholder="masukkan jenis kelamin"
|
toast.success('Data kematian berhasil diperbarui!');
|
||||||
/>
|
router.push(
|
||||||
<TextInput
|
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian'
|
||||||
value={formData.alamat}
|
);
|
||||||
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
} catch (error) {
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
console.error('Error updating data kematian:', error);
|
||||||
placeholder="masukkan alamat"
|
toast.error('Terjadi kesalahan saat memperbarui data kematian');
|
||||||
/>
|
}
|
||||||
<Box>
|
};
|
||||||
<Text fz={"sm"} fw={"bold"}>Penyebab</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.penyebab}
|
return (
|
||||||
onChange={(htmlContent) => {
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
setFormData((prev) => ({ ...prev, penyebab: htmlContent }));
|
{/* Header dengan tombol back */}
|
||||||
editState.edit.form.penyebab = htmlContent;
|
<Group mb="md">
|
||||||
}}
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
/>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Button onClick={handleSubmit}>Simpan</Button>
|
</Button>
|
||||||
</Stack>
|
</Tooltip>
|
||||||
</Paper>
|
<Title order={4} ml="sm" c="dark">
|
||||||
</Box>
|
Edit Data Kematian
|
||||||
);
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Card 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"
|
||||||
|
placeholder="Masukkan nama"
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
type="date"
|
||||||
|
label="Tanggal"
|
||||||
|
placeholder="Masukkan tanggal"
|
||||||
|
value={formData.tanggal}
|
||||||
|
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Jenis Kelamin"
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
value={formData.jenisKelamin}
|
||||||
|
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
|
Penyebab
|
||||||
|
</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.penyebab}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData((prev) => ({ ...prev, penyebab: htmlContent }));
|
||||||
|
editState.edit.form.penyebab = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditKematian;
|
|
||||||
|
export default EditKematian;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
|
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
@@ -13,114 +13,153 @@ import colors from '@/con/colors';
|
|||||||
|
|
||||||
|
|
||||||
function DetailKematian() {
|
function DetailKematian() {
|
||||||
const state = useProxy(persentaseKelahiranKematian.kematian)
|
const state = useProxy(persentaseKelahiranKematian.kematian);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
state.findUnique.load(params?.id as string)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
useShallowEffect(() => {
|
||||||
if (selectedId) {
|
state.findUnique.load(params?.id as string);
|
||||||
state.delete.byId(selectedId)
|
}, []);
|
||||||
setModalHapus(false)
|
|
||||||
setSelectedId(null)
|
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
|
||||||
return (
|
|
||||||
<Stack py={10}>
|
|
||||||
<Skeleton h={40} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const handleHapus = () => {
|
||||||
<Box>
|
if (selectedId) {
|
||||||
<Box mb={10}>
|
state.delete.byId(selectedId);
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
setModalHapus(false);
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
setSelectedId(null);
|
||||||
</Button>
|
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran");
|
||||||
</Box>
|
}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
};
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Data Kematian</Text>
|
|
||||||
{state.findUnique.data ? (
|
|
||||||
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
|
||||||
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
|
||||||
<Text fz={"lg"}>
|
|
||||||
{state.findUnique.data?.tanggal instanceof Date
|
|
||||||
? state.findUnique.data.tanggal.toLocaleDateString('id-ID', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric'
|
|
||||||
})
|
|
||||||
: state.findUnique.data?.tanggal}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
|
|
||||||
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Penyebab</Text>
|
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.penyebab || '' }} />
|
|
||||||
</Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (state.findUnique.data) {
|
|
||||||
setSelectedId(state.findUnique.data.id);
|
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={state.delete.loading || !state.findUnique.data}
|
|
||||||
color={"red"}
|
|
||||||
>
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (state.findUnique.data) {
|
|
||||||
router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${state.findUnique.data.id}/edit`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!state.findUnique.data}
|
|
||||||
color={"green"}
|
|
||||||
>
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
if (!state.findUnique.data) {
|
||||||
opened={modalHapus}
|
return (
|
||||||
onClose={() => setModalHapus(false)}
|
<Stack py={10}>
|
||||||
onConfirm={handleHapus}
|
<Skeleton height={500} radius="md" />
|
||||||
text='Apakah anda yakin ingin menghapus data ini?'
|
</Stack>
|
||||||
/>
|
);
|
||||||
</Box>
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
{/* Tombol kembali */}
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: "100%", md: "50%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Data Kematian
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nama</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data?.nama || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Tanggal</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data?.tanggal instanceof Date
|
||||||
|
? data.tanggal.toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
})
|
||||||
|
: data?.tanggal || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Jenis Kelamin</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data?.jenisKelamin || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Alamat</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data?.alamat || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Penyebab</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.penyebab || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() => router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${data.id}/edit`
|
||||||
|
)}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah Anda yakin ingin menghapus data ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default DetailKematian;
|
export default DetailKematian;
|
||||||
@@ -1,94 +1,146 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CreateKematian() {
|
function CreateKematian() {
|
||||||
const createState = useProxy(persentaseKelahiranKematian.kematian)
|
const createState = useProxy(persentaseKelahiranKematian.kematian);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
createState.create.form = {
|
|
||||||
nama: "",
|
|
||||||
tanggal: "",
|
|
||||||
jenisKelamin: "",
|
|
||||||
alamat: "",
|
|
||||||
penyebab: "",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const resetForm = () => {
|
||||||
await createState.create.create();
|
createState.create.form = {
|
||||||
resetForm();
|
nama: '',
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian")
|
tanggal: '',
|
||||||
};
|
jenisKelamin: '',
|
||||||
|
alamat: '',
|
||||||
|
penyebab: '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
const handleSubmit = async () => {
|
||||||
<Stack gap={"xs"}>
|
if (!createState.create.form.nama) {
|
||||||
<Title order={4}>Create Kematian</Title>
|
return toast.warn('Nama wajib diisi');
|
||||||
<TextInput
|
}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama</Text>}
|
if (!createState.create.form.tanggal) {
|
||||||
placeholder='Masukkan nama'
|
return toast.warn('Tanggal wajib diisi');
|
||||||
value={createState.create.form.nama}
|
}
|
||||||
onChange={(val) => {
|
|
||||||
createState.create.form.nama = val.target.value;
|
|
||||||
}}
|
await createState.create.create();
|
||||||
/>
|
resetForm();
|
||||||
<TextInput
|
router.push(
|
||||||
type='date'
|
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian'
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal</Text>}
|
);
|
||||||
placeholder='Masukkan tanggal'
|
};
|
||||||
value={createState.create.form.tanggal}
|
|
||||||
onChange={(val) => {
|
|
||||||
createState.create.form.tanggal = val.target.value;
|
return (
|
||||||
}}
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
/>
|
{/* Header */}
|
||||||
<TextInput
|
<Group mb="md">
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Jenis Kelamin</Text>}
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
placeholder='Masukkan jenis kelamin'
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
value={createState.create.form.jenisKelamin}
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
onChange={(val) => {
|
</Button>
|
||||||
createState.create.form.jenisKelamin = val.target.value;
|
</Tooltip>
|
||||||
}}
|
<Title order={4} ml="sm" c="dark">
|
||||||
/>
|
Tambah Data Kematian
|
||||||
<TextInput
|
</Title>
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Alamat</Text>}
|
</Group>
|
||||||
placeholder='Masukkan alamat'
|
|
||||||
value={createState.create.form.alamat}
|
|
||||||
onChange={(val) => {
|
{/* Form Card */}
|
||||||
createState.create.form.alamat = val.target.value;
|
<Paper
|
||||||
}}
|
w={{ base: '100%', md: '50%' }}
|
||||||
/>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fw={"bold"} fz={"sm"}>Penyebab</Text>
|
radius="md"
|
||||||
<CreateEditor
|
shadow="sm"
|
||||||
value={createState.create.form.penyebab}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
onChange={(htmlContent) => {
|
>
|
||||||
createState.create.form.penyebab = htmlContent;
|
<Stack gap="md">
|
||||||
}}
|
<TextInput
|
||||||
/>
|
label="Nama"
|
||||||
</Box>
|
placeholder="Masukkan nama"
|
||||||
<Group>
|
value={createState.create.form.nama}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
onChange={(e) => (createState.create.form.nama = e.target.value)}
|
||||||
</Group>
|
required
|
||||||
</Stack>
|
/>
|
||||||
</Paper>
|
<TextInput
|
||||||
</Box>
|
type="date"
|
||||||
);
|
label="Tanggal"
|
||||||
|
placeholder="Masukkan tanggal"
|
||||||
|
value={createState.create.form.tanggal}
|
||||||
|
onChange={(e) => (createState.create.form.tanggal = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jenis Kelamin"
|
||||||
|
placeholder="Masukkan jenis kelamin"
|
||||||
|
value={createState.create.form.jenisKelamin}
|
||||||
|
onChange={(e) => (createState.create.form.jenisKelamin = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
value={createState.create.form.alamat}
|
||||||
|
onChange={(e) => (createState.create.form.alamat = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Title order={6} mb={6}>
|
||||||
|
Penyebab
|
||||||
|
</Title>
|
||||||
|
<CreateEditor
|
||||||
|
value={createState.create.form.penyebab}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
createState.create.form.penyebab = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKematian;
|
|
||||||
|
export default CreateKematian;
|
||||||
@@ -1,118 +1,191 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||||
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
|
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconSearch } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function Kematian() {
|
function Kematian() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
return (
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Box>
|
||||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
{/* Tombol Back */}
|
||||||
</Button>
|
<Box mb={10}>
|
||||||
</Box>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<HeaderSearch
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
title='Data Kematian'
|
</Button>
|
||||||
placeholder='pencarian'
|
</Box>
|
||||||
searchIcon={<IconSearch size={20} />}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
{/* Header dengan Search */}
|
||||||
/>
|
<HeaderSearch
|
||||||
<ListKematian search={search} />
|
title='Data Kematian'
|
||||||
</Box >
|
placeholder='Cari nama atau alamat...'
|
||||||
);
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<ListKematian search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ListKematian({ search }: { search: string }) {
|
function ListKematian({ search }: { search: string }) {
|
||||||
const statePersentase = useProxy(persentasekelahiran.kematian);
|
const statePersentase = useProxy(persentasekelahiran.kematian);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = statePersentase.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load
|
|
||||||
} = statePersentase.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
load(page, 10, search)
|
|
||||||
}, [search])
|
|
||||||
|
|
||||||
const filteredData = data || []
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search);
|
||||||
|
}, [page, search]);
|
||||||
|
|
||||||
if (loading || !data) {
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const filteredData = data || [];
|
||||||
<Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
{/* Form Input */}
|
if (loading || !data) {
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<JudulList
|
<Stack py={10}>
|
||||||
title='List Data Kematian'
|
<Skeleton height={600} radius="md" />
|
||||||
href='/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/create'
|
</Stack>
|
||||||
/>
|
);
|
||||||
<Table striped withTableBorder withRowBorders>
|
}
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>Nama</TableTh>
|
return (
|
||||||
<TableTh>Tanggal</TableTh>
|
<Box py={10}>
|
||||||
<TableTh>Jenis Kelamin</TableTh>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<TableTh>Alamat</TableTh>
|
<Group justify="space-between" mb="md">
|
||||||
<TableTh>Detail</TableTh>
|
<Title order={4}>Daftar Data Kematian</Title>
|
||||||
</TableTr>
|
<Tooltip label="Tambah Data Kematian" withArrow>
|
||||||
</TableThead>
|
<Button
|
||||||
<TableTbody>
|
leftSection={<IconPlus size={18} />}
|
||||||
{filteredData.map((item) => (
|
color="blue"
|
||||||
<TableTr key={item.id}>
|
variant="light"
|
||||||
<TableTd>{item.nama}</TableTd>
|
onClick={() =>
|
||||||
<TableTd>
|
router.push(
|
||||||
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
'/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/create'
|
||||||
day: '2-digit',
|
)
|
||||||
month: 'long',
|
}
|
||||||
year: 'numeric'
|
>
|
||||||
})}
|
Tambah Baru
|
||||||
</TableTd>
|
</Button>
|
||||||
<TableTd>{item.jenisKelamin}</TableTd>
|
</Tooltip>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
</Group>
|
||||||
<TableTd>
|
|
||||||
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}>
|
|
||||||
<IconEdit size={20} />
|
<Box style={{ overflowX: "auto" }}>
|
||||||
</Button>
|
<Table highlightOnHover>
|
||||||
</TableTd>
|
<TableThead>
|
||||||
</TableTr>
|
<TableTr>
|
||||||
))}
|
<TableTh>Nama</TableTh>
|
||||||
</TableTbody>
|
<TableTh>Tanggal</TableTh>
|
||||||
</Table>
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
</Paper>
|
<TableTh>Alamat</TableTh>
|
||||||
</Stack>
|
<TableTh>Aksi</TableTh>
|
||||||
<Center>
|
</TableTr>
|
||||||
<Pagination
|
</TableThead>
|
||||||
value={page}
|
<TableTbody>
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
{filteredData.length > 0 ? (
|
||||||
total={totalPages}
|
filteredData.map((item) => (
|
||||||
mt="md"
|
<TableTr key={item.id}>
|
||||||
mb="md"
|
<TableTd>
|
||||||
/>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Center>
|
{item.nama}
|
||||||
</Box>
|
</Text>
|
||||||
);
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>{item.jenisKelamin}</TableTd>
|
||||||
|
<TableTd>{item.alamat}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada data kematian yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Kematian;
|
|
||||||
|
export default Kematian;
|
||||||
@@ -1,228 +1,277 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ActionIcon, Box, Center, Flex, Paper, Select, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
|
import { ActionIcon, Badge, Box, Center, Flex, Tooltip as MantineTooltip, Paper, Select, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
|
import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Bar, BarChart, Legend, Tooltip, TooltipProps, XAxis, YAxis } from 'recharts';
|
import { Bar, BarChart, Legend, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
type TooltipPayload = {
|
type TooltipPayload = {
|
||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
payload: any;
|
payload: any;
|
||||||
color: string;
|
color: string;
|
||||||
dataKey: string;
|
dataKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
type CustomTooltipProps = TooltipProps<number, string> & {
|
type CustomTooltipProps = TooltipProps<number, string> & {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
payload?: TooltipPayload[];
|
payload?: TooltipPayload[];
|
||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function PersentaseDataKelahiranKematian() {
|
function PersentaseDataKelahiranKematian() {
|
||||||
return (
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="md">
|
||||||
<GrafikPersentaseKelahiranKematian />
|
<GrafikPersentaseKelahiranKematian />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function GrafikPersentaseKelahiranKematian() {
|
function GrafikPersentaseKelahiranKematian() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
type DataTahunan = {
|
|
||||||
tahun: string;
|
|
||||||
totalKelahiran: number;
|
|
||||||
totalKematian: number;
|
|
||||||
data: Array<{
|
|
||||||
id: string;
|
|
||||||
bulan: string;
|
|
||||||
kelahiran: number;
|
|
||||||
kematian: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Count occurrences per year
|
|
||||||
const countByYear = (data: any[], dateField: string) => {
|
|
||||||
const counts: Record<string, number> = {};
|
|
||||||
data?.forEach(item => {
|
|
||||||
const year = new Date(item[dateField]).getFullYear().toString();
|
|
||||||
counts[year] = (counts[year] || 0) + 1;
|
|
||||||
});
|
|
||||||
return counts;
|
|
||||||
};
|
|
||||||
|
|
||||||
const statePersentase = useProxy(persentasekelahiran);
|
type DataTahunan = {
|
||||||
const [chartData, setChartData] = useState<DataTahunan[]>([]);
|
tahun: string;
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)');
|
totalKelahiran: number;
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
totalKematian: number;
|
||||||
const [selectedYear, setSelectedYear] = useState<string | null>(null);
|
data: Array<{
|
||||||
|
id: string;
|
||||||
|
bulan: string;
|
||||||
|
kelahiran: number;
|
||||||
|
kematian: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
// Format number to Indonesian locale
|
|
||||||
const formatNumber = (num: number) => {
|
|
||||||
return new Intl.NumberFormat('id-ID').format(num);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format tooltip
|
// ✅ Fungsi hitung tahunan + bulanan
|
||||||
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
|
const countByYearAndMonth = (kelahiran: any[], kematian: any[]): DataTahunan[] => {
|
||||||
if (active && payload && payload.length) {
|
const dataTahunan: Record<string, DataTahunan> = {};
|
||||||
return (
|
|
||||||
<Paper p="md" shadow="md" withBorder>
|
|
||||||
<Text size="sm" fw={500} mb={5}>Tahun {label}</Text>
|
|
||||||
<Text size="sm" c="blue">Kelahiran: {formatNumber(payload[0].value)}</Text>
|
|
||||||
<Text size="sm" c="red">Kematian: {formatNumber(payload[1].value)}</Text>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data
|
|
||||||
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const namaBulan = [
|
||||||
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
|
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
|
||||||
// Count kelahiran and kematian by year
|
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
|
||||||
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
|
];
|
||||||
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
|
|
||||||
|
|
||||||
// Get all unique years
|
|
||||||
const allYears = new Set([
|
|
||||||
...Object.keys(kelahiranByYear),
|
|
||||||
...Object.keys(kematianByYear)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create data structure for the chart
|
// Proses kelahiran
|
||||||
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
|
kelahiran?.forEach((item: any) => {
|
||||||
acc[year] = {
|
const date = new Date(item.tanggal);
|
||||||
tahun: year,
|
const tahun = date.getFullYear().toString();
|
||||||
totalKelahiran: kelahiranByYear[year] || 0,
|
const bulanIndex = date.getMonth();
|
||||||
totalKematian: kematianByYear[year] || 0,
|
|
||||||
data: []
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const sortedData = Object.values(dataByYear).sort((a, b) =>
|
|
||||||
parseInt(a.tahun) - parseInt(b.tahun)
|
|
||||||
);
|
|
||||||
|
|
||||||
setChartData(sortedData);
|
if (!dataTahunan[tahun]) {
|
||||||
setSelectedYear(sortedData[0]?.tahun || '');
|
dataTahunan[tahun] = {
|
||||||
}
|
tahun,
|
||||||
}, [
|
totalKelahiran: 0,
|
||||||
statePersentase.kelahiran.findMany.data,
|
totalKematian: 0,
|
||||||
statePersentase.kematian.findMany.data,
|
data: namaBulan.map((nama, idx) => ({
|
||||||
]);
|
id: `${tahun}-${idx + 1}`,
|
||||||
|
bulan: nama,
|
||||||
|
kelahiran: 0,
|
||||||
|
kematian: 0
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!statePersentase.kelahiran.findMany.data || !statePersentase.kematian.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedYearData = chartData.find(d => d.tahun === selectedYear);
|
dataTahunan[tahun].totalKelahiran += 1;
|
||||||
|
dataTahunan[tahun].data[bulanIndex].kelahiran += 1;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper bg={colors['white-1']} p="md">
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3} mb="md">Statistik Kelahiran & Kematian</Title>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<ActionIcon size={30} color={colors['blue-button']} onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran')}>
|
|
||||||
<IconBabyCarriage size={30} color={colors['white-1']} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<ActionIcon size={30} color={colors['blue-button']} onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian')} >
|
|
||||||
<IconGrave2 size={30} color={colors['white-1']} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{chartData.length === 0 ? (
|
// Proses kematian
|
||||||
<Text c="dimmed" ta="center" py="xl">
|
kematian?.forEach((item: any) => {
|
||||||
Belum ada data yang tersedia untuk ditampilkan
|
const date = new Date(item.tanggal);
|
||||||
</Text>
|
const tahun = date.getFullYear().toString();
|
||||||
) : (
|
const bulanIndex = date.getMonth();
|
||||||
<>
|
|
||||||
{/* Year Selector */}
|
|
||||||
<Box mb="md" style={{ maxWidth: '200px' }}>
|
|
||||||
<Select
|
|
||||||
label="Pilih Tahun"
|
|
||||||
placeholder="Pilih Tahun"
|
|
||||||
data={chartData.map((item) => ({
|
|
||||||
value: item.tahun,
|
|
||||||
label: item.tahun
|
|
||||||
}))}
|
|
||||||
value={selectedYear}
|
|
||||||
onChange={(value) => setSelectedYear(value || '')}
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Main Chart */}
|
|
||||||
<Center>
|
|
||||||
<Box h={400}>
|
|
||||||
<BarChart
|
|
||||||
width={isMobile ? window.innerWidth * 0.9 : isTablet ? 700 : 800}
|
|
||||||
height={350}
|
|
||||||
data={chartData}
|
|
||||||
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
|
|
||||||
>
|
|
||||||
<XAxis dataKey="tahun" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip content={<CustomTooltip />} />
|
|
||||||
<Legend />
|
|
||||||
<Bar dataKey="totalKelahiran" name="Total Kelahiran" fill="#4dabf7" />
|
|
||||||
<Bar dataKey="totalKematian" name="Total Kematian" fill="#f03e3e" />
|
|
||||||
</BarChart>
|
|
||||||
</Box>
|
|
||||||
</Center>
|
|
||||||
|
|
||||||
{/* Yearly Breakdown */}
|
if (!dataTahunan[tahun]) {
|
||||||
{selectedYearData && (
|
dataTahunan[tahun] = {
|
||||||
<Box mt="xl">
|
tahun,
|
||||||
<Title order={4} mb="md">Rincian Tahun {selectedYear}</Title>
|
totalKelahiran: 0,
|
||||||
<Table striped withTableBorder>
|
totalKematian: 0,
|
||||||
<Table.Thead>
|
data: namaBulan.map((nama, idx) => ({
|
||||||
<Table.Tr>
|
id: `${tahun}-${idx + 1}`,
|
||||||
<Table.Th>Bulan</Table.Th>
|
bulan: nama,
|
||||||
<Table.Th ta="right">Kelahiran</Table.Th>
|
kelahiran: 0,
|
||||||
<Table.Th ta="right">Kematian</Table.Th>
|
kematian: 0
|
||||||
</Table.Tr>
|
}))
|
||||||
</Table.Thead>
|
};
|
||||||
<Table.Tbody>
|
}
|
||||||
{selectedYearData.data.map((item) => (
|
|
||||||
<Table.Tr key={item.id}>
|
|
||||||
<Table.Td>{item.bulan}</Table.Td>
|
dataTahunan[tahun].totalKematian += 1;
|
||||||
<Table.Td ta="right">{formatNumber(item.kelahiran)}</Table.Td>
|
dataTahunan[tahun].data[bulanIndex].kematian += 1;
|
||||||
<Table.Td ta="right">{formatNumber(item.kematian)}</Table.Td>
|
});
|
||||||
</Table.Tr>
|
|
||||||
))}
|
|
||||||
<Table.Tr style={{ fontWeight: 'bold' }}>
|
return Object.values(dataTahunan).sort((a, b) => parseInt(a.tahun) - parseInt(b.tahun));
|
||||||
<Table.Td>Total</Table.Td>
|
};
|
||||||
<Table.Td ta="right">{formatNumber(selectedYearData.totalKelahiran)}</Table.Td>
|
|
||||||
<Table.Td ta="right">{formatNumber(selectedYearData.totalKematian)}</Table.Td>
|
|
||||||
</Table.Tr>
|
const statePersentase = useProxy(persentasekelahiran);
|
||||||
</Table.Tbody>
|
const [chartData, setChartData] = useState<DataTahunan[]>([]);
|
||||||
</Table>
|
const [selectedYear, setSelectedYear] = useState<string | null>(null);
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</>
|
const formatNumber = (num: number) => new Intl.NumberFormat('id-ID').format(num);
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
);
|
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<Paper p="sm" shadow="md" withBorder radius="md">
|
||||||
|
<Text size="sm" fw={600}>Tahun {label}</Text>
|
||||||
|
<Text size="sm" c="blue.6">Kelahiran: {formatNumber(payload[0].value)}</Text>
|
||||||
|
<Text size="sm" c="red.6">Kematian: {formatNumber(payload[1].value)}</Text>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePersentase.kelahiran.findMany.load(1, 1000);
|
||||||
|
statePersentase.kematian.findMany.load(1, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
|
||||||
|
const hasil = countByYearAndMonth(
|
||||||
|
statePersentase.kelahiran.findMany.data,
|
||||||
|
statePersentase.kematian.findMany.data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
setChartData(hasil);
|
||||||
|
setSelectedYear(hasil[0]?.tahun || null);
|
||||||
|
}
|
||||||
|
}, [statePersentase.kelahiran.findMany.data, statePersentase.kematian.findMany.data]);
|
||||||
|
|
||||||
|
|
||||||
|
if (!statePersentase.kelahiran.findMany.data || !statePersentase.kematian.findMany.data) {
|
||||||
|
return <Skeleton h={400} radius="lg" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const selectedYearData = chartData.find(d => d.tahun === selectedYear);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Flex justify="space-between" align="center">
|
||||||
|
<Title order={3} fw={700}>Statistik Kelahiran & Kematian</Title>
|
||||||
|
<Flex gap="sm">
|
||||||
|
<MantineTooltip label="Tambah Data Kelahiran" withArrow>
|
||||||
|
<ActionIcon size="lg" radius="xl" color="blue.6" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran')}>
|
||||||
|
<IconBabyCarriage size={22} />
|
||||||
|
</ActionIcon>
|
||||||
|
</MantineTooltip>
|
||||||
|
<MantineTooltip label="Tambah Data Kematian" withArrow>
|
||||||
|
<ActionIcon size="lg" radius="xl" color="red.6" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian')}>
|
||||||
|
<IconGrave2 size={22} />
|
||||||
|
</ActionIcon>
|
||||||
|
</MantineTooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
|
||||||
|
{chartData.length === 0 ? (
|
||||||
|
<Center py="xl">
|
||||||
|
<Text c="dimmed" fs="italic">Belum ada data untuk ditampilkan</Text>
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Box maw={220}>
|
||||||
|
<Select
|
||||||
|
label="Pilih Tahun"
|
||||||
|
placeholder="Pilih tahun data"
|
||||||
|
data={chartData.map((item) => ({ value: item.tahun, label: item.tahun }))}
|
||||||
|
value={selectedYear}
|
||||||
|
onChange={(value) => setSelectedYear(value || null)}
|
||||||
|
size="sm"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box h={360}>
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<BarChart data={chartData} margin={{ top: 20, right: 30, left: 0, bottom: 10 }}>
|
||||||
|
<XAxis dataKey="tahun" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip content={<CustomTooltip />} />
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="totalKelahiran" name="Kelahiran" fill="#4dabf7" radius={[6, 6, 0, 0]} />
|
||||||
|
<Bar dataKey="totalKematian" name="Kematian" fill="#f03e3e" radius={[6, 6, 0, 0]} />
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{selectedYearData && (
|
||||||
|
<Box>
|
||||||
|
<Flex align="center" gap="sm" mb="md">
|
||||||
|
<Title order={4} fw={600}>Rincian Tahun {selectedYear}</Title>
|
||||||
|
<Badge variant="light" color="blue">{formatNumber(selectedYearData.totalKelahiran)} kelahiran</Badge>
|
||||||
|
<Badge variant="light" color="red">{formatNumber(selectedYearData.totalKematian)} kematian</Badge>
|
||||||
|
</Flex>
|
||||||
|
<Table striped withTableBorder highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Bulan</Table.Th>
|
||||||
|
<Table.Th ta="right">Kelahiran</Table.Th>
|
||||||
|
<Table.Th ta="right">Kematian</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{selectedYearData.data.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{selectedYearData.data.map((item) => (
|
||||||
|
<Table.Tr key={item.id}>
|
||||||
|
<Table.Td>{item.bulan}</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(item.kelahiran)}</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(item.kematian)}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
<Table.Tr style={{ fontWeight: 'bold' }}>
|
||||||
|
<Table.Td>Total</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(selectedYearData.totalKelahiran)}</Table.Td>
|
||||||
|
<Table.Td ta="right">{formatNumber(selectedYearData.totalKematian)}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td colSpan={3} ta="center" c="dimmed">Tidak ada rincian bulanan</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PersentaseDataKelahiranKematian;
|
|
||||||
|
export default PersentaseDataKelahiranKematian;
|
||||||
@@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -12,11 +23,10 @@ import { useEffect, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function EditInfoWabahPenyakit() {
|
function EditInfoWabahPenyakit() {
|
||||||
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
const infoWabahPenyakitState = useProxy(infoWabahPenyakit);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
@@ -25,7 +35,7 @@ function EditInfoWabahPenyakit() {
|
|||||||
deskripsiSingkat: infoWabahPenyakitState.edit.form.deskripsiSingkat || '',
|
deskripsiSingkat: infoWabahPenyakitState.edit.form.deskripsiSingkat || '',
|
||||||
deskripsi: infoWabahPenyakitState.edit.form.deskripsiLengkap || '',
|
deskripsi: infoWabahPenyakitState.edit.form.deskripsiLengkap || '',
|
||||||
imageId: infoWabahPenyakitState.edit.form.imageId || '',
|
imageId: infoWabahPenyakitState.edit.form.imageId || '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadInfoWabahPenyakit = async () => {
|
const loadInfoWabahPenyakit = async () => {
|
||||||
@@ -47,8 +57,8 @@ function EditInfoWabahPenyakit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program kesehatan:", error);
|
console.error('Error loading info wabah penyakit:', error);
|
||||||
toast.error("Gagal memuat data program kesehatan");
|
toast.error('Gagal memuat data info wabah penyakit');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,115 +80,143 @@ function EditInfoWabahPenyakit() {
|
|||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
return toast.error('Gagal upload gambar');
|
||||||
}
|
}
|
||||||
|
|
||||||
infoWabahPenyakitState.edit.form.imageId = uploaded.id;
|
infoWabahPenyakitState.edit.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await infoWabahPenyakitState.edit.update();
|
await infoWabahPenyakitState.edit.update();
|
||||||
toast.success("Info wabah penyakit berhasil diperbarui!");
|
toast.success('Info wabah penyakit berhasil diperbarui!');
|
||||||
router.push("/admin/kesehatan/info-wabah-penyakit");
|
router.push('/admin/kesehatan/info-wabah-penyakit');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating info wabah penyakit:", error);
|
console.error('Error updating info wabah penyakit:', error);
|
||||||
toast.error("Gagal memuat data info wabah penyakit");
|
toast.error('Terjadi kesalahan saat memperbarui info wabah penyakit');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap={"xs"}>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Info Wabah Penyakit</Title>
|
Edit Info Wabah Penyakit
|
||||||
<TextInput
|
</Title>
|
||||||
value={formData.name}
|
</Group>
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
|
||||||
placeholder="masukkan judul"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
{/* Form */}
|
||||||
value={formData.deskripsiSingkat}
|
<Paper
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
w={{ base: '100%', md: '50%' }}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
bg={colors['white-1']}
|
||||||
placeholder="masukkan deskripsi"
|
p="lg"
|
||||||
/>
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label="Judul"
|
||||||
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<Box>
|
<TextInput
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
value={formData.deskripsiSingkat}
|
||||||
<EditEditor
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
value={formData.deskripsi}
|
label="Deskripsi Singkat"
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
placeholder="Masukkan deskripsi singkat"
|
||||||
/>
|
required
|
||||||
</Box>
|
/>
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Box>
|
||||||
<Box>
|
<Text fz="sm" fw="bold">
|
||||||
<Dropzone
|
Deskripsi
|
||||||
onDrop={(files) => {
|
</Text>
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
<EditEditor
|
||||||
if (selectedFile) {
|
value={formData.deskripsi}
|
||||||
setFile(selectedFile);
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
/>
|
||||||
}
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">
|
||||||
|
Gambar
|
||||||
|
</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0];
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
/>
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
import {
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
Box,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Skeleton,
|
||||||
|
Tooltip,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
@@ -10,86 +20,135 @@ import { useShallowEffect } from '@mantine/hooks';
|
|||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
function DetailInfoWabahPenyakit() {
|
function DetailInfoWabahPenyakit() {
|
||||||
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
const state = useProxy(infoWabahPenyakit);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
infoWabahPenyakitState.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
infoWabahPenyakitState.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/info-wabah-penyakit")
|
router.push('/admin/kesehatan/info-wabah-penyakit');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!infoWabahPenyakitState.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={400} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Info Wabah Penyakit</Text>
|
Kembali
|
||||||
{infoWabahPenyakitState.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Wrapper Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
withBorder
|
||||||
<Text fz={"lg"}>{infoWabahPenyakitState.findUnique.data.name}</Text>
|
w={{ base: '100%', md: '50%' }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Singkat</Text>
|
radius="md"
|
||||||
<Text fz={"lg"}>{infoWabahPenyakitState.findUnique.data.deskripsiSingkat}</Text>
|
shadow="sm"
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Stack gap="md">
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: infoWabahPenyakitState.findUnique.data.deskripsiLengkap }} />
|
Detail Info Wabah Penyakit
|
||||||
</Box>
|
</Text>
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Image src={infoWabahPenyakitState.findUnique.data.image?.link} alt="gambar" />
|
<Stack gap="sm">
|
||||||
</Box>
|
<Box>
|
||||||
<Box>
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
<Flex gap={"xs"}>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
<Button color="red" onClick={() => {
|
</Box>
|
||||||
if (infoWabahPenyakitState.findUnique.data) {
|
|
||||||
setSelectedId(infoWabahPenyakitState.findUnique.data.id)
|
<Box>
|
||||||
setModalHapus(true)
|
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||||
}
|
<Text fz="md" c="dimmed">{data.deskripsiSingkat || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi Lengkap</Text>
|
||||||
|
<Text
|
||||||
|
fz="md"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt="gambar wabah"
|
||||||
|
radius="md"
|
||||||
|
mt="xs"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
disabled={infoWabahPenyakitState.delete.loading || !infoWabahPenyakitState.findUnique.data}
|
variant="light"
|
||||||
>
|
radius="md"
|
||||||
<IconX size={20} />
|
size="md"
|
||||||
</Button>
|
>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${infoWabahPenyakitState.findUnique.data?.id}/edit`)} color="green">
|
<IconTrash size={20} />
|
||||||
<IconEdit size={20} />
|
</Button>
|
||||||
</Button>
|
</Tooltip>
|
||||||
</Flex>
|
|
||||||
</Box>
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
</Stack>
|
<Button
|
||||||
</Paper>
|
color="green"
|
||||||
) : null}
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/kesehatan/info-wabah-penyakit/${data.id}/edit`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -18,15 +29,12 @@ function CreateInfoWabahPenyakit() {
|
|||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
// Reset state di valtio
|
|
||||||
infoWabahPenyakitState.create.form = {
|
infoWabahPenyakitState.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
deskripsiSingkat: "",
|
deskripsiSingkat: "",
|
||||||
deskripsiLengkap: "",
|
deskripsiLengkap: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset state lokal
|
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
@@ -36,7 +44,6 @@ function CreateInfoWabahPenyakit() {
|
|||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload gambar dulu
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
@@ -47,34 +54,50 @@ function CreateInfoWabahPenyakit() {
|
|||||||
return toast.error("Gagal upload gambar");
|
return toast.error("Gagal upload gambar");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simpan ID gambar ke form
|
|
||||||
infoWabahPenyakitState.create.form.imageId = uploaded.id;
|
infoWabahPenyakitState.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
// Submit data berita
|
|
||||||
await infoWabahPenyakitState.create.create();
|
await infoWabahPenyakitState.create.create();
|
||||||
|
|
||||||
// Reset form setelah submit
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/info-wabah-penyakit")
|
router.push("/admin/kesehatan/info-wabah-penyakit")
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
onClick={() => router.back()}
|
||||||
<Stack gap="xs">
|
p="xs"
|
||||||
<Title order={3}>Create Info Wabah Penyakit</Title>
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Info Wabah Penyakit
|
||||||
|
</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
|
<TextInput
|
||||||
value={infoWabahPenyakitState.create.form.name}
|
value={infoWabahPenyakitState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
infoWabahPenyakitState.create.form.name = val.target.value;
|
infoWabahPenyakitState.create.form.name = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -83,7 +106,8 @@ function CreateInfoWabahPenyakit() {
|
|||||||
infoWabahPenyakitState.create.form.deskripsiSingkat = val.target.value;
|
infoWabahPenyakitState.create.form.deskripsiSingkat = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
placeholder="masukkan deskripsi"
|
placeholder="Masukkan deskripsi singkat"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
@@ -95,65 +119,74 @@ function CreateInfoWabahPenyakit() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fz="sm" fw="bold">Gambar</Text>
|
||||||
<Box>
|
<Dropzone
|
||||||
<Dropzone
|
onDrop={(files) => {
|
||||||
onDrop={(files) => {
|
const selectedFile = files[0];
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
if (selectedFile) {
|
||||||
if (selectedFile) {
|
setFile(selectedFile);
|
||||||
setFile(selectedFile);
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
}
|
||||||
}
|
}}
|
||||||
}}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
maxSize={5 * 1024 ** 2}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
accept={{ 'image/*': [] }}
|
||||||
accept={{ 'image/*': [] }}
|
>
|
||||||
>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
<Dropzone.Accept>
|
||||||
<Dropzone.Accept>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
</Dropzone.Accept>
|
||||||
</Dropzone.Accept>
|
<Dropzone.Reject>
|
||||||
<Dropzone.Reject>
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
</Dropzone.Reject>
|
||||||
</Dropzone.Reject>
|
<Dropzone.Idle>
|
||||||
<Dropzone.Idle>
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
</Dropzone.Idle>
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="xl" inline>
|
<Text size="xl" inline>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
Maksimal 5MB dan harus format gambar
|
Maksimal 5MB dan harus format gambar
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
{previewImage && (
|
||||||
{previewImage && (
|
<Box mt="sm">
|
||||||
<Box mt="sm">
|
<Image
|
||||||
<Image
|
src={previewImage}
|
||||||
src={previewImage}
|
alt="Preview"
|
||||||
alt="Preview"
|
style={{
|
||||||
style={{
|
maxWidth: '100%',
|
||||||
maxWidth: '100%',
|
maxHeight: '200px',
|
||||||
maxHeight: '200px',
|
objectFit: 'contain',
|
||||||
objectFit: 'contain',
|
borderRadius: '8px',
|
||||||
borderRadius: '8px',
|
border: '1px solid #ddd',
|
||||||
border: '1px solid #ddd',
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
<Group justify="right">
|
||||||
</Button>
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
import JudulList from '../../_com/judulList';
|
Button,
|
||||||
|
Center,
|
||||||
|
Image,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Group,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -14,9 +32,10 @@ function InfoWabahPenyakit() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Info Wabah Penyakit'
|
title='Info Wabah Penyakit'
|
||||||
placeholder='pencarian'
|
placeholder='Cari judul atau deskripsi...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -46,64 +65,99 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Judul + Tombol Tambah */}
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Info Wabah Penyakit'
|
<Title order={4}>Daftar Info Wabah Penyakit</Title>
|
||||||
href='/admin/kesehatan/info-wabah-penyakit/create'
|
<Tooltip label="Tambah Info Wabah" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowX: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
color="blue"
|
||||||
<TableThead>
|
variant="light"
|
||||||
<TableTr>
|
onClick={() => router.push('/admin/kesehatan/info-wabah-penyakit/create')}
|
||||||
<TableTh>Judul</TableTh>
|
>
|
||||||
<TableTh>Deskripsi Singkat</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Image</TableTh>
|
</Button>
|
||||||
<TableTh>Detail</TableTh>
|
</Tooltip>
|
||||||
</TableTr>
|
</Group>
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
{/* Tabel */}
|
||||||
{filteredData.map((item) => (
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Judul</TableTh>
|
||||||
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
{item.name}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text truncate fz="sm" c="dimmed">
|
||||||
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
|
{item.deskripsiSingkat}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="image" />
|
<Image w={100} src={item.image?.link} alt="image" radius="md" />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada data info wabah penyakit yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10)
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -13,11 +24,10 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function EditKontakDarurat() {
|
function EditKontakDarurat() {
|
||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const kontakDaruratState = useProxy(kontakDarurat);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
@@ -25,10 +35,10 @@ function EditKontakDarurat() {
|
|||||||
name: kontakDaruratState.edit.form.name || '',
|
name: kontakDaruratState.edit.form.name || '',
|
||||||
deskripsi: kontakDaruratState.edit.form.deskripsi || '',
|
deskripsi: kontakDaruratState.edit.form.deskripsi || '',
|
||||||
imageId: kontakDaruratState.edit.form.imageId || '',
|
imageId: kontakDaruratState.edit.form.imageId || '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProgramKesehatan = async () => {
|
const loadKontakDarurat = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
@@ -46,12 +56,12 @@ function EditKontakDarurat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program kesehatan:", error);
|
console.error("Error loading kontak darurat:", error);
|
||||||
toast.error("Gagal memuat data program kesehatan");
|
toast.error("Gagal memuat data kontak darurat");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadProgramKesehatan();
|
loadKontakDarurat();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
@@ -79,95 +89,116 @@ function EditKontakDarurat() {
|
|||||||
router.push("/admin/kesehatan/kontak-darurat");
|
router.push("/admin/kesehatan/kontak-darurat");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating kontak darurat:", error);
|
console.error("Error updating kontak darurat:", error);
|
||||||
toast.error("Gagal memuat data kontak darurat");
|
toast.error("Terjadi kesalahan saat memperbarui kontak darurat");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap={"xs"}>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Kontak Darurat</Title>
|
Edit Kontak Darurat
|
||||||
<TextInput
|
</Title>
|
||||||
value={formData.name}
|
</Group>
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
{/* Form */}
|
||||||
placeholder="masukkan judul"
|
<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
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label="Judul"
|
||||||
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
/>
|
/>
|
||||||
<Box>
|
</Box>
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
|
||||||
<EditEditor
|
<Box>
|
||||||
value={formData.deskripsi}
|
<Text fz="sm" fw="bold">Gambar</Text>
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
<Dropzone
|
||||||
/>
|
onDrop={(files) => {
|
||||||
</Box>
|
const selectedFile = files[0];
|
||||||
<Box>
|
if (selectedFile) {
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
setFile(selectedFile);
|
||||||
<Box>
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
<Dropzone
|
}
|
||||||
onDrop={(files) => {
|
}}
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
if (selectedFile) {
|
maxSize={5 * 1024 ** 2}
|
||||||
setFile(selectedFile);
|
accept={{ 'image/*': [] }}
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
>
|
||||||
}
|
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="lg" fw={500}>Drag gambar ke sini atau klik untuk pilih file</Text>
|
||||||
|
<Text size="sm" c="dimmed" mt={5}>Maksimal 5MB dan format gambar</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
/>
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -10,82 +10,129 @@ import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|||||||
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
|
||||||
function DetailKontakDarurat() {
|
function DetailKontakDarurat() {
|
||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const state = useProxy(kontakDarurat);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
kontakDaruratState.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
kontakDaruratState.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/kontak-darurat")
|
router.push("/admin/kesehatan/kontak-darurat");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!kontakDaruratState.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={400} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Kontak Darurat</Text>
|
Kembali
|
||||||
{kontakDaruratState.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Wrapper Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
withBorder
|
||||||
<Text fz={"lg"}>{kontakDaruratState.findUnique.data.name}</Text>
|
w={{ base: "100%", md: "50%" }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
radius="md"
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kontakDaruratState.findUnique.data.deskripsi }} />
|
shadow="sm"
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Stack gap="md">
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Image src={kontakDaruratState.findUnique.data.image?.link} alt="gambar" />
|
Detail Kontak Darurat
|
||||||
</Box>
|
</Text>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Button color="red" onClick={() => {
|
<Stack gap="sm">
|
||||||
if (kontakDaruratState.findUnique.data) {
|
<Box>
|
||||||
setSelectedId(kontakDaruratState.findUnique.data.id)
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
setModalHapus(true)
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
}
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text
|
||||||
|
fz="md"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt="gambar"
|
||||||
|
radius="md"
|
||||||
|
maw={300}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
disabled={kontakDaruratState.delete.loading || !kontakDaruratState.findUnique.data}
|
variant="light"
|
||||||
>
|
radius="md"
|
||||||
<IconX size={20} />
|
size="md"
|
||||||
</Button>
|
disabled={state.delete.loading}
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${kontakDaruratState.findUnique.data?.id}/edit`)} color="green">
|
>
|
||||||
<IconEdit size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
|
||||||
</Stack>
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
</Paper>
|
<Button
|
||||||
) : null}
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/kesehatan/kontak-darurat/${data.id}/edit`)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import {
|
||||||
|
IconArrowBack,
|
||||||
|
IconPhoto,
|
||||||
|
IconUpload,
|
||||||
|
IconX,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -11,18 +27,17 @@ import CreateEditor from '../../../_com/createEditor';
|
|||||||
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateKontakDarurat() {
|
function CreateKontakDarurat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const kontakDaruratState = useProxy(kontakDarurat);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
kontakDaruratState.create.form = {
|
kontakDaruratState.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
imageId: "",
|
imageId: '',
|
||||||
};
|
};
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
@@ -30,7 +45,7 @@ function CreateKontakDarurat() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn('Pilih file gambar terlebih dahulu');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
@@ -40,7 +55,7 @@ function CreateKontakDarurat() {
|
|||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
return toast.error('Gagal upload gambar');
|
||||||
}
|
}
|
||||||
|
|
||||||
kontakDaruratState.create.form.imageId = uploaded.id;
|
kontakDaruratState.create.form.imageId = uploaded.id;
|
||||||
@@ -48,27 +63,46 @@ function CreateKontakDarurat() {
|
|||||||
await kontakDaruratState.create.create();
|
await kontakDaruratState.create.create();
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/kontak-darurat")
|
router.push('/admin/kesehatan/kontak-darurat');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
p="xs"
|
||||||
<Stack gap="xs">
|
radius="md"
|
||||||
<Title order={3}>Create Kontak Darurat</Title>
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Kontak Darurat
|
||||||
|
</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
|
<TextInput
|
||||||
value={kontakDaruratState.create.form.name}
|
value={kontakDaruratState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
kontakDaruratState.create.form.name = val.target.value;
|
kontakDaruratState.create.form.name = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
@@ -80,64 +114,91 @@ function CreateKontakDarurat() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fz="sm" fw="bold">Gambar</Text>
|
||||||
<Box>
|
<Dropzone
|
||||||
<Dropzone
|
onDrop={(files) => {
|
||||||
onDrop={(files) => {
|
const selectedFile = files[0];
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
if (selectedFile) {
|
||||||
if (selectedFile) {
|
setFile(selectedFile);
|
||||||
setFile(selectedFile);
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
}
|
||||||
}
|
}}
|
||||||
}}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
maxSize={5 * 1024 ** 2}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
accept={{ 'image/*': [] }}
|
||||||
accept={{ 'image/*': [] }}
|
>
|
||||||
|
<Group
|
||||||
|
justify="center"
|
||||||
|
gap="xl"
|
||||||
|
mih={220}
|
||||||
|
style={{ pointerEvents: 'none' }}
|
||||||
>
|
>
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
<Dropzone.Accept>
|
||||||
<Dropzone.Accept>
|
<IconUpload
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
size={52}
|
||||||
</Dropzone.Accept>
|
color="var(--mantine-color-blue-6)"
|
||||||
<Dropzone.Reject>
|
stroke={1.5}
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Dropzone.Accept>
|
||||||
)}
|
<Dropzone.Reject>
|
||||||
</Box>
|
<IconX
|
||||||
|
size={52}
|
||||||
|
color="var(--mantine-color-red-6)"
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto
|
||||||
|
size={52}
|
||||||
|
color="var(--mantine-color-dimmed)"
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
<Group justify="right">
|
||||||
</Button>
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,26 +1,46 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
import JudulList from '../../_com/judulList';
|
Button,
|
||||||
import HeaderSearch from '../../_com/header';
|
Center,
|
||||||
import { useRouter } from 'next/navigation';
|
Image,
|
||||||
import { useProxy } from 'valtio/utils';
|
Pagination,
|
||||||
import kontakDarurat from '../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import kontakDarurat from '../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
|
||||||
function KontakDarurat() {
|
function KontakDarurat() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Kontak Darurat'
|
title='Kontak Darurat'
|
||||||
placeholder='pencarian'
|
placeholder='Cari judul atau deskripsi...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListKontakDarurat search={search} />
|
<ListKontakDarurat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -30,13 +50,7 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = kontakDaruratState.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = kontakDaruratState.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, search)
|
||||||
@@ -46,65 +60,97 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Judul + Tombol Tambah */}
|
||||||
<JudulList
|
<Stack mb="md" gap="sm">
|
||||||
title='List Kontak Darurat'
|
<Box display="flex" style={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||||
href='/admin/kesehatan/kontak-darurat/create'
|
<Title order={4}>Daftar Kontak Darurat</Title>
|
||||||
/>
|
<Tooltip label="Tambah Kontak Darurat" withArrow>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Button
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Judul</TableTh>
|
onClick={() => router.push('/admin/kesehatan/kontak-darurat/create')}
|
||||||
<TableTh>Deskripsi</TableTh>
|
>
|
||||||
<TableTh>Image</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Detail</TableTh>
|
</Button>
|
||||||
</TableTr>
|
</Tooltip>
|
||||||
</TableThead>
|
</Box>
|
||||||
<TableTbody>
|
</Stack>
|
||||||
{filteredData.map((item) => (
|
|
||||||
|
{/* Tabel */}
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Judul</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
{item.name}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text truncate fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="image" />
|
<Image w={100} src={item.image?.link} alt="image" radius="md" />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data kontak darurat yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -77,97 +88,120 @@ function EditPenangananDarurat() {
|
|||||||
router.push("/admin/kesehatan/penanganan-darurat");
|
router.push("/admin/kesehatan/penanganan-darurat");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating penanganan darurat:", error);
|
console.error("Error updating penanganan darurat:", error);
|
||||||
toast.error("Gagal memuat data penanganan darurat");
|
toast.error("Gagal memperbarui data penanganan darurat");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap={"xs"}>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p={"md"} w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Penanganan Darurat</Title>
|
Edit Penanganan Darurat
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<TextInput
|
{/* Form */}
|
||||||
value={formData.name}
|
<Paper
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
w={{ base: '100%', md: '50%' }}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
bg={colors['white-1']}
|
||||||
placeholder="masukkan judul"
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label="Judul"
|
||||||
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
<Text fz="sm" fw="bold">Gambar</Text>
|
||||||
<EditEditor
|
<Dropzone
|
||||||
value={formData.deskripsi}
|
onDrop={(files) => {
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
const selectedFile = files[0];
|
||||||
/>
|
if (selectedFile) {
|
||||||
</Box>
|
setFile(selectedFile);
|
||||||
<Box>
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
}
|
||||||
<Box>
|
}}
|
||||||
<Dropzone
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
onDrop={(files) => {
|
maxSize={5 * 1024 ** 2}
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
accept={{ 'image/*': [] }}
|
||||||
if (selectedFile) {
|
>
|
||||||
setFile(selectedFile);
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
<Dropzone.Accept>
|
||||||
}
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
/>
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
</Stack >
|
</Paper>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +1,134 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip, Image } from '@mantine/core';
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
import penangananDarurat from '../../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useParams } from 'next/navigation';
|
|
||||||
import { Skeleton } from '@mantine/core';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import penangananDarurat from '../../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
|
||||||
function DetailPenangananDarurat() {
|
function DetailPenangananDarurat() {
|
||||||
const penangananDaruratState = useProxy(penangananDarurat)
|
const state = useProxy(penangananDarurat);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
penangananDaruratState.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
penangananDaruratState.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/penanganan-darurat")
|
router.push("/admin/kesehatan/penanganan-darurat");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!penangananDaruratState.findUnique.data) {
|
const data = state.findUnique.data;
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Penanganan Darurat</Text>
|
Kembali
|
||||||
{penangananDaruratState.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Wrapper Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Penanganan Darurat</Text>
|
withBorder
|
||||||
<Text fz={"lg"}>{penangananDaruratState.findUnique.data.name}</Text>
|
w={{ base: "100%", md: "50%" }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
radius="md"
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: penangananDaruratState.findUnique.data.deskripsi }} />
|
shadow="sm"
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Stack gap="md">
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Image src={penangananDaruratState.findUnique.data.image?.link} alt="gambar" />
|
Detail Penanganan Darurat
|
||||||
</Box>
|
</Text>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Button color="red" onClick={() => {
|
<Stack gap="sm">
|
||||||
if (penangananDaruratState.findUnique.data) {
|
<Box>
|
||||||
setSelectedId(penangananDaruratState.findUnique.data.id)
|
<Text fz="lg" fw="bold">Nama Penanganan Darurat</Text>
|
||||||
setModalHapus(true)
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
}
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text
|
||||||
|
fz="md"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
<Image
|
||||||
|
src={data.image?.link}
|
||||||
|
alt="gambar penanganan darurat"
|
||||||
|
radius="md"
|
||||||
|
mah={250}
|
||||||
|
fit="contain"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
disabled={penangananDaruratState.delete.loading || !penangananDaruratState.findUnique.data}>
|
variant="light"
|
||||||
<IconX size={20} />
|
radius="md"
|
||||||
</Button>
|
size="md"
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${penangananDaruratState.findUnique.data?.id}/edit`)} color="green">
|
>
|
||||||
<IconEdit size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
|
||||||
</Stack>
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
</Paper>
|
<Button
|
||||||
) : null}
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/kesehatan/penanganan-darurat/${data.id}/edit`)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import {
|
||||||
|
IconArrowBack,
|
||||||
|
IconPhoto,
|
||||||
|
IconUpload,
|
||||||
|
IconX,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -11,18 +27,17 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import penangananDarurat from '../../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
import penangananDarurat from '../../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
|
||||||
|
|
||||||
function CreatePenangananDarurat() {
|
function CreatePenangananDarurat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const penangananDaruratState = useProxy(penangananDarurat)
|
const penangananDaruratState = useProxy(penangananDarurat);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
penangananDaruratState.create.form = {
|
penangananDaruratState.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
imageId: "",
|
imageId: '',
|
||||||
};
|
};
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
@@ -30,7 +45,7 @@ function CreatePenangananDarurat() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn('Pilih file gambar terlebih dahulu');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
@@ -40,7 +55,7 @@ function CreatePenangananDarurat() {
|
|||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
return toast.error('Gagal upload gambar');
|
||||||
}
|
}
|
||||||
|
|
||||||
penangananDaruratState.create.form.imageId = uploaded.id;
|
penangananDaruratState.create.form.imageId = uploaded.id;
|
||||||
@@ -48,31 +63,52 @@ function CreatePenangananDarurat() {
|
|||||||
await penangananDaruratState.create.create();
|
await penangananDaruratState.create.create();
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/penanganan-darurat")
|
router.push('/admin/kesehatan/penanganan-darurat');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
p="xs"
|
||||||
<Stack gap="xs">
|
radius="md"
|
||||||
<Title order={3}>Create Penanganan Darurat</Title>
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Penanganan Darurat
|
||||||
|
</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">
|
||||||
|
{/* Judul */}
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label={<Text fw="bold" fz="sm">Judul</Text>}
|
||||||
|
placeholder="Masukkan judul"
|
||||||
value={penangananDaruratState.create.form.name}
|
value={penangananDaruratState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
penangananDaruratState.create.form.name = val.target.value;
|
penangananDaruratState.create.form.name = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
required
|
||||||
placeholder="masukkan judul"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Deskripsi */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
<Text fw="bold" fz="sm">Deskripsi</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={penangananDaruratState.create.form.deskripsi}
|
value={penangananDaruratState.create.form.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
@@ -80,30 +116,49 @@ function CreatePenangananDarurat() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Upload Gambar */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm">Gambar</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onDrop={(files) => {
|
onDrop={(files) => {
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
const selectedFile = files[0];
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
setFile(selectedFile);
|
setFile(selectedFile);
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
maxSize={5 * 1024 ** 2}
|
||||||
accept={{ 'image/*': [] }}
|
accept={{ 'image/*': [] }}
|
||||||
>
|
>
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
<Group
|
||||||
|
justify="center"
|
||||||
|
gap="xl"
|
||||||
|
mih={220}
|
||||||
|
style={{ pointerEvents: 'none' }}
|
||||||
|
>
|
||||||
<Dropzone.Accept>
|
<Dropzone.Accept>
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
<IconUpload
|
||||||
|
size={52}
|
||||||
|
color="var(--mantine-color-blue-6)"
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
</Dropzone.Accept>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Reject>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconX
|
||||||
|
size={52}
|
||||||
|
color="var(--mantine-color-red-6)"
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
</Dropzone.Reject>
|
</Dropzone.Reject>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Idle>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconPhoto
|
||||||
|
size={52}
|
||||||
|
color="var(--mantine-color-dimmed)"
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
</Dropzone.Idle>
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -117,7 +172,6 @@ function CreatePenangananDarurat() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
{previewImage && (
|
||||||
<Box mt="sm">
|
<Box mt="sm">
|
||||||
<Image
|
<Image
|
||||||
@@ -133,12 +187,24 @@ function CreatePenangananDarurat() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
{/* Button Simpan */}
|
||||||
</Button>
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
import JudulList from '../../_com/judulList';
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -14,100 +32,135 @@ function PenangananDarurat() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='PenangananDarurat'
|
title='Penanganan Darurat'
|
||||||
placeholder='pencarian'
|
placeholder='Cari judul atau deskripsi...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListPenangananDarurat search={search} />
|
<ListPenangananDarurat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListPenangananDarurat({ search }: { search: string }) {
|
function ListPenangananDarurat({ search }: { search: string }) {
|
||||||
const penangananDaruratState = useProxy(penangananDarurat)
|
const state = useProxy(penangananDarurat);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = penangananDaruratState.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Judul + Tombol Tambah */}
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Penanganan Darurat'
|
<Title order={4}>Daftar Penanganan Darurat</Title>
|
||||||
href='/admin/kesehatan/penanganan-darurat/create'
|
<Tooltip label="Tambah Penanganan Darurat" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowX: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
color="blue"
|
||||||
<TableThead>
|
variant="light"
|
||||||
<TableTr>
|
onClick={() => router.push('/admin/kesehatan/penanganan-darurat/create')}
|
||||||
<TableTh>Judul</TableTh>
|
>
|
||||||
<TableTh>Deskripsi</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Image</TableTh>
|
</Button>
|
||||||
<TableTh>Detail</TableTh>
|
</Tooltip>
|
||||||
</TableTr>
|
</Group>
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
{/* Tabel */}
|
||||||
{filteredData.map((item) => (
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Judul</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Gambar</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
{item.name}
|
||||||
</Box></TableTd>
|
</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text
|
||||||
<Text lineClamp={1} truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
fz="sm"
|
||||||
</Box>
|
c="dimmed"
|
||||||
|
truncate
|
||||||
|
lineClamp={1}
|
||||||
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
|
/>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="image" />
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data penanganan darurat</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PenangananDarurat;
|
export default PenangananDarurat;
|
||||||
|
|||||||
@@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
|
import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -14,185 +25,231 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
|
|
||||||
function EditPosyandu() {
|
function EditPosyandu() {
|
||||||
const statePosyandu = useProxy(posyandustate)
|
const statePosyandu = useProxy(posyandustate);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
|
||||||
const [file, setFile] = useState<File | null>(null);
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: statePosyandu.edit.form.name || '',
|
|
||||||
nomor: statePosyandu.edit.form.nomor || '',
|
|
||||||
deskripsi: statePosyandu.edit.form.deskripsi || '',
|
|
||||||
imageId: statePosyandu.edit.form.imageId || '',
|
|
||||||
jadwalPelayanan: statePosyandu.edit.form.jadwalPelayanan || '',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const loadPosyandu = async () => {
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const id = params?.id as string;
|
const [formData, setFormData] = useState({
|
||||||
if (!id) return;
|
name: statePosyandu.edit.form.name || '',
|
||||||
|
nomor: statePosyandu.edit.form.nomor || '',
|
||||||
|
deskripsi: statePosyandu.edit.form.deskripsi || '',
|
||||||
|
imageId: statePosyandu.edit.form.imageId || '',
|
||||||
|
jadwalPelayanan: statePosyandu.edit.form.jadwalPelayanan || '',
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await statePosyandu.edit.load(id);
|
|
||||||
if (data) {
|
|
||||||
setFormData({
|
|
||||||
name: data.name || '',
|
|
||||||
nomor: data.nomor || '',
|
|
||||||
deskripsi: data.deskripsi || '',
|
|
||||||
imageId: data.imageId || '',
|
|
||||||
jadwalPelayanan: data.jadwalPelayanan || '',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data?.image?.link) {
|
useEffect(() => {
|
||||||
setPreviewImage(data.image.link);
|
const loadPosyandu = async () => {
|
||||||
}
|
const id = params?.id as string;
|
||||||
}
|
if (!id) return;
|
||||||
} catch (error) {
|
|
||||||
console.error("Error loading posyandu:", error);
|
|
||||||
toast.error("Gagal memuat data posyandu");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadPosyandu();
|
|
||||||
}, [params?.id])
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
statePosyandu.edit.form = {
|
|
||||||
...statePosyandu.edit.form,
|
|
||||||
name: formData.name,
|
|
||||||
nomor: formData.nomor,
|
|
||||||
deskripsi: formData.deskripsi,
|
|
||||||
imageId: formData.imageId,
|
|
||||||
jadwalPelayanan: formData.jadwalPelayanan,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file) {
|
try {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const data = await statePosyandu.edit.load(id);
|
||||||
const uploaded = res.data?.data;
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
nomor: data.nomor || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
jadwalPelayanan: data.jadwalPelayanan || '',
|
||||||
|
});
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
statePosyandu.edit.form.imageId = uploaded.id;
|
if (data?.image?.link) {
|
||||||
}
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading posyandu:", error);
|
||||||
|
toast.error("Gagal memuat data posyandu");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadPosyandu();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
await statePosyandu.edit.update();
|
|
||||||
toast.success("Posyandu berhasil diperbarui!");
|
|
||||||
router.push("/admin/kesehatan/posyandu");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating posyandu:", error);
|
|
||||||
toast.error("Gagal memuat data posyandu");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const handleSubmit = async () => {
|
||||||
<Box>
|
try {
|
||||||
<Box mb={10}>
|
statePosyandu.edit.form = {
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
...statePosyandu.edit.form,
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
...formData,
|
||||||
</Button>
|
};
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Edit Posyandu</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Box>
|
|
||||||
<Dropzone
|
|
||||||
onDrop={(files) => {
|
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
|
||||||
if (selectedFile) {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
if (file) {
|
||||||
<Text size="xl" inline>
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
const uploaded = res.data?.data;
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
if (!uploaded?.id) {
|
||||||
</Box>
|
return toast.error("Gagal upload gambar");
|
||||||
<TextInput
|
}
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
statePosyandu.edit.form.imageId = uploaded.id;
|
||||||
placeholder='Masukkan nama posyandu'
|
}
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
value={formData.nomor}
|
await statePosyandu.edit.update();
|
||||||
onChange={(e) => setFormData({ ...formData, nomor: e.target.value })}
|
toast.success("Posyandu berhasil diperbarui!");
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nomor Posyandu</Text>}
|
router.push("/admin/kesehatan/posyandu");
|
||||||
placeholder='Masukkan nomor posyandu'
|
} catch (error) {
|
||||||
/>
|
console.error("Error updating posyandu:", error);
|
||||||
<Box>
|
toast.error("Terjadi kesalahan saat memperbarui posyandu");
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Posyandu</Text>
|
}
|
||||||
<EditEditor
|
};
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(htmlContent) => {
|
|
||||||
setFormData({ ...formData, deskripsi: htmlContent });
|
return (
|
||||||
statePosyandu.edit.form.deskripsi = htmlContent;
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
}}
|
{/* Tombol Back */}
|
||||||
/>
|
<Group mb="md">
|
||||||
</Box>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<Box>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<Text fw={"bold"} fz={"sm"}>Jadwal Pelayanan</Text>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<EditEditor
|
</Button>
|
||||||
value={formData.jadwalPelayanan}
|
</Tooltip>
|
||||||
onChange={(htmlContent) => {
|
<Title order={4} ml="sm" c="dark">
|
||||||
setFormData({ ...formData, jadwalPelayanan: htmlContent });
|
Edit Posyandu
|
||||||
statePosyandu.edit.form.jadwalPelayanan = htmlContent;
|
</Title>
|
||||||
}}
|
</Group>
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
{/* Card utama */}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
<Paper
|
||||||
</Group>
|
w={{ base: '100%', md: '50%' }}
|
||||||
</Stack>
|
bg={colors['white-1']}
|
||||||
</Paper>
|
p="lg"
|
||||||
</Box>
|
radius="md"
|
||||||
);
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Upload Gambar */}
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Gambar Posyandu
|
||||||
|
</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0];
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
radius="md"
|
||||||
|
p="xl"
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview Gambar"
|
||||||
|
radius="md"
|
||||||
|
style={{
|
||||||
|
maxHeight: 220,
|
||||||
|
objectFit: 'contain',
|
||||||
|
border: `1px solid ${colors['blue-button']}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Input Form */}
|
||||||
|
<TextInput
|
||||||
|
label="Nama Posyandu"
|
||||||
|
placeholder="Masukkan nama posyandu"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Nomor Posyandu"
|
||||||
|
placeholder="Masukkan nomor posyandu"
|
||||||
|
value={formData.nomor}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nomor: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>Deskripsi Posyandu</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData({ ...formData, deskripsi: htmlContent });
|
||||||
|
statePosyandu.edit.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>Jadwal Pelayanan</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.jadwalPelayanan}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData({ ...formData, jadwalPelayanan: htmlContent });
|
||||||
|
statePosyandu.edit.form.jadwalPelayanan = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Tombol Submit */}
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditPosyandu;
|
|
||||||
|
export default EditPosyandu;
|
||||||
@@ -1,105 +1,175 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
import { IconArrowBack, IconTrash, IconEdit } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import posyandustate from '../../../_state/kesehatan/posyandu/posyandu';
|
import posyanduState from '../../../_state/kesehatan/posyandu/posyandu';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailPosyandu() {
|
function DetailPosyandu() {
|
||||||
const statePosyandu = useProxy(posyandustate)
|
const statePosyandu = useProxy(posyanduState);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
statePosyandu.findUnique.load(params?.id as string)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
useShallowEffect(() => {
|
||||||
if (selectedId) {
|
statePosyandu.findUnique.load(params?.id as string);
|
||||||
statePosyandu.delete.byId(selectedId)
|
}, []);
|
||||||
setModalHapus(false)
|
|
||||||
setSelectedId(null)
|
|
||||||
router.push("/admin/kesehatan/posyandu")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!statePosyandu.findUnique.data) {
|
|
||||||
return (
|
|
||||||
<Stack py={10}>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const handleHapus = () => {
|
||||||
<Box>
|
if (selectedId) {
|
||||||
<Box mb={10}>
|
statePosyandu.delete.byId(selectedId);
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
setModalHapus(false);
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
setSelectedId(null);
|
||||||
</Button>
|
router.push("/admin/kesehatan/posyandu");
|
||||||
</Box>
|
}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
};
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Posyandu</Text>
|
|
||||||
{statePosyandu.findUnique.data ? (
|
|
||||||
<Paper key={statePosyandu.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Posyandu</Text>
|
|
||||||
<Text fz={"lg"}>{statePosyandu.findUnique.data.name}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nomor Posyandu</Text>
|
|
||||||
<Text fz={"lg"}>{statePosyandu.findUnique.data.nomor}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Posyandu</Text>
|
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: statePosyandu.findUnique.data.deskripsi }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Jadwal Pelayanan</Text>
|
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: statePosyandu.findUnique.data.jadwalPelayanan }} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={statePosyandu.findUnique.data.image?.link} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button onClick={() => {
|
|
||||||
if (statePosyandu.findUnique.data) {
|
|
||||||
setSelectedId(statePosyandu.findUnique.data.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}
|
|
||||||
}} color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/posyandu/${statePosyandu.findUnique.data?.id}/edit`)} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
if (!statePosyandu.findUnique.data) {
|
||||||
onClose={() => setModalHapus(false)}
|
return (
|
||||||
onConfirm={handleHapus}
|
<Stack py={10}>
|
||||||
text="Apakah anda yakin ingin menghapus posyandu ini?"
|
<Skeleton height={500} radius="md" />
|
||||||
/>
|
</Stack>
|
||||||
</Box>
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const data = statePosyandu.findUnique.data;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
{/* Tombol kembali */}
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Card utama */}
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: "100%", md: "60%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Posyandu
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nama Posyandu</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nomor Posyandu</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.nomor || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text
|
||||||
|
fz="md"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Jadwal Pelayanan</Text>
|
||||||
|
<Text
|
||||||
|
fz="md"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt={data.name || 'Gambar Posyandu'}
|
||||||
|
w={200}
|
||||||
|
h={200}
|
||||||
|
radius="md"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Aksi */}
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Posyandu" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
<Tooltip label="Edit Posyandu" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/posyandu/${data.id}/edit`)}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Modal konfirmasi hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah Anda yakin ingin menghapus posyandu ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailPosyandu;
|
|
||||||
|
export default DetailPosyandu;
|
||||||
@@ -1,7 +1,18 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -11,159 +22,193 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import posyandustate from '../../../_state/kesehatan/posyandu/posyandu';
|
import posyandustate from '../../../_state/kesehatan/posyandu/posyandu';
|
||||||
|
|
||||||
|
|
||||||
function CreatePosyandu() {
|
function CreatePosyandu() {
|
||||||
const statePosyandu = useProxy(posyandustate)
|
const statePosyandu = useProxy(posyandustate);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
statePosyandu.create.form = {
|
|
||||||
name: "",
|
|
||||||
nomor: "",
|
|
||||||
deskripsi: "",
|
|
||||||
imageId: "",
|
|
||||||
jadwalPelayanan: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
setFile(null);
|
|
||||||
setPreviewImage(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!file) {
|
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload gambar dulu
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
|
||||||
file,
|
|
||||||
name: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
statePosyandu.create.form.imageId = uploaded.id;
|
|
||||||
|
|
||||||
await statePosyandu.create.create();
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/kesehatan/posyandu")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
statePosyandu.create.form = {
|
||||||
|
name: '',
|
||||||
|
nomor: '',
|
||||||
|
deskripsi: '',
|
||||||
|
imageId: '',
|
||||||
|
jadwalPelayanan: '',
|
||||||
|
};
|
||||||
|
setFile(null);
|
||||||
|
setPreviewImage(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
const handleSubmit = async () => {
|
||||||
<Stack gap={"xs"}>
|
if (!file) {
|
||||||
<Title order={4}>Create Posyandu</Title>
|
return toast.warn('Silakan pilih file gambar terlebih dahulu');
|
||||||
<Box>
|
}
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Box>
|
|
||||||
<Dropzone
|
|
||||||
onDrop={(files) => {
|
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
|
||||||
if (selectedFile) {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
// Upload gambar dulu
|
||||||
{previewImage && (
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
<Box mt="sm">
|
file,
|
||||||
<Image
|
name: file.name,
|
||||||
src={previewImage}
|
});
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
const uploaded = res.data?.data;
|
||||||
<TextInput
|
if (!uploaded?.id) {
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
return toast.error('Gagal upload gambar');
|
||||||
placeholder='Masukkan nama posyandu'
|
}
|
||||||
value={statePosyandu.create.form.name}
|
|
||||||
onChange={(e) => {
|
|
||||||
statePosyandu.create.form.name = e.target.value;
|
statePosyandu.create.form.imageId = uploaded.id;
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
await statePosyandu.create.create();
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nomor Posyandu</Text>}
|
|
||||||
placeholder='Masukkan nomor posyandu'
|
|
||||||
value={statePosyandu.create.form.nomor}
|
resetForm();
|
||||||
onChange={(e) => {
|
router.push('/admin/kesehatan/posyandu');
|
||||||
statePosyandu.create.form.nomor = e.target.value;
|
};
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box>
|
return (
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Posyandu</Text>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<CreateEditor
|
{/* Header */}
|
||||||
value={statePosyandu.create.form.deskripsi}
|
<Group mb="md">
|
||||||
onChange={(htmlContent) => {
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
statePosyandu.create.form.deskripsi = htmlContent;
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
}}
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
/>
|
</Button>
|
||||||
</Box>
|
</Tooltip>
|
||||||
<Box>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Text fw={"bold"} fz={"sm"}>Jadwal Pelayanan</Text>
|
Tambah Posyandu
|
||||||
<CreateEditor
|
</Title>
|
||||||
value={statePosyandu.create.form.jadwalPelayanan}
|
</Group>
|
||||||
onChange={(htmlContent) => {
|
|
||||||
statePosyandu.create.form.jadwalPelayanan = htmlContent;
|
|
||||||
}}
|
<Paper
|
||||||
/>
|
w={{ base: '100%', md: '50%' }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Group>
|
p="lg"
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
radius="md"
|
||||||
</Group>
|
shadow="sm"
|
||||||
</Stack>
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
</Paper>
|
>
|
||||||
</Box>
|
<Stack gap="md">
|
||||||
);
|
{/* Upload Gambar */}
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Gambar Posyandu
|
||||||
|
</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0];
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
radius="md"
|
||||||
|
p="xl"
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
</Group>
|
||||||
|
<Text ta="center" mt="sm" size="sm" color="dimmed">
|
||||||
|
Seret gambar atau klik untuk memilih file (maks 5MB)
|
||||||
|
</Text>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview Gambar"
|
||||||
|
radius="md"
|
||||||
|
style={{
|
||||||
|
maxHeight: 200,
|
||||||
|
objectFit: 'contain',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Input Form */}
|
||||||
|
<TextInput
|
||||||
|
label="Nama Posyandu"
|
||||||
|
placeholder="Masukkan nama posyandu"
|
||||||
|
value={statePosyandu.create.form.name || ''}
|
||||||
|
onChange={(e) => (statePosyandu.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Telepon Posyandu"
|
||||||
|
placeholder="Masukkan telepon posyandu"
|
||||||
|
value={statePosyandu.create.form.nomor || ''}
|
||||||
|
onChange={(e) => (statePosyandu.create.form.nomor = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi Posyandu
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={statePosyandu.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
statePosyandu.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Jadwal Pelayanan
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={statePosyandu.create.form.jadwalPelayanan}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
statePosyandu.create.form.jadwalPelayanan = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Button */}
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreatePosyandu;
|
|
||||||
|
export default CreatePosyandu;
|
||||||
@@ -1,114 +1,170 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
import HeaderSearch from '../../_com/header';
|
Button,
|
||||||
import JudulList from '../../_com/judulList';
|
Center,
|
||||||
import { useRouter } from 'next/navigation';
|
Group,
|
||||||
import { useProxy } from 'valtio/utils';
|
Pagination,
|
||||||
import posyandustate from '../../_state/kesehatan/posyandu/posyandu';
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import posyandustate from '../../_state/kesehatan/posyandu/posyandu';
|
||||||
|
|
||||||
|
|
||||||
function Posyandu() {
|
function Posyandu() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Posyandu'
|
title='Posyandu'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama posyandu atau nomor...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListPosyandu search={search} />
|
<ListPosyandu search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ListPosyandu({ search }: { search: string }) {
|
function ListPosyandu({ search }: { search: string }) {
|
||||||
const statePosyandu = useProxy(posyandustate)
|
const statePosyandu = useProxy(posyandustate)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = statePosyandu.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
const {
|
||||||
load(page, 10, search)
|
data,
|
||||||
}, [page, search])
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = statePosyandu.findMany;
|
||||||
|
|
||||||
const filteredData = data || [];
|
|
||||||
|
|
||||||
if (loading || !data) {
|
useShallowEffect(() => {
|
||||||
return (
|
load(page, 10, search)
|
||||||
<Box py={10}>
|
}, [page, search])
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
const filteredData = data || [];
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<JudulList
|
|
||||||
title='List Posyandu'
|
if (loading || !data) {
|
||||||
href='/admin/kesehatan/posyandu/create'
|
return (
|
||||||
/>
|
<Stack py={10}>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Skeleton height={600} radius="md" />
|
||||||
<Table striped withTableBorder withRowBorders>
|
</Stack>
|
||||||
<TableThead>
|
)
|
||||||
<TableTr>
|
}
|
||||||
<TableTh>Nama Posyandu</TableTh>
|
|
||||||
<TableTh>Nomor Posyandu</TableTh>
|
|
||||||
<TableTh>Deskripsi</TableTh>
|
return (
|
||||||
<TableTh>Detail</TableTh>
|
<Box py={10}>
|
||||||
</TableTr>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
</TableThead>
|
<Group justify="space-between" mb="md">
|
||||||
<TableTbody>
|
<Title order={4}>Daftar Posyandu</Title>
|
||||||
{filteredData.map((item) => (
|
<Tooltip label="Tambah Posyandu" withArrow>
|
||||||
<TableTr key={item.id}>
|
<Button
|
||||||
<TableTd>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Box w={100}>
|
color="blue"
|
||||||
<Text truncate="end" lineClamp={1} fz={"sm"}>{item.name}</Text>
|
variant="light"
|
||||||
</Box>
|
onClick={() => router.push('/admin/kesehatan/posyandu/create')}
|
||||||
</TableTd>
|
>
|
||||||
<TableTd>
|
Tambah Baru
|
||||||
<Box w={100}>
|
</Button>
|
||||||
<Text truncate="end" lineClamp={1} fz={"sm"}>{item.nomor}</Text>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</TableTd>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<TableTd>
|
<Table highlightOnHover>
|
||||||
<Box w={100}>
|
<TableThead>
|
||||||
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<TableTr>
|
||||||
</Box>
|
<TableTh style={{ width: '25%' }}>Nama Posyandu</TableTh>
|
||||||
</TableTd>
|
<TableTh style={{ width: '20%' }}>Nomor Posyandu</TableTh>
|
||||||
<TableTd>
|
<TableTh style={{ width: '30%' }}>Deskripsi</TableTh>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)}>
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
<IconDeviceImac size={20} />
|
</TableTr>
|
||||||
</Button>
|
</TableThead>
|
||||||
</TableTd>
|
<TableTbody>
|
||||||
</TableTr>
|
{filteredData.length > 0 ? (
|
||||||
))}
|
filteredData.map((item) => (
|
||||||
</TableTbody>
|
<TableTr key={item.id}>
|
||||||
</Table>
|
<TableTd style={{ width: '25%' }}>
|
||||||
</Box>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Paper>
|
{item.name}
|
||||||
<Center>
|
</Text>
|
||||||
<Pagination
|
</TableTd>
|
||||||
value={page}
|
<TableTd style={{ width: '20%' }}>
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
<Text truncate fz="sm" c="dimmed">
|
||||||
total={totalPages}
|
{item.nomor || '-'}
|
||||||
mt="md"
|
</Text>
|
||||||
mb="md"
|
</TableTd>
|
||||||
/>
|
<TableTd style={{ width: '30%' }}>
|
||||||
</Center>
|
<Text
|
||||||
</Box>
|
truncate
|
||||||
);
|
fz="sm"
|
||||||
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
|
/>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ width: '15%' }}>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data posyandu yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Posyandu;
|
|
||||||
|
export default Posyandu;
|
||||||
@@ -4,7 +4,18 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -12,11 +23,10 @@ import { useEffect, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function EditProgramKesehatan() {
|
function EditProgramKesehatan() {
|
||||||
const programKesehatanState = useProxy(programKesehatan)
|
const programKesehatanState = useProxy(programKesehatan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
@@ -25,7 +35,7 @@ function EditProgramKesehatan() {
|
|||||||
deskripsiSingkat: programKesehatanState.edit.form.deskripsiSingkat || '',
|
deskripsiSingkat: programKesehatanState.edit.form.deskripsiSingkat || '',
|
||||||
deskripsi: programKesehatanState.edit.form.deskripsi || '',
|
deskripsi: programKesehatanState.edit.form.deskripsi || '',
|
||||||
imageId: programKesehatanState.edit.form.imageId || '',
|
imageId: programKesehatanState.edit.form.imageId || '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProgramKesehatan = async () => {
|
const loadProgramKesehatan = async () => {
|
||||||
@@ -47,8 +57,8 @@ function EditProgramKesehatan() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program kesehatan:", error);
|
console.error('Error loading program kesehatan:', error);
|
||||||
toast.error("Gagal memuat data program kesehatan");
|
toast.error('Gagal memuat data program kesehatan');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,114 +80,143 @@ function EditProgramKesehatan() {
|
|||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
return toast.error('Gagal upload gambar');
|
||||||
}
|
}
|
||||||
|
|
||||||
programKesehatanState.edit.form.imageId = uploaded.id;
|
programKesehatanState.edit.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await programKesehatanState.edit.update();
|
await programKesehatanState.edit.update();
|
||||||
toast.success("Program kesehatan berhasil diperbarui!");
|
toast.success('Program kesehatan berhasil diperbarui!');
|
||||||
router.push("/admin/kesehatan/program-kesehatan");
|
router.push('/admin/kesehatan/program-kesehatan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating program kesehatan:", error);
|
console.error('Error updating program kesehatan:', error);
|
||||||
toast.error("Gagal memuat data program kesehatan");
|
toast.error('Terjadi kesalahan saat memperbarui program kesehatan');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header dengan tombol back */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap={"xs"}>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Program Kesehatan</Title>
|
Edit Program Kesehatan
|
||||||
<TextInput
|
</Title>
|
||||||
value={formData.name}
|
</Group>
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
|
||||||
placeholder="masukkan judul"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
{/* Card Form */}
|
||||||
value={formData.deskripsiSingkat}
|
<Paper
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
w={{ base: '100%', md: '50%' }}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
bg={colors['white-1']}
|
||||||
placeholder="masukkan deskripsi"
|
p="lg"
|
||||||
/>
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label="Judul"
|
||||||
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<Box>
|
<TextInput
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
value={formData.deskripsiSingkat}
|
||||||
<EditEditor
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
value={formData.deskripsi}
|
label="Deskripsi Singkat"
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
placeholder="Masukkan deskripsi singkat"
|
||||||
/>
|
required
|
||||||
</Box>
|
/>
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Box>
|
||||||
<Box>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
<Dropzone
|
Deskripsi
|
||||||
onDrop={(files) => {
|
</Text>
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
<EditEditor
|
||||||
if (selectedFile) {
|
value={formData.deskripsi}
|
||||||
setFile(selectedFile);
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
/>
|
||||||
}
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
|
Gambar
|
||||||
|
</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0];
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
/>
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip, Image } from '@mantine/core';
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import programKesehatan from '../../../_state/kesehatan/program-kesehatan/programKesehatan';
|
import programKesehatan from '../../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
@@ -10,82 +10,116 @@ import { useShallowEffect } from '@mantine/hooks';
|
|||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
function DetailProgramKesehatan() {
|
function DetailProgramKesehatan() {
|
||||||
const programKesehatanState = useProxy(programKesehatan)
|
const state = useProxy(programKesehatan);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
programKesehatanState.findUnique.load(params?.id as string)
|
state.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
programKesehatanState.delete.byId(selectedId)
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/program-kesehatan")
|
router.push("/admin/kesehatan/program-kesehatan");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!programKesehatanState.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={400} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol kembali */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Program Kesehatan</Text>
|
Kembali
|
||||||
{programKesehatanState.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Box>
|
withBorder
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Text fz={"lg"}>{programKesehatanState.findUnique.data.name}</Text>
|
bg={colors['white-1']}
|
||||||
</Box>
|
p="lg"
|
||||||
<Box>
|
radius="md"
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Singkat</Text>
|
shadow="sm"
|
||||||
<Text fz={"lg"}>{programKesehatanState.findUnique.data.deskripsiSingkat}</Text>
|
>
|
||||||
</Box>
|
<Stack gap="md">
|
||||||
<Box>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
Detail Program Kesehatan
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: programKesehatanState.findUnique.data.deskripsi }} />
|
</Text>
|
||||||
</Box>
|
|
||||||
<Box>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Stack gap="sm">
|
||||||
<Image src={programKesehatanState.findUnique.data.image?.link} alt="gambar" />
|
<Box>
|
||||||
</Box>
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
|
||||||
<Flex gap={"xs"}>
|
</Box>
|
||||||
<Button color="red" onClick={() => {
|
|
||||||
if (programKesehatanState.findUnique.data) {
|
<Box>
|
||||||
setSelectedId(programKesehatanState.findUnique.data.id)
|
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||||
setModalHapus(true)
|
<Text fz="md" c="dimmed">{data?.deskripsiSingkat || '-'}</Text>
|
||||||
}
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data?.image?.link ? (
|
||||||
|
<Image src={data.image.link} alt="gambar program kesehatan" radius="md" />
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
disabled={programKesehatanState.delete.loading || !programKesehatanState.findUnique.data}
|
variant="light"
|
||||||
>
|
radius="md"
|
||||||
<IconX size={20} />
|
size="md"
|
||||||
</Button>
|
>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${programKesehatanState.findUnique.data?.id}/edit`)} color="green">
|
<IconTrash size={20} />
|
||||||
<IconEdit size={20} />
|
</Button>
|
||||||
</Button>
|
</Tooltip>
|
||||||
</Flex>
|
|
||||||
</Box>
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
</Stack>
|
<Button
|
||||||
</Paper>
|
color="green"
|
||||||
) : null}
|
onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${data?.id}/edit`)}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -94,7 +128,7 @@ function DetailProgramKesehatan() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus program kesehatan ini?"
|
text="Apakah Anda yakin ingin menghapus program kesehatan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -13,30 +24,32 @@ import { Dropzone } from '@mantine/dropzone';
|
|||||||
|
|
||||||
function CreateProgramKesehatan() {
|
function CreateProgramKesehatan() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const programKesehatanState = useProxy(programKesehatan)
|
const programKesehatanState = useProxy(programKesehatan);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
// Reset state di valtio
|
|
||||||
programKesehatanState.create.form = {
|
programKesehatanState.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
deskripsiSingkat: "",
|
deskripsiSingkat: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset state lokal
|
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!programKesehatanState.create.form.name) {
|
||||||
|
return toast.warn("Judul wajib diisi");
|
||||||
|
}
|
||||||
|
if (!programKesehatanState.create.form.deskripsiSingkat) {
|
||||||
|
return toast.warn("Deskripsi singkat wajib diisi");
|
||||||
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload gambar dulu
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
@@ -47,34 +60,45 @@ function CreateProgramKesehatan() {
|
|||||||
return toast.error("Gagal upload gambar");
|
return toast.error("Gagal upload gambar");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simpan ID gambar ke form
|
|
||||||
programKesehatanState.create.form.imageId = uploaded.id;
|
programKesehatanState.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
// Submit data berita
|
|
||||||
await programKesehatanState.create.create();
|
await programKesehatanState.create.create();
|
||||||
|
|
||||||
// Reset form setelah submit
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/program-kesehatan")
|
router.push("/admin/kesehatan/program-kesehatan");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Button>
|
||||||
<Stack gap="xs">
|
</Tooltip>
|
||||||
<Title order={3}>Create Program Kesehatan</Title>
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Program Kesehatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<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
|
<TextInput
|
||||||
value={programKesehatanState.create.form.name}
|
value={programKesehatanState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
programKesehatanState.create.form.name = val.target.value;
|
programKesehatanState.create.form.name = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label="Judul"
|
||||||
placeholder="masukkan judul"
|
placeholder="Masukkan judul"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -82,12 +106,15 @@ function CreateProgramKesehatan() {
|
|||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
programKesehatanState.create.form.deskripsiSingkat = val.target.value;
|
programKesehatanState.create.form.deskripsiSingkat = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
label="Deskripsi Singkat"
|
||||||
placeholder="masukkan deskripsi"
|
placeholder="Masukkan deskripsi singkat"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
<Title order={6} mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Title>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={programKesehatanState.create.form.deskripsi}
|
value={programKesehatanState.create.form.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
@@ -95,64 +122,76 @@ function CreateProgramKesehatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Title order={6} mb={6}>
|
||||||
<Box>
|
Gambar
|
||||||
<Dropzone
|
</Title>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
>
|
||||||
<Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
<Dropzone.Accept>
|
||||||
</Dropzone.Accept>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
<Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<Dropzone.Reject>
|
||||||
</Dropzone.Reject>
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
<Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<Dropzone.Idle>
|
||||||
</Dropzone.Idle>
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="xl" inline>
|
<Text size="xl" inline>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
Maksimal 5MB dan harus format gambar
|
Maksimal 5MB dan harus format gambar
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
{previewImage && (
|
||||||
{previewImage && (
|
<Box mt="sm">
|
||||||
<Box mt="sm">
|
<Image
|
||||||
<Image
|
src={previewImage}
|
||||||
src={previewImage}
|
alt="Preview"
|
||||||
alt="Preview"
|
style={{
|
||||||
style={{
|
maxWidth: '100%',
|
||||||
maxWidth: '100%',
|
maxHeight: '200px',
|
||||||
maxHeight: '200px',
|
objectFit: 'contain',
|
||||||
objectFit: 'contain',
|
borderRadius: '8px',
|
||||||
borderRadius: '8px',
|
border: '1px solid #ddd',
|
||||||
border: '1px solid #ddd',
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
<Group justify="right">
|
||||||
</Button>
|
<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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
import JudulList from '../../_com/judulList';
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import programKesehatan from '../../_state/kesehatan/program-kesehatan/programKesehatan';
|
import programKesehatan from '../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
@@ -14,9 +32,10 @@ function ProgramKesehatan() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Header dengan Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Program Kesehatan'
|
title='Program Kesehatan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari program kesehatan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,88 +46,112 @@ function ProgramKesehatan() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListProgramKesehatan({ search }: { search: string }) {
|
function ListProgramKesehatan({ search }: { search: string }) {
|
||||||
const programKesehatanState = useProxy(programKesehatan)
|
const stateProgram = useProxy(programKesehatan);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stateProgram.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = programKesehatanState.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
{/* Header List + Tombol Tambah */}
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Program Kesehatan'
|
<Title order={4}>Daftar Program Kesehatan</Title>
|
||||||
href='/admin/kesehatan/program-kesehatan/create'
|
<Tooltip label="Tambah Program Kesehatan" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowX: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
color="blue"
|
||||||
<TableThead>
|
variant="light"
|
||||||
<TableTr>
|
onClick={() => router.push('/admin/kesehatan/program-kesehatan/create')}
|
||||||
<TableTh w={250}>Judul</TableTh>
|
>
|
||||||
<TableTh w={250}>Deskripsi Singkat</TableTh>
|
Tambah Baru
|
||||||
<TableTh w={250}>Image</TableTh>
|
</Button>
|
||||||
<TableTh w={200}>Detail</TableTh>
|
</Tooltip>
|
||||||
</TableTr>
|
</Group>
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
{/* Tabel */}
|
||||||
{filteredData.map((item) => (
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Judul</TableTh>
|
||||||
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
{item.name}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fz="sm" truncate="end" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="image" />
|
<Image w={100} src={item.image?.link} alt="image" radius="md" />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada program kesehatan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProgramKesehatan;
|
export default ProgramKesehatan;
|
||||||
|
|||||||
@@ -4,7 +4,18 @@
|
|||||||
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -85,7 +96,6 @@ function EditPuskesmas() {
|
|||||||
imageId: form.imageId,
|
imageId: form.imageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if there's an existing image URL in the form data
|
|
||||||
const formWithImage = form as PuskesmasFormData;
|
const formWithImage = form as PuskesmasFormData;
|
||||||
if (formWithImage.image?.link) {
|
if (formWithImage.image?.link) {
|
||||||
setPreviewImage(formWithImage.image.link);
|
setPreviewImage(formWithImage.image.link);
|
||||||
@@ -105,17 +115,8 @@ function EditPuskesmas() {
|
|||||||
...statePuskesmas.edit.form,
|
...statePuskesmas.edit.form,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
alamat: formData.alamat,
|
alamat: formData.alamat,
|
||||||
jam: {
|
jam: { ...formData.jam },
|
||||||
workDays: formData.jam.workDays,
|
kontak: { ...formData.kontak },
|
||||||
weekDays: formData.jam.weekDays,
|
|
||||||
holiday: formData.jam.holiday,
|
|
||||||
},
|
|
||||||
kontak: {
|
|
||||||
kontakPuskesmas: formData.kontak.kontakPuskesmas,
|
|
||||||
email: formData.kontak.email,
|
|
||||||
facebook: formData.kontak.facebook,
|
|
||||||
kontakUGD: formData.kontak.kontakUGD,
|
|
||||||
},
|
|
||||||
imageId: formData.imageId,
|
imageId: formData.imageId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,166 +145,182 @@ function EditPuskesmas() {
|
|||||||
|
|
||||||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({ ...prev, [name]: value }));
|
||||||
...prev,
|
|
||||||
[name]: value
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNestedChange = (section: 'jam' | 'kontak', field: string, value: string) => {
|
const handleNestedChange = (section: 'jam' | 'kontak', field: string, value: string) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[section]: {
|
[section]: { ...prev[section], [field]: value }
|
||||||
...prev[section],
|
|
||||||
[field]: value
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header dengan tombol back */}
|
||||||
<Button onClick={() => router.back()} variant="subtle" color="blue">
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Stack gap="xs">
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
</Tooltip>
|
||||||
<Stack gap="xs">
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Edit Puskesmas</Title>
|
Edit Puskesmas
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<TextInput
|
{/* Card Form */}
|
||||||
label={<Text fz="sm" fw="bold">Nama Puskesmas</Text>}
|
<Paper
|
||||||
placeholder="masukkan nama puskesmas"
|
w={{ base: '100%', md: '50%' }}
|
||||||
name="name"
|
bg={colors['white-1']}
|
||||||
value={formData.name}
|
p="lg"
|
||||||
onChange={handleInputChange}
|
radius="md"
|
||||||
/>
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Puskesmas"
|
||||||
|
placeholder="Masukkan nama puskesmas"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
label="Alamat"
|
||||||
placeholder="masukkan alamat"
|
placeholder="Masukkan alamat"
|
||||||
name="alamat"
|
name="alamat"
|
||||||
value={formData.alamat}
|
value={formData.alamat}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Jam Buka</Text>}
|
label="Jam Buka"
|
||||||
placeholder="masukkan jam buka"
|
placeholder="Masukkan jam buka"
|
||||||
value={formData.jam.workDays}
|
value={formData.jam.workDays}
|
||||||
onChange={(e) => handleNestedChange('jam', 'workDays', e.target.value)}
|
onChange={(e) => handleNestedChange('jam', 'workDays', e.target.value)}
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Jam Tutup</Text>}
|
label="Jam Tutup"
|
||||||
placeholder="masukkan jam tutup"
|
placeholder="Masukkan jam tutup"
|
||||||
value={formData.jam.weekDays}
|
value={formData.jam.weekDays}
|
||||||
onChange={(e) => handleNestedChange('jam', 'weekDays', e.target.value)}
|
onChange={(e) => handleNestedChange('jam', 'weekDays', e.target.value)}
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Jam Libur</Text>}
|
label="Jam Libur"
|
||||||
placeholder="masukkan jam libur"
|
placeholder="Masukkan jam libur"
|
||||||
value={formData.jam.holiday}
|
value={formData.jam.holiday}
|
||||||
onChange={(e) => handleNestedChange('jam', 'holiday', e.target.value)}
|
onChange={(e) => handleNestedChange('jam', 'holiday', e.target.value)}
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Kontak Puskesmas</Text>}
|
label="Kontak Puskesmas"
|
||||||
placeholder="masukkan kontak puskesmas"
|
placeholder="Masukkan kontak puskesmas"
|
||||||
value={formData.kontak.kontakPuskesmas}
|
value={formData.kontak.kontakPuskesmas}
|
||||||
onChange={(e) => handleNestedChange('kontak', 'kontakPuskesmas', e.target.value)}
|
onChange={(e) => handleNestedChange('kontak', 'kontakPuskesmas', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Email</Text>}
|
label="Email"
|
||||||
placeholder="masukkan email"
|
placeholder="Masukkan email"
|
||||||
value={formData.kontak.email}
|
value={formData.kontak.email}
|
||||||
onChange={(e) => handleNestedChange('kontak', 'email', e.target.value)}
|
onChange={(e) => handleNestedChange('kontak', 'email', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Facebook</Text>}
|
label="Facebook"
|
||||||
placeholder="masukkan facebook"
|
placeholder="Masukkan facebook"
|
||||||
value={formData.kontak.facebook}
|
value={formData.kontak.facebook}
|
||||||
onChange={(e) => handleNestedChange('kontak', 'facebook', e.target.value)}
|
onChange={(e) => handleNestedChange('kontak', 'facebook', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Kontak UGD</Text>}
|
label="Kontak UGD"
|
||||||
placeholder="masukkan kontak UGD"
|
placeholder="Masukkan kontak UGD"
|
||||||
value={formData.kontak.kontakUGD}
|
value={formData.kontak.kontakUGD}
|
||||||
onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)}
|
onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
{/* Upload Gambar */}
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Box>
|
||||||
<Box>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
<Dropzone
|
Gambar
|
||||||
onDrop={(files) => {
|
</Text>
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
<Dropzone
|
||||||
if (selectedFile) {
|
onDrop={(files) => {
|
||||||
setFile(selectedFile);
|
const selectedFile = files[0];
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
if (selectedFile) {
|
||||||
}
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
/>
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{ 'image/*': [] }}
|
|
||||||
>
|
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
|
||||||
<Dropzone.Accept>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Accept>
|
|
||||||
<Dropzone.Reject>
|
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
|
||||||
</Dropzone.Reject>
|
|
||||||
<Dropzone.Idle>
|
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format gambar
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
bg={colors['blue-button']}
|
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)',
|
||||||
|
}}
|
||||||
loading={statePuskesmas.edit.loading}
|
loading={statePuskesmas.edit.loading}
|
||||||
>
|
>
|
||||||
Simpan Perubahan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import puskesmasState from '../../../_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '../../../_state/kesehatan/puskesmas/puskesmas';
|
||||||
@@ -10,90 +10,128 @@ import { useShallowEffect } from '@mantine/hooks';
|
|||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
function DetailPuskesmas() {
|
function DetailPuskesmas() {
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const statePuskesmas = useProxy(puskesmasState)
|
const statePuskesmas = useProxy(puskesmasState);
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
statePuskesmas.findUnique.load(params?.id as string)
|
statePuskesmas.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
statePuskesmas.delete.byId(selectedId)
|
statePuskesmas.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/puskesmas")
|
router.push("/admin/kesehatan/puskesmas");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!statePuskesmas.findUnique.data) {
|
if (!statePuskesmas.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = statePuskesmas.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol kembali */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Puskesmas</Text>
|
Kembali
|
||||||
{statePuskesmas.findUnique.data ? (
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Box>
|
withBorder
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Puskesmas</Text>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Text fz={"lg"}>{statePuskesmas.findUnique.data.name}</Text>
|
bg={colors['white-1']}
|
||||||
</Box>
|
p="lg"
|
||||||
<Box>
|
radius="md"
|
||||||
<Text fz={"lg"} fw={"bold"}>Alamat</Text>
|
shadow="sm"
|
||||||
<Text fz={"lg"}>{statePuskesmas.findUnique.data.alamat}</Text>
|
>
|
||||||
</Box>
|
<Stack gap="md">
|
||||||
<Box>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"lg"} fw={"bold"}>Jam Operasional</Text>
|
Detail Puskesmas
|
||||||
<Text fz={"lg"}>{statePuskesmas.findUnique.data.jam.workDays}</Text>
|
</Text>
|
||||||
<Text fz={"lg"}>{statePuskesmas.findUnique.data.jam.weekDays}</Text>
|
|
||||||
<Text fz={"lg"}>{statePuskesmas.findUnique.data.jam.holiday}</Text>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
</Box>
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Text fz="lg" fw="bold">Nama Puskesmas</Text>
|
||||||
<Image src={statePuskesmas.findUnique.data.image?.link} alt="gambar" />
|
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Kontak</Text>
|
<Box>
|
||||||
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.kontakPuskesmas}</Text>
|
<Text fz="lg" fw="bold">Alamat</Text>
|
||||||
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.email}</Text>
|
<Text fz="md" c="dimmed">{data?.alamat || '-'}</Text>
|
||||||
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.facebook}</Text>
|
</Box>
|
||||||
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.kontakUGD}</Text>
|
|
||||||
</Box>
|
<Box>
|
||||||
<Box>
|
<Text fz="lg" fw="bold">Jam Operasional</Text>
|
||||||
<Flex gap={"xs"}>
|
<Text fz="md" c="dimmed">{data?.jam?.workDays || '-'}</Text>
|
||||||
<Button color="red" onClick={() => {
|
<Text fz="md" c="dimmed">{data?.jam?.weekDays || '-'}</Text>
|
||||||
if (statePuskesmas.findUnique.data) {
|
<Text fz="md" c="dimmed">{data?.jam?.holiday || '-'}</Text>
|
||||||
setSelectedId(statePuskesmas.findUnique.data.id)
|
</Box>
|
||||||
setModalHapus(true)
|
|
||||||
}
|
<Box>
|
||||||
}}>
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
<IconX size={20} />
|
{data?.image?.link ? (
|
||||||
</Button>
|
<Image src={data.image.link} alt="gambar" radius="md" />
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${statePuskesmas.findUnique.data?.id}/edit`)} color="green">
|
) : (
|
||||||
<IconEdit size={20} />
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
</Button>
|
)}
|
||||||
</Flex>
|
</Box>
|
||||||
</Box>
|
|
||||||
</Stack>
|
<Box>
|
||||||
</Paper>
|
<Text fz="lg" fw="bold">Kontak</Text>
|
||||||
) : null}
|
<Text fz="md" c="dimmed">{data?.kontak?.kontakPuskesmas || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data?.kontak?.email || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data?.kontak?.facebook || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data?.kontak?.kontakUGD || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/kesehatan/puskesmas/${data.id}/edit`)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -102,7 +140,7 @@ function DetailPuskesmas() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
text="Apakah anda yakin ingin menghapus data ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -11,44 +22,39 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import puskesmasState from '../../../_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '../../../_state/kesehatan/puskesmas/puskesmas';
|
||||||
|
|
||||||
function CreatePuskesmas() {
|
function CreatePuskesmas() {
|
||||||
const statePuskesmas = useProxy(puskesmasState)
|
const statePuskesmas = useProxy(puskesmasState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
statePuskesmas.create.form = {
|
statePuskesmas.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
alamat: "",
|
alamat: '',
|
||||||
jam: {
|
jam: {
|
||||||
workDays: "",
|
workDays: '',
|
||||||
weekDays: "",
|
weekDays: '',
|
||||||
holiday: "",
|
holiday: '',
|
||||||
},
|
},
|
||||||
kontak: {
|
kontak: {
|
||||||
kontakPuskesmas: "",
|
kontakPuskesmas: '',
|
||||||
email: "",
|
email: '',
|
||||||
facebook: "",
|
facebook: '',
|
||||||
kontakUGD: "",
|
kontakUGD: '',
|
||||||
},
|
},
|
||||||
imageId: "",
|
imageId: '',
|
||||||
image: undefined,
|
image: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn('Pilih file gambar terlebih dahulu');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload gambar dulu
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
@@ -56,162 +62,171 @@ function CreatePuskesmas() {
|
|||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
return toast.error('Gagal upload gambar');
|
||||||
}
|
}
|
||||||
|
|
||||||
statePuskesmas.create.form.imageId = uploaded.id;
|
statePuskesmas.create.form.imageId = uploaded.id;
|
||||||
// State is already being updated directly in the form inputs
|
|
||||||
|
|
||||||
await statePuskesmas.create.submit();
|
await statePuskesmas.create.submit();
|
||||||
|
|
||||||
toast.success("Data berhasil disimpan");
|
toast.success('Data berhasil disimpan');
|
||||||
resetForm();
|
resetForm();
|
||||||
// After successful submission, redirect to the list page
|
|
||||||
router.push('/admin/kesehatan/puskesmas');
|
router.push('/admin/kesehatan/puskesmas');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="form" onSubmit={handleSubmit}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}>
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Data Puskesmas
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
{/* Form Card */}
|
||||||
<Stack gap="xs">
|
<Paper
|
||||||
<Title order={3}>Create Puskesmas</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Puskesmas</Text>}
|
label="Nama Puskesmas"
|
||||||
placeholder="masukkan nama puskesmas"
|
placeholder="Masukkan nama puskesmas"
|
||||||
value={statePuskesmas.create.form.name}
|
value={statePuskesmas.create.form.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.name = e.target.value)}
|
||||||
statePuskesmas.create.form.name = e.target.value;
|
required
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
label="Alamat"
|
||||||
placeholder="masukkan alamat"
|
placeholder="Masukkan alamat"
|
||||||
value={statePuskesmas.create.form.alamat}
|
value={statePuskesmas.create.form.alamat}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.alamat = e.target.value)}
|
||||||
statePuskesmas.create.form.alamat = e.target.value;
|
required
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Jam Buka</Text>}
|
label="Jam Buka"
|
||||||
placeholder="masukkan jam buka"
|
placeholder="Masukkan jam buka"
|
||||||
value={statePuskesmas.create.form.jam.workDays}
|
value={statePuskesmas.create.form.jam.workDays}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.jam.workDays = e.target.value)}
|
||||||
statePuskesmas.create.form.jam.workDays = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Jam Tutup</Text>}
|
label="Jam Tutup"
|
||||||
placeholder="masukkan jam tutup"
|
placeholder="Masukkan jam tutup"
|
||||||
value={statePuskesmas.create.form.jam.weekDays}
|
value={statePuskesmas.create.form.jam.weekDays}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.jam.weekDays = e.target.value)}
|
||||||
statePuskesmas.create.form.jam.weekDays = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Holiday</Text>}
|
label="Holiday"
|
||||||
placeholder="masukkan holiday"
|
placeholder="Masukkan hari libur"
|
||||||
value={statePuskesmas.create.form.jam.holiday}
|
value={statePuskesmas.create.form.jam.holiday}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.jam.holiday = e.target.value)}
|
||||||
statePuskesmas.create.form.jam.holiday = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Kontak Puskesmas</Text>}
|
label="Kontak Puskesmas"
|
||||||
placeholder="masukkan kontak puskesmas"
|
placeholder="Masukkan kontak puskesmas"
|
||||||
value={statePuskesmas.create.form.kontak.kontakPuskesmas}
|
value={statePuskesmas.create.form.kontak.kontakPuskesmas}
|
||||||
onChange={(e) => {
|
onChange={(e) =>
|
||||||
statePuskesmas.create.form.kontak.kontakPuskesmas = e.target.value;
|
(statePuskesmas.create.form.kontak.kontakPuskesmas = e.target.value)
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Email</Text>}
|
label="Email"
|
||||||
placeholder="masukkan email"
|
placeholder="Masukkan email"
|
||||||
value={statePuskesmas.create.form.kontak.email}
|
value={statePuskesmas.create.form.kontak.email}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.kontak.email = e.target.value)}
|
||||||
statePuskesmas.create.form.kontak.email = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Facebook</Text>}
|
label="Facebook"
|
||||||
placeholder="masukkan facebook"
|
placeholder="Masukkan facebook"
|
||||||
value={statePuskesmas.create.form.kontak.facebook}
|
value={statePuskesmas.create.form.kontak.facebook}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.kontak.facebook = e.target.value)}
|
||||||
statePuskesmas.create.form.kontak.facebook = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Kontak UGD</Text>}
|
label="Kontak UGD"
|
||||||
placeholder="masukkan kontak ugd"
|
placeholder="Masukkan kontak UGD"
|
||||||
value={statePuskesmas.create.form.kontak.kontakUGD}
|
value={statePuskesmas.create.form.kontak.kontakUGD}
|
||||||
onChange={(e) => {
|
onChange={(e) => (statePuskesmas.create.form.kontak.kontakUGD = e.target.value)}
|
||||||
statePuskesmas.create.form.kontak.kontakUGD = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Title order={6} mb={6}>
|
||||||
<Box>
|
Gambar
|
||||||
<Dropzone
|
</Title>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
>
|
||||||
<Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={200} style={{ pointerEvents: 'none' }}>
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
<Dropzone.Accept>
|
||||||
</Dropzone.Accept>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
<Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<Dropzone.Reject>
|
||||||
</Dropzone.Reject>
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
<Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<Dropzone.Idle>
|
||||||
</Dropzone.Idle>
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="xl" inline>
|
<Text size="xl" inline>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
Maksimal 5MB dan harus format gambar
|
Maksimal 5MB dan harus format gambar
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
{previewImage && (
|
||||||
{previewImage && (
|
<Box mt="sm">
|
||||||
<Box mt="sm">
|
<Image
|
||||||
<Image
|
src={previewImage}
|
||||||
src={previewImage}
|
alt="Preview"
|
||||||
alt="Preview"
|
style={{
|
||||||
style={{
|
maxWidth: '100%',
|
||||||
maxWidth: '100%',
|
maxHeight: '200px',
|
||||||
maxHeight: '200px',
|
objectFit: 'contain',
|
||||||
objectFit: 'contain',
|
borderRadius: '8px',
|
||||||
borderRadius: '8px',
|
border: '1px solid #ddd',
|
||||||
border: '1px solid #ddd',
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan Puskesmas
|
{/* Action Button */}
|
||||||
</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
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)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,105 +1,155 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import puskesmasState from '../../_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '../../_state/kesehatan/puskesmas/puskesmas';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
function Puskesmas() {
|
function Puskesmas() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* Header dengan Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Puskesmas'
|
title='Puskesmas'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama atau alamat...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListPuskesmas search={search} />
|
<ListPuskesmas search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListPuskesmas({ search }: { search: string }) {
|
function ListPuskesmas({ search }: { search: string }) {
|
||||||
const statePuskesmas = useProxy(puskesmasState)
|
const statePuskesmas = useProxy(puskesmasState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = statePuskesmas.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = statePuskesmas.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
<Group justify="space-between" mb="md">
|
||||||
<JudulList
|
<Title order={4}>Daftar Puskesmas</Title>
|
||||||
title='List Puskesmas'
|
<Tooltip label="Tambah Puskesmas" withArrow>
|
||||||
href='/admin/kesehatan/puskesmas/create'
|
<Button
|
||||||
/>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Box style={{ overflowX: "auto" }}>
|
color="blue"
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
variant="light"
|
||||||
<TableThead>
|
onClick={() => router.push('/admin/kesehatan/puskesmas/create')}
|
||||||
<TableTr>
|
>
|
||||||
<TableTh>Nama Puskesmas</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Alamat</TableTh>
|
</Button>
|
||||||
<TableTh>Image</TableTh>
|
</Tooltip>
|
||||||
<TableTh>Detail</TableTh>
|
</Group>
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<TableTbody>
|
<Table highlightOnHover>
|
||||||
{filteredData.map((item) => (
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Puskesmas</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTd>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
<TableTd>{item.alamat}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image.link} alt="image" />
|
<Image w={100} src={item.image.link} alt="image" radius="md" />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data puskesmas yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Puskesmas;
|
export default Puskesmas;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Divider my="md" color={colors['blue-button']} />
|
<Divider my="md" color={colors['blue-button']} />
|
||||||
<Box px={{ base: 0, md: 50 }} pb="xl">
|
<Box px={{ base: 0, md: 50 }} pb="xl">
|
||||||
<Paper bg={colors['BG-trans']} radius="md" shadow="xs" p="lg">
|
<Paper withBorder bg={"#fff"} radius="md" shadow="xs" p="lg">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Center>
|
<Center>
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Paper, Pagination, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud';
|
import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud';
|
||||||
|
|
||||||
|
|
||||||
function JenjangPendidikan() {
|
function JenjangPendidikan() {
|
||||||
const [search, setSearch] = useState("")
|
// const [search, setSearch] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<Title order={4}>Jenjang Pendidikan</Title>
|
||||||
|
{/* <HeaderSearch
|
||||||
title='Jenjang Pendidikan'
|
title='Jenjang Pendidikan'
|
||||||
placeholder='Cari jenjang pendidikan...'
|
placeholder='Cari jenjang pendidikan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/> */}
|
||||||
<ListJenjangPendidikan search={search} />
|
<ListJenjangPendidikan />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListJenjangPendidikan({ search }: { search: string }) {
|
function ListJenjangPendidikan() {
|
||||||
const stateList = useProxy(infoSekolahPaud.jenjangPendidikan)
|
const stateList = useProxy(infoSekolahPaud.jenjangPendidikan)
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
@@ -50,8 +50,8 @@ function ListJenjangPendidikan({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10)
|
||||||
}, [page, search])
|
}, [page])
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || []
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconCheck, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
@@ -75,9 +75,9 @@ function ListUser({ search }: { search: string }) {
|
|||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Nama User</TableTh>
|
<TableTh style={{ width: '25%' }}>Nama User</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Email</TableTh>
|
<TableTh style={{ width: '20%' }}>Nomor</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Role</TableTh>
|
<TableTh style={{ width: '20%' }}>Role</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Hapus</TableTh>
|
<TableTh style={{ width: '15%' }}>Aktif / Nonaktif</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
@@ -98,16 +98,19 @@ function ListUser({ search }: { search: string }) {
|
|||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '15%' }}>
|
||||||
<Tooltip label="Hapus user" withArrow>
|
<Tooltip
|
||||||
|
label={item.isActive ? "Nonaktifkan user" : "Aktifkan user"}
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color={item.isActive ? "green" : "red"}
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
setSelectedId(item.id)
|
await stateUser.updateActive.submit(item.id, !item.isActive)
|
||||||
setModalHapus(true)
|
stateUser.findMany.load(page, 10, search)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconTrash size={20} />
|
{item.isActive ? <IconCheck size={20} /> : <IconX size={20} />}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
export {
|
export {
|
||||||
apiFetchLogin
|
apiFetchLogin,
|
||||||
|
apiFetchRegister
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiFetchLogin = async ({ nomor }: { nomor: string }) => {
|
const apiFetchLogin = async ({ nomor }: { nomor: string }) => {
|
||||||
const respone = await fetch("/api/auth/login", {
|
const response = await fetch("/api/auth/login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ nomor: nomor }),
|
body: JSON.stringify({ nomor: nomor }),
|
||||||
headers: {
|
headers: {
|
||||||
@@ -11,5 +12,30 @@ const apiFetchLogin = async ({ nomor }: { nomor: string }) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await respone.json().catch(() => null);
|
return await response.json().catch(() => null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiFetchRegister = async ({
|
||||||
|
nomor,
|
||||||
|
username,
|
||||||
|
}: {
|
||||||
|
nomor: string;
|
||||||
|
username: string;
|
||||||
|
}) => {
|
||||||
|
const data = {
|
||||||
|
username: username,
|
||||||
|
nomor: nomor,
|
||||||
|
};
|
||||||
|
const respone = await fetch("/api/auth/register", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ data }),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await respone.json();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
// return await respone.json().catch(() => null);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Center, Flex, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconUserFilled } from '@tabler/icons-react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
|
||||||
// const router = useRouter()
|
|
||||||
// const snap = useSnapshot(userState.userState)
|
|
||||||
// const handleSubmit = async () => {
|
|
||||||
// router.push("/darmasaba/pendidikan/perpustakaan-digital")
|
|
||||||
// await snap.login.submit()
|
|
||||||
// }
|
|
||||||
return (
|
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
|
||||||
<BackButton />
|
|
||||||
</Box>
|
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
|
||||||
<Center>
|
|
||||||
<Image src={"/darmasaba-icon.png"} alt="" w={80} />
|
|
||||||
</Center>
|
|
||||||
<Box>
|
|
||||||
<Title ta={'center'} order={1} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
E-Book Desa Darmasaba
|
|
||||||
</Title>
|
|
||||||
<Text ta={'center'} fz={'h4'} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
Silahkan masukkan akun anda untuk menjelajahi berbagai macam buku di perpustakaan digital
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box px={{ base: 'md', md: 100 }} pb={50}>
|
|
||||||
<Group justify='center'>
|
|
||||||
<Paper p={'xl'} radius={'md'} bg={colors['white-trans-1']}>
|
|
||||||
<Stack align='center'>
|
|
||||||
<Title order={2} fw={'bold'} c={colors['blue-button']}>
|
|
||||||
Login
|
|
||||||
</Title>
|
|
||||||
<IconUserFilled size={80} color={colors['blue-button']} />
|
|
||||||
<Box>
|
|
||||||
<Text c={colors['blue-button']} fw={'bold'}>Masuk Untuk Akses Lebih Banyak Buku</Text>
|
|
||||||
<TextInput
|
|
||||||
type='email'
|
|
||||||
label='Email'
|
|
||||||
placeholder='Email'
|
|
||||||
// value={snap.login.form.email}
|
|
||||||
// onChange={(e) => {
|
|
||||||
// userState.userState.login.form.email = e.target.value
|
|
||||||
// }}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<TextInput py={20}
|
|
||||||
type='password'
|
|
||||||
label='Password'
|
|
||||||
placeholder='Password'
|
|
||||||
// value={snap.login.form.password}
|
|
||||||
// onChange={(e) => {
|
|
||||||
// userState.userState.login.form.password = e.target.value
|
|
||||||
// }}
|
|
||||||
/>
|
|
||||||
<Box pb={20} >
|
|
||||||
<Button fullWidth bg={colors['blue-button']} radius={'xl'}>Masuk</Button>
|
|
||||||
</Box>
|
|
||||||
<Flex justify={'center'} align={'center'}>
|
|
||||||
<Text>Belum punya akun? </Text>
|
|
||||||
<Button variant='transparent' component={Link} href={'/registrasi'}>
|
|
||||||
<Text c={colors['blue-button']} fw={'bold'}>Registrasi</Text>
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Group>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Page;
|
|
||||||
@@ -21,7 +21,7 @@ import { useDisclosure } from "@mantine/hooks";
|
|||||||
import {
|
import {
|
||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconDoorExit,
|
IconLogout2
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -107,7 +107,21 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||||
>
|
>
|
||||||
<IconDoorExit size={22} />
|
<Image src="/assets/images/darmasaba-icon.png" alt="Logo Darmasaba" w={25} h={25} radius="md" />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Keluar" position="bottom" withArrow>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() => {
|
||||||
|
router.push("/login");
|
||||||
|
}}
|
||||||
|
color={colors["blue-button"]}
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||||
|
>
|
||||||
|
<IconLogout2 size={22} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,30 +1,64 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function artikelKesehatanFindMany(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 = [
|
||||||
|
{ title: { contains: search, mode: "insensitive" } },
|
||||||
|
{ introduction: { content: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ symptom: { title: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ prevention: { title: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ firstaid: { title: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ mythvsfact: { title: { contains: search, mode: "insensitive" } } },
|
||||||
|
{ doctorsign: { content: { contains: search, mode: "insensitive" } } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.artikelKesehatan.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
introduction: true,
|
||||||
|
symptom: true,
|
||||||
|
prevention: true,
|
||||||
|
firstaid: true,
|
||||||
|
mythvsfact: true,
|
||||||
|
doctorsign: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
}),
|
||||||
|
prisma.artikelKesehatan.count({ where }),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil keamanan lingkungan dengan pagination",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findMany paginated:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data keamanan lingkungan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function artikelKesehatanFindMany() {
|
|
||||||
try {
|
|
||||||
const data = await prisma.artikelKesehatan.findMany({
|
|
||||||
where: {
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
introduction: true,
|
|
||||||
symptom: true,
|
|
||||||
prevention: true,
|
|
||||||
firstaid: true,
|
|
||||||
mythvsfact: true,
|
|
||||||
doctorsign: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "Success fetch artikel kesehatan",
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Find many error:", error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Failed fetch artikel kesehatan",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,35 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
// /api/berita/findManyPaginated.ts
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function findManyFasilitasKesehatan() {
|
async function fasilitasKesehatanFindMany(context: Context) {
|
||||||
try {
|
// Ambil parameter dari query
|
||||||
const data = await prisma.fasilitasKesehatan.findMany({
|
const page = Number(context.query.page) || 1;
|
||||||
where: {
|
const limit = Number(context.query.limit) || 10;
|
||||||
isActive: true,
|
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 = [
|
||||||
|
{ informasiUmum: { fasilitas: { contains: search, mode: 'insensitive' } } },
|
||||||
|
{ layananUnggulan: { content: { contains: search, mode: 'insensitive' } } },
|
||||||
|
{ dokterdanTenagaMedis: { name: { contains: search, mode: 'insensitive' } } },
|
||||||
|
{ fasilitasPendukung: { content: { contains: search, mode: 'insensitive' } } },
|
||||||
|
{ prosedurPendaftaran: { content: { contains: search, mode: 'insensitive' } } },
|
||||||
|
{ tarifdanlayanan: { layanan: { contains: search, mode: 'insensitive' } } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.fasilitasKesehatan.findMany({
|
||||||
|
where,
|
||||||
include: {
|
include: {
|
||||||
informasiumum: true,
|
informasiumum: true,
|
||||||
layananunggulan: true,
|
layananunggulan: true,
|
||||||
@@ -13,18 +37,29 @@ export default async function findManyFasilitasKesehatan() {
|
|||||||
fasilitaspendukung: true,
|
fasilitaspendukung: true,
|
||||||
prosedurpendaftaran: true,
|
prosedurpendaftaran: true,
|
||||||
tarifdanlayanan: true,
|
tarifdanlayanan: true,
|
||||||
}
|
},
|
||||||
})
|
skip,
|
||||||
return {
|
take: limit,
|
||||||
success: true,
|
orderBy: { createdAt: 'desc' },
|
||||||
message: "Success fetch fasilitas kesehatan",
|
}),
|
||||||
data,
|
prisma.fasilitasKesehatan.count({ where }),
|
||||||
}
|
]);
|
||||||
} catch (error) {
|
|
||||||
console.error("Find many error:", error);
|
return {
|
||||||
return {
|
success: true,
|
||||||
success: false,
|
message: "Berhasil ambil keamanan lingkungan dengan pagination",
|
||||||
message: "Failed fetch fasilitas kesehatan",
|
data,
|
||||||
}
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findMany paginated:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data keamanan lingkungan",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export default fasilitasKesehatanFindMany
|
||||||
@@ -1,30 +1,60 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function jadwalKegiatanFindMany() {
|
export default async function jadwalKegiatanFindMany(context: Context) {
|
||||||
try {
|
// Ambil parameter dari query
|
||||||
const data = await prisma.jadwalKegiatan.findMany({
|
const page = Number(context.query.page) || 1;
|
||||||
where: {
|
const limit = Number(context.query.limit) || 10;
|
||||||
isActive: true,
|
const search = (context.query.search as string) || "";
|
||||||
},
|
const skip = (page - 1) * limit;
|
||||||
include: {
|
|
||||||
informasijadwalkegiatan: true,
|
// Buat where clause
|
||||||
deskripsijadwalkegiatan: true,
|
const where: any = { isActive: true };
|
||||||
layananjadwalkegiatan: true,
|
|
||||||
syaratketentuanjadwalkegiatan: true,
|
// Tambahkan pencarian (jika ada)
|
||||||
dokumenjadwalkegiatan: true,
|
if (search) {
|
||||||
pendaftaranjadwalkegiatan: true,
|
where.OR = [
|
||||||
}
|
{ informasijadwalkegiatan: { name: { contains: search, mode: "insensitive" } } },
|
||||||
})
|
{ deskripsijadwalkegiatan: { deskripsi: { contains: search, mode: "insensitive" } } },
|
||||||
return {
|
{layananjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
success: true,
|
{syaratketentuanjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
message: "Success fetch jadwal kegiatan",
|
{dokumenjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
data,
|
{pendaftaranjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
}
|
];
|
||||||
} catch (error) {
|
}
|
||||||
console.error("Find many error:", error);
|
try {
|
||||||
return {
|
const [data, total] = await Promise.all([
|
||||||
success: false,
|
prisma.jadwalKegiatan.findMany({
|
||||||
message: "Failed fetch jadwal kegiatan",
|
where,
|
||||||
}
|
include: {
|
||||||
}
|
informasijadwalkegiatan: true,
|
||||||
}
|
deskripsijadwalkegiatan: true,
|
||||||
|
layananjadwalkegiatan: true,
|
||||||
|
syaratketentuanjadwalkegiatan: true,
|
||||||
|
dokumenjadwalkegiatan: true,
|
||||||
|
pendaftaranjadwalkegiatan: true,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
}),
|
||||||
|
prisma.jadwalKegiatan.count({ where }),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success fetch jadwal kegiatan",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Find many error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed fetch jadwal kegiatan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Elysia, t } from "elysia";
|
|||||||
import userFindMany from "./findMany";
|
import userFindMany from "./findMany";
|
||||||
import userFindUnique from "./findUnique";
|
import userFindUnique from "./findUnique";
|
||||||
import userDelete from "./del"; // `delete` nggak boleh jadi nama file JS langsung, jadi biasanya `del.ts`
|
import userDelete from "./del"; // `delete` nggak boleh jadi nama file JS langsung, jadi biasanya `del.ts`
|
||||||
|
import userUpdate from "./updt";
|
||||||
|
|
||||||
const User = new Elysia({ prefix: "/api/user" })
|
const User = new Elysia({ prefix: "/api/user" })
|
||||||
.get("/findMany", userFindMany)
|
.get("/findMany", userFindMany)
|
||||||
@@ -12,6 +13,7 @@ const User = new Elysia({ prefix: "/api/user" })
|
|||||||
params: t.Object({
|
params: t.Object({
|
||||||
id: t.String(),
|
id: t.String(),
|
||||||
}),
|
}),
|
||||||
}); // pakai PUT untuk soft delete
|
}) // pakai PUT untuk soft delete
|
||||||
|
.put("/updt", userUpdate);
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|||||||
40
src/app/api/[[...slugs]]/_lib/user/updt.ts
Normal file
40
src/app/api/[[...slugs]]/_lib/user/updt.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function userUpdate(context: Context) {
|
||||||
|
try {
|
||||||
|
const { id, isActive } = await context.body as { id: string, isActive: boolean };
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID user wajib ada",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data: { isActive },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
nomor: true,
|
||||||
|
isActive: true,
|
||||||
|
updatedAt: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `User berhasil ${isActive ? "diaktifkan" : "dinonaktifkan"}`,
|
||||||
|
data: updatedUser,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("Error update user:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengupdate status user",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/app/api/auth/_lib/decrypt.ts
Normal file
50
src/app/api/auth/_lib/decrypt.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { jwtVerify } from "jose";
|
||||||
|
|
||||||
|
export async function decrypt({
|
||||||
|
token,
|
||||||
|
encodedKey,
|
||||||
|
}: {
|
||||||
|
token: string;
|
||||||
|
encodedKey: string;
|
||||||
|
}): Promise<Record<string, any> | null> {
|
||||||
|
if (!token || !encodedKey) {
|
||||||
|
console.error("Missing required parameters:", {
|
||||||
|
hasToken: !!token,
|
||||||
|
hasEncodedKey: !!encodedKey,
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const enc = new TextEncoder().encode(encodedKey);
|
||||||
|
const { payload } = await jwtVerify(token, enc, {
|
||||||
|
algorithms: ["HS256"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!payload || !payload.user) {
|
||||||
|
console.error("Invalid payload structure:", {
|
||||||
|
hasPayload: !!payload,
|
||||||
|
hasUser: payload ? !!payload.user : false,
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging untuk debug
|
||||||
|
// console.log("Decrypt successful:", {
|
||||||
|
// payloadExists: !!payload,
|
||||||
|
// userExists: !!payload.user,
|
||||||
|
// tokenPreview: token.substring(0, 10) + "...",
|
||||||
|
// });
|
||||||
|
|
||||||
|
return payload.user as Record<string, any>;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Token verification failed:", {
|
||||||
|
error,
|
||||||
|
tokenLength: token?.length,
|
||||||
|
errorName: error instanceof Error ? error.name : "Unknown error",
|
||||||
|
errorMessage: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/app/api/auth/_lib/encrypt.ts
Normal file
26
src/app/api/auth/_lib/encrypt.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { SignJWT } from "jose";
|
||||||
|
|
||||||
|
export async function encrypt({
|
||||||
|
user,
|
||||||
|
exp = "7 year",
|
||||||
|
encodedKey,
|
||||||
|
}: {
|
||||||
|
user: Record<string, any>;
|
||||||
|
exp?: string;
|
||||||
|
encodedKey: string;
|
||||||
|
}): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const enc = new TextEncoder().encode(encodedKey);
|
||||||
|
return new SignJWT({ user })
|
||||||
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
|
.setIssuedAt()
|
||||||
|
.setExpirationTime(exp)
|
||||||
|
.sign(enc);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal mengenkripsi", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wibu:0.2.82
|
||||||
13
src/app/api/auth/_lib/get_KodeOtp_By_Id.ts
Normal file
13
src/app/api/auth/_lib/get_KodeOtp_By_Id.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export async function auth_getCodeOtpByNumber({kodeId}: {kodeId: string}) {
|
||||||
|
const data = await prisma.kodeOtp.findFirst({
|
||||||
|
where: {
|
||||||
|
id: kodeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
4
src/app/api/auth/_lib/randomOTP.ts
Normal file
4
src/app/api/auth/_lib/randomOTP.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export function randomOTP() {
|
||||||
|
const random = Math.floor(Math.random() * (9000 - 1000 )) + 1000
|
||||||
|
return random;
|
||||||
|
}
|
||||||
36
src/app/api/auth/_lib/session_create.ts
Normal file
36
src/app/api/auth/_lib/session_create.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { encrypt } from "./encrypt";
|
||||||
|
|
||||||
|
export async function sessionCreate({
|
||||||
|
sessionKey,
|
||||||
|
exp = "7 year",
|
||||||
|
encodedKey,
|
||||||
|
user,
|
||||||
|
}: {
|
||||||
|
sessionKey: string;
|
||||||
|
exp?: string;
|
||||||
|
encodedKey: string;
|
||||||
|
user: Record<string, unknown>;
|
||||||
|
}) {
|
||||||
|
const token = await encrypt({
|
||||||
|
exp,
|
||||||
|
encodedKey,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cookie: any = {
|
||||||
|
key: sessionKey,
|
||||||
|
value: token,
|
||||||
|
options: {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "lax",
|
||||||
|
path: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(await cookies()).set(cookie.key, cookie.value, { ...cookie.options });
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wibu:0.2.82
|
||||||
63
src/app/api/auth/login/route.ts
Normal file
63
src/app/api/auth/login/route.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { randomOTP } from "../_lib/randomOTP";
|
||||||
|
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: "Method Not Allowed" },
|
||||||
|
{ status: 405 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const codeOtp = randomOTP();
|
||||||
|
const body = await req.json();
|
||||||
|
const { nomor } = body;
|
||||||
|
const res = await fetch(
|
||||||
|
`https://wa.wibudev.com/code?nom=${nomor}&text=Website Desa Darmasaba - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun Admin lainnya.
|
||||||
|
\n
|
||||||
|
>> Kode OTP anda: ${codeOtp}.
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const sendWa = await res.json();
|
||||||
|
|
||||||
|
if (sendWa.status !== "success")
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: "Nomor Whatsapp Tidak Aktif" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const createOtpId = await prisma.kodeOtp.create({
|
||||||
|
data: {
|
||||||
|
nomor: nomor,
|
||||||
|
otp: codeOtp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createOtpId)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: "Gagal mengirim kode OTP" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
message: "Kode verifikasi terkirim",
|
||||||
|
kodeId: createOtpId.id,
|
||||||
|
},
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error Login", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: "Terjadi masalah saat login" , reason: error as Error },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/app/login/page.tsx
Normal file
10
src/app/login/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Box } from "@mantine/core";
|
||||||
|
import Login from "../admin/(dashboard)/auth/login-admin/page";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Login />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
src/app/registrasi/page.tsx
Normal file
12
src/app/registrasi/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Registrasi from '../admin/(dashboard)/auth/registrasi-admin/page';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Registrasi/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
9
src/app/validasi/page.tsx
Normal file
9
src/app/validasi/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import Validasi from "../admin/(dashboard)/auth/validasi-admin/page";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Validasi />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user