Compare commits
1 Commits
nico/18-de
...
nico/19-de
| Author | SHA1 | Date | |
|---|---|---|---|
| bf20cd55e8 |
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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%' }}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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} />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,7 +58,7 @@ function Page() {
|
||||
</GridCol>
|
||||
<GridCol span={12}>
|
||||
<Title
|
||||
order={2}
|
||||
order={4}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
lh={1.15}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user