Fix QC Kak Inno 18 Des

Fix UI Admin Menu Kesehatan
Fix Search : Sudah diberi useDebounced menu Kesehatan
This commit is contained in:
2025-12-19 15:43:55 +08:00
parent af60bcd6fc
commit bf20cd55e8
85 changed files with 1838 additions and 956 deletions

View File

@@ -86,7 +86,7 @@ function ListKategoriBerita({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'lg' }}> <Box py={{ base: 'sm', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}> <Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Kategori Berita Daftar Kategori Berita
</Title> </Title>
<Button <Button

View File

@@ -67,8 +67,7 @@ function ListBerita({ search }: { search: string }) {
<Box py="md"> <Box py="md">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={2} visibleFrom="md">Daftar Berita</Title> <Title order={4}>Daftar Berita</Title>
<Title order={3} hiddenFrom="md">Daftar Berita</Title>
<Button <Button
leftSection={<IconCircleDashedPlus size={18} />} leftSection={<IconCircleDashedPlus size={18} />}
color="blue" color="blue"
@@ -140,7 +139,7 @@ function ListBerita({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md"> <Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={4}> <Stack gap={"xs"}>
<Text fz="sm" fw={600} lh={1.4} c="dimmed"> <Text fz="sm" fw={600} lh={1.4} c="dimmed">
Judul Judul
</Text> </Text>

View File

@@ -73,7 +73,7 @@ function ListFoto({ search }: { search: string }) {
<Box py={{ base: 'md', md: 'lg' }}> <Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}>Daftar Foto</Title> <Title order={4} lh={1.2}>Daftar Foto</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"

View File

@@ -73,7 +73,7 @@ function ListVideo({ search }: { search: string }) {
<Box py={20}> <Box py={20}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Video Daftar Video
</Title> </Title>
<Button <Button
@@ -152,7 +152,7 @@ function ListVideo({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} p="sm" withBorder radius="sm"> <Paper key={item.id} p="sm" withBorder radius="sm">
<Stack gap={4}> <Stack gap={"xs"}>
<Box> <Box>
<Text fz="xs" fw={600} lh={1.4}>Judul Video</Text> <Text fz="xs" fw={600} lh={1.4}>Judul Video</Text>
<Text fz="sm" fw={500} lh={1.45}> <Text fz="sm" fw={500} lh={1.45}>

View File

@@ -142,7 +142,7 @@ function ListAjukanPermohonan({ search }: { search: string }) {
{data.length > 0 ? ( {data.length > 0 ? (
data.map((item) => ( data.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md" shadow="xs"> <Paper key={item.id} withBorder p="md" radius="md" shadow="xs">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text> <Text fz="sm" fw={600} lh={1.4}>Nama</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.nama}</Text> <Text fz="sm" fw={500} lh={1.5}>{item.nama}</Text>

View File

@@ -75,20 +75,21 @@ function DetailSuratKeterangan() {
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs"> <Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm"> <Stack gap="sm">
<Box> <Stack gap={"xs"}>
<Text fz="lg" fw="bold"> <Text fz="lg" fw="bold">
Nama Nama
</Text> </Text>
<Text fz="md" c="dimmed"> <Text fz="md" c="dimmed">
{data?.name || '-'} {data?.name || '-'}
</Text> </Text>
</Box> </Stack>
<Box> <Stack gap={"xs"}>
<Text fz="lg" fw="bold"> <Text fz="lg" fw="bold">
Deskripsi Deskripsi
</Text> </Text>
<Text <Box pl={10}>
<Text
fz="md" fz="md"
c="dimmed" c="dimmed"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@@ -96,9 +97,10 @@ function DetailSuratKeterangan() {
}} }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }} style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/> />
</Box> </Box>
</Stack>
<Box> <Stack gap={"xs"}>
<Text fz="lg" fw="bold"> <Text fz="lg" fw="bold">
Gambar Konten Pelayanan Gambar Konten Pelayanan
</Text> </Text>
@@ -117,7 +119,7 @@ function DetailSuratKeterangan() {
Tidak ada gambar Tidak ada gambar
</Text> </Text>
)} )}
</Box> </Stack>
<Box> <Box>
<Text fz="lg" fw="bold"> <Text fz="lg" fw="bold">

View File

@@ -82,7 +82,7 @@ function ListSuratKeterangan({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'md' }}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
List Surat Keterangan List Surat Keterangan
</Title> </Title>
<Button <Button
@@ -169,7 +169,7 @@ function ListSuratKeterangan({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md"> <Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="sm" fw={600} lh={1.4}> <Text fz="sm" fw={600} lh={1.4}>
Nama Nama

View File

@@ -69,7 +69,7 @@ function ListPelayananTelunjukSakti({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'md' }}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Pelayanan Telunjuk Sakti Daftar Pelayanan Telunjuk Sakti
</Title> </Title>
<Button <Button

View File

@@ -69,8 +69,7 @@ function ListPenghargaan({ search }: { search: string }) {
<Box py="md"> <Box py="md">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="lg"> <Group justify="space-between" mb="lg">
<Title order={2} visibleFrom="md">List Penghargaan</Title> <Title order={4}>List Penghargaan</Title>
<Title order={3} hiddenFrom="md">List Penghargaan</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"

View File

@@ -82,7 +82,7 @@ function ListKategoriPengumuman({ search }: { search: string }) {
visibleFrom="md" visibleFrom="md"
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
> >
<Title order={2} lh={1.1}> <Title order={4} lh={1.1}>
List Kategori Pengumuman List Kategori Pengumuman
</Title> </Title>
<Button <Button

View File

@@ -68,7 +68,7 @@ function ListPengumuman({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'md' }}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Pengumuman Daftar Pengumuman
</Title> </Title>
<Button <Button

View File

@@ -80,7 +80,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Stack gap="xl"> <Stack gap="xl">
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
List Kategori Potensi List Kategori Potensi
</Title> </Title>
<Button <Button
@@ -174,7 +174,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item, index) => ( filteredData.map((item, index) => (
<Paper key={item.id} withBorder p="sm" radius="md"> <Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="xs" fw={600} lh={1.4}> <Text fz="xs" fw={600} lh={1.4}>
No No

View File

@@ -76,7 +76,7 @@ function ListPotensi({ search }: { search: string }) {
<Box py="lg"> <Box py="lg">
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Potensi Desa Daftar Potensi Desa
</Title> </Title>
<Button <Button

View File

@@ -66,7 +66,7 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'md' }}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify='space-between' mb={{ base: 'sm', md: 'md' }}> <Group justify='space-between' mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
List Perbekel Dari Masa Ke Masa List Perbekel Dari Masa Ke Masa
</Title> </Title>
<Button <Button
@@ -134,7 +134,7 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md"> <Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="xs" fw={600} lh={1.4} c="dark.9">Nama Perbekel</Text> <Text fz="xs" fw={600} lh={1.4} c="dark.9">Nama Perbekel</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.nama}</Text> <Text fz="sm" fw={500} lh={1.5}>{item.nama}</Text>

View File

@@ -1,7 +1,7 @@
/* 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 { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconActivity, IconBuildingHospital, IconCalendarEvent, IconGauge, IconNotes } from '@tabler/icons-react'; 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';
@@ -81,52 +81,93 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false} keepMounted={false}
> >
{/* ✅ Scroll horizontal wrapper */} {/* ✅ Scroll horizontal wrapper */}
<ScrollArea type="auto" offsetScrollbars> <Box visibleFrom='md' pb={10}>
<TabsList <ScrollArea type="auto" offsetScrollbars>
p="sm" <TabsList
p="sm"
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
<Box hiddenFrom='md' pb={10}>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => (
<TabsPanel
key={i}
value={tab.value}
style={{ style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)", padding: "1.5rem",
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
borderRadius: "1rem", borderRadius: "1rem",
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)", boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}} }}
> >
{tabs.map((tab, i) => ( {children}
<TabsTab </TabsPanel>
key={i} ))}
value={tab.value} </Tabs>
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
transition: "all 0.2s ease",
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
{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 > </Stack >
); );
} }

View File

@@ -147,7 +147,7 @@ function EditArtikelKesehatan() {
); );
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -49,7 +49,7 @@ function DetailArtikelKesehatan() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -63,7 +63,7 @@ function DetailArtikelKesehatan() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '70%' }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -94,7 +94,7 @@ function CreateArtikelKesehatan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}> <Box px={{ base: 0, md: 'lg' }} py="xs" component="form" onSubmit={handleSubmit}>
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -50,10 +50,11 @@ function ListArtikelKesehatan({ search }: { search: string }) {
const router = useRouter(); const router = useRouter();
const { data, page, totalPages, loading, load } = stateArtikel.findMany; const { data, page, totalPages, loading, load } = stateArtikel.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000);
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];

View File

@@ -131,7 +131,7 @@ function EditFasilitasKesehatan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}> <Box px={{ base: 0, md: 'xs' }} py="xs" component="form" onSubmit={handleSubmit}>
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -56,7 +56,7 @@ function DetailFasilitasKesehatan() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -83,12 +83,12 @@ function DetailFasilitasKesehatan() {
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs"> <Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm"> <Stack gap="sm">
<Box> <Box pl={10}>
<Text fz="lg" fw="bold">Nama Fasilitas</Text> <Text fz="lg" fw="bold">Nama Fasilitas</Text>
<Text fz="md" c="dimmed">{data.name || '-'}</Text> <Text fz="md" c="dimmed">{data.name || '-'}</Text>
</Box> </Box>
<Box> <Box pl={10}>
<Text fz="lg" fw="bold">Informasi Umum</Text> <Text fz="lg" fw="bold">Informasi Umum</Text>
<Text fz="md" fw="bold">Fasilitas</Text> <Text fz="md" fw="bold">Fasilitas</Text>
<Text fz="md" c="dimmed">{data.informasiumum?.fasilitas || '-'}</Text> <Text fz="md" c="dimmed">{data.informasiumum?.fasilitas || '-'}</Text>
@@ -98,22 +98,22 @@ function DetailFasilitasKesehatan() {
<Text fz="md" c="dimmed">{data.informasiumum?.jamOperasional || '-'}</Text> <Text fz="md" c="dimmed">{data.informasiumum?.jamOperasional || '-'}</Text>
</Box> </Box>
<Box> <Box pl={10}>
<Text fz="lg" fw="bold">Layanan Unggulan</Text> <Text fz="lg" fw="bold">Layanan Unggulan</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.layananunggulan?.content || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.layananunggulan?.content || '-' }} />
</Box> </Box>
<Box> <Box pl={10}>
<Text fz="lg" fw="bold">Fasilitas Pendukung</Text> <Text fz="lg" fw="bold">Fasilitas Pendukung</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.fasilitaspendukung?.content || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.fasilitaspendukung?.content || '-' }} />
</Box> </Box>
<Box> <Box pl={10}>
<Text fz="lg" fw="bold">Prosedur Pendaftaran</Text> <Text fz="lg" fw="bold">Prosedur Pendaftaran</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.prosedurpendaftaran?.content || '-' }} /> <Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.prosedurpendaftaran?.content || '-' }} />
</Box> </Box>
<Box> <Box pl={10}>
<Text fz="lg" fw="bold" mb="sm">Dokter & Tenaga Medis</Text> <Text fz="lg" fw="bold" mb="sm">Dokter & Tenaga Medis</Text>
{Array.isArray(data.dokterdantenagamedis) && data.dokterdantenagamedis.length > 0 ? ( {Array.isArray(data.dokterdantenagamedis) && data.dokterdantenagamedis.length > 0 ? (
<Box style={{ overflowX: 'auto', width: '100%' }}> <Box style={{ overflowX: 'auto', width: '100%' }}>
@@ -159,7 +159,7 @@ function DetailFasilitasKesehatan() {
)} )}
</Box> </Box>
<Box mt="xl"> <Box pl={10} mt="xl">
<Text fz="lg" fw="bold" mb="sm">Tarif & Layanan</Text> <Text fz="lg" fw="bold" mb="sm">Tarif & Layanan</Text>
{Array.isArray(data.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? ( {Array.isArray(data.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? (
<Box style={{ overflowX: 'auto', width: '100%' }}> <Box style={{ overflowX: 'auto', width: '100%' }}>

View File

@@ -70,7 +70,7 @@ function CreateFasilitasKesehatan() {
}, []); }, []);
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}> <Box px={{ base: 0, md: 'xs' }} py="xs" component="form" onSubmit={handleSubmit}>
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -125,7 +125,7 @@ function EditDokterTenagaMedis() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -50,7 +50,7 @@ function DetailDokterTenagaMedis() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"

View File

@@ -56,7 +56,7 @@ function CreateDokter() {
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}> <Box px={{ base: 0, md: 'lg' }} py="xs" component="form" onSubmit={handleSubmit}>
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconDeviceImacCog, IconPlus, 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';
@@ -10,13 +10,12 @@ import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
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 { useState } from 'react'; import { useState } from 'react';
function DokterTenagaMedis() { function DokterTenagaMedis() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const router = useRouter(); const router = useRouter();
return ( return (
<Box> <Box>
<Box mb={10}> <Box mb="sm">
<Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan')}> <Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan')}>
<IconArrowBack color={colors['blue-button']} size={25} /> <IconArrowBack color={colors['blue-button']} size={25} />
</Button> </Button>
@@ -44,25 +43,29 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
totalPages totalPages
} = stateFasilitasKesehatan.findMany } = stateFasilitasKesehatan.findMany
const [debouncedSearch] = useDebouncedValue(search, 1000);
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch)
}, [page, search]) }, [page, debouncedSearch])
const filteredData = data || [] const filteredData = data || []
if (loading || !data) { if (loading || !data) {
return ( return (
<Box py={10}> <Box py="md">
<Skeleton h={500} /> <Skeleton h={500} />
</Box> </Box>
) )
} }
return ( return (
<Box py={10}> <Box py="md">
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Dokter dan Tenaga Medis</Title> <Title order={3} visibleFrom="md">Daftar Dokter dan Tenaga Medis</Title>
<Title order={4} hiddenFrom="md">Daftar Dokter dan Tenaga Medis</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -77,15 +80,15 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama Dokter</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Nama Dokter</Text></TableTh>
<TableTh>Spesialis</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Spesialis</Text></TableTh>
<TableTh>Jadwal</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Jadwal</Text></TableTh>
<TableTh>Aksi</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Aksi</Text></TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -93,21 +96,17 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5}>
{item.specialist || '-'} {item.specialist || '-'}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} dangerouslySetInnerHTML={{ __html: item.jadwal || '-' }} />
<Text dangerouslySetInnerHTML={{ __html: item.jadwal || '-' }} />
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -120,7 +119,7 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
} }
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml="xs" fz="sm" fw={500}>Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -128,8 +127,8 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py="xl">
<Text c="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada fasilitas kesehatan yang cocok Tidak ada fasilitas kesehatan yang cocok
</Text> </Text>
</Center> </Center>
@@ -139,6 +138,47 @@ function ListDokterTenagaMedis({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" mb="xs" radius="sm">
<Box mb="xs">
<Text fz="sm" fw={600} lh={1.4}>Nama Dokter</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.name}</Text>
</Box>
<Box mb="xs">
<Text fz="sm" fw={600} lh={1.4}>Spesialis</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.specialist || '-'}</Text>
</Box>
<Box mb="md">
<Text fz="sm" fw={600} lh={1.4}>Jadwal</Text>
<Text fz="sm" fw={500} lh={1.5} dangerouslySetInnerHTML={{ __html: item.jadwal || '-' }} />
</Box>
<Button
fullWidth
variant="light"
color="blue"
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/${item.id}`
)
}
>
<IconDeviceImacCog size={20} />
<Text ml="xs" fz="sm" fw={500}>Detail</Text>
</Button>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada fasilitas kesehatan yang cocok
</Text>
</Center>
)}
</Box>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}

View File

@@ -5,8 +5,6 @@ import {
Box, Box,
Button, Button,
Center, Center,
Grid,
GridCol,
Group, Group,
Pagination, Pagination,
Paper, Paper,
@@ -23,82 +21,138 @@ import {
Title, Title,
Tooltip Tooltip
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconCoin, IconDeviceImacCog, IconPlus, IconReportMedical, IconSearch } from '@tabler/icons-react'; import { IconCoin, IconDeviceImacCog, IconPlus, IconReportMedical, 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 fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
function FasilitasKesehatan() { function FasilitasKesehatan() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const router = useRouter() const router = useRouter();
return ( return (
<Box> <Box>
<Grid mb={10}> <Paper p="lg" radius="md" mb="lg" bg={colors['white-1']} shadow="sm">
<GridCol span={{ base: 12, md: 8 }}> <Group justify='space-between' visibleFrom='md'>
<Title order={3}>Fasilitas Kesehatan</Title> <Title order={2} visibleFrom="md" size="lg" lh={1.2}>
</GridCol> Fasilitas Kesehatan
<GridCol span={{ base: 12, md: 4 }}> </Title>
<Group gap={"xs"}> <Title order={2} hiddenFrom="md" size="md" lh={1.2}>
Fasilitas Kesehatan
</Title>
<Group gap="xs" justify="flex-end">
<Tooltip label="List Dokter" withArrow> <Tooltip label="List Dokter" withArrow>
<ActionIcon onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis')} size="lg" radius="xl" color="green.6"> <ActionIcon
onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis')}
size="lg"
radius="xl"
color="green.6"
>
<IconReportMedical size={20} /> <IconReportMedical size={20} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip label="List Tarif Layanan" withArrow> <Tooltip label="List Tarif Layanan" withArrow>
<ActionIcon onClick={()=> router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan')} size="lg" radius="xl" color="blue.6"> <ActionIcon
onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan')}
size="lg"
radius="xl"
color="blue.6"
>
<IconCoin size={20} /> <IconCoin size={20} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Paper radius="lg" bg={colors['white-1']}>
<TextInput <TextInput
radius="lg" radius="lg"
placeholder='Cari nama, alamat, atau jam operasional...' placeholder='Cari nama, alamat, atau jam operasional...'
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w="133%"
value={search} value={search}
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch(e.currentTarget.value)}
fz={{ base: 'xs', md: 'sm' }}
px="sm"
py="xs"
/> />
</Paper>
</Group> </Group>
</GridCol> </Group>
</Grid>
<Group justify='space-between' hiddenFrom='md'>
<Title order={2} visibleFrom="md" size="lg" lh={1.2}>
Fasilitas Kesehatan
</Title>
<Title order={2} hiddenFrom="md" size="md" lh={1.2}>
Fasilitas Kesehatan
</Title>
<Group gap="xs" justify="flex-start">
<Tooltip label="List Dokter" withArrow>
<ActionIcon
onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis')}
size="lg"
radius="xl"
color="green.6"
>
<IconReportMedical size={20} />
</ActionIcon>
</Tooltip>
<Tooltip label="List Tarif Layanan" withArrow>
<ActionIcon
onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan')}
size="lg"
radius="xl"
color="blue.6"
>
<IconCoin size={20} />
</ActionIcon>
</Tooltip>
<TextInput
radius="lg"
placeholder='Cari nama, alamat, atau jam operasional...'
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
fz={{ base: 'xs', md: 'sm' }}
px="sm"
py="xs"
/>
</Group>
</Group>
</Paper>
<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 [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = stateFasilitasKesehatan.findMany; const { data, page, totalPages, loading, load } = stateFasilitasKesehatan.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, debouncedSearch);
load(page, 10, search); }, [page, debouncedSearch]);
}, [page, search]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="lg">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
) );
} }
return ( return (
<Box py={10}> <Box py="lg">
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
{/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Fasilitas Kesehatan</Title> <Title order={3} visibleFrom="md" size="md" lh={1.2}>
Daftar Fasilitas Kesehatan
</Title>
<Title order={3} hiddenFrom="md" size="sm" lh={1.2}>
Daftar Fasilitas Kesehatan
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -108,13 +162,15 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
'/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create' '/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
) )
} }
fz={{ base: 'sm', md: 'md' }}
px="sm"
> >
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
@@ -129,27 +185,23 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5}>
{item.dokterdantenagamedis?.length {item.dokterdantenagamedis?.length
? `${item.dokterdantenagamedis.length} dokter` ? `${item.dokterdantenagamedis.length} dokter`
: '-'} : '-'}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5}>
<Text truncate="end" lineClamp={1}> {item.tarifdanlayanan?.length
{item.tarifdanlayanan?.length ? `${item.tarifdanlayanan.length} layanan`
? `${item.tarifdanlayanan.length} layanan` : '-'}
: '-'} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -160,6 +212,9 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}` `/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`
) )
} }
fz="sm"
px="sm"
h={36}
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml={5}>Detail</Text>
@@ -170,8 +225,8 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py="xl">
<Text c="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada fasilitas kesehatan yang cocok Tidak ada fasilitas kesehatan yang cocok
</Text> </Text>
</Center> </Center>
@@ -181,6 +236,65 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card View */}
<Stack gap="sm" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Box mb="xs">
<Text fz="xs" fw={600} lh={1.4}>
Fasilitas Kesehatan
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.name}
</Text>
</Box>
<Box mb="xs">
<Text fz="xs" fw={600} lh={1.4}>
Jumlah Dokter
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.dokterdantenagamedis?.length
? `${item.dokterdantenagamedis.length} dokter`
: '-'}
</Text>
</Box>
<Box mb="xs">
<Text fz="xs" fw={600} lh={1.4}>
Jumlah Layanan
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.tarifdanlayanan?.length
? `${item.tarifdanlayanan.length} layanan`
: '-'}
</Text>
</Box>
<Button
variant="light"
color="blue"
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`
)
}
fullWidth
fz="sm"
mt="md"
>
<IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text>
</Button>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada fasilitas kesehatan yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
@@ -199,7 +313,7 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
/> />
</Center> </Center>
</Box> </Box>
) );
} }
export default FasilitasKesehatan; export default FasilitasKesehatan;

View File

@@ -95,7 +95,7 @@ function EditTarifLayanan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Back Button + Title */} {/* Back Button + Title */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -44,7 +44,7 @@ function CreateTarifLayanan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header dengan back button */} {/* Header dengan back button */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { IconArrowBack, IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -11,13 +11,12 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirma
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 { useState } from 'react'; import { useState } from 'react';
function TarifLayanan() { function TarifLayanan() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const router = useRouter(); const router = useRouter();
return ( return (
<Box> <Box>
<Box mb={10}> <Box mb="sm">
<Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan')}> <Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan')}>
<IconArrowBack color={colors['blue-button']} size={25} /> <IconArrowBack color={colors['blue-button']} size={25} />
</Button> </Button>
@@ -39,6 +38,7 @@ function ListTarifLayanan({ search }: { search: string }) {
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);
const [debouncedSearch] = useDebouncedValue(search, 10000);
const { const {
data, data,
@@ -46,36 +46,39 @@ function ListTarifLayanan({ search }: { search: string }) {
load, load,
page, page,
totalPages totalPages
} = stateFasilitasKesehatan.findMany } = stateFasilitasKesehatan.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const handleDelete = () => { const handleDelete = () => {
if (selectedId) { if (selectedId) {
stateFasilitasKesehatan.delete.byId(selectedId); stateFasilitasKesehatan.delete.byId(selectedId);
setModalHapus(false); setModalHapus(false);
setSelectedId(null); setSelectedId(null);
load(page, 10, search); load(page, 10, debouncedSearch);
} }
}; };
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Box py={10}> <Box py="lg">
<Skeleton h={500} /> <Skeleton h={500} />
</Box> </Box>
) );
} }
return ( return (
<Box py={10}> <Box py="lg">
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Tarif dan Layanan</Title> <Title order={4} lh={1.2}>
Daftar Tarif dan Layanan
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -90,15 +93,31 @@ function ListTarifLayanan({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Layanan</TableTh> <TableTh>
<TableTh>Tarif</TableTh> <Text fz="sm" fw={600} lh={1.4} ta="left">
<TableTh>Edit</TableTh> Layanan
<TableTh>Hapus</TableTh> </Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.4} ta="left">
Tarif
</Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.4} ta="center">
Edit
</Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.4} ta="center">
Hapus
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -106,18 +125,16 @@ function ListTarifLayanan({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
{item.layanan || '-'} {item.layanan || '-'}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.tarif}
{item.tarif} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd ta="center">
<Button <Button
variant="light" variant="light"
color="green" color="green"
@@ -126,11 +143,12 @@ function ListTarifLayanan({ search }: { search: string }) {
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan/${item.id}` `/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan/${item.id}`
) )
} }
size="compact-sm"
> >
<IconEdit size={18} /> <IconEdit size={18} />
</Button> </Button>
</TableTd> </TableTd>
<TableTd> <TableTd ta="center">
<Button <Button
variant="light" variant="light"
color="red" color="red"
@@ -139,6 +157,7 @@ function ListTarifLayanan({ search }: { search: string }) {
setSelectedId(item.id); setSelectedId(item.id);
setModalHapus(true); setModalHapus(true);
}} }}
size="compact-sm"
> >
<IconTrash size={18} /> <IconTrash size={18} />
</Button> </Button>
@@ -148,8 +167,8 @@ function ListTarifLayanan({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py="lg">
<Text c="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada fasilitas kesehatan yang cocok Tidak ada fasilitas kesehatan yang cocok
</Text> </Text>
</Center> </Center>
@@ -159,6 +178,64 @@ function ListTarifLayanan({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" mb="xs" radius="sm">
<Box mb="xs">
<Text fz="sm" fw={600} lh={1.4}>
Layanan
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.layanan || '-'}
</Text>
</Box>
<Box mb="md">
<Text fz="sm" fw={600} lh={1.4}>
Tarif
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.tarif}
</Text>
</Box>
<Group justify="center" gap="xs">
<Button
variant="light"
color="green"
size="compact-xs"
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan/${item.id}`
)
}
>
<IconEdit size={16} />
</Button>
<Button
variant="light"
color="red"
size="compact-xs"
disabled={stateFasilitasKesehatan.delete.loading}
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
<IconTrash size={16} />
</Button>
</Group>
</Paper>
))
) : (
<Center py="lg">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada fasilitas kesehatan yang cocok
</Text>
</Center>
)}
</Box>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
@@ -176,6 +253,7 @@ function ListTarifLayanan({ search }: { search: string }) {
radius="md" radius="md"
/> />
</Center> </Center>
<ModalKonfirmasiHapus <ModalKonfirmasiHapus
opened={modalHapus} opened={modalHapus}
onClose={() => setModalHapus(false)} onClose={() => setModalHapus(false)}
@@ -183,7 +261,7 @@ function ListTarifLayanan({ search }: { search: string }) {
text="Apakah anda yakin ingin menghapus tarif layanan ini?" text="Apakah anda yakin ingin menghapus tarif layanan ini?"
/> />
</Box> </Box>
) );
} }
export default TarifLayanan; export default TarifLayanan;

View File

@@ -145,7 +145,7 @@ function EditJadwalKegiatan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -40,7 +40,7 @@ function DetailJadwalKegiatan() {
const data = stateJadwalKegiatan.findUnique.data const data = stateJadwalKegiatan.findUnique.data
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -54,7 +54,7 @@ function DetailJadwalKegiatan() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -65,7 +65,7 @@ function CreateJadwalKegiatan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}> <Box px={{ base: 0, md: 'lg' }} py="xs" component="form" onSubmit={handleSubmit}>
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -50,23 +50,24 @@ function ListJadwalKegiatan({ search }: { search: string }) {
const router = useRouter(); const router = useRouter();
const { data, page, totalPages, loading, load } = state.findMany; const { data, page, totalPages, loading, load } = state.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000);
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="lg">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py="lg">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
@@ -83,16 +84,16 @@ function ListJadwalKegiatan({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama</TableTh> <TableTh fz="sm" fw={600} lh={1.2}>Nama</TableTh>
<TableTh>Tanggal</TableTh> <TableTh fz="sm" fw={600} lh={1.2}>Tanggal</TableTh>
<TableTh>Waktu</TableTh> <TableTh fz="sm" fw={600} lh={1.2}>Waktu</TableTh>
<TableTh>Lokasi</TableTh> <TableTh fz="sm" fw={600} lh={1.2}>Lokasi</TableTh>
<TableTh>Aksi</TableTh> <TableTh fz="sm" fw={600} lh={1.2}>Aksi</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -100,14 +101,12 @@ function ListJadwalKegiatan({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.informasijadwalkegiatan.name}
{item.informasijadwalkegiatan.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5}>
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString( {new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString(
'id-ID', 'id-ID',
{ {
@@ -116,19 +115,17 @@ function ListJadwalKegiatan({ search }: { search: string }) {
year: 'numeric', year: 'numeric',
} }
)} )}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5}>
{item.informasijadwalkegiatan.waktu} {item.informasijadwalkegiatan.waktu}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5} c="dimmed" truncate="end">
<Text truncate fz="sm" c="dimmed"> {item.informasijadwalkegiatan.lokasi}
{item.informasijadwalkegiatan.lokasi} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -141,7 +138,9 @@ function ListJadwalKegiatan({ search }: { search: string }) {
} }
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml={5} fz="sm" fw={500} lh={1.5}>
Detail
</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -149,8 +148,8 @@ function ListJadwalKegiatan({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={5}> <TableTd colSpan={5}>
<Center py={20}> <Center py="xl">
<Text color="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada jadwal kegiatan yang cocok Tidak ada jadwal kegiatan yang cocok
</Text> </Text>
</Center> </Center>
@@ -160,6 +159,72 @@ function ListJadwalKegiatan({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Stack gap="sm" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
<Text fz="sm" fw={500} lh={1.5}>
{item.informasijadwalkegiatan.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Tanggal</Text>
<Text fz="sm" fw={500} lh={1.5}>
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString(
'id-ID',
{
day: '2-digit',
month: 'long',
year: 'numeric',
}
)}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Waktu</Text>
<Text fz="sm" fw={500} lh={1.5}>
{item.informasijadwalkegiatan.waktu}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Lokasi</Text>
<Text fz="sm" fw={500} lh={1.5} c="dimmed">
{item.informasijadwalkegiatan.lokasi}
</Text>
</Box>
<Box>
<Button
fullWidth
variant="light"
color="blue"
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan/${item.id}`
)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5} fz="sm" fw={500} lh={1.5}>
Detail
</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada jadwal kegiatan yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}

View File

@@ -110,7 +110,7 @@ function EditGrafikHasilKepuasan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -41,7 +41,7 @@ function DetailGrafikHasilKepuasan() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -55,7 +55,7 @@ function DetailGrafikHasilKepuasan() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -50,7 +50,7 @@ function CreateGrafikHasilKepuasanMasyarakat() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* 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 { import {
Box, Box,
@@ -18,26 +18,32 @@ import {
TableThead, TableThead,
TableTr, TableTr,
Text, Text,
Title Title,
} from '@mantine/core'; } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { 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, Tooltip as ChartTooltip, Legend, 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 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('');
return ( return (
<Box> <Box>
{/* Header Search */}
<HeaderSearch <HeaderSearch
title='Penderita Penyakit' title="Penderita Penyakit"
placeholder='Cari nama atau alamat...' 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)}
@@ -59,9 +65,9 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
}; };
const stateGrafikKepuasan = useProxy(grafikkepuasan); const stateGrafikKepuasan = useProxy(grafikkepuasan);
const [debouncedSearch] = useDebouncedValue(search, 1000);
const [chartData, setChartData] = useState<PDKMGrafik[]>([]); const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const isTablet = useMediaQuery('(max-width: 1024px)');
const isMobile = useMediaQuery('(max-width: 768px)'); const isMobile = useMediaQuery('(max-width: 768px)');
const router = useRouter(); const router = useRouter();
@@ -69,21 +75,26 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
useShallowEffect(() => { useShallowEffect(() => {
setMounted(true); setMounted(true);
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
setChartData(data.map((item) => ({ setChartData(
...item, data.map((item) => ({
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal ...item,
}))); tanggal:
item.tanggal instanceof Date
? item.tanggal.toISOString()
: item.tanggal,
}))
);
} }
}, [data]); }, [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;
@@ -92,7 +103,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
return Object.entries(diseaseCount).map(([name, count]) => ({ name, count })); return Object.entries(diseaseCount).map(([name, count]) => ({ name, count }));
}; };
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]); const [diseaseChartData, setDiseaseChartData] = useState<{ name: string; count: number }[]>([]);
useEffect(() => { useEffect(() => {
if (data && data.length > 0) { if (data && data.length > 0) {
@@ -104,18 +115,23 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'md', md: 'lg' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Stack gap='lg' py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> {/* Daftar Penderita Penyakit */}
{/* Judul + Tombol Tambah */} <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Penderita Penyakit</Title> <Title
order={4}
lh={{ base: 1.2, md: 1.15 }}
>
Daftar Penderita Penyakit
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -130,8 +146,8 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
@@ -146,29 +162,21 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd fz="md" fw={500} lh={1.5}>
<Box w={150}> {item.nama}
{item.nama}
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd fz="md" fw={500} lh={1.5}>
<Box w={150}> {new Date(item.tanggal).toLocaleDateString('id-ID', {
{new Date(item.tanggal).toLocaleDateString('id-ID', { day: '2-digit',
day: '2-digit', month: 'long',
month: 'long', year: 'numeric',
year: 'numeric', })}
})}
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd fz="md" fw={500} lh={1.5}>
<Box w={150}> {item.jenisKelamin}
{item.jenisKelamin}
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd fz="md" fw={500} lh={1.5}>
<Box w={150}> {item.penyakit}
{item.penyakit}
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -181,7 +189,9 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
} }
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml={5} fz="sm" fw={500}>
Detail
</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -190,7 +200,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
<TableTr> <TableTr>
<TableTd colSpan={5}> <TableTd colSpan={5}>
<Center py={20}> <Center py={20}>
<Text color="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kepuasan masyarakat yang cocok Tidak ada data kepuasan masyarakat yang cocok
</Text> </Text>
</Center> </Center>
@@ -200,6 +210,72 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card View */}
<Stack gap="xs" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={4}>
<Text fz="sm" fw={600} lh={1.4}>
Nama
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.nama}
</Text>
<Text fz="sm" fw={600} lh={1.4}>
Tanggal
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})}
</Text>
<Text fz="sm" fw={600} lh={1.4}>
Jenis Kelamin
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.jenisKelamin}
</Text>
<Text fz="sm" fw={600} lh={1.4}>
Penyakit
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.penyakit}
</Text>
<Button
variant="light"
color="blue"
fullWidth
mt="xs"
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/${item.id}`
)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5} fz="sm" fw={500}>
Detail
</Text>
</Button>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kepuasan masyarakat yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
@@ -218,37 +294,46 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
/> />
</Center> </Center>
{/* Chart */} {/* Chart Section */}
<Box mt="lg" style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Paper withBorder bg={colors['white-1']} p={'md'}> <Title
<Title pb={10} order={4}>Penderita Penyakit</Title> order={2}
{mounted && diseaseChartData.length > 0 ? ( lh={{ base: 1.2, md: 1.15 }}
<Center> mb={{ base: 'sm', md: 'md' }}
<BarChart >
width={isMobile ? 320 : isTablet ? 600 : 800} // kecilin biar muat Penderita Penyakit
height={350} </Title>
data={diseaseChartData}
> {mounted && diseaseChartData.length > 0 ? (
<XAxis <Center>
dataKey="name" <BarChart
tick={{ fontSize: 12 }} width={isMobile ? 320 : 800}
interval={0} height={350}
angle={-45} data={diseaseChartData}
textAnchor="end" >
height={70} <XAxis
/> dataKey="name"
<YAxis /> tick={{ fontSize: 12 }}
<ChartTooltip /> interval={0}
<Legend /> angle={-45}
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" /> textAnchor="end"
</BarChart> height={70}
</Center> />
) : ( <YAxis />
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text> <ChartTooltip />
)} <Legend />
</Paper> <Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
</Box> </BarChart>
</Box> </Center>
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
</Center>
)}
</Paper>
</Stack>
); );
} }

View File

@@ -106,7 +106,7 @@ function EditKelahiran() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -50,7 +50,7 @@ function DetailKelahiran() {
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -65,7 +65,7 @@ function DetailKelahiran() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -52,7 +52,7 @@ function CreateKelahiran() {
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -20,7 +20,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconDeviceImacCog, IconPlus, 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';
@@ -31,17 +31,15 @@ function Kelahiran() {
const router = useRouter(); const router = useRouter();
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
return ( return (
<Box> <Box>
{/* Tombol Back */} {/* Tombol Back */}
<Box mb={10}> <Box mb="sm">
<Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian')}> <Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian')}>
<IconArrowBack color={colors["blue-button"]} size={25} /> <IconArrowBack color={colors["blue-button"]} size={25} />
</Button> </Button>
</Box> </Box>
{/* Header Search */} {/* Header Search */}
<HeaderSearch <HeaderSearch
title='Data Kelahiran' title='Data Kelahiran'
@@ -51,7 +49,6 @@ function Kelahiran() {
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch(e.currentTarget.value)}
/> />
<ListKelahiran search={search} /> <ListKelahiran search={search} />
</Box> </Box>
); );
@@ -61,34 +58,32 @@ function Kelahiran() {
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 [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const { data, page, totalPages, loading, load } = statePersentase.findMany; const { data, page, totalPages, loading, load } = statePersentase.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="md">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Stack py="md" gap="xl">
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="lg">
<Title order={4}>Daftar Data Kelahiran</Title> <Title order={2} lh={1.2}>
Daftar Data Kelahiran
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -103,17 +98,16 @@ function ListKelahiran({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Desktop Table */}
{/* Tabel */} <Box visibleFrom="md">
<Box style={{ overflowX: "auto" }}> <Table highlightOnHover fz="md">
<Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Nama</Text></TableTh>
<TableTh>Tanggal</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Tanggal</Text></TableTh>
<TableTh>Jenis Kelamin</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Jenis Kelamin</Text></TableTh>
<TableTh>Alamat</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Alamat</Text></TableTh>
<TableTh>Aksi</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.4}>Aksi</Text></TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -121,32 +115,28 @@ function ListKelahiran({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.nama}
{item.nama} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5}>
{new Date(item.tanggal).toLocaleDateString('id-ID', { {new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit', day: '2-digit',
month: 'long', month: 'long',
year: 'numeric', year: 'numeric',
})} })}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5}>
{item.jenisKelamin} {item.jenisKelamin}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" lh={1.5} c="dimmed" truncate="end" lineClamp={1}>
<Text truncate fz="sm" c="dimmed"> {item.alamat}
{item.alamat} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -157,9 +147,10 @@ function ListKelahiran({ search }: { search: string }) {
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}` `/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`
) )
} }
size="compact-sm"
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={18} />
<Text ml={5}>Detail</Text> <Text ml="xs">Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -167,8 +158,8 @@ function ListKelahiran({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={5}> <TableTd colSpan={5}>
<Center py={20}> <Center py="lg">
<Text color="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kelahiran yang cocok Tidak ada data kelahiran yang cocok
</Text> </Text>
</Center> </Center>
@@ -178,27 +169,83 @@ function ListKelahiran({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card View */}
<Stack hiddenFrom="md" gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} p="md" withBorder radius="sm">
<Stack gap={4}>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Nama</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.nama}</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Tanggal</Text>
<Text fz="sm" fw={500} lh={1.5}>
{new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})}
</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Jenis Kelamin</Text>
<Text fz="sm" fw={500} lh={1.5}>{item.jenisKelamin}</Text>
</Box>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Alamat</Text>
<Text fz="sm" fw={500} lh={1.5} c="dimmed">{item.alamat}</Text>
</Box>
<Box>
<Button
variant="light"
color="blue"
fullWidth
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`
)
}
size="sm"
>
<IconDeviceImacCog size={18} />
<Text ml="xs">Detail</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="lg">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kelahiran yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
<Center> {totalPages > 1 && (
<Pagination <Center>
value={page} <Pagination
onChange={(newPage) => { value={page}
load(newPage, 10); onChange={(newPage) => {
window.scrollTo({ top: 0, behavior: 'smooth' }); load(newPage, 10);
}} window.scrollTo({ top: 0, behavior: 'smooth' });
total={totalPages} }}
mt="md" total={totalPages}
mb="md" mt="md"
color="blue" mb="md"
radius="md" color="blue"
/> radius="md"
</Center> />
</Box> </Center>
)}
</Stack>
); );
} }
export default Kelahiran; export default Kelahiran;

View File

@@ -114,7 +114,7 @@ function EditKematian() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -48,7 +48,7 @@ function DetailKematian() {
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */} {/* Tombol kembali */}
<Button <Button
variant="subtle" variant="subtle"
@@ -62,7 +62,7 @@ function DetailKematian() {
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -60,7 +60,7 @@ function CreateKematian() {
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -20,28 +20,25 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconPlus, 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 Kematian() { function Kematian() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const router = useRouter(); const router = useRouter();
return ( return (
<Box> <Box>
{/* Tombol Back */} {/* Tombol Back */}
<Box mb={10}> <Box mb="md">
<Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian')}> <Button variant="subtle" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian')}>
<IconArrowBack color={colors["blue-button"]} size={30} /> <IconArrowBack color={colors["blue-button"]} size={30} />
</Button> </Button>
</Box> </Box>
{/* Header dengan Search */} {/* Header dengan Search */}
<HeaderSearch <HeaderSearch
title='Data Kematian' title='Data Kematian'
@@ -51,43 +48,38 @@ function Kematian() {
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch(e.currentTarget.value)}
/> />
<ListKematian search={search} /> <ListKematian search={search} />
</Box> </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 [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = statePersentase.findMany; const { data, page, totalPages, loading, load } = statePersentase.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="md">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py="md">
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Daftar Data Kematian</Title> <Title order={2} visibleFrom="md">Daftar Data Kematian</Title>
<Title order={3} hiddenFrom="md">Daftar Data Kematian</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -102,16 +94,16 @@ function ListKematian({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel untuk desktop */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.2}>Nama</Text></TableTh>
<TableTh>Tanggal</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.2}>Tanggal</Text></TableTh>
<TableTh>Jenis Kelamin</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.2}>Jenis Kelamin</Text></TableTh>
<TableTh>Alamat</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.2}>Alamat</Text></TableTh>
<TableTh>Aksi</TableTh> <TableTh><Text fz="sm" fw={600} lh={1.2}>Aksi</Text></TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -119,45 +111,39 @@ function ListKematian({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end">
<Text fw={500} truncate="end" lineClamp={1}> {item.nama}
{item.nama} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5}>
{new Date(item.tanggal).toLocaleDateString('id-ID', { {new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit', day: '2-digit',
month: 'long', month: 'long',
year: 'numeric', year: 'numeric',
})} })}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5}>{item.jenisKelamin}</Text>
{item.jenisKelamin}
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} c="dimmed" truncate="end">
<Text truncate fz="sm" c="dimmed"> {item.alamat}
{item.alamat} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
variant="light" variant="light"
color="blue" color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => onClick={() =>
router.push( router.push(
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${item.id}` `/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${item.id}`
) )
} }
> >
<IconEdit size={18} /> Detail
<Text ml={5}>Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -165,8 +151,8 @@ function ListKematian({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={5}> <TableTd colSpan={5}>
<Center py={20}> <Center py="xl">
<Text color="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kematian yang cocok Tidak ada data kematian yang cocok
</Text> </Text>
</Center> </Center>
@@ -176,27 +162,80 @@ function ListKematian({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Card untuk mobile */}
<Stack gap="sm" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap="xs">
<Box>
<Text fz="sm" fw={600} lh={1.4}>Nama</Text>
<Text fz="sm" fw={500} lh={1.45}>{item.nama}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Tanggal</Text>
<Text fz="sm" fw={500} lh={1.45}>
{new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Jenis Kelamin</Text>
<Text fz="sm" fw={500} lh={1.45}>{item.jenisKelamin}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Alamat</Text>
<Text fz="sm" fw={500} lh={1.45} c="dimmed">{item.alamat}</Text>
</Box>
<Box mt="xs">
<Button
fullWidth
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${item.id}`
)
}
>
Detail
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kematian yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
<Center> {totalPages > 1 && (
<Pagination <Center mt="lg">
value={page} <Pagination
onChange={(newPage) => { value={page}
load(newPage, 10, search); onChange={(newPage) => {
window.scrollTo({ top: 0, behavior: 'smooth' }); load(newPage, 10, search);
}} window.scrollTo({ top: 0, behavior: 'smooth' });
total={totalPages} }}
mt="md" total={totalPages}
mb="md" color="blue"
color="blue" radius="md"
radius="md" />
/> </Center>
</Center> )}
</Box> </Box>
); );
} }
export default Kematian; export default Kematian;

View File

@@ -3,275 +3,317 @@
'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, Badge, Box, Center, Flex, Tooltip as MantineTooltip, Paper, Select, Skeleton, Stack, Table, Text, Title } from '@mantine/core'; import { ActionIcon, Badge, Box, Center, Flex, Group, Paper, Select, Skeleton, Stack, Table, Text, Title, Tooltip as MantineTooltip } from '@mantine/core';
import { 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, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from 'recharts'; import { Bar, BarChart, Legend, ResponsiveContainer, Tooltip as RechartsTooltip, 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="md"> <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;
}>;
};
type DataTahunan = { // ✅ Fungsi hitung tahunan + bulanan
tahun: string; const countByYearAndMonth = (kelahiran: any[], kematian: any[]): DataTahunan[] => {
totalKelahiran: number; const dataTahunan: Record<string, DataTahunan> = {};
totalKematian: number;
data: Array<{
id: string;
bulan: string;
kelahiran: number;
kematian: number;
}>;
};
const namaBulan = [
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
// ✅ Fungsi hitung tahunan + bulanan kelahiran?.forEach((item: any) => {
const countByYearAndMonth = (kelahiran: any[], kematian: any[]): DataTahunan[] => { const date = new Date(item.tanggal);
const dataTahunan: Record<string, DataTahunan> = {}; const tahun = date.getFullYear().toString();
const bulanIndex = date.getMonth();
if (!dataTahunan[tahun]) {
dataTahunan[tahun] = {
tahun,
totalKelahiran: 0,
totalKematian: 0,
data: namaBulan.map((nama, idx) => ({
id: `${tahun}-${idx + 1}`,
bulan: nama,
kelahiran: 0,
kematian: 0
}))
};
}
const namaBulan = [ dataTahunan[tahun].totalKelahiran += 1;
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', dataTahunan[tahun].data[bulanIndex].kelahiran += 1;
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember' });
];
kematian?.forEach((item: any) => {
const date = new Date(item.tanggal);
const tahun = date.getFullYear().toString();
const bulanIndex = date.getMonth();
// Proses kelahiran if (!dataTahunan[tahun]) {
kelahiran?.forEach((item: any) => { dataTahunan[tahun] = {
const date = new Date(item.tanggal); tahun,
const tahun = date.getFullYear().toString(); totalKelahiran: 0,
const bulanIndex = date.getMonth(); totalKematian: 0,
data: namaBulan.map((nama, idx) => ({
id: `${tahun}-${idx + 1}`,
bulan: nama,
kelahiran: 0,
kematian: 0
}))
};
}
dataTahunan[tahun].totalKematian += 1;
dataTahunan[tahun].data[bulanIndex].kematian += 1;
});
if (!dataTahunan[tahun]) { return Object.values(dataTahunan).sort((a, b) => parseInt(a.tahun) - parseInt(b.tahun));
dataTahunan[tahun] = { };
tahun,
totalKelahiran: 0,
totalKematian: 0,
data: namaBulan.map((nama, idx) => ({
id: `${tahun}-${idx + 1}`,
bulan: nama,
kelahiran: 0,
kematian: 0
}))
};
}
const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<DataTahunan[]>([]);
const [selectedYear, setSelectedYear] = useState<string | null>(null);
dataTahunan[tahun].totalKelahiran += 1; const formatNumber = (num: number) => new Intl.NumberFormat('id-ID').format(num);
dataTahunan[tahun].data[bulanIndex].kelahiran += 1;
});
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
if (active && payload && payload.length) {
return (
<Paper p="sm" shadow="md" withBorder radius="md">
<Text fz="sm" fw={600} lh={1.4}>Tahun {label}</Text>
<Text fz="sm" c="blue.6" lh={1.4}>Kelahiran: {formatNumber(payload[0].value)}</Text>
<Text fz="sm" c="red.6" lh={1.4}>Kematian: {formatNumber(payload[1].value)}</Text>
</Paper>
);
}
return null;
};
// Proses kematian useShallowEffect(() => {
kematian?.forEach((item: any) => { statePersentase.kelahiran.findMany.load(1, 1000);
const date = new Date(item.tanggal); statePersentase.kematian.findMany.load(1, 1000);
const tahun = date.getFullYear().toString(); }, []);
const bulanIndex = date.getMonth();
useEffect(() => {
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
const hasil = countByYearAndMonth(
statePersentase.kelahiran.findMany.data,
statePersentase.kematian.findMany.data
);
if (!dataTahunan[tahun]) { setChartData(hasil);
dataTahunan[tahun] = { setSelectedYear(hasil[0]?.tahun || null);
tahun, }
totalKelahiran: 0, }, [statePersentase.kelahiran.findMany.data, statePersentase.kematian.findMany.data]);
totalKematian: 0,
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 <Skeleton h={400} radius="lg" />;
}
dataTahunan[tahun].totalKematian += 1; const selectedYearData = chartData.find(d => d.tahun === selectedYear);
dataTahunan[tahun].data[bulanIndex].kematian += 1;
});
return (
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Stack gap='md'>
<Group justify="space-between" align="center">
<Title order={3} fw={700} fz={{ base: 'sm', md: 'md' }} lh={1.2}>
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>
</Group>
return Object.values(dataTahunan).sort((a, b) => parseInt(a.tahun) - parseInt(b.tahun)); {chartData.length === 0 ? (
}; <Center py={{ base: 'xl', md: '2xl' }}>
<Text c="dimmed" fs="italic" fz="sm" lh={1.4}>
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 />
<RechartsTooltip 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>
const statePersentase = useProxy(persentasekelahiran); {selectedYearData && (
const [chartData, setChartData] = useState<DataTahunan[]>([]); <Stack gap="md">
const [selectedYear, setSelectedYear] = useState<string | null>(null); <Flex align="center" gap="sm">
<Title order={4} fw={600} fz={{ base: 'xs', md: 'sm' }} lh={1.2}>
Rincian Tahun {selectedYear}
</Title>
<Badge variant="light" color="blue" fz={{ base: 'xs', md: 'sm' }}>
{formatNumber(selectedYearData.totalKelahiran)} kelahiran
</Badge>
<Badge variant="light" color="red" fz={{ base: 'xs', md: 'sm' }}>
{formatNumber(selectedYearData.totalKematian)} kematian
</Badge>
</Flex>
{/* Desktop: Table */}
<Box visibleFrom="md">
<Table striped withTableBorder highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th fz="sm" fw={600} lh={1.4}>Bulan</Table.Th>
<Table.Th ta="right" fz="sm" fw={600} lh={1.4}>Kelahiran</Table.Th>
<Table.Th ta="right" fz="sm" fw={600} lh={1.4}>Kematian</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{selectedYearData.data.length > 0 ? (
<>
{selectedYearData.data.map((item) => (
<Table.Tr key={item.id}>
<Table.Td fz="sm" fw={500} lh={1.5}>{item.bulan}</Table.Td>
<Table.Td ta="right" fz="sm" fw={500} lh={1.5}>
{formatNumber(item.kelahiran)}
</Table.Td>
<Table.Td ta="right" fz="sm" fw={500} lh={1.5}>
{formatNumber(item.kematian)}
</Table.Td>
</Table.Tr>
))}
<Table.Tr>
<Table.Td fz="sm" fw={600} lh={1.5}>Total</Table.Td>
<Table.Td ta="right" fz="sm" fw={600} lh={1.5}>
{formatNumber(selectedYearData.totalKelahiran)}
</Table.Td>
<Table.Td ta="right" fz="sm" fw={600} lh={1.5}>
{formatNumber(selectedYearData.totalKematian)}
</Table.Td>
</Table.Tr>
</>
) : (
<Table.Tr>
<Table.Td colSpan={3} ta="center" c="dimmed" fz="sm" lh={1.4}>
Tidak ada rincian bulanan
</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
</Box>
const formatNumber = (num: number) => new Intl.NumberFormat('id-ID').format(num); {/* Mobile: Card List */}
<Box hiddenFrom="md">
<Stack gap="xs">
{selectedYearData.data.length > 0 ? (
selectedYearData.data.map((item) => (
<Paper key={item.id} p="sm" radius="md" withBorder>
<Text fz="xs" fw={600} lh={1.4}>Bulan</Text>
<Text fz="sm" fw={500} lh={1.4} mb="sm">
{item.bulan}
</Text>
<Text fz="xs" fw={600} lh={1.4}>Kelahiran</Text>
<Text fz="sm" fw={500} lh={1.4} mb="sm">
{formatNumber(item.kelahiran)}
</Text>
<Text fz="xs" fw={600} lh={1.4}>Kematian</Text>
<Text fz="sm" fw={500} lh={1.4}>
{formatNumber(item.kematian)}
</Text>
</Paper>
))
) : (
<Center py="md">
<Text c="dimmed" fz="sm" lh={1.4}>Tidak ada rincian bulanan</Text>
</Center>
)}
{/* Total row mobile */}
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => { {selectedYearData.data.length > 0 && (
if (active && payload && payload.length) { <Paper p="sm" radius="md" withBorder bg="gray.1">
return ( <Text fz="xs" fw={600} lh={1.4}>Total</Text>
<Paper p="sm" shadow="md" withBorder radius="md"> <Flex justify="space-between" mt="xs">
<Text size="sm" fw={600}>Tahun {label}</Text> <Text fz="sm" fw={600} lh={1.4}>Kelahiran</Text>
<Text size="sm" c="blue.6">Kelahiran: {formatNumber(payload[0].value)}</Text> <Text fz="sm" fw={600} lh={1.4}>
<Text size="sm" c="red.6">Kematian: {formatNumber(payload[1].value)}</Text> {formatNumber(selectedYearData.totalKelahiran)}
</Paper> </Text>
); </Flex>
} <Flex justify="space-between" mt="xs">
return null; <Text fz="sm" fw={600} lh={1.4}>Kematian</Text>
}; <Text fz="sm" fw={600} lh={1.4}>
{formatNumber(selectedYearData.totalKematian)}
</Text>
useShallowEffect(() => { </Flex>
statePersentase.kelahiran.findMany.load(1, 1000); </Paper>
statePersentase.kematian.findMany.load(1, 1000); )}
}, []); </Stack>
</Box>
</Stack>
useEffect(() => { )}
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) { </>
const hasil = countByYearAndMonth( )}
statePersentase.kelahiran.findMany.data, </Stack>
statePersentase.kematian.findMany.data </Paper>
); );
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;

View File

@@ -141,7 +141,7 @@ function EditInfoWabahPenyakit() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -49,7 +49,7 @@ function DetailInfoWabahPenyakit() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -63,7 +63,7 @@ function DetailInfoWabahPenyakit() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: '100%', md: '50%' }} w={{ base: '100%', md: '70%' }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -89,6 +89,7 @@ function DetailInfoWabahPenyakit() {
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi Lengkap</Text> <Text fz="lg" fw="bold">Deskripsi Lengkap</Text>
<Text <Text
pl={10}
fz="md" fz="md"
c="dimmed" c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap }} dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap }}

View File

@@ -72,7 +72,7 @@ function CreateInfoWabahPenyakit() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -16,9 +16,9 @@ import {
TableThead, TableThead,
TableTr, TableTr,
Text, Text,
Title Title,
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -27,7 +27,7 @@ import HeaderSearch from '../../_com/header';
import infoWabahPenyakit from '../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit'; import infoWabahPenyakit from '../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
function InfoWabahPenyakit() { function InfoWabahPenyakit() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState('');
return ( return (
<Box> <Box>
{/* Header Search */} {/* Header Search */}
@@ -44,8 +44,9 @@ function InfoWabahPenyakit() {
} }
function ListInfoWabahPenyakit({ search }: { search: string }) { function ListInfoWabahPenyakit({ search }: { search: string }) {
const infoWabahPenyakitState = useProxy(infoWabahPenyakit) const infoWabahPenyakitState = useProxy(infoWabahPenyakit);
const router = useRouter() const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { const {
data, data,
@@ -56,25 +57,30 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
} = infoWabahPenyakitState.findMany; } = infoWabahPenyakitState.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="lg">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
) );
} }
return ( return (
<Box py={10}> <Box py="lg">
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Info Wabah Penyakit</Title> <Title
order={4}
lh={{ base: 1.2, md: 1.1 }}
>
Daftar Info Wabah Penyakit
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -85,8 +91,8 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md" style={{ overflowX: 'auto' }}>
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
@@ -100,16 +106,19 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={200}> <Text fz="md" fw={500} lh={1.45} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={200}> <Text
<Text truncate="end" fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} /> fz="sm"
</Box> c="dimmed"
lh={1.45}
truncate="end"
lineClamp={1}
dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }}
/>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -118,16 +127,18 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)} onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml={5} fz="sm" fw={500} lh={1.45}>
Detail
</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={3}>
<Center py={20}> <Center py="xl">
<Text color="dimmed"> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data info wabah penyakit yang cocok Tidak ada data info wabah penyakit yang cocok
</Text> </Text>
</Center> </Center>
@@ -137,6 +148,53 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card List */}
<Stack gap="sm" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="sm">
<Stack gap={"xs"}>
<Text fz="sm" fw={600} lh={1.4}>
Judul
</Text>
<Text fz="sm" fw={500} lh={1.45}>
{item.name}
</Text>
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi Singkat
</Text>
<Text
fz="sm"
fw={500}
lh={1.45}
dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }}
/>
<Button
variant="light"
color="blue"
fullWidth
mt="xs"
onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}
>
<IconDeviceImacCog size={20} />
<Text ml={6} fz="sm" fw={500} lh={1.45}>
Detail
</Text>
</Button>
</Stack>
</Paper>
))
) : (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data info wabah penyakit yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
@@ -144,8 +202,8 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => { onChange={(newPage) => {
load(newPage, 10) load(newPage, 10);
window.scrollTo({ top: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, behavior: 'smooth' });
}} }}
total={totalPages} total={totalPages}
mt="md" mt="md"
@@ -155,7 +213,7 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
/> />
</Center> </Center>
</Box> </Box>
) );
} }
export default InfoWabahPenyakit; export default InfoWabahPenyakit;

View File

@@ -128,7 +128,7 @@ function EditKontakDarurat() {
if (loading) return <Text>Loading...</Text>; if (loading) return <Text>Loading...</Text>;
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -40,7 +40,7 @@ function DetailKontakDarurat() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -54,7 +54,7 @@ function DetailKontakDarurat() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -78,7 +78,7 @@ function CreateKontakDarurat() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect, useDebouncedValue } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -46,45 +46,44 @@ function KontakDarurat() {
} }
function ListKontakDarurat({ search }: { search: string }) { function ListKontakDarurat({ search }: { search: string }) {
const kontakDaruratState = useProxy(kontakDarurat) const kontakDaruratState = useProxy(kontakDarurat);
const router = useRouter(); const router = useRouter();
const { data, page, totalPages, loading, load } = kontakDaruratState.findMany; const { data, page, totalPages, loading, load } = kontakDaruratState.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000);
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
) );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Stack mb="md" gap="sm"> <Group justify="space-between" mb="md">
<Group justify="space-between"> <Title order={4}>Daftar Kontak Darurat</Title>
<Title order={4}>Daftar Kontak Darurat</Title> <Button
<Button leftSection={<IconPlus size={18} />}
leftSection={<IconPlus size={18} />} color="blue"
color="blue" variant="light"
variant="light" onClick={() => router.push('/admin/kesehatan/kontak-darurat/create')}
onClick={() => router.push('/admin/kesehatan/kontak-darurat/create')} >
> Tambah Baru
Tambah Baru </Button>
</Button> </Group>
</Group>
</Stack>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
@@ -98,16 +97,12 @@ function ListKontakDarurat({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fw={500} fz="md" lh={1.45} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={200}> <Text fz="sm" lh={1.45} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Text truncate fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -116,16 +111,16 @@ function ListKontakDarurat({ search }: { search: string }) {
onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${item.id}`)} onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${item.id}`)}
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml={5} fz="sm" fw={500} lh={1.45}>Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={3}>
<Center py={20}> <Center py={20}>
<Text color="dimmed">Tidak ada data kontak darurat yang cocok</Text> <Text c="dimmed" fz="sm" lh={1.4}>Tidak ada data kontak darurat yang cocok</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -133,6 +128,40 @@ function ListKontakDarurat({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card View */}
<Stack hiddenFrom="md" gap="xs" mt="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Judul</Text>
<Text fz="sm" fw={500} lh={1.45}>{item.name}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
<Text fz="sm" fw={500} lh={1.45} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
<Button
variant="light"
color="blue"
fullWidth
onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${item.id}`)}
mt="xs"
>
<IconDeviceImacCog size={18} />
<Text ml={5} fz="sm" fw={500} lh={1.45}>Detail</Text>
</Button>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>Tidak ada data kontak darurat yang cocok</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
@@ -151,7 +180,7 @@ function ListKontakDarurat({ search }: { search: string }) {
/> />
</Center> </Center>
</Box> </Box>
) );
} }
export default KontakDarurat; export default KontakDarurat;

