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' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
<Title order={2} lh={1.2}>
<Title order={4} lh={1.2}>
Daftar Kategori Berita
</Title>
<Button

View File

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

View File

@@ -73,7 +73,7 @@ function ListFoto({ search }: { search: string }) {
<Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="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
leftSection={<IconPlus size={18} />}
color="blue"

View File

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

View File

@@ -142,7 +142,7 @@ function ListAjukanPermohonan({ search }: { search: string }) {
{data.length > 0 ? (
data.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md" shadow="xs">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>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">
<Stack gap="sm">
<Box>
<Stack gap={"xs"}>
<Text fz="lg" fw="bold">
Nama
</Text>
<Text fz="md" c="dimmed">
{data?.name || '-'}
</Text>
</Box>
</Stack>
<Box>
<Stack gap={"xs"}>
<Text fz="lg" fw="bold">
Deskripsi
</Text>
<Text
<Box pl={10}>
<Text
fz="md"
c="dimmed"
dangerouslySetInnerHTML={{
@@ -96,9 +97,10 @@ function DetailSuratKeterangan() {
}}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
</Box>
</Box>
</Stack>
<Box>
<Stack gap={"xs"}>
<Text fz="lg" fw="bold">
Gambar Konten Pelayanan
</Text>
@@ -117,7 +119,7 @@ function DetailSuratKeterangan() {
Tidak ada gambar
</Text>
)}
</Box>
</Stack>
<Box>
<Text fz="lg" fw="bold">

View File

@@ -82,7 +82,7 @@ function ListSuratKeterangan({ search }: { search: string }) {
<Box py={{ base: 'sm', md: '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' }}>
<Title order={2} lh={1.2}>
<Title order={4} lh={1.2}>
List Surat Keterangan
</Title>
<Button
@@ -169,7 +169,7 @@ function ListSuratKeterangan({ search }: { search: string }) {
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Nama

View File

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

View File

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

View File

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

View File

@@ -68,7 +68,7 @@ function ListPengumuman({ search }: { search: string }) {
<Box py={{ base: 'sm', md: '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' }}>
<Title order={2} lh={1.2}>
<Title order={4} lh={1.2}>
Daftar Pengumuman
</Title>
<Button

View File

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

View File

@@ -76,7 +76,7 @@ function ListPotensi({ search }: { search: string }) {
<Box py="lg">
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="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
</Title>
<Button

View File

@@ -66,7 +66,7 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
<Box py={{ base: 'sm', md: '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' }}>
<Title order={2} lh={1.2}>
<Title order={4} lh={1.2}>
List Perbekel Dari Masa Ke Masa
</Title>
<Button
@@ -134,7 +134,7 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<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>

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
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 { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
@@ -81,52 +81,93 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false}
>
{/* ✅ Scroll horizontal wrapper */}
<ScrollArea type="auto" offsetScrollbars>
<TabsList
p="sm"
<Box visibleFrom='md' pb={10}>
<ScrollArea type="auto" offsetScrollbars>
<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={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
padding: "1.5rem",
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
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
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
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>
{children}
</TabsPanel>
))}
</Tabs>
</Stack >
);
}

View File

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

View File

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

View File

@@ -94,7 +94,7 @@ function CreateArtikelKesehatan() {
};
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 */}
<Group mb="md">
<Button

View File

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

View File

@@ -131,7 +131,7 @@ function EditFasilitasKesehatan() {
};
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 */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

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

View File

@@ -70,7 +70,7 @@ function CreateFasilitasKesehatan() {
}, []);
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 */}
<Group mb="md">
<Button

View File

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

View File

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

View File

@@ -56,7 +56,7 @@ function CreateDokter() {
}
};
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 */}
<Group mb="md">
<Button

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ function CreateJadwalKegiatan() {
};
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 */}
<Group mb="md">
<Button

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,275 +3,317 @@
'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
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 { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
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';
type TooltipPayload = {
name: string;
value: number;
payload: any;
color: string;
dataKey: string;
name: string;
value: number;
payload: any;
color: string;
dataKey: string;
};
type CustomTooltipProps = TooltipProps<number, string> & {
active?: boolean;
payload?: TooltipPayload[];
label?: string;
active?: boolean;
payload?: TooltipPayload[];
label?: string;
};
function PersentaseDataKelahiranKematian() {
return (
<Stack gap="md">
<GrafikPersentaseKelahiranKematian />
</Stack>
);
return (
<Stack gap="md">
<GrafikPersentaseKelahiranKematian />
</Stack>
);
}
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 = {
tahun: string;
totalKelahiran: number;
totalKematian: number;
data: Array<{
id: string;
bulan: string;
kelahiran: number;
kematian: number;
}>;
};
// ✅ Fungsi hitung tahunan + bulanan
const countByYearAndMonth = (kelahiran: any[], kematian: any[]): DataTahunan[] => {
const dataTahunan: Record<string, DataTahunan> = {};
const namaBulan = [
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
// ✅ Fungsi hitung tahunan + bulanan
const countByYearAndMonth = (kelahiran: any[], kematian: any[]): DataTahunan[] => {
const dataTahunan: Record<string, DataTahunan> = {};
kelahiran?.forEach((item: any) => {
const date = new Date(item.tanggal);
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 = [
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
dataTahunan[tahun].totalKelahiran += 1;
dataTahunan[tahun].data[bulanIndex].kelahiran += 1;
});
kematian?.forEach((item: any) => {
const date = new Date(item.tanggal);
const tahun = date.getFullYear().toString();
const bulanIndex = date.getMonth();
// Proses kelahiran
kelahiran?.forEach((item: any) => {
const date = new Date(item.tanggal);
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
}))
};
}
dataTahunan[tahun].totalKematian += 1;
dataTahunan[tahun].data[bulanIndex].kematian += 1;
});
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
}))
};
}
return Object.values(dataTahunan).sort((a, b) => parseInt(a.tahun) - parseInt(b.tahun));
};
const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<DataTahunan[]>([]);
const [selectedYear, setSelectedYear] = useState<string | null>(null);
dataTahunan[tahun].totalKelahiran += 1;
dataTahunan[tahun].data[bulanIndex].kelahiran += 1;
});
const formatNumber = (num: number) => new Intl.NumberFormat('id-ID').format(num);
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
kematian?.forEach((item: any) => {
const date = new Date(item.tanggal);
const tahun = date.getFullYear().toString();
const bulanIndex = date.getMonth();
useShallowEffect(() => {
statePersentase.kelahiran.findMany.load(1, 1000);
statePersentase.kematian.findMany.load(1, 1000);
}, []);
useEffect(() => {
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
const hasil = countByYearAndMonth(
statePersentase.kelahiran.findMany.data,
statePersentase.kematian.findMany.data
);
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
}))
};
}
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" />;
}
dataTahunan[tahun].totalKematian += 1;
dataTahunan[tahun].data[bulanIndex].kematian += 1;
});
const selectedYearData = chartData.find(d => d.tahun === selectedYear);
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);
const [chartData, setChartData] = useState<DataTahunan[]>([]);
const [selectedYear, setSelectedYear] = useState<string | null>(null);
{selectedYearData && (
<Stack gap="md">
<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>
)}
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
if (active && payload && payload.length) {
return (
<Paper p="sm" shadow="md" withBorder radius="md">
<Text size="sm" fw={600}>Tahun {label}</Text>
<Text size="sm" c="blue.6">Kelahiran: {formatNumber(payload[0].value)}</Text>
<Text size="sm" c="red.6">Kematian: {formatNumber(payload[1].value)}</Text>
</Paper>
);
}
return null;
};
useShallowEffect(() => {
statePersentase.kelahiran.findMany.load(1, 1000);
statePersentase.kematian.findMany.load(1, 1000);
}, []);
useEffect(() => {
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
const hasil = countByYearAndMonth(
statePersentase.kelahiran.findMany.data,
statePersentase.kematian.findMany.data
);
setChartData(hasil);
setSelectedYear(hasil[0]?.tahun || null);
}
}, [statePersentase.kelahiran.findMany.data, statePersentase.kematian.findMany.data]);
if (!statePersentase.kelahiran.findMany.data || !statePersentase.kematian.findMany.data) {
return <Skeleton h={400} radius="lg" />;
}
const selectedYearData = chartData.find(d => d.tahun === selectedYear);
return (
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
<Stack gap="lg">
<Flex justify="space-between" align="center">
<Title order={3} fw={700}>Statistik Kelahiran & Kematian</Title>
<Flex gap="sm">
<MantineTooltip label="Tambah Data Kelahiran" withArrow>
<ActionIcon size="lg" radius="xl" color="blue.6" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran')}>
<IconBabyCarriage size={22} />
</ActionIcon>
</MantineTooltip>
<MantineTooltip label="Tambah Data Kematian" withArrow>
<ActionIcon size="lg" radius="xl" color="red.6" onClick={() => router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian')}>
<IconGrave2 size={22} />
</ActionIcon>
</MantineTooltip>
</Flex>
</Flex>
{chartData.length === 0 ? (
<Center py="xl">
<Text c="dimmed" fs="italic">Belum ada data untuk ditampilkan</Text>
</Center>
) : (
<>
<Box maw={220}>
<Select
label="Pilih Tahun"
placeholder="Pilih tahun data"
data={chartData.map((item) => ({ value: item.tahun, label: item.tahun }))}
value={selectedYear}
onChange={(value) => setSelectedYear(value || null)}
size="sm"
radius="md"
/>
</Box>
<Box h={360}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData} margin={{ top: 20, right: 30, left: 0, bottom: 10 }}>
<XAxis dataKey="tahun" />
<YAxis />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Bar dataKey="totalKelahiran" name="Kelahiran" fill="#4dabf7" radius={[6, 6, 0, 0]} />
<Bar dataKey="totalKematian" name="Kematian" fill="#f03e3e" radius={[6, 6, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</Box>
{selectedYearData && (
<Box>
<Flex align="center" gap="sm" mb="md">
<Title order={4} fw={600}>Rincian Tahun {selectedYear}</Title>
<Badge variant="light" color="blue">{formatNumber(selectedYearData.totalKelahiran)} kelahiran</Badge>
<Badge variant="light" color="red">{formatNumber(selectedYearData.totalKematian)} kematian</Badge>
</Flex>
<Table striped withTableBorder highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Bulan</Table.Th>
<Table.Th ta="right">Kelahiran</Table.Th>
<Table.Th ta="right">Kematian</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{selectedYearData.data.length > 0 ? (
<>
{selectedYearData.data.map((item) => (
<Table.Tr key={item.id}>
<Table.Td>{item.bulan}</Table.Td>
<Table.Td ta="right">{formatNumber(item.kelahiran)}</Table.Td>
<Table.Td ta="right">{formatNumber(item.kematian)}</Table.Td>
</Table.Tr>
))}
<Table.Tr style={{ fontWeight: 'bold' }}>
<Table.Td>Total</Table.Td>
<Table.Td ta="right">{formatNumber(selectedYearData.totalKelahiran)}</Table.Td>
<Table.Td ta="right">{formatNumber(selectedYearData.totalKematian)}</Table.Td>
</Table.Tr>
</>
) : (
<Table.Tr>
<Table.Td colSpan={3} ta="center" c="dimmed">Tidak ada rincian bulanan</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
</Box>
)}
</>
)}
</Stack>
</Paper>
);
{/* Total row mobile */}
{selectedYearData.data.length > 0 && (
<Paper p="sm" radius="md" withBorder bg="gray.1">
<Text fz="xs" fw={600} lh={1.4}>Total</Text>
<Flex justify="space-between" mt="xs">
<Text fz="sm" fw={600} lh={1.4}>Kelahiran</Text>
<Text fz="sm" fw={600} lh={1.4}>
{formatNumber(selectedYearData.totalKelahiran)}
</Text>
</Flex>
<Flex justify="space-between" mt="xs">
<Text fz="sm" fw={600} lh={1.4}>Kematian</Text>
<Text fz="sm" fw={600} lh={1.4}>
{formatNumber(selectedYearData.totalKematian)}
</Text>
</Flex>
</Paper>
)}
</Stack>
</Box>
</Stack>
)}
</>
)}
</Stack>
</Paper>
);
}
export default PersentaseDataKelahiranKematian;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,7 +84,7 @@ function CreatePuskesmas() {
};
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 */}
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">

