diff --git a/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts b/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts new file mode 100644 index 00000000..83d9ded2 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts @@ -0,0 +1,217 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +const templateForm = z.object({ + name: z.string().min(1, { message: "Name is required" }), + nomor: z.string().min(1, { message: "Nomor is required" }), + deskripsi: z.string().min(1, { message: "Deskripsi is required" }), + imageId: z.string().nonempty(), +}); + +const defaultForm = { + name: "", + nomor: "", + deskripsi: "", + imageId: "", +}; + +const posyandustate = proxy({ + create: { + form: { ...defaultForm }, + loading: false, + async create() { + const cek = templateForm.safeParse(posyandustate.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + posyandustate.create.loading = true; + const res = await ApiFetch.api.kesehatan.posyandu["create"].post(posyandustate.create.form); + if (res.status === 200) { + posyandustate.findMany.load(); + return toast.success("Posyandu berhasil disimpan!"); + } + return toast.error("Gagal menyimpan posyandu"); + } catch (error) { + console.log((error as Error).message); + } finally { + posyandustate.create.loading = false; + } + }, + resetForm(){ + posyandustate.create.form = { ...defaultForm }; + } + }, + findMany: { + data: null as + | Prisma.PosyanduGetPayload<{ + include: { + image: true; + } + }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get(); + if (res.status === 200) { + posyandustate.findMany.data = res.data?.data ?? []; + } + } + }, + findUnique: { + data: null as + | Prisma.PosyanduGetPayload<{ + include: { + image: true; + } + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/kesehatan/posyandu/${id}`); + if (res.ok) { + const data = await res.json(); + posyandustate.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch posyandu:", res.statusText); + posyandustate.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching posyandu:", error); + posyandustate.findUnique.data = null; + } + } + }, + delete: { + loading: false, + async byId(id: string) { + try { + posyandustate.delete.loading = true; + const response = await fetch(`/api/kesehatan/posyandu/del/${id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Posyandu berhasil dihapus"); + await posyandustate.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus posyandu"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus posyandu"); + } finally { + posyandustate.delete.loading = false; + } + } + }, + edit: { + id: "", + form: {...defaultForm}, + loading: false, + + async load(id: string) { + if(!id){ + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/kesehatan/posyandu/${id}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if(!response.ok){ + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if(result?.success){ + const data = result.data; + this.id = data.id; + this.form = { + name: data.name, + nomor: data.nomor, + deskripsi: data.deskripsi, + imageId: data.imageId || "", + }; + return data; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching posyandu:", error); + toast.error(error instanceof Error ? error.message : "Gagal memuat data"); + return null; + } + }, + async update() { + const cek = templateForm.safeParse(posyandustate.edit.form); + if(!cek.success){ + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + posyandustate.edit.loading = true; + const response = await fetch(`/api/kesehatan/posyandu/${this.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: this.form.name, + nomor: this.form.nomor, + deskripsi: this.form.deskripsi, + imageId: this.form.imageId, + }), + }); + + if(!response.ok){ + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if(result.success){ + toast.success(result.message || "Posyandu berhasil diperbarui"); + await posyandustate.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching posyandu:", error); + toast.error(error instanceof Error ? error.message : "Gagal memuat data"); + return false; + } finally { + posyandustate.edit.loading = false; + } + }, + + reset() { + posyandustate.edit.id = ""; + posyandustate.edit.form = {...defaultForm}; + } + } +}) + +export default posyandustate; diff --git a/src/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas.ts b/src/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas.ts new file mode 100644 index 00000000..25252570 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas.ts @@ -0,0 +1,5 @@ +// import { z } from "zod"; + +// const templateForm = z.object({ + +// }) \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts index 164c6e81..b889d6a5 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts @@ -5,9 +5,9 @@ import { proxy } from "valtio"; import { z } from "zod"; const templateGrafikJenisKelamin = z.object({ - laki: z.string().min(2, "Data laki-laki harus diisi"), - perempuan: z.string().min(2, "Data perempuan harus diisi"), -}); + laki: z.string().min(1, "Data laki-laki harus diisi"), + perempuan: z.string().min(1, "Data perempuan harus diisi"), +}); type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{ select: { diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts index 7b3f4693..eec88dc4 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts @@ -5,10 +5,10 @@ import { proxy } from "valtio"; import { z } from "zod"; const templateGrafikUmur = z.object({ - remaja: z.string().min(2, "Data remaja harus diisi"), - dewasa: z.string().min(2, "Data dewasa harus diisi"), - orangtua: z.string().min(2, "Data orangtua harus diisi"), - lansia: z.string().min(2, "Data lansia harus diisi"), + remaja: z.string().min(1, "Data remaja harus diisi"), + dewasa: z.string().min(1, "Data dewasa harus diisi"), + orangtua: z.string().min(1, "Data orangtua harus diisi"), + lansia: z.string().min(1, "Data lansia harus diisi"), }); type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{ diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts index 65287269..dc735972 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts @@ -6,7 +6,7 @@ import { z } from "zod"; const templateGrafikHasilKepuasanMasyarakat = z.object({ label: z.string().min(2, "Label harus diisi"), - kepuasan: z.string().min(2, "Kepuasan harus diisi"), + kepuasan: z.string().min(1, "Kepuasan harus diisi"), }); type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{ diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/[id]/edit/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/[id]/edit/page.tsx new file mode 100644 index 00000000..192f363a --- /dev/null +++ b/src/app/admin/(dashboard)/kesehatan/posyandu/[id]/edit/page.tsx @@ -0,0 +1,147 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; +import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; + + +function EditPosyandu() { + const statePosyandu = useProxy(posyandustate) + const router = useRouter(); + const params = useParams() + + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(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 || '', + }); + + useEffect(() => { + const loadPosyandu = async () => { + const id = params?.id as string; + if (!id) return; + + try { + const data = await statePosyandu.edit.load(id); + if (data) { + setFormData({ + name: data.name || '', + nomor: data.nomor || '', + deskripsi: data.deskripsi || '', + imageId: data.imageId || '', + }); + + 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]) + + const handleSubmit = async () => { + try { + statePosyandu.edit.form = { + ...statePosyandu.edit.form, + name: formData.name, + nomor: formData.nomor, + deskripsi: formData.deskripsi, + imageId: formData.imageId, + } + + if (file) { + 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.edit.form.imageId = uploaded.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 ( + + + + + + + + Edit Posyandu + {previewImage ? ( + + ) : ( +
+ +
+ )} + Upload Gambar} + value={file} + onChange={async (e) => { + if (!e) return; + setFile(e); + const base64 = await e.arrayBuffer().then((buf) => + "data:image/png;base64," + Buffer.from(buf).toString("base64") + ); + setPreviewImage(base64); + }} + /> + setFormData({ ...formData, name: e.target.value })} + label={Nama Posyandu} + placeholder='Masukkan nama posyandu' + /> + setFormData({ ...formData, nomor: e.target.value })} + label={Nomor Posyandu} + placeholder='Masukkan nomor posyandu' + /> + + Deskripsi Posyandu + { + setFormData({ ...formData, deskripsi: htmlContent }); + statePosyandu.edit.form.deskripsi = htmlContent; + }} + /> + + + + +
+
+
+ ); +} + +export default EditPosyandu; diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/[id]/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/[id]/page.tsx new file mode 100644 index 00000000..415a17c4 --- /dev/null +++ b/src/app/admin/(dashboard)/kesehatan/posyandu/[id]/page.tsx @@ -0,0 +1,101 @@ +'use client' +import colors from '@/con/colors'; +import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core'; +import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import React, { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import posyandustate from '../../../_state/kesehatan/posyandu/posyandu'; +import { useShallowEffect } from '@mantine/hooks'; +import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; +// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; + +function DetailPosyandu() { + const statePosyandu = useProxy(posyandustate) + const params = useParams() + const router = useRouter(); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null) + + useShallowEffect(() => { + statePosyandu.findUnique.load(params?.id as string) + }, []) + + const handleHapus = () => { + if (selectedId) { + statePosyandu.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) + router.push("/admin/kesehatan/posyandu") + } + } + + if (!statePosyandu.findUnique.data) { + return ( + + + + ) + } + + return ( + + + + + + + Detail Posyandu + {statePosyandu.findUnique.data ? ( + + + + Nama Posyandu + {statePosyandu.findUnique.data.name} + + + Nomor Posyandu + {statePosyandu.findUnique.data.nomor} + + + Deskripsi Posyandu + + + + Gambar + gambar + + + + + + + + + + ) : null} + + + + setModalHapus(false)} + onConfirm={handleHapus} + text="Apakah anda yakin ingin menghapus posyandu ini?" + /> + + ); +} + +export default DetailPosyandu; diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/create/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/create/page.tsx index 6060f701..f0c09ea7 100644 --- a/src/app/admin/(dashboard)/kesehatan/posyandu/create/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/posyandu/create/page.tsx @@ -1,13 +1,59 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import ApiFetch from '@/lib/api-fetch'; +import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import React from 'react'; -import { KesehatanEditor } from '../../_com/kesehatanEditor'; +import { useState } from 'react'; +import { toast } from 'react-toastify'; +import { useProxy } from 'valtio/utils'; +import CreateEditor from '../../../_com/createEditor'; +import posyandustate from '../../../_state/kesehatan/posyandu/posyandu'; function CreatePosyandu() { + const statePosyandu = useProxy(posyandustate) const router = useRouter(); + const [file, setFile] = useState(null); + const [previewImage, setPreviewImage] = useState(null); + + const resetForm = () => { + statePosyandu.create.form = { + name: "", + nomor: "", + deskripsi: "", + imageId: "", + }; + + 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") + } + + + return ( @@ -19,26 +65,52 @@ function CreatePosyandu() { Create Posyandu - - Masukkan Image - - + {previewImage ? ( + + ) : ( +
+ +
+ )} + Upload Gambar} + value={file} + onChange={async (e) => { + if (!e) return; + setFile(e); + const base64 = await e.arrayBuffer().then((buf) => + "data:image/png;base64," + Buffer.from(buf).toString("base64") + ); + setPreviewImage(base64); + }} + /> Nama Posyandu} placeholder='Masukkan nama posyandu' + value={statePosyandu.create.form.name} + onChange={(e) => { + statePosyandu.create.form.name = e.target.value; + }} /> Nomor Posyandu} placeholder='Masukkan nomor posyandu' + value={statePosyandu.create.form.nomor} + onChange={(e) => { + statePosyandu.create.form.nomor = e.target.value; + }} /> Deskripsi Posyandu - { + statePosyandu.create.form.deskripsi = htmlContent; + }} /> - +
diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/detail/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/detail/page.tsx deleted file mode 100644 index ec649457..00000000 --- a/src/app/admin/(dashboard)/kesehatan/posyandu/detail/page.tsx +++ /dev/null @@ -1,66 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core'; -import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import React from 'react'; -// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; - -function DetailPosyandu() { - const router = useRouter(); - return ( - - - - - - - Detail Posyandu - - - - - Nama Posyandu - Test Judul - - - Nomor Posyandu - 089647038426 - - - Deskripsi Posyandu - Test Kategori - - - Gambar - gambar - - - - - - - - - - - - - {/* Modal Hapus - setModalHapus(false)} - onConfirm={handleHapus} - text="Apakah anda yakin ingin menghapus penanganan darurat ini?" - /> */} - - ); -} - -export default DetailPosyandu; diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/edit/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/edit/page.tsx deleted file mode 100644 index 5ecb68d2..00000000 --- a/src/app/admin/(dashboard)/kesehatan/posyandu/edit/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' -import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; -import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react'; -import { useRouter } from 'next/navigation'; -import React from 'react'; -import { KesehatanEditor } from '../../_com/kesehatanEditor'; - -function EditPosyandu() { - const router = useRouter(); - return ( - - - - - - - - Edit Posyandu - - Masukkan Image - - - Nama Posyandu} - placeholder='Masukkan nama posyandu' - /> - Nomor Posyandu} - placeholder='Masukkan nomor posyandu' - /> - - Deskripsi Posyandu - - - - - - - - - ); -} - -export default EditPosyandu; diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/page.tsx index b6c17360..f25f694f 100644 --- a/src/app/admin/(dashboard)/kesehatan/posyandu/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/posyandu/page.tsx @@ -1,10 +1,13 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; +import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; import HeaderSearch from '../../_com/header'; import JudulList from '../../_com/judulList'; import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; +import posyandustate from '../../_state/kesehatan/posyandu/posyandu'; +import { useShallowEffect } from '@mantine/hooks'; function Posyandu() { return ( @@ -14,13 +17,27 @@ function Posyandu() { placeholder='pencarian' searchIcon={} /> - +
); } function ListPosyandu() { + const statePosyandu = useProxy(posyandustate) const router = useRouter(); + + useShallowEffect(() => { + statePosyandu.findMany.load() + }, []) + + if (!statePosyandu.findMany.data) { + return ( + + + + ) + } + return ( @@ -28,28 +45,34 @@ function ListPosyandu() { title='List Posyandu' href='/admin/kesehatan/posyandu/create' /> - - - - Nama Posyandu - Nomor Posyandu - Deskripsi - Detail + +
+ + + Nama Posyandu + Nomor Posyandu + Deskripsi + Detail - - - - Posyandu 1 - 0896232831883 - Posyandu 1 - - - - - -
+ + + {statePosyandu.findMany.data?.map((item) => ( + + {item.name} + {item.nomor} + + + + + + + + ))} + + +
); diff --git a/src/app/admin/(dashboard)/ppid/visi-misi-ppid/edit/page.tsx b/src/app/admin/(dashboard)/ppid/visi-misi-ppid/edit/page.tsx index f88bb835..df696fe3 100644 --- a/src/app/admin/(dashboard)/ppid/visi-misi-ppid/edit/page.tsx +++ b/src/app/admin/(dashboard)/ppid/visi-misi-ppid/edit/page.tsx @@ -42,6 +42,7 @@ function VisiMisiPPIDEdit() { visiMisi.findById.data.misi = draftMisi; visiMisi.update.save(visiMisi.findById.data); } + router.push('/admin/ppid/visi-misi-ppid') }; return (