View File

@@ -144,7 +144,7 @@ function EditPenangananDarurat() {
if (loading) return <Text>Loading...</Text>; if (loading) return <Text>Loading...</Text>;
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -40,7 +40,7 @@ function DetailPenangananDarurat() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Button <Button
variant="subtle" variant="subtle"
@@ -54,7 +54,7 @@ function DetailPenangananDarurat() {
{/* Wrapper Detail */} {/* Wrapper Detail */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -75,6 +75,7 @@ function DetailPenangananDarurat() {
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Text fz="lg" fw="bold">Deskripsi</Text>
<Text <Text
pl={10}
fz="md" fz="md"
c="dimmed" c="dimmed"
dangerouslySetInnerHTML={{ __html: data.deskripsi }} dangerouslySetInnerHTML={{ __html: data.deskripsi }}

View File

@@ -77,7 +77,7 @@ function CreatePenangananDarurat() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button <Button

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -47,29 +47,35 @@ function PenangananDarurat() {
function ListPenangananDarurat({ search }: { search: string }) { function ListPenangananDarurat({ search }: { search: string }) {
const state = useProxy(penangananDarurat); const state = useProxy(penangananDarurat);
const router = useRouter(); const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000)
const { data, page, totalPages, loading, load } = state.findMany; const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
{/* Judul + Tombol Tambah */} {/* Judul + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Penanganan Darurat</Title> <Title
order={4}
lh={1.2}
>
Daftar Penanganan Darurat
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -80,14 +86,14 @@ function ListPenangananDarurat({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Judul</TableTh> <TableTh fz="sm" fw={600} lh={1.4}>Judul</TableTh>
<TableTh>Deskripsi</TableTh> <TableTh fz="sm" fw={600} lh={1.4}>Deskripsi</TableTh>
<TableTh>Aksi</TableTh> <TableTh fz="sm" fw={600} lh={1.4}>Aksi</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -95,22 +101,19 @@ function ListPenangananDarurat({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={200}> <Text
<Text fz="sm"
fz="sm" fw={400}
c="dimmed" lh={1.5}
truncate c="gray.7"
lineClamp={1} lineClamp={1}
dangerouslySetInnerHTML={{ __html: item.deskripsi }} dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/> />
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
@@ -121,16 +124,18 @@ function ListPenangananDarurat({ search }: { search: string }) {
} }
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={20} />
<Text ml={5}>Detail</Text> <Text ml={5} fz="sm" fw={500}>Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={3}>
<Center py={20}> <Center py={20}>
<Text color="dimmed">Tidak ada data penanganan darurat</Text> <Text c="gray.6" fz="sm" fw={500} lh={1.4}>
Tidak ada data penanganan darurat
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -138,6 +143,56 @@ function ListPenangananDarurat({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="sm">
<Stack gap={'xs'}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Judul</Text>
<Text fz="sm" fw={500} lh={1.45} lineClamp={1}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
<Text
fz="sm"
fw={500}
lh={1.45}
c="gray.7"
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</Box>
<Box>
<Button
variant="light"
color="blue"
fullWidth
onClick={() =>
router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)
}
>
<IconDeviceImacCog size={20} />
<Text ml={5} fz="sm" fw={500}>Detail</Text>
</Button>
</Box>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="gray.6" fz="sm" fw={500} lh={1.4}>
Tidak ada data penanganan darurat
</Text>
</Center>
)}
</Stack>
</Box>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}

View File

@@ -124,7 +124,7 @@ function EditPosyandu() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol Back */} {/* Tombol Back */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -46,7 +46,7 @@ function DetailPosyandu() {
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
{/* Tombol kembali */} {/* Tombol kembali */}
<Button <Button
variant="subtle" variant="subtle"
@@ -61,7 +61,7 @@ function DetailPosyandu() {
{/* Card utama */} {/* Card utama */}
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "60%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -89,12 +89,14 @@ function DetailPosyandu() {
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Text fz="lg" fw="bold">Deskripsi</Text>
<Text <Box pl={10}>
fz="md" <Text
c="dimmed" fz="md"
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} c="dimmed"
style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
/> style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
</Box>
</Box> </Box>
@@ -109,7 +111,7 @@ function DetailPosyandu() {
</Box> </Box>
<Box> <Stack gap={3}>
<Text fz="lg" fw="bold">Gambar</Text> <Text fz="lg" fw="bold">Gambar</Text>
{data.image?.link ? ( {data.image?.link ? (
<Image <Image
@@ -124,7 +126,7 @@ function DetailPosyandu() {
) : ( ) : (
<Text fz="sm" c="dimmed">Tidak ada gambar</Text> <Text fz="sm" c="dimmed">Tidak ada gambar</Text>
)} )}
</Box> </Stack>
{/* Aksi */} {/* Aksi */}

View File

@@ -72,7 +72,7 @@ function CreatePosyandu() {
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -18,15 +18,14 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconPlus, 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 { useState } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
import posyandustate from '../../_state/kesehatan/posyandu/posyandu'; import posyandustate from '../../_state/kesehatan/posyandu/posyandu';
function Posyandu() { function Posyandu() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
return ( return (
@@ -43,11 +42,10 @@ function Posyandu() {
); );
} }
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 [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const { const {
data, data,
@@ -57,29 +55,27 @@ function ListPosyandu({ search }: { search: string }) {
load, load,
} = statePosyandu.findMany; } = statePosyandu.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, debouncedSearch);
}, [page, search]) }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
) );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Posyandu</Title> <Title order={4} lh={1.2}>
Daftar Posyandu
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -89,52 +85,51 @@ function ListPosyandu({ search }: { search: string }) {
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: "auto" }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '25%' }}>Nama Posyandu</TableTh> <TableTh fz="sm" fw={600} ta="left" lh={1.4}>Nama Posyandu</TableTh>
<TableTh style={{ width: '20%' }}>Nomor Posyandu</TableTh> <TableTh fz="sm" fw={600} ta="left" lh={1.4}>Nomor Posyandu</TableTh>
<TableTh style={{ width: '30%' }}>Deskripsi</TableTh> <TableTh fz="sm" fw={600} ta="left" lh={1.4}>Deskripsi</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh> <TableTh fz="sm" fw={600} ta="left" lh={1.4}>Aksi</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd style={{ width: '25%' }}> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd style={{ width: '20%' }}> <TableTd>
<Box w={150}> <Text fz="sm" c="dimmed" lh={1.5}>
<Text truncate fz="sm" c="dimmed"> {item.nomor || '-'}
{item.nomor || '-'} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd style={{ width: '30%' }}> <TableTd>
<Box w={150}> <Text
<Text fz="sm"
lineClamp={1} lh={1.5}
truncate lineClamp={1}
fz="sm" truncate
dangerouslySetInnerHTML={{ __html: item.deskripsi }} dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/> />
</Box>
</TableTd> </TableTd>
<TableTd style={{ width: '15%' }}> <TableTd>
<Button <Button
size="xs"
radius="md"
variant="light" variant="light"
color="blue" color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)} onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)}
> >
<IconDeviceImac size={20} /> Detail
<Text ml={5}>Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -143,7 +138,9 @@ function ListPosyandu({ search }: { search: string }) {
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py={20}>
<Text color="dimmed">Tidak ada data posyandu yang cocok</Text> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data posyandu yang cocok
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -151,7 +148,66 @@ function ListPosyandu({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={"xs"}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Posyandu
</Text>
<Text fz="sm" fw={500} lh={1.5}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nomor Posyandu
</Text>
<Text fz="sm" fw={500} lh={1.5} c={item.nomor ? undefined : 'dimmed'}>
{item.nomor || '-'}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Deskripsi
</Text>
<Text
fz="sm"
fw={500}
lh={1.5}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</Box>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)}
fullWidth
>
Detail
</Button>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data posyandu yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper> </Paper>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
@@ -170,5 +226,4 @@ function ListPosyandu({ search }: { search: string }) {
); );
} }
export default Posyandu; export default Posyandu;

