Fix Admin Submenu Posyandu, Menu Kesehatan, dan Sinkronisasi UI & API Admin - User Submenu Posyandu
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -9,6 +10,7 @@ const templateForm = z.object({
|
||||
nomor: z.string().min(1, { message: "Nomor is required" }),
|
||||
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||
imageId: z.string().nonempty(),
|
||||
jadwalPelayanan: z.string().min(1, { message: "Jadwal Pelayanan is required" }),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
@@ -16,6 +18,7 @@ const defaultForm = {
|
||||
nomor: "",
|
||||
deskripsi: "",
|
||||
imageId: "",
|
||||
jadwalPelayanan: "",
|
||||
};
|
||||
|
||||
const posyandustate = proxy({
|
||||
@@ -50,19 +53,43 @@ const posyandustate = proxy({
|
||||
}
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.PosyanduGetPayload<{
|
||||
include: {
|
||||
data: null as
|
||||
| Prisma.PosyanduGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
posyandustate.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
posyandustate.findMany.page = page;
|
||||
posyandustate.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
posyandustate.findMany.data = res.data.data ?? [];
|
||||
posyandustate.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
posyandustate.findMany.data = [];
|
||||
posyandustate.findMany.totalPages = 1;
|
||||
}
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
posyandustate.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch posyandu paginated:", err);
|
||||
posyandustate.findMany.data = [];
|
||||
posyandustate.findMany.totalPages = 1;
|
||||
} finally {
|
||||
posyandustate.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as
|
||||
@@ -148,6 +175,7 @@ const posyandustate = proxy({
|
||||
nomor: data.nomor,
|
||||
deskripsi: data.deskripsi,
|
||||
imageId: data.imageId || "",
|
||||
jadwalPelayanan: data.jadwalPelayanan || "",
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -181,6 +209,7 @@ const posyandustate = proxy({
|
||||
nomor: this.form.nomor,
|
||||
deskripsi: this.form.deskripsi,
|
||||
imageId: this.form.imageId,
|
||||
jadwalPelayanan: this.form.jadwalPelayanan,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ function ListBerita({ search }: { search: string }) {
|
||||
if (loading || !data) {
|
||||
return <Skeleton h={500} />;
|
||||
}
|
||||
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,6 +25,7 @@ function EditPosyandu() {
|
||||
nomor: statePosyandu.edit.form.nomor || '',
|
||||
deskripsi: statePosyandu.edit.form.deskripsi || '',
|
||||
imageId: statePosyandu.edit.form.imageId || '',
|
||||
jadwalPelayanan: statePosyandu.edit.form.jadwalPelayanan || '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -40,6 +41,7 @@ function EditPosyandu() {
|
||||
nomor: data.nomor || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
imageId: data.imageId || '',
|
||||
jadwalPelayanan: data.jadwalPelayanan || '',
|
||||
});
|
||||
|
||||
if (data?.image?.link) {
|
||||
@@ -62,6 +64,7 @@ function EditPosyandu() {
|
||||
nomor: formData.nomor,
|
||||
deskripsi: formData.deskripsi,
|
||||
imageId: formData.imageId,
|
||||
jadwalPelayanan: formData.jadwalPelayanan,
|
||||
}
|
||||
|
||||
if (file) {
|
||||
@@ -173,6 +176,16 @@ function EditPosyandu() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Jadwal Pelayanan</Text>
|
||||
<EditEditor
|
||||
value={formData.jadwalPelayanan}
|
||||
onChange={(htmlContent) => {
|
||||
setFormData({ ...formData, jadwalPelayanan: htmlContent });
|
||||
statePosyandu.edit.form.jadwalPelayanan = htmlContent;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
|
||||
@@ -63,6 +63,10 @@ function DetailPosyandu() {
|
||||
<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" />
|
||||
|
||||
@@ -23,6 +23,7 @@ function CreatePosyandu() {
|
||||
nomor: "",
|
||||
deskripsi: "",
|
||||
imageId: "",
|
||||
jadwalPelayanan: "",
|
||||
};
|
||||
|
||||
setFile(null);
|
||||
@@ -147,6 +148,15 @@ function CreatePosyandu() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Jadwal Pelayanan</Text>
|
||||
<CreateEditor
|
||||
value={statePosyandu.create.form.jadwalPelayanan}
|
||||
onChange={(htmlContent) => {
|
||||
statePosyandu.create.form.jadwalPelayanan = htmlContent;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Pagination, 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';
|
||||
@@ -30,19 +30,21 @@ function ListPosyandu({ search }: { search: string }) {
|
||||
const statePosyandu = useProxy(posyandustate)
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = statePosyandu.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
statePosyandu.findMany.load()
|
||||
}, [])
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const filteredData = (statePosyandu.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.nomor.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = data || [];
|
||||
|
||||
if (!statePosyandu.findMany.data) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
@@ -70,10 +72,20 @@ function ListPosyandu({ search }: { search: string }) {
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{item.nomor}</TableTd>
|
||||
<TableTd>
|
||||
<Text fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Box w={100}>
|
||||
<Text truncate="end" lineClamp={1} fz={"sm"}>{item.name}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" lineClamp={1} fz={"sm"}>{item.nomor}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)}>
|
||||
@@ -86,6 +98,15 @@ function ListPosyandu({ search }: { search: string }) {
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type FormCreate = Prisma.PosyanduGetPayload<{
|
||||
nomor: true;
|
||||
deskripsi: true;
|
||||
imageId: true;
|
||||
jadwalPelayanan: true;
|
||||
};
|
||||
}>;
|
||||
export default async function posyanduCreate(context: Context) {
|
||||
@@ -19,6 +20,7 @@ export default async function posyanduCreate(context: Context) {
|
||||
nomor: body.nomor,
|
||||
deskripsi: body.deskripsi,
|
||||
imageId: body.imageId,
|
||||
jadwalPelayanan: body.jadwalPelayanan,
|
||||
}
|
||||
})
|
||||
return {
|
||||
|
||||
@@ -1,26 +1,59 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function posyanduFindMany() {
|
||||
try {
|
||||
const data = await prisma.posyandu.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
},
|
||||
async function posyanduFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
{ nomor: { contains: search, mode: 'insensitive' } },
|
||||
{ jadwalPelayanan: { contains: search, mode: 'insensitive' } }
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.posyandu.findMany({
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
}
|
||||
})
|
||||
image: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.posyandu.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch posyandu",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
success: true,
|
||||
message: "Berhasil ambil posyandu dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di posyanduFindMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch posyandu",
|
||||
}
|
||||
success: false,
|
||||
message: "Gagal mengambil data posyandu",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export default posyanduFindMany;
|
||||
@@ -15,6 +15,7 @@ const Posyandu = new Elysia({
|
||||
nomor: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imageId: t.String(),
|
||||
jadwalPelayanan: t.String(),
|
||||
})
|
||||
})
|
||||
.get("/find-many", posyanduFindMany)
|
||||
@@ -35,6 +36,7 @@ const Posyandu = new Elysia({
|
||||
nomor: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imageId: t.String(),
|
||||
jadwalPelayanan: t.String(),
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ type FormUpdate = Prisma.PosyanduGetPayload<{
|
||||
nomor: true;
|
||||
deskripsi: true;
|
||||
imageId: true;
|
||||
jadwalPelayanan: true;
|
||||
}
|
||||
}>
|
||||
|
||||
@@ -24,6 +25,7 @@ export default async function posyanduUpdate(context: Context) {
|
||||
nomor,
|
||||
deskripsi,
|
||||
imageId,
|
||||
jadwalPelayanan,
|
||||
} = body;
|
||||
|
||||
if(!id) {
|
||||
@@ -79,6 +81,7 @@ export default async function posyanduUpdate(context: Context) {
|
||||
nomor,
|
||||
deskripsi,
|
||||
imageId,
|
||||
jadwalPelayanan,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,36 +1,58 @@
|
||||
'use client'
|
||||
import colors from "@/con/colors";
|
||||
import { Stack, Box, Text, SimpleGrid, Paper, Center, Image, Flex, List, ListItem } from "@mantine/core";
|
||||
import { Box, Center, Flex, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Spoiler, Stack, Text, TextInput } from "@mantine/core";
|
||||
import BackButton from "../../desa/layanan/_com/BackButto";
|
||||
// import { useTransitionRouter } from "next-view-transitions";
|
||||
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import { useState } from "react";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'Posyandu Banjar Bucu',
|
||||
nomor: '082345678910',
|
||||
image: '/api/img/posyandu.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Posyandu Banjar Bucu',
|
||||
nomor: '082345678910',
|
||||
image: '/api/img/posyandu.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'Posyandu Banjar Bucu',
|
||||
nomor: '082345678910',
|
||||
image: '/api/img/posyandu.png'
|
||||
}
|
||||
]
|
||||
export default function Page() {
|
||||
const state = useProxy(posyandustate)
|
||||
// const router = useTransitionRouter()
|
||||
const [search, setSearch] = useState("")
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = state.findMany;
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, search)
|
||||
}, [page, search])
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
<Flex mt={10} justify={"space-between"} align={"center"}>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Posyandu Darmasaba
|
||||
</Text>
|
||||
<TextInput
|
||||
placeholder="Cari Posyandu"
|
||||
radius="lg"
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "25%", md: "30%" }}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Posyandu Darmasaba
|
||||
</Text>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
<SimpleGrid
|
||||
@@ -39,49 +61,46 @@ export default function Page() {
|
||||
base: 1,
|
||||
md: 3,
|
||||
}}>
|
||||
{data.map((v, k) => {
|
||||
{data?.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p={"xl"} bg={colors["white-trans-1"]}>
|
||||
<Stack gap={'xs'}>
|
||||
<Text c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Text fz={'h4'}>
|
||||
{v.nomor}
|
||||
{v.name}
|
||||
</Text>
|
||||
<Center>
|
||||
<Image src={v.image} alt="" />
|
||||
<Image src={v.image.link} alt="" />
|
||||
</Center>
|
||||
<Text fz={'h4'}>
|
||||
Jadwal Pelayanan
|
||||
</Text>
|
||||
<Text fz={'h4'}>
|
||||
Setiap tanggal 5, Pukul 09:00 -
|
||||
12:00 WITA
|
||||
No.Telp : {v.nomor}
|
||||
</Text>
|
||||
<Box>
|
||||
<Flex justify={'space-between'}>
|
||||
<Text fz={'h4'}>Balita</Text>
|
||||
<Box>
|
||||
<Text fz={'h4'}>Selasa minggu pertama</Text>
|
||||
<Text fz={'h4'}>(09:00-11:00 WITA)</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex justify={'space-between'}>
|
||||
<Text fz={'h4'}>Lansia</Text>
|
||||
<Box>
|
||||
<Text fz={'h4'}>Selasa minggu pertama</Text>
|
||||
<Text fz={'h4'}>(09:00-11:00 WITA)</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Text fz={'h4'}>
|
||||
Jadwal Pelayanan
|
||||
</Text>
|
||||
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} />
|
||||
</Box>
|
||||
<Spoiler
|
||||
maxHeight={60} // tinggi maksimum sebelum di-collapse
|
||||
showLabel="Read more"
|
||||
hideLabel="Read less"
|
||||
>
|
||||
<Text fz={'h4'} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Spoiler>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
<Text fz={'h4'} fw={"bold"}>
|
||||
Pelayanan Posyandu
|
||||
</Text>
|
||||
|
||||
Reference in New Issue
Block a user