View File

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

View File

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

View File

@@ -69,7 +69,7 @@ function ListAPBDes({ search }: { search: string }) {
<Box visibleFrom="md">
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="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
</Title>
<Button

View File

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

View File

@@ -66,7 +66,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
<Stack gap={'md'}>
<Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
<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
</Title>
<Button

View File

@@ -89,7 +89,7 @@ function ListResponden({ search }: ListRespondenProps) {
{/* Desktop Table */}
<Box visibleFrom="md">
<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
</Title>
<Table
@@ -158,7 +158,7 @@ function ListResponden({ search }: ListRespondenProps) {
{/* Mobile Cards */}
<Box hiddenFrom="md">
<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
</Title>
{filteredData.length === 0 ? (
@@ -170,7 +170,7 @@ function ListResponden({ search }: ListRespondenProps) {
) : (
filteredData.map((item) => (
<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="md" lh={1.5}>{item.name}</Text>

View File

@@ -69,7 +69,7 @@ function ListKategoriPrestasi({ search }: { search: string }) {
{/* DESKTOP: Table */}
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm" withBorder>
<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
leftSection={<IconPlus size={18} />}
color="blue"

View File

@@ -57,7 +57,7 @@ function ListPrestasi({ search }: { search: string }) {
<Box py={{ base: 'sm', md: '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' }}>
<Title order={2} size={isMobile ? 'md' : 'lg'} lh={1.2}>
<Title order={4} size={isMobile ? 'md' : 'lg'} lh={1.2}>
Daftar Prestasi Desa
</Title>
<Button

View File

@@ -69,7 +69,7 @@ function ListDaftarInformasi({ search }: { search: string }) {
<Box py={{ base: 'md', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="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
</Title>
<Button
@@ -155,7 +155,7 @@ function ListDaftarInformasi({ search }: { search: string }) {
<Stack hiddenFrom="md" gap="sm">
{filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<Text fw={600} lh={1.4}>
Jenis Informasi

View File

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

View File

@@ -61,7 +61,7 @@ function Page() {
<Stack gap={'sm'}>
<Grid mb={10}>
<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
</Title>
</GridCol>
@@ -100,7 +100,7 @@ function Page() {
<Stack gap={'sm'}>
<Grid mb={10}>
<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
</Title>
</GridCol>
@@ -202,7 +202,7 @@ function Page() {
<Stack hiddenFrom="md" gap="xs">
{data.map((item, index) => (
<Paper key={item.id} p="sm" radius="md" withBorder bg="white">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<Text fz="xs" fw={600} lh={1.4} c="dark">
No

View File

@@ -61,7 +61,7 @@ function Page() {
<Stack gap={'sm'}>
<Grid mb={10}>
<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
</Title>
</GridCol>
@@ -99,7 +99,7 @@ function Page() {
<Stack gap={'sm'}>
<Grid mb={10}>
<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
</Title>
</GridCol>
@@ -207,7 +207,7 @@ function Page() {
<Stack hiddenFrom="md" gap="xs">
{data.map((item, index) => (
<Paper key={item.id} p="sm" radius="md" withBorder bg="white">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<Text fz="xs" fw={600} lh={1.4} c="dimmed">
No

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
<Box py={{ base: 'sm', md: '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' }}>
<Title order={2}>Daftar Posisi Organisasi PPID</Title>
<Title order={4}>Daftar Posisi Organisasi PPID</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
@@ -148,7 +148,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={4}>
<Stack gap={'xs'}>
<Box>
<Text fz="xs" fw={600} lh={1.4}>Nama Posisi</Text>
<Text fz="sm" fw={600} lh={1.5}>{item.nama}</Text>