View File

@@ -125,7 +125,7 @@ function EditProgramKesehatan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -40,7 +40,7 @@ function DetailProgramKesehatan() {
const data = state.findUnique.data; const data = state.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */} {/* Tombol kembali */}
<Button <Button
variant="subtle" variant="subtle"
@@ -53,7 +53,7 @@ function DetailProgramKesehatan() {
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"
@@ -73,12 +73,12 @@ function DetailProgramKesehatan() {
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi Singkat</Text> <Text fz="lg" fw="bold">Deskripsi Singkat</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsiSingkat || '-' }} /> <Text pl={10} fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsiSingkat || '-' }} />
</Box> </Box>
<Box> <Box>
<Text fz="lg" fw="bold">Deskripsi</Text> <Text fz="lg" fw="bold">Deskripsi</Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} /> <Text pl={10} fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
</Box> </Box>
<Box> <Box>

View File

@@ -78,7 +78,7 @@ function CreateProgramKesehatan() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -18,7 +18,7 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -48,27 +48,30 @@ function ListProgramKesehatan({ search }: { search: string }) {
const router = useRouter(); const router = useRouter();
const { data, page, totalPages, loading, load } = stateProgram.findMany; const { data, page, totalPages, loading, load } = stateProgram.findMany;
const [debouncedSearch] = useDebouncedValue(search, 1000);
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'md', md: 'lg' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
{/* Header List + Tombol Tambah */} {/* Header List + Tombol Tambah */}
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Program Kesehatan</Title> <Title order={4} lh={1.2}>
Daftar Program Kesehatan
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -79,15 +82,23 @@ function ListProgramKesehatan({ search }: { search: string }) {
</Button> </Button>
</Group> </Group>
{/* Tabel */} {/* Desktop Table */}
<Box style={{ overflowX: "auto" }}> <Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Judul</TableTh> <TableTh>
<TableTh>Deskripsi Singkat</TableTh> <Text fz="sm" fw={600} lh={1.4}>Judul</Text>
<TableTh>Deskripsi</TableTh> </TableTh>
<TableTh>Aksi</TableTh> <TableTh>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi Singkat</Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.4}>Aksi</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -95,28 +106,25 @@ function ListProgramKesehatan({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Text fw={500} truncate="end" lineClamp={1}> <Text fw={500} fz="md" lh={1.5} truncate="end" lineClamp={1}>
{item.name} {item.name}
</Text> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd w={200}>
<Box w={200}> <Text fz="sm" lh={1.5} truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
<Text fz="sm" truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd w={200}>
<Box w={200}> <Text fz="sm" lh={1.5} truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Text fz="sm" truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
variant="light" variant="light"
color="blue" color="blue"
onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${item.id}`)} onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${item.id}`)}
size="xs"
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={18} />
<Text ml={5}>Detail</Text> <Text ml={5} fz="sm" fw={500} lh={1.4}>Detail</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -125,7 +133,9 @@ function ListProgramKesehatan({ search }: { search: string }) {
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py={20}>
<Text color="dimmed">Tidak ada program kesehatan yang cocok</Text> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada program kesehatan yang cocok
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -133,6 +143,52 @@ function ListProgramKesehatan({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Card View */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={'xs'}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Judul</Text>
<Text fz="sm" fw={500} lh={1.4}>{item.name}</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi Singkat</Text>
<Box pl={10}>
<Text fz="sm" lineClamp={3} fw={500} lh={1.4} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
</Box>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
<Box pl={10}>
<Text fz="sm" lineClamp={3} fw={500} lh={1.4} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</Box>
<Button
variant="light"
color="blue"
fullWidth
size="xs"
onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${item.id}`)}
>
<IconDeviceImacCog size={18} />
<Text ml={5} fz="sm" fw={500} lh={1.4}>Detail</Text>
</Button>
</Stack>
</Paper>
))
) : (
<Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada program kesehatan yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}

View File

@@ -201,7 +201,7 @@ function EditPuskesmas() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header dengan tombol back */} {/* Header dengan tombol back */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -40,7 +40,7 @@ function DetailPuskesmas() {
const data = statePuskesmas.findUnique.data; const data = statePuskesmas.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Tombol kembali */} {/* Tombol kembali */}
<Button <Button
variant="subtle" variant="subtle"
@@ -53,7 +53,7 @@ function DetailPuskesmas() {
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -84,7 +84,7 @@ function CreatePuskesmas() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md" component="form" onSubmit={handleSubmit}> <Box px={{ base: 0, md: 'lg' }} py="xs" component="form" onSubmit={handleSubmit}>
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

@@ -16,9 +16,9 @@ import {
TableThead, TableThead,
TableTr, TableTr,
Text, Text,
Title Title,
} from '@mantine/core'; } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, 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 { useState } from 'react';
@@ -48,46 +48,64 @@ function Puskesmas() {
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 [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = statePuskesmas.findMany; const { data, page, totalPages, loading, load } = statePuskesmas.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search); load(page, 10, debouncedSearch);
}, [page, search]); }, [page, debouncedSearch]);
const filteredData = data || []; const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Puskesmas</Title> <Title order={4}>Daftar Puskesmas</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
variant="light" variant="light"
onClick={() => router.push('/admin/kesehatan/puskesmas/create')} onClick={() => router.push('/admin/kesehatan/puskesmas/create')}
> >
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: "auto" }}> {/* Desktop Table */}
<Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama Puskesmas</TableTh> <TableTh>
<TableTh>Alamat</TableTh> <Text fz="sm" fw={600} lh={1.2}>
<TableTh>Kontak</TableTh> Nama Puskesmas
<TableTh>Aksi</TableTh> </Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.2}>
Alamat
</Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.2}>
Kontak
</Text>
</TableTh>
<TableTh>
<Text fz="sm" fw={600} lh={1.2}>
Aksi
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -95,34 +113,33 @@ function ListPuskesmas({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={150}> <Text fz="md" fw={500} lh={1.5} truncate="end" lineClamp={1}>
<Text fw={500} truncate="end" lineClamp={1}> {item.name}
{item.name} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="sm" fw={500} lh={1.5} c="dimmed" truncate="end" lineClamp={1}>
<Text truncate fz="sm" c="dimmed" lineClamp={1}> {item.alamat}
{item.alamat} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Box w={150}> <Text fz="sm" fw={500} lh={1.5} c="dimmed" truncate="end" lineClamp={1}>
<Text truncate fz="sm" c="dimmed" lineClamp={1}> {item.kontak.kontakPuskesmas}
{item.kontak.kontakPuskesmas} </Text>
</Text>
</Box>
</TableTd> </TableTd>
<TableTd> <TableTd>
<Button <Button
variant="light" variant="light"
color="blue" color="blue"
onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)} onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}
radius="md"
px="sm"
h={34}
> >
<IconDeviceImacCog size={20} /> <IconDeviceImacCog size={18} />
<Text ml={5}>Detail</Text> <Text ml="xs" fz="sm" fw={500}>
Detail
</Text>
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -130,8 +147,10 @@ function ListPuskesmas({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py={{ base: 'sm', md: 'md' }}>
<Text color="dimmed">Tidak ada data puskesmas yang cocok</Text> <Text fz="sm" c="dimmed" ta="center">
Tidak ada data puskesmas yang cocok
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -139,6 +158,61 @@ function ListPuskesmas({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Stack gap="sm" hiddenFrom="md">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={'xs'}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama Puskesmas
</Text>
<Text fz="sm" fw={500} lh={1.45}>
{item.name}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Alamat
</Text>
<Text fz="sm" fw={500} lh={1.45} c="dimmed">
{item.alamat}
</Text>
</Box>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Kontak
</Text>
<Text fz="sm" fw={500} lh={1.45} c="dimmed">
{item.kontak.kontakPuskesmas}
</Text>
</Box>
<Button
variant="light"
color="blue"
onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}
radius="md"
fullWidth
mt="xs"
>
<IconDeviceImacCog size={18} />
<Text ml="xs" fz="sm" fw={500}>
Detail
</Text>
</Button>
</Stack>
</Paper>
))
) : (
<Center py="md">
<Text fz="sm" c="dimmed">
Tidak ada data puskesmas yang cocok
</Text>
</Center>
)}
</Stack>
</Paper> </Paper>
{/* Pagination */} {/* Pagination */}
@@ -150,8 +224,7 @@ function ListPuskesmas({ search }: { search: string }) {
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });
}} }}
total={totalPages} total={totalPages}
mt="md" my="md"
mb="md"
color="blue" color="blue"
radius="md" radius="md"
/> />

View File

@@ -60,7 +60,7 @@ function ListSdgsDesa({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'lg' }}> <Box py={{ base: 'sm', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Sdgs Desa Daftar Sdgs Desa
</Title> </Title>
<Button <Button
@@ -148,7 +148,7 @@ function ListSdgsDesa({ search }: { search: string }) {
<Stack gap="sm"> <Stack gap="sm">
{filteredData.map((item) => ( {filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md"> <Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="sm" fw={600} lh={1.4}>Nama SDGs Desa</Text> <Text fz="sm" fw={600} lh={1.4}>Nama SDGs Desa</Text>
<Text fz="sm" fw={500} lh={1.4}> <Text fz="sm" fw={500} lh={1.4}>

View File

@@ -69,7 +69,7 @@ function ListAPBDes({ search }: { search: string }) {
<Box visibleFrom="md"> <Box visibleFrom="md">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={2} size="lg" lh={1.2}> <Title order={4} size="lg" lh={1.2}>
Daftar APBDes Daftar APBDes
</Title> </Title>
<Button <Button

View File

@@ -194,7 +194,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
<Box py={{ base: 20, md: 20 }}> <Box py={{ base: 20, md: 20 }}>
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="md" radius="md"> <Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}> <Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Kategori Kegiatan Daftar Kategori Kegiatan
</Title> </Title>
<Button <Button

View File

@@ -66,7 +66,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
<Stack gap={'md'}> <Stack gap={'md'}>
<Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder> <Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Program Desa Anti Korupsi Daftar Program Desa Anti Korupsi
</Title> </Title>
<Button <Button

View File

@@ -89,7 +89,7 @@ function ListResponden({ search }: ListRespondenProps) {
{/* Desktop Table */} {/* Desktop Table */}
<Box visibleFrom="md"> <Box visibleFrom="md">
<Paper p="lg" radius="lg" shadow="md" withBorder> <Paper p="lg" radius="lg" shadow="md" withBorder>
<Title order={2} size="lg" mb="md" lh={1.2}> <Title order={4} size="lg" mb="md" lh={1.2}>
Daftar Responden Daftar Responden
</Title> </Title>
<Table <Table
@@ -158,7 +158,7 @@ function ListResponden({ search }: ListRespondenProps) {
{/* Mobile Cards */} {/* Mobile Cards */}
<Box hiddenFrom="md"> <Box hiddenFrom="md">
<Stack gap="sm"> <Stack gap="sm">
<Title order={2} size="md" lh={1.2} px="md"> <Title order={4} size="md" lh={1.2} px="md">
Daftar Responden Daftar Responden
</Title> </Title>
{filteredData.length === 0 ? ( {filteredData.length === 0 ? (
@@ -170,7 +170,7 @@ function ListResponden({ search }: ListRespondenProps) {
) : ( ) : (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} p="md" radius="lg" shadow="sm" mx="md"> <Paper key={item.id} p="md" radius="lg" shadow="sm" mx="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Text fz="sm" c="dimmed" lh={1.4}>Nama</Text> <Text fz="sm" c="dimmed" lh={1.4}>Nama</Text>
<Text fz="md" lh={1.5}>{item.name}</Text> <Text fz="md" lh={1.5}>{item.name}</Text>

View File

@@ -69,7 +69,7 @@ function ListKategoriPrestasi({ search }: { search: string }) {
{/* DESKTOP: Table */} {/* DESKTOP: Table */}
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm" withBorder> <Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm" withBorder>
<Group justify="space-between" mb="xl"> <Group justify="space-between" mb="xl">
<Title order={2} size="lg" lh={1.2}>List Kategori Prestasi</Title> <Title order={4} size="lg" lh={1.2}>List Kategori Prestasi</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"

View File

@@ -57,7 +57,7 @@ function ListPrestasi({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'md' }}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} size={isMobile ? 'md' : 'lg'} lh={1.2}> <Title order={4} size={isMobile ? 'md' : 'lg'} lh={1.2}>
Daftar Prestasi Desa Daftar Prestasi Desa
</Title> </Title>
<Button <Button

View File

@@ -69,7 +69,7 @@ function ListDaftarInformasi({ search }: { search: string }) {
<Box py={{ base: 'md', md: 'lg' }}> <Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
List Daftar Informasi Publik List Daftar Informasi Publik
</Title> </Title>
<Button <Button
@@ -155,7 +155,7 @@ function ListDaftarInformasi({ search }: { search: string }) {
<Stack hiddenFrom="md" gap="sm"> <Stack hiddenFrom="md" gap="sm">
{filteredData.map((item) => ( {filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md"> <Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fw={600} lh={1.4}> <Text fw={600} lh={1.4}>
Jenis Informasi Jenis Informasi

View File

@@ -70,7 +70,7 @@ function ListResponden({ search }: ListRespondenProps) {
return ( return (
<Paper withBorder bg="white" p={{ base: 'md', sm: 'lg' }} radius="md" shadow="sm"> <Paper withBorder bg="white" p={{ base: 'md', sm: 'lg' }} radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Data Responden Data Responden
</Title> </Title>
<Box visibleFrom="md"> <Box visibleFrom="md">
@@ -97,7 +97,7 @@ function ListResponden({ search }: ListRespondenProps) {
return ( return (
<Paper withBorder bg="white" p={{ base: 'md', sm: 'lg' }} radius="md" shadow="sm"> <Paper withBorder bg="white" p={{ base: 'md', sm: 'lg' }} radius="md" shadow="sm">
<Stack gap="md"> <Stack gap="md">
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Data Responden Data Responden
</Title> </Title>
@@ -166,7 +166,7 @@ function ListResponden({ search }: ListRespondenProps) {
<Stack gap="sm"> <Stack gap="sm">
{filteredData.map((item, index) => ( {filteredData.map((item, index) => (
<Paper key={item.id} withBorder p="sm" radius="md"> <Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="sm" fw={600} lh={1.4}> <Text fz="sm" fw={600} lh={1.4}>
No No

View File

@@ -61,7 +61,7 @@ function Page() {
<Stack gap={'sm'}> <Stack gap={'sm'}>
<Grid mb={10}> <Grid mb={10}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Title order={2} lh={1.2} c="dark"> <Title order={4} lh={1.2} c="dark">
Daftar Permohonan Informasi Publik Daftar Permohonan Informasi Publik
</Title> </Title>
</GridCol> </GridCol>
@@ -100,7 +100,7 @@ function Page() {
<Stack gap={'sm'}> <Stack gap={'sm'}>
<Grid mb={10}> <Grid mb={10}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Title order={2} lh={1.2} c="dark"> <Title order={4} lh={1.2} c="dark">
Daftar Permohonan Informasi Publik Daftar Permohonan Informasi Publik
</Title> </Title>
</GridCol> </GridCol>
@@ -202,7 +202,7 @@ function Page() {
<Stack hiddenFrom="md" gap="xs"> <Stack hiddenFrom="md" gap="xs">
{data.map((item, index) => ( {data.map((item, index) => (
<Paper key={item.id} p="sm" radius="md" withBorder bg="white"> <Paper key={item.id} p="sm" radius="md" withBorder bg="white">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="xs" fw={600} lh={1.4} c="dark"> <Text fz="xs" fw={600} lh={1.4} c="dark">
No No

View File

@@ -61,7 +61,7 @@ function Page() {
<Stack gap={'sm'}> <Stack gap={'sm'}>
<Grid mb={10}> <Grid mb={10}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Title order={2} lh={1.2} c="dark"> <Title order={4} lh={1.2} c="dark">
Daftar Permohonan Keberatan Informasi Publik Daftar Permohonan Keberatan Informasi Publik
</Title> </Title>
</GridCol> </GridCol>
@@ -99,7 +99,7 @@ function Page() {
<Stack gap={'sm'}> <Stack gap={'sm'}>
<Grid mb={10}> <Grid mb={10}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Title order={2} lh={1.2} c="dark"> <Title order={4} lh={1.2} c="dark">
Daftar Permohonan Keberatan Informasi Publik Daftar Permohonan Keberatan Informasi Publik
</Title> </Title>
</GridCol> </GridCol>
@@ -207,7 +207,7 @@ function Page() {
<Stack hiddenFrom="md" gap="xs"> <Stack hiddenFrom="md" gap="xs">
{data.map((item, index) => ( {data.map((item, index) => (
<Paper key={item.id} p="sm" radius="md" withBorder bg="white"> <Paper key={item.id} p="sm" radius="md" withBorder bg="white">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="xs" fw={600} lh={1.4} c="dimmed"> <Text fz="xs" fw={600} lh={1.4} c="dimmed">
No No

View File

@@ -58,7 +58,7 @@ function Page() {
</GridCol> </GridCol>
<GridCol span={12}> <GridCol span={12}>
<Title <Title
order={2} order={4}
c={colors['blue-button']} c={colors['blue-button']}
ta="center" ta="center"
lh={1.15} lh={1.15}

View File

@@ -77,7 +77,7 @@ function ListPegawaiPPID({ search }: { search: string }) {
<Box py="xl"> <Box py="xl">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Pegawai PPID Daftar Pegawai PPID
</Title> </Title>
<Button <Button
@@ -103,7 +103,7 @@ function ListPegawaiPPID({ search }: { search: string }) {
<Box py="xl"> <Box py="xl">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={2} lh={1.2}> <Title order={4} lh={1.2}>
Daftar Pegawai PPID Daftar Pegawai PPID
</Title> </Title>
<Button <Button

View File

@@ -67,7 +67,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
<Box py={{ base: 'sm', md: 'md' }}> <Box py={{ base: 'sm', md: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={2}>Daftar Posisi Organisasi PPID</Title> <Title order={4}>Daftar Posisi Organisasi PPID</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color="blue" color="blue"
@@ -148,7 +148,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm"> <Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={4}> <Stack gap={'xs'}>
<Box> <Box>
<Text fz="xs" fw={600} lh={1.4}>Nama Posisi</Text> <Text fz="xs" fw={600} lh={1.4}>Nama Posisi</Text>
<Text fz="sm" fw={600} lh={1.5}>{item.nama}</Text> <Text fz="sm" fw={600} lh={1.5}>{item.nama}</Text>