Fix QC Kak Inno 8 Des
Fix QC Kak Ayu 8 Des Fix QC Pak Jun 8 Des
This commit is contained in:
@@ -20,9 +20,9 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
icon: <IconActivity size={18} stroke={1.8} />
|
icon: <IconActivity size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Grafik Hasil Kepuasan Masyarakat",
|
label: "Penderita Penyakit",
|
||||||
value: "grafikhasilkepuasan",
|
value: "penderitapenyakit",
|
||||||
href: "/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan",
|
href: "/admin/kesehatan/data-kesehatan-warga/penderita_penyakit",
|
||||||
icon: <IconGauge size={18} stroke={1.8} />
|
icon: <IconGauge size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ function EditGrafikHasilKepuasan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading grafik hasil kepuasan:", err);
|
console.error("Error loading penderita penyakit:", err);
|
||||||
toast.error("Gagal memuat data grafik hasil kepuasan");
|
toast.error("Gagal memuat data penderita penyakit");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,11 +99,11 @@ function EditGrafikHasilKepuasan() {
|
|||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
editState.update.form = { ...editState.update.form, ...formData };
|
editState.update.form = { ...editState.update.form, ...formData };
|
||||||
await editState.update.submit();
|
await editState.update.submit();
|
||||||
toast.success('Grafik hasil kepuasan berhasil diperbarui!');
|
toast.success('penderita penyakit berhasil diperbarui!');
|
||||||
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
|
router.push('/admin/kesehatan/data-kesehatan-warga/penderita_penyakit');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating grafik hasil kepuasan:', err);
|
console.error('Error updating penderita penyakit:', err);
|
||||||
toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan');
|
toast.error('Terjadi kesalahan saat memperbarui penderita penyakit');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ function EditGrafikHasilKepuasan() {
|
|||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
<Title order={4} ml="sm" c="dark">
|
<Title order={4} ml="sm" c="dark">
|
||||||
Edit Grafik Hasil Kepuasan
|
Edit Penderita Penyakit
|
||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ function DetailGrafikHasilKepuasan() {
|
|||||||
state.delete.byId(selectedId);
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false);
|
setModalHapus(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
router.push("/admin/kesehatan/data-kesehatan-warga/penderita_penyakit");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ function DetailGrafikHasilKepuasan() {
|
|||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
Detail Data Grafik Hasil Kepuasan
|
Detail Data Penderita Penyakit
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
@@ -118,7 +118,7 @@ function DetailGrafikHasilKepuasan() {
|
|||||||
color="green"
|
color="green"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${data.id}/edit`
|
`/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/${data.id}/edit`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -40,7 +40,7 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
await stateGrafikKepuasan.create.create();
|
await stateGrafikKepuasan.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
|
router.push("/admin/kesehatan/data-kesehatan-warga/penderita_penyakit");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating grafik kepuasan:", error);
|
console.error("Error creating grafik kepuasan:", error);
|
||||||
toast.error("Terjadi kesalahan saat membuat grafik kepuasan");
|
toast.error("Terjadi kesalahan saat membuat grafik kepuasan");
|
||||||
@@ -62,7 +62,7 @@ function CreateGrafikHasilKepuasanMasyarakat() {
|
|||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
<Title order={4} ml="sm" c="dark">
|
<Title order={4} ml="sm" c="dark">
|
||||||
Tambah Grafik Hasil Kepuasan Masyarakat
|
Tambah Penderita Penyakit
|
||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ function GrafikHasilKepuasanMasyarakat() {
|
|||||||
<Box>
|
<Box>
|
||||||
{/* Header Search */}
|
{/* Header Search */}
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Grafik Hasil Kepuasan Masyarakat'
|
title='Penderita Penyakit'
|
||||||
placeholder='Cari nama atau alamat...'
|
placeholder='Cari nama atau alamat...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
@@ -115,14 +115,14 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
{/* Judul + Tombol Tambah */}
|
{/* Judul + Tombol Tambah */}
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title order={4}>Daftar Penderita Penyakit</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
'/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
|
'/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/create'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -176,7 +176,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
color="blue"
|
color="blue"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`
|
`/admin/kesehatan/data-kesehatan-warga/penderita_penyakit/${item.id}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -221,7 +221,7 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
<Box mt="lg" style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
<Box mt="lg" style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title pb={10} order={4}>Penderita Penyakit</Title>
|
||||||
{mounted && diseaseChartData.length > 0 ? (
|
{mounted && diseaseChartData.length > 0 ? (
|
||||||
<Center>
|
<Center>
|
||||||
<BarChart
|
<BarChart
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import Elysia from "elysia";
|
import Elysia from "elysia";
|
||||||
import DaftarInformasiPublik from "./daftar_informasi_publik";
|
import DaftarInformasiPublik from "./daftar_informasi_publik";
|
||||||
import GrafikHasilKepuasanMasyarakat from "./ikm/grafik_hasil_kepuasan_masyarakat";
|
|
||||||
import GrafikBerdasarkanJenisKelamin from "./ikm/grafik_berdasarkan_jenis_kelamin";
|
import GrafikBerdasarkanJenisKelamin from "./ikm/grafik_berdasarkan_jenis_kelamin";
|
||||||
import GrafikBerdasarkanResponden from "./ikm/grafik_responden";
|
import GrafikBerdasarkanResponden from "./ikm/grafik_responden";
|
||||||
import GrafikBerdasarkanUmur from "./ikm/grafik_berdasarkan_umur";
|
import GrafikBerdasarkanUmur from "./ikm/grafik_berdasarkan_umur";
|
||||||
@@ -10,6 +9,7 @@ import ProfilePPID from "./profile_ppid";
|
|||||||
import VisiMisiPPID from "./visi_misi_ppid/visi_misi_ppid";
|
import VisiMisiPPID from "./visi_misi_ppid/visi_misi_ppid";
|
||||||
import DasarHukumPPID from "./dasar_hukum";
|
import DasarHukumPPID from "./dasar_hukum";
|
||||||
import StrukturPPID from "./struktur_ppid";
|
import StrukturPPID from "./struktur_ppid";
|
||||||
|
import GrafikHasilKepuasanMasyarakat from "./ikm/grafik_hasil_kepuasan_masyarakat";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function Page() {
|
|||||||
const state = useProxy(lowonganKerjaState)
|
const state = useProxy(lowonganKerjaState)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function Page() {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const state = useProxy(pasarDesaState.pasarDesa)
|
const state = useProxy(pasarDesaState.pasarDesa)
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ interface ProgramKemiskinanData {
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const state = useProxy(programKemiskinanState)
|
const state = useProxy(programKemiskinanState)
|
||||||
|
|
||||||
// 🔧 Get valid statistics data with proper type checking
|
// 🔧 Get valid statistics data with proper type checking
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useRouter } from 'next/navigation';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const state = useProxy(desaDigitalState)
|
const state = useProxy(desaDigitalState)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { IconSearch } from '@tabler/icons-react';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const state = useProxy(infoTeknoState)
|
const state = useProxy(infoTeknoState)
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import BackButton from '../../desa/layanan/_com/BackButto';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const listState = useProxy(programKreatifState);
|
const listState = useProxy(programKreatifState);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter()
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function Page() {
|
|||||||
const state = useProxy(keamananLingkunganState)
|
const state = useProxy(keamananLingkunganState)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const kontakState = useProxy(kontakDarurat.kontakDaruratKeamananState);
|
const kontakState = useProxy(kontakDarurat.kontakDaruratKeamananState);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
|
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, ColorSwatch, Flex, Group, Modal, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
ColorSwatch,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
Modal,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
} from '@mantine/core';
|
||||||
import { DateTimePicker } from '@mantine/dates';
|
import { DateTimePicker } from '@mantine/dates';
|
||||||
import { useDebouncedValue, useDisclosure, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useDisclosure, useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowRight, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconArrowRight, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -12,9 +28,10 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState("");
|
const mobile = useMediaQuery('(max-width: 768px)');
|
||||||
const router = useTransitionRouter()
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
const router = useTransitionRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const stateLaporan = useProxy(laporanPublikState);
|
const stateLaporan = useProxy(laporanPublikState);
|
||||||
const {
|
const {
|
||||||
@@ -49,143 +66,219 @@ function Page() {
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await stateLaporan.create.create();
|
await stateLaporan.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
|
{/* Header: Back + Search */}
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius="lg"
|
||||||
placeholder='Cari Laporan Publik'
|
placeholder="Cari Laporan Publik"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
w={{ base: "100%", md: "30%" }}
|
w={{ base: '100%', md: '30%' }}
|
||||||
/>
|
size={mobile ? 'sm' : 'md'}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Title + Add Button */}
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between" align="flex-start">
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text
|
||||||
|
ta="center"
|
||||||
|
fz={{ base: 'xl', sm: '2xl', md: '2.5rem' }}
|
||||||
|
c={colors['blue-button']}
|
||||||
|
fw="bold"
|
||||||
|
lineClamp={2}
|
||||||
|
style={{ wordBreak: 'break-word' }}
|
||||||
|
>
|
||||||
Laporan Keamanan Lingkungan
|
Laporan Keamanan Lingkungan
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
onClick={open}
|
onClick={open}
|
||||||
bg={colors['blue-button']}
|
bg={colors['blue-button']}
|
||||||
size="md"
|
size={mobile ? 'sm' : 'md'}
|
||||||
radius="md"
|
radius="md"
|
||||||
rightSection={<IconPlus size={20} />}
|
rightSection={<IconPlus size={20} />}
|
||||||
>
|
>
|
||||||
Tambah Laporan
|
{mobile ? 'Tambah' : 'Tambah Laporan'}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
{/* Legend Status */}
|
||||||
<Flex justify={'space-between'} align={'center'}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text fz={{ base: 'sm', md: 'h4' }} fw={'bold'}>Laporan Terbaru</Text>
|
<Flex
|
||||||
<Box>
|
justify="space-between"
|
||||||
<Flex gap={'lg'}>
|
align="center"
|
||||||
<Box>
|
direction={mobile ? 'column' : 'row'}
|
||||||
<Flex gap={{ base: 2, md: 5 }} align={'center'}>
|
gap={mobile ? 'xs' : 'lg'}
|
||||||
<Text fz={{ base: 'sm', md: 'h4' }}>Terselesaikan</Text>
|
>
|
||||||
<ColorSwatch color="#2A742D" size={20} />
|
<Text fz={{ base: 'sm', md: 'h4' }} fw="bold">
|
||||||
</Flex>
|
Laporan Terbaru
|
||||||
</Box>
|
</Text>
|
||||||
<Box>
|
<Flex
|
||||||
<Flex gap={{ base: 2, md: 5 }} align={'center'}>
|
gap={mobile ? 'xs' : 'lg'}
|
||||||
<Text fz={{ base: 'sm', md: 'h4' }}>Dalam Proses</Text>
|
wrap="wrap"
|
||||||
<ColorSwatch color="#D1961F" size={20} />
|
justify={mobile ? 'center' : 'flex-start'}
|
||||||
</Flex>
|
align="center"
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={{ base: 2, md: 5 }} align={'center'}>
|
|
||||||
<Text fz={{ base: 'sm', md: 'h4' }}>Gagal</Text>
|
|
||||||
<ColorSwatch color="#A34437" size={20} />
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 3
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{data.map((v, k) => {
|
<Flex gap={2} align="center">
|
||||||
return (
|
<ColorSwatch color="#2A742D" size={16} />
|
||||||
<Paper radius={'lg'} key={k} bg={colors['white-trans-1']} p={'xl'}>
|
<Text fz={{ base: 'xs', md: 'sm' }}>Terselesaikan</Text>
|
||||||
<Stack>
|
</Flex>
|
||||||
<Text c={colors['blue-button']} lineClamp={3} truncate="end" fz="h4" fw="bold">{v.judul}</Text>
|
<Flex gap={2} align="center">
|
||||||
<Text fs={'italic'} fz={'xl'}>
|
<ColorSwatch color="#D1961F" size={16} />
|
||||||
{v.tanggalWaktu
|
<Text fz={{ base: 'xs', md: 'sm' }}>Dalam Proses</Text>
|
||||||
? new Date(v.tanggalWaktu).toLocaleString('id-ID')
|
</Flex>
|
||||||
: '-'}
|
<Flex gap={2} align="center">
|
||||||
</Text>
|
<ColorSwatch color="#A34437" size={16} />
|
||||||
<Box>
|
<Text fz={{ base: 'xs', md: 'sm' }}>Gagal</Text>
|
||||||
<Text fw={'bold'}>Penanganan:</Text>
|
</Flex>
|
||||||
{v.penanganan?.length ? (
|
</Flex>
|
||||||
v.penanganan.map((item, index) => (
|
</Flex>
|
||||||
<Box key={index}>
|
|
||||||
<Text
|
|
||||||
fz="md"
|
|
||||||
c="dimmed"
|
|
||||||
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
|
|
||||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<Text fz="sm" fs="italic" c="dimmed">
|
|
||||||
Belum ada penanganan
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
padding: '4px 12px',
|
|
||||||
borderRadius: '16px',
|
|
||||||
backgroundColor:
|
|
||||||
v.status === 'Selesai' ? '#94EF95FF' :
|
|
||||||
v.status === 'Proses' ? '#F1D295FF' :
|
|
||||||
'#F38E8EFF',
|
|
||||||
color:
|
|
||||||
v.status === 'Selesai' ? '#01BA01FF' :
|
|
||||||
v.status === 'Proses' ? '#B67A00FF' :
|
|
||||||
'#AE1700FF',
|
|
||||||
fontWeight: 900,
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
textAlign: 'center',
|
|
||||||
minWidth: '80px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{v.status}
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
|
|
||||||
onClick={() => router.push(`/darmasaba/keamanan/laporan-publik/${v.id}`)}
|
|
||||||
>Lihat Detail Kronologi
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
<Center>
|
|
||||||
<Pagination
|
|
||||||
value={page}
|
|
||||||
onChange={(newPage) => load(newPage)}
|
|
||||||
total={totalPages}
|
|
||||||
my="md"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Modal opened={opened} onClose={close} title="Tambah Laporan Publik">
|
|
||||||
|
{/* Cards Grid */}
|
||||||
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
|
<SimpleGrid
|
||||||
|
cols={{
|
||||||
|
base: 1,
|
||||||
|
md: 3,
|
||||||
|
}}
|
||||||
|
spacing="lg"
|
||||||
|
>
|
||||||
|
{data.map((v, k) => (
|
||||||
|
<Paper
|
||||||
|
key={k}
|
||||||
|
radius="lg"
|
||||||
|
bg={colors['white-trans-1']}
|
||||||
|
p="lg"
|
||||||
|
shadow="sm"
|
||||||
|
style={{
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'translateY(-4px)',
|
||||||
|
boxShadow: '0 8px 20px rgba(0,0,0,0.1)',
|
||||||
|
},
|
||||||
|
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text
|
||||||
|
c={colors['blue-button']}
|
||||||
|
lineClamp={2}
|
||||||
|
fz={{ base: 'lg', md: 'xl' }}
|
||||||
|
fw="bold"
|
||||||
|
style={{ wordBreak: 'break-word' }}
|
||||||
|
>
|
||||||
|
{v.judul}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
fs="italic"
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
c="dimmed"
|
||||||
|
>
|
||||||
|
{v.tanggalWaktu
|
||||||
|
? new Date(v.tanggalWaktu).toLocaleString('id-ID')
|
||||||
|
: '-'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm">
|
||||||
|
Penanganan:
|
||||||
|
</Text>
|
||||||
|
{v.penanganan?.length ? (
|
||||||
|
v.penanganan.map((item, index) => (
|
||||||
|
<Box key={index}>
|
||||||
|
<Text
|
||||||
|
fz="xs"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: item.deskripsi || '-',
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
maxHeight: '80px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Text fz="xs" fs="italic" c="dimmed">
|
||||||
|
Belum ada penanganan
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
backgroundColor:
|
||||||
|
v.status === 'Selesai'
|
||||||
|
? '#94EF95FF'
|
||||||
|
: v.status === 'Proses'
|
||||||
|
? '#F1D295FF'
|
||||||
|
: '#F38E8EFF',
|
||||||
|
color:
|
||||||
|
v.status === 'Selesai'
|
||||||
|
? '#01BA01FF'
|
||||||
|
: v.status === 'Proses'
|
||||||
|
? '#B67A00FF'
|
||||||
|
: '#AE1700FF',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
textAlign: 'center',
|
||||||
|
minWidth: '70px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{v.status}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
rightSection={
|
||||||
|
<IconArrowRight
|
||||||
|
size={18}
|
||||||
|
color={colors['white-1']}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={() => router.push(`/darmasaba/keamanan/laporan-publik/${v.id}`)}
|
||||||
|
size={mobile ? 'sm' : 'md'}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{mobile ? 'Detail' : 'Lihat Detail Kronologi'}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center px={{ base: 'md', md: 100 }}>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
size={mobile ? 'sm' : 'md'}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
{/* Modal Form */}
|
||||||
|
<Modal opened={opened} onClose={close} title="Tambah Laporan Publik" size="xl">
|
||||||
<Paper
|
<Paper
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
@@ -196,18 +289,26 @@ function Page() {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={stateLaporan.create.form.judul}
|
value={stateLaporan.create.form.judul}
|
||||||
onChange={(e) => (stateLaporan.create.form.judul = e.target.value)}
|
onChange={(e) =>
|
||||||
|
(stateLaporan.create.form.judul = e.target.value)
|
||||||
|
}
|
||||||
label={<Text fw="bold" fz="sm">Judul Laporan Publik</Text>}
|
label={<Text fw="bold" fz="sm">Judul Laporan Publik</Text>}
|
||||||
placeholder="Masukkan judul laporan publik"
|
placeholder="Masukkan judul laporan publik"
|
||||||
required
|
required
|
||||||
|
w="100%"
|
||||||
|
size={mobile ? 'sm' : 'md'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={stateLaporan.create.form.lokasi}
|
value={stateLaporan.create.form.lokasi}
|
||||||
onChange={(e) => (stateLaporan.create.form.lokasi = e.target.value)}
|
onChange={(e) =>
|
||||||
|
(stateLaporan.create.form.lokasi = e.target.value)
|
||||||
|
}
|
||||||
label={<Text fw="bold" fz="sm">Lokasi Laporan Publik</Text>}
|
label={<Text fw="bold" fz="sm">Lokasi Laporan Publik</Text>}
|
||||||
placeholder="Masukkan lokasi laporan publik"
|
placeholder="Masukkan lokasi laporan publik"
|
||||||
required
|
required
|
||||||
|
w="100%"
|
||||||
|
size={mobile ? 'sm' : 'md'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
@@ -220,6 +321,8 @@ function Page() {
|
|||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
stateLaporan.create.form.tanggalWaktu = val ? val.toString() : '';
|
stateLaporan.create.form.tanggalWaktu = val ? val.toString() : '';
|
||||||
}}
|
}}
|
||||||
|
w="100%"
|
||||||
|
size={mobile ? 'sm' : 'md'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
@@ -238,7 +341,7 @@ function Page() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size={mobile ? 'sm' : 'md'}
|
||||||
style={{
|
style={{
|
||||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useDebouncedValue } from '@mantine/hooks';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(polsekTerdekatState);
|
const state = useProxy(polsekTerdekatState);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { IconSearch } from '@tabler/icons-react';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(tipsKeamananState)
|
const state = useProxy(tipsKeamananState)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Divider, Group, Image, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Divider, Flex, Group, Image, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
|
import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
@@ -78,25 +78,33 @@ function Page() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">Pendahuluan</Text>
|
<Text fz="h4" fw="bold">Pendahuluan</Text>
|
||||||
<Divider my="xs" />
|
<Divider my="xs" />
|
||||||
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text>
|
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text>
|
||||||
<Divider my="xs" />
|
<Divider my="xs" />
|
||||||
<Text fz="md" lh={1.6} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">{state.findUnique.data.prevention?.title}</Text>
|
<Text fz="h4" fw="bold">{state.findUnique.data.prevention?.title}</Text>
|
||||||
<Divider my="xs" />
|
<Divider my="xs" />
|
||||||
<Text fz="md" lh={1.6} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">{state.findUnique.data.firstaid?.title}</Text>
|
<Text fz="h4" fw="bold">{state.findUnique.data.firstaid?.title}</Text>
|
||||||
<Divider my="xs" />
|
<Divider my="xs" />
|
||||||
<Text fz="md" lh={1.6} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
@@ -114,10 +122,14 @@ function Page() {
|
|||||||
{state.findUnique.data?.mythvsfact ? (
|
{state.findUnique.data?.mythvsfact ? (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz="sm" lh={1.6} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz="sm" lh={1.6} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
) : (
|
) : (
|
||||||
@@ -133,17 +145,15 @@ function Page() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">Kapan Harus ke Dokter?</Text>
|
<Text fz="h4" fw="bold">Kapan Harus ke Dokter?</Text>
|
||||||
<Divider my="xs" />
|
<Divider my="xs" />
|
||||||
<Group gap="xs" mb="xs">
|
<Flex justify={'flex-start'} gap={"xs"} align={"center"} mb="xs">
|
||||||
<IconAlertCircle size={18} color="red" />
|
<IconAlertCircle size={18} color="red" />
|
||||||
<Text fz="md">Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
|
<Text fz="md">Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
|
||||||
</Group>
|
</Flex>
|
||||||
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} />
|
<Box pl={20}>
|
||||||
|
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} /></Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">Kasus DBD di Wilayah Abiansemal</Text>
|
|
||||||
<Divider my="xs" />
|
|
||||||
|
|
||||||
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
|
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
|
||||||
<Group gap="xs" mb="sm">
|
<Group gap="xs" mb="sm">
|
||||||
<IconInfoCircle size={20} color={colors['white-1']} />
|
<IconInfoCircle size={20} color={colors['white-1']} />
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function GrafikPenyakit() {
|
|||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Center>
|
<Center>
|
||||||
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title pb={10} order={2}>Penderita Penyakit</Title>
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -103,7 +103,7 @@ function GrafikPenyakit() {
|
|||||||
) : (
|
) : (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
|
||||||
<Paper bg={colors["white-trans-1"]} p={'md'}>
|
<Paper bg={colors["white-trans-1"]} p={'md'}>
|
||||||
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title pb={10} order={2}>Penderita Penyakit</Title>
|
||||||
{mounted && diseaseChartData.length > 0 && (
|
{mounted && diseaseChartData.length > 0 && (
|
||||||
<Center>
|
<Center>
|
||||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
|
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
|
||||||
|
|||||||
@@ -69,25 +69,33 @@ function Page() {
|
|||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
|
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
<Box pl={20}>
|
||||||
|
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
|
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
|
<Box pl={20}>
|
||||||
|
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
|
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
|
<Box pl={20}>
|
||||||
|
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
|
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
|
<Box pl={20}>
|
||||||
|
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
|||||||
@@ -71,11 +71,13 @@ function DetailInfoWabahPenyakitUser() {
|
|||||||
{/* Deskripsi */}
|
{/* Deskripsi */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text
|
<Box pl={20}>
|
||||||
fz="md"
|
<Text
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
|
fz="md"
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
|
||||||
/>
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function Page() {
|
|||||||
const state = useProxy(infoWabahPenyakit);
|
const state = useProxy(infoWabahPenyakit);
|
||||||
const router = useTransitionRouter();
|
const router = useTransitionRouter();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500)
|
const [debouncedSearch] = useDebouncedValue(search, 1000)
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
|
|||||||
111
src/app/darmasaba/(pages)/kesehatan/kontak-darurat/[id]/page.tsx
Normal file
111
src/app/darmasaba/(pages)/kesehatan/kontak-darurat/[id]/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
const state = useProxy(kontakDarurat);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(params?.id as string);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{base: 'md', md: 100}} py={10}>
|
||||||
|
{/* Tombol Back */}
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Wrapper Detail */}
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w="100%"
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Kontak Darurat
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Whatsapp</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.whatsapp || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text
|
||||||
|
fz="md"
|
||||||
|
c="dimmed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt="gambar"
|
||||||
|
radius="md"
|
||||||
|
maw={300}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
leftSection={<IconBrandWhatsapp size={18} />}
|
||||||
|
component="a"
|
||||||
|
href={`https://wa.me/${data.whatsapp.replace(/\D/g, '')}`}
|
||||||
|
target="_blank"
|
||||||
|
aria-label="Hubungi WhatsApp"
|
||||||
|
>
|
||||||
|
WhatsApp
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Center,
|
Center,
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
GridCol,
|
||||||
|
Group,
|
||||||
Image,
|
Image,
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
@@ -17,17 +18,18 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconBrandWhatsapp, IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(kontakDarurat);
|
const state = useProxy(kontakDarurat);
|
||||||
|
const router = useTransitionRouter()
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500)
|
const [debouncedSearch] = useDebouncedValue(search, 1000)
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
@@ -88,81 +90,77 @@ function Page() {
|
|||||||
) : (
|
) : (
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg">
|
||||||
{data.map((v, k) => (
|
{data.map((v, k) => (
|
||||||
<Paper
|
<Paper
|
||||||
key={k}
|
key={k}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
withBorder
|
withBorder
|
||||||
p="lg"
|
p="lg"
|
||||||
bg={colors['white-trans-1']}
|
bg={colors['white-trans-1']}
|
||||||
style={{
|
style={{
|
||||||
transition: 'all 200ms ease',
|
transition: 'all 200ms ease',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'space-between', // ✅ biar button selalu di bawah
|
justifyContent: 'space-between', // ✅ biar button selalu di bawah
|
||||||
height: '100%', // ✅ bikin tinggi seragam
|
height: '100%', // ✅ bikin tinggi seragam
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack align="center" gap="sm" style={{ flexGrow: 1 }}>
|
<Stack align="center" gap="sm" style={{ flexGrow: 1 }}>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
aspectRatio: '16/9',
|
aspectRatio: '16/9',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={v.image.link}
|
src={v.image.link}
|
||||||
alt={v.name}
|
alt={v.name}
|
||||||
fit="cover"
|
fit="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
transition: 'transform 0.4s ease',
|
transition: 'transform 0.4s ease',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
|
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}>
|
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
fz="sm"
|
fz="sm"
|
||||||
ta="center"
|
ta="center"
|
||||||
lineClamp={3}
|
lineClamp={3}
|
||||||
lh={1.6}
|
lh={1.6}
|
||||||
style={{
|
style={{
|
||||||
minHeight: '4.8em', // tinggi tetap 3 baris
|
minHeight: '4.8em', // tinggi tetap 3 baris
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* ✅ Tombol selalu di bagian bawah card */}
|
{/* ✅ Tombol selalu di bagian bawah card */}
|
||||||
<Center mt="md">
|
<Group mt="md" justify='center'>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
bg={colors['blue-button']}
|
||||||
leftSection={<IconBrandWhatsapp size={18} />}
|
onClick={() => router.push(`/darmasaba/kesehatan/kontak-darurat/${v.id}`)}
|
||||||
component="a"
|
>
|
||||||
href={`https://wa.me/${v.whatsapp.replace(/\D/g, '')}`}
|
Detail
|
||||||
target="_blank"
|
</Button>
|
||||||
aria-label="Hubungi WhatsApp"
|
</Group>
|
||||||
>
|
</Paper>
|
||||||
WhatsApp
|
|
||||||
</Button>
|
|
||||||
</Center>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
|
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import BackButton from '../../desa/layanan/_com/BackButto'
|
|||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(penangananDarurat)
|
const state = useProxy(penangananDarurat)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500)
|
const [debouncedSearch] = useDebouncedValue(search, 1000)
|
||||||
const { data, page, totalPages, loading, load } = state.findMany
|
const { data, page, totalPages, loading, load } = state.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { useTransitionRouter } from "next-view-transitions";
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
const state = useProxy(posyandustate);
|
const state = useProxy(posyandustate);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter()
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function Page() {
|
|||||||
const state = useProxy(programKesehatan);
|
const state = useProxy(programKesehatan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(puskesmasState)
|
const state = useProxy(puskesmasState)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -21,8 +23,8 @@ function Page() {
|
|||||||
} = state.findMany;
|
} = state.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 6, search)
|
load(page, 6, debouncedSearch)
|
||||||
}, [page, search])
|
}, [page, debouncedSearch])
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
@@ -95,17 +97,17 @@ function Page() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Stack gap={6}>
|
<Stack gap={6}>
|
||||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||||
<Box pt={2}><IconMapPin size={16} /></Box>
|
<Box pt={2}><IconMapPin size={20} /></Box>
|
||||||
<Text fz="sm" c="dimmed">{v.alamat}</Text>
|
<Text fz="sm" c="dimmed">{v.alamat}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||||
<Box pt={2}><IconPhone size={16} /></Box>
|
<Box pt={2}><IconPhone size={20} /></Box>
|
||||||
<Text fz="sm" c="dimmed">{v.kontak.kontakPuskesmas}</Text>
|
<Text fz="sm" c="dimmed">{v.kontak.kontakPuskesmas}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||||
<Box pt={2}><IconMail size={16} /></Box>
|
<Box pt={2}><IconMail size={20} /></Box>
|
||||||
<Text fz="sm" c="dimmed">{v.kontak.email}</Text>
|
<Text fz="sm" c="dimmed">{v.kontak.email}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import BackButton from '../../desa/layanan/_com/BackButto';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(dataLingkunganDesaState.findMany)
|
const state = useProxy(dataLingkunganDesaState.findMany)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: Edu
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Text
|
<Text
|
||||||
size="sm"
|
size="sm"
|
||||||
|
pl={20}
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.6,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function Page() {
|
|||||||
const state2 = useProxy(pengelolaanSampahState.keteranganSampah)
|
const state2 = useProxy(pengelolaanSampahState.keteranganSampah)
|
||||||
|
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useTransitionRouter } from 'next-view-transitions';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(programPenghijauanState);
|
const state = useProxy(programPenghijauanState);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter()
|
||||||
const { data, load, page, totalPages, loading } = state.findMany;
|
const { data, load, page, totalPages, loading } = state.findMany;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface PageProps {
|
|||||||
|
|
||||||
function Page({ params }: PageProps) {
|
function Page({ params }: PageProps) {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
|
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
|
||||||
const { jenjangPendidikan } = use(params);
|
const { jenjangPendidikan } = use(params);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface PageProps {
|
|||||||
|
|
||||||
function Page({ params }: PageProps) {
|
function Page({ params }: PageProps) {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const stateList = useProxy(infoSekolahPaud.pengajar)
|
const stateList = useProxy(infoSekolahPaud.pengajar)
|
||||||
const { jenjangPendidikan } = use(params);
|
const { jenjangPendidikan } = use(params);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface PageProps {
|
|||||||
|
|
||||||
function Page({ params }: PageProps) {
|
function Page({ params }: PageProps) {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const stateList = useProxy(infoSekolahPaud.siswa)
|
const stateList = useProxy(infoSekolahPaud.siswa)
|
||||||
const { jenjangPendidikan } = use(params);
|
const { jenjangPendidikan } = use(params);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
|
const stateList = useProxy(infoSekolahPaud.lembagaPendidikan)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useProxy } from 'valtio/utils';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const stateList = useProxy(infoSekolahPaud.pengajar)
|
const stateList = useProxy(infoSekolahPaud.pengajar)
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const stateList = useProxy(infoSekolahPaud.siswa)
|
const stateList = useProxy(infoSekolahPaud.siswa)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { useTransitionRouter } from 'next-view-transitions';
|
|||||||
function Page() {
|
function Page() {
|
||||||
const listData = useProxy(daftarInformasiPublik)
|
const listData = useProxy(daftarInformasiPublik)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
const router = useTransitionRouter()
|
const router = useTransitionRouter()
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -82,13 +82,13 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
|
|
||||||
// Update gender chart data
|
// Update gender chart data
|
||||||
setDonutDataJenisKelamin([
|
setDonutDataJenisKelamin([
|
||||||
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] },
|
{ name: 'Laki-laki', value: totalLaki, color: '#52ABE3FF' },
|
||||||
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
|
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update rating chart data
|
// Update rating chart data
|
||||||
setDonutDataRating([
|
setDonutDataRating([
|
||||||
{ name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] },
|
{ name: 'Sangat Baik', value: totalSangatBaik, color: '#52ABE3FF' },
|
||||||
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
|
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
|
||||||
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
|
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
|
||||||
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
|
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
|
||||||
@@ -96,7 +96,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
|
|
||||||
// Update age group chart data
|
// Update age group chart data
|
||||||
setDonutDataKelompokUmur([
|
setDonutDataKelompokUmur([
|
||||||
{ name: 'Muda', value: totalMuda, color: colors['blue-button'] },
|
{ name: 'Muda', value: totalMuda, color: '#52ABE3FF' },
|
||||||
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
|
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
|
||||||
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
|
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
|
||||||
]);
|
]);
|
||||||
@@ -216,6 +216,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
<PieChart
|
<PieChart
|
||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
|
labelsPosition="inside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={250} // Fixed size in pixels
|
size={250} // Fixed size in pixels
|
||||||
data={donutDataJenisKelamin}
|
data={donutDataJenisKelamin}
|
||||||
@@ -253,7 +254,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
withTooltip
|
withTooltip
|
||||||
tooltipAnimationDuration={200}
|
tooltipAnimationDuration={200}
|
||||||
withLabels
|
withLabels
|
||||||
labelsPosition="outside"
|
labelsPosition="inside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
size={250}
|
size={250}
|
||||||
@@ -296,7 +297,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
withTooltip
|
withTooltip
|
||||||
tooltipAnimationDuration={200}
|
tooltipAnimationDuration={200}
|
||||||
withLabels
|
withLabels
|
||||||
labelsPosition="outside"
|
labelsPosition="inside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
size={250}
|
size={250}
|
||||||
@@ -482,6 +483,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
<PieChart
|
<PieChart
|
||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
|
labelsPosition="inside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={200}
|
size={200}
|
||||||
data={donutDataJenisKelamin}
|
data={donutDataJenisKelamin}
|
||||||
@@ -519,7 +521,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
withTooltip
|
withTooltip
|
||||||
tooltipAnimationDuration={200}
|
tooltipAnimationDuration={200}
|
||||||
withLabels
|
withLabels
|
||||||
labelsPosition="outside"
|
labelsPosition="inside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
size={200}
|
size={200}
|
||||||
@@ -562,7 +564,7 @@ const state = useProxy(indeksKepuasanState.responden);
|
|||||||
withTooltip
|
withTooltip
|
||||||
tooltipAnimationDuration={200}
|
tooltipAnimationDuration={200}
|
||||||
withLabels
|
withLabels
|
||||||
labelsPosition="outside"
|
labelsPosition="inside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
size={190}
|
size={190}
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ function APBDesProgress({ apbdesData }: APBDesProgressProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={label}>
|
<Box key={label}>
|
||||||
<Text fw={600} fz="sm">{label}</Text>
|
<Text fw={600} fz={{base: "sm", md: "md"}}>{label}</Text>
|
||||||
<Text fw={700} mb="xs">
|
<Text fw={700} fz={{base: "sm", md: "md"}} mb="xs">
|
||||||
{formatRupiah(dataset.realisasi)} | {formatRupiah(dataset.anggaran)}
|
{formatRupiah(dataset.realisasi)} | {formatRupiah(dataset.anggaran)}
|
||||||
</Text>
|
</Text>
|
||||||
<Progress
|
<Progress
|
||||||
|
|||||||
@@ -39,22 +39,20 @@ function Slider() {
|
|||||||
const state = useProxy(penghargaanState);
|
const state = useProxy(penghargaanState);
|
||||||
const router = useTransitionRouter();
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
// Refs for smooth animation
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollPositionRef = useRef(0);
|
const scrollPosRef = useRef(0);
|
||||||
const animationFrameRef = useRef<number>(0);
|
const animFrameRef = useRef<number>(0);
|
||||||
const isHoveredRef = useRef(false);
|
const isHoveredRef = useRef(false);
|
||||||
|
|
||||||
// Refs for drag functionality
|
|
||||||
const isDraggingRef = useRef(false);
|
const isDraggingRef = useRef(false);
|
||||||
const startXRef = useRef(0);
|
const startXRef = useRef(0);
|
||||||
const scrollLeftRef = useRef(0);
|
const scrollLeftRef = useRef(0);
|
||||||
const velocityRef = useRef(0);
|
const velocityRef = useRef(0);
|
||||||
const lastScrollTimeRef = useRef(0);
|
const lastScrollRef = useRef(0);
|
||||||
|
|
||||||
// Speed configuration
|
const SPEED_NORMAL = 1.0;
|
||||||
const normalSpeed = 1.0; // pixels per frame
|
const SPEED_HOVER = 0.5;
|
||||||
const hoverSpeed = 0.5; // slower speed on hover
|
const VELOCITY_DECAY = 0.95;
|
||||||
|
const SCROLL_THRESHOLD = 100;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
state.findMany.load();
|
state.findMany.load();
|
||||||
@@ -63,92 +61,88 @@ function Slider() {
|
|||||||
const data = state.findMany.data || [];
|
const data = state.findMany.data || [];
|
||||||
const loading = state.findMany.loading;
|
const loading = state.findMany.loading;
|
||||||
|
|
||||||
// Duplicate slides for seamless infinite loop
|
// Triple data untuk infinite loop (desktop only)
|
||||||
const slidesData = [...data, ...data, ...data];
|
const slidesData = mobile ? data : [...data, ...data, ...data];
|
||||||
|
|
||||||
|
// Auto-scroll animation untuk desktop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading || !containerRef.current || slidesData.length === 0) return;
|
if (loading || !containerRef.current || data.length === 0 || mobile) return;
|
||||||
|
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
const slideWidth = container.scrollWidth / slidesData.length;
|
const slideWidth = container.scrollWidth / slidesData.length;
|
||||||
const originalDataLength = data.length;
|
const originalLength = data.length;
|
||||||
|
|
||||||
// Start from the middle set of slides
|
// Start dari middle set
|
||||||
scrollPositionRef.current = slideWidth * originalDataLength;
|
scrollPosRef.current = slideWidth * originalLength;
|
||||||
container.scrollLeft = scrollPositionRef.current;
|
container.scrollLeft = scrollPosRef.current;
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
const slideWidth = container.scrollWidth / slidesData.length;
|
const slideWidth = container.scrollWidth / slidesData.length;
|
||||||
|
const timeSinceScroll = Date.now() - lastScrollRef.current;
|
||||||
|
const isUserScrolling = timeSinceScroll < SCROLL_THRESHOLD;
|
||||||
|
|
||||||
// Check if user recently scrolled manually
|
|
||||||
const timeSinceLastScroll = Date.now() - lastScrollTimeRef.current;
|
|
||||||
const isUserScrolling = timeSinceLastScroll < 100;
|
|
||||||
|
|
||||||
// Only auto-scroll if user is not actively scrolling or dragging
|
|
||||||
if (!isDraggingRef.current && !isUserScrolling) {
|
if (!isDraggingRef.current && !isUserScrolling) {
|
||||||
const currentSpeed = isHoveredRef.current ? hoverSpeed : normalSpeed;
|
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
|
||||||
scrollPositionRef.current += currentSpeed;
|
scrollPosRef.current += speed;
|
||||||
|
|
||||||
// Reset position for infinite loop
|
// Reset untuk infinite loop
|
||||||
if (scrollPositionRef.current >= slideWidth * (originalDataLength * 2)) {
|
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
|
||||||
scrollPositionRef.current -= slideWidth * originalDataLength;
|
scrollPosRef.current -= slideWidth * originalLength;
|
||||||
|
}
|
||||||
|
if (scrollPosRef.current <= 0) {
|
||||||
|
scrollPosRef.current += slideWidth * originalLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollPositionRef.current <= 0) {
|
container.scrollLeft = scrollPosRef.current;
|
||||||
scrollPositionRef.current += slideWidth * originalDataLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.scrollLeft = scrollPositionRef.current;
|
|
||||||
} else {
|
} else {
|
||||||
// Sync scroll position when user is scrolling
|
scrollPosRef.current = container.scrollLeft;
|
||||||
scrollPositionRef.current = container.scrollLeft;
|
|
||||||
|
|
||||||
// Apply momentum/velocity for smooth drag release
|
// Momentum untuk drag release
|
||||||
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
||||||
scrollPositionRef.current += velocityRef.current;
|
scrollPosRef.current += velocityRef.current;
|
||||||
velocityRef.current *= 0.95; // Decay velocity
|
velocityRef.current *= VELOCITY_DECAY;
|
||||||
container.scrollLeft = scrollPositionRef.current;
|
container.scrollLeft = scrollPosRef.current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
animationFrameRef.current = requestAnimationFrame(animate);
|
animFrameRef.current = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
animationFrameRef.current = requestAnimationFrame(animate);
|
animFrameRef.current = requestAnimationFrame(animate);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (animationFrameRef.current) {
|
if (animFrameRef.current) {
|
||||||
cancelAnimationFrame(animationFrameRef.current);
|
cancelAnimationFrame(animFrameRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [loading, slidesData.length, data.length, mobile]);
|
}, [loading, data.length, mobile]);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
isHoveredRef.current = true;
|
if (!mobile) isHoveredRef.current = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
isHoveredRef.current = false;
|
if (!mobile) {
|
||||||
isDraggingRef.current = false;
|
isHoveredRef.current = false;
|
||||||
|
isDraggingRef.current = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mouse drag handlers
|
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || mobile) return;
|
||||||
|
|
||||||
isDraggingRef.current = true;
|
isDraggingRef.current = true;
|
||||||
startXRef.current = e.pageX - containerRef.current.offsetLeft;
|
startXRef.current = e.pageX - containerRef.current.offsetLeft;
|
||||||
scrollLeftRef.current = containerRef.current.scrollLeft;
|
scrollLeftRef.current = containerRef.current.scrollLeft;
|
||||||
velocityRef.current = 0;
|
velocityRef.current = 0;
|
||||||
|
|
||||||
containerRef.current.style.cursor = 'grabbing';
|
containerRef.current.style.cursor = 'grabbing';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e: React.MouseEvent) => {
|
const handleMouseMove = (e: React.MouseEvent) => {
|
||||||
if (!isDraggingRef.current || !containerRef.current) return;
|
if (!isDraggingRef.current || !containerRef.current || mobile) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const x = e.pageX - containerRef.current.offsetLeft;
|
const x = e.pageX - containerRef.current.offsetLeft;
|
||||||
@@ -156,27 +150,25 @@ function Slider() {
|
|||||||
const newScrollLeft = scrollLeftRef.current - walk;
|
const newScrollLeft = scrollLeftRef.current - walk;
|
||||||
|
|
||||||
velocityRef.current = containerRef.current.scrollLeft - newScrollLeft;
|
velocityRef.current = containerRef.current.scrollLeft - newScrollLeft;
|
||||||
|
|
||||||
containerRef.current.scrollLeft = newScrollLeft;
|
containerRef.current.scrollLeft = newScrollLeft;
|
||||||
scrollPositionRef.current = newScrollLeft;
|
scrollPosRef.current = newScrollLeft;
|
||||||
lastScrollTimeRef.current = Date.now();
|
lastScrollRef.current = Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || mobile) return;
|
||||||
|
|
||||||
isDraggingRef.current = false;
|
isDraggingRef.current = false;
|
||||||
containerRef.current.style.cursor = 'grab';
|
containerRef.current.style.cursor = 'grab';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wheel scroll handler
|
|
||||||
const handleWheel = (e: React.WheelEvent) => {
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || mobile) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
containerRef.current.scrollLeft += e.deltaY;
|
containerRef.current.scrollLeft += e.deltaY;
|
||||||
scrollPositionRef.current = containerRef.current.scrollLeft;
|
scrollPosRef.current = containerRef.current.scrollLeft;
|
||||||
lastScrollTimeRef.current = Date.now();
|
lastScrollRef.current = Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -211,37 +203,45 @@ function Slider() {
|
|||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
py="xl"
|
py="xl"
|
||||||
style={{
|
style={{
|
||||||
overflow: "hidden",
|
overflowX: mobile ? "auto" : "hidden",
|
||||||
cursor: "grab",
|
overflowY: "hidden",
|
||||||
|
cursor: mobile ? "default" : "grab",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
WebkitOverflowScrolling: "touch",
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
msOverflowStyle: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Blur edges effect */}
|
{/* Blur edges - hanya untuk desktop */}
|
||||||
<Box
|
{!mobile && (
|
||||||
style={{
|
<>
|
||||||
position: "absolute",
|
<Box
|
||||||
top: 0,
|
style={{
|
||||||
left: 0,
|
position: "absolute",
|
||||||
width: "120px",
|
top: 0,
|
||||||
height: "100%",
|
left: 0,
|
||||||
background: "linear-gradient(to right, rgba(249, 250, 251, 1), rgba(249, 250, 251, 0))",
|
width: "120px",
|
||||||
zIndex: 10,
|
height: "100%",
|
||||||
pointerEvents: "none",
|
background: "linear-gradient(to right, rgba(249, 250, 251, 1), rgba(249, 250, 251, 0))",
|
||||||
}}
|
zIndex: 10,
|
||||||
/>
|
pointerEvents: "none",
|
||||||
<Box
|
}}
|
||||||
style={{
|
/>
|
||||||
position: "absolute",
|
<Box
|
||||||
top: 0,
|
style={{
|
||||||
right: 0,
|
position: "absolute",
|
||||||
width: "120px",
|
top: 0,
|
||||||
height: "100%",
|
right: 0,
|
||||||
background: "linear-gradient(to left, rgba(249, 250, 251, 1), rgba(249, 250, 251, 0))",
|
width: "120px",
|
||||||
zIndex: 10,
|
height: "100%",
|
||||||
pointerEvents: "none",
|
background: "linear-gradient(to left, rgba(249, 250, 251, 1), rgba(249, 250, 251, 0))",
|
||||||
}}
|
zIndex: 10,
|
||||||
/>
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@@ -255,8 +255,8 @@ function Slider() {
|
|||||||
<Box
|
<Box
|
||||||
key={`${item.id}-${index}`}
|
key={`${item.id}-${index}`}
|
||||||
style={{
|
style={{
|
||||||
flex: `0 0 ${mobile ? "90%" : "calc(33.333% - 1rem)"}`,
|
flex: `0 0 ${mobile ? "85%" : "calc(33.333% - 1rem)"}`,
|
||||||
minWidth: mobile ? "90%" : "calc(33.333% - 1rem)",
|
minWidth: mobile ? "85%" : "calc(33.333% - 1rem)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paper
|
<Paper
|
||||||
@@ -272,12 +272,16 @@ function Slider() {
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.transform = "translateY(-8px) scale(1.02)";
|
if (!mobile) {
|
||||||
e.currentTarget.style.boxShadow = "0 12px 28px rgba(0,0,0,0.25)";
|
e.currentTarget.style.transform = "translateY(-8px) scale(1.02)";
|
||||||
|
e.currentTarget.style.boxShadow = "0 12px 28px rgba(0,0,0,0.25)";
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.transform = "translateY(0) scale(1)";
|
if (!mobile) {
|
||||||
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
e.currentTarget.style.transform = "translateY(0) scale(1)";
|
||||||
|
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function Page() {
|
|||||||
const state = useProxy(prestasiState.prestasiDesa);
|
const state = useProxy(prestasiState.prestasiDesa);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
|
|||||||
@@ -25,25 +25,27 @@ const textHeading = {
|
|||||||
des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!",
|
des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HEIGHT = 720;
|
||||||
|
|
||||||
function Layanan() {
|
function Layanan() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.grey[1]} gap={"xl"} py={"md"}>
|
<Stack pos="relative" bg={colors.grey[1]} gap="xl" py="md">
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"md"}>
|
<Container w={{ base: "100%", md: "80%" }} p="md">
|
||||||
<Stack align="center" gap={"0"}>
|
<Stack align="center" gap="0">
|
||||||
<Text
|
<Text
|
||||||
fw={"bold"}
|
fw="bold"
|
||||||
c={colors["blue-button"]}
|
c={colors["blue-button"]}
|
||||||
fz={{ base: "1.8rem", md: "3.4rem" }}
|
fz={{ base: "1.8rem", md: "3.4rem" }}
|
||||||
>
|
>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
<Text ta="center" fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
{textHeading.des}
|
{textHeading.des}
|
||||||
</Text>
|
</Text>
|
||||||
<Box p={"md"}>
|
<Box p="md">
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
href={"/darmasaba/desa/layanan"}
|
href="/darmasaba/desa/layanan"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
bg={colors["blue-button"]}
|
bg={colors["blue-button"]}
|
||||||
radius={100}
|
radius={100}
|
||||||
@@ -59,30 +61,26 @@ function Layanan() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = 720;
|
|
||||||
|
|
||||||
function Slider() {
|
function Slider() {
|
||||||
const state = useProxy(stateLayananDesa);
|
const state = useProxy(stateLayananDesa);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const mobile = useMediaQuery("(max-width: 768px)", false);
|
const mobile = useMediaQuery("(max-width: 768px)", false);
|
||||||
const router = useTransitionRouter();
|
const router = useTransitionRouter();
|
||||||
|
|
||||||
// Refs for smooth animation
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollPositionRef = useRef(0);
|
const scrollPosRef = useRef(0);
|
||||||
const animationFrameRef = useRef<number>(0);
|
const animFrameRef = useRef<number>(0);
|
||||||
const isHoveredRef = useRef(false);
|
const isHoveredRef = useRef(false);
|
||||||
|
|
||||||
// Refs for drag functionality
|
|
||||||
const isDraggingRef = useRef(false);
|
const isDraggingRef = useRef(false);
|
||||||
const startXRef = useRef(0);
|
const startXRef = useRef(0);
|
||||||
const scrollLeftRef = useRef(0);
|
const scrollLeftRef = useRef(0);
|
||||||
const velocityRef = useRef(0);
|
const velocityRef = useRef(0);
|
||||||
const lastScrollTimeRef = useRef(0);
|
const lastScrollRef = useRef(0);
|
||||||
|
|
||||||
// Speed configuration
|
const SPEED_NORMAL = 1.0;
|
||||||
const normalSpeed = 1.0; // pixels per frame
|
const SPEED_HOVER = 0.3;
|
||||||
const hoverSpeed = 0.3; // slower speed on hover
|
const VELOCITY_DECAY = 0.95;
|
||||||
|
const SCROLL_THRESHOLD = 100;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -100,126 +98,118 @@ function Slider() {
|
|||||||
|
|
||||||
const data = state.suratKeterangan.findMany.data || [];
|
const data = state.suratKeterangan.findMany.data || [];
|
||||||
|
|
||||||
// Duplicate slides for seamless infinite loop
|
// Triple data untuk infinite loop (desktop only)
|
||||||
// We need at least 3x the data for smooth infinite scrolling
|
const slidesData = mobile ? data : [...data, ...data, ...data];
|
||||||
const slidesData = [...data, ...data, ...data];
|
|
||||||
|
|
||||||
|
// Auto-scroll animation untuk desktop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading || !containerRef.current || slidesData.length === 0) return;
|
if (loading || !containerRef.current || data.length === 0 || mobile) return;
|
||||||
|
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
const slideWidth = container.scrollWidth / slidesData.length;
|
const slideWidth = container.scrollWidth / slidesData.length;
|
||||||
const originalDataLength = data.length;
|
const originalLength = data.length;
|
||||||
|
|
||||||
// Start from the middle set of slides
|
// Start dari middle set
|
||||||
scrollPositionRef.current = slideWidth * originalDataLength;
|
scrollPosRef.current = slideWidth * originalLength;
|
||||||
container.scrollLeft = scrollPositionRef.current;
|
container.scrollLeft = scrollPosRef.current;
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
const slideWidth = container.scrollWidth / slidesData.length;
|
const slideWidth = container.scrollWidth / slidesData.length;
|
||||||
|
const timeSinceScroll = Date.now() - lastScrollRef.current;
|
||||||
|
const isUserScrolling = timeSinceScroll < SCROLL_THRESHOLD;
|
||||||
|
|
||||||
// Check if user recently scrolled manually
|
|
||||||
const timeSinceLastScroll = Date.now() - lastScrollTimeRef.current;
|
|
||||||
const isUserScrolling = timeSinceLastScroll < 100;
|
|
||||||
|
|
||||||
// Only auto-scroll if user is not actively scrolling or dragging
|
|
||||||
if (!isDraggingRef.current && !isUserScrolling) {
|
if (!isDraggingRef.current && !isUserScrolling) {
|
||||||
const currentSpeed = isHoveredRef.current ? hoverSpeed : normalSpeed;
|
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
|
||||||
scrollPositionRef.current += currentSpeed;
|
scrollPosRef.current += speed;
|
||||||
|
|
||||||
// Reset position for infinite loop
|
// Reset untuk infinite loop
|
||||||
if (scrollPositionRef.current >= slideWidth * (originalDataLength * 2)) {
|
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
|
||||||
scrollPositionRef.current -= slideWidth * originalDataLength;
|
scrollPosRef.current -= slideWidth * originalLength;
|
||||||
|
}
|
||||||
|
if (scrollPosRef.current <= 0) {
|
||||||
|
scrollPosRef.current += slideWidth * originalLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollPositionRef.current <= 0) {
|
container.scrollLeft = scrollPosRef.current;
|
||||||
scrollPositionRef.current += slideWidth * originalDataLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.scrollLeft = scrollPositionRef.current;
|
|
||||||
} else {
|
} else {
|
||||||
// Sync scroll position when user is scrolling
|
scrollPosRef.current = container.scrollLeft;
|
||||||
scrollPositionRef.current = container.scrollLeft;
|
|
||||||
|
|
||||||
// Apply momentum/velocity for smooth drag release
|
// Momentum untuk drag release
|
||||||
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
|
||||||
scrollPositionRef.current += velocityRef.current;
|
scrollPosRef.current += velocityRef.current;
|
||||||
velocityRef.current *= 0.95; // Decay velocity
|
velocityRef.current *= VELOCITY_DECAY;
|
||||||
container.scrollLeft = scrollPositionRef.current;
|
container.scrollLeft = scrollPosRef.current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
animationFrameRef.current = requestAnimationFrame(animate);
|
animFrameRef.current = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
animationFrameRef.current = requestAnimationFrame(animate);
|
animFrameRef.current = requestAnimationFrame(animate);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (animationFrameRef.current) {
|
if (animFrameRef.current) {
|
||||||
cancelAnimationFrame(animationFrameRef.current);
|
cancelAnimationFrame(animFrameRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [loading, slidesData.length, data.length, mobile]);
|
}, [loading, data.length, mobile]);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
isHoveredRef.current = true;
|
if (!mobile) isHoveredRef.current = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
isHoveredRef.current = false;
|
if (!mobile) {
|
||||||
isDraggingRef.current = false;
|
isHoveredRef.current = false;
|
||||||
|
isDraggingRef.current = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mouse drag handlers
|
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || mobile) return;
|
||||||
|
|
||||||
isDraggingRef.current = true;
|
isDraggingRef.current = true;
|
||||||
startXRef.current = e.pageX - containerRef.current.offsetLeft;
|
startXRef.current = e.pageX - containerRef.current.offsetLeft;
|
||||||
scrollLeftRef.current = containerRef.current.scrollLeft;
|
scrollLeftRef.current = containerRef.current.scrollLeft;
|
||||||
velocityRef.current = 0;
|
velocityRef.current = 0;
|
||||||
|
|
||||||
containerRef.current.style.cursor = 'grabbing';
|
containerRef.current.style.cursor = 'grabbing';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e: React.MouseEvent) => {
|
const handleMouseMove = (e: React.MouseEvent) => {
|
||||||
if (!isDraggingRef.current || !containerRef.current) return;
|
if (!isDraggingRef.current || !containerRef.current || mobile) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const x = e.pageX - containerRef.current.offsetLeft;
|
const x = e.pageX - containerRef.current.offsetLeft;
|
||||||
const walk = (x - startXRef.current) * 2; // Multiply for faster scroll
|
const walk = (x - startXRef.current) * 2;
|
||||||
const newScrollLeft = scrollLeftRef.current - walk;
|
const newScrollLeft = scrollLeftRef.current - walk;
|
||||||
|
|
||||||
// Calculate velocity for momentum
|
|
||||||
velocityRef.current = containerRef.current.scrollLeft - newScrollLeft;
|
velocityRef.current = containerRef.current.scrollLeft - newScrollLeft;
|
||||||
|
|
||||||
containerRef.current.scrollLeft = newScrollLeft;
|
containerRef.current.scrollLeft = newScrollLeft;
|
||||||
scrollPositionRef.current = newScrollLeft;
|
scrollPosRef.current = newScrollLeft;
|
||||||
lastScrollTimeRef.current = Date.now();
|
lastScrollRef.current = Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || mobile) return;
|
||||||
|
|
||||||
isDraggingRef.current = false;
|
isDraggingRef.current = false;
|
||||||
containerRef.current.style.cursor = 'grab';
|
containerRef.current.style.cursor = 'grab';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wheel scroll handler
|
|
||||||
const handleWheel = (e: React.WheelEvent) => {
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || mobile) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
containerRef.current.scrollLeft += e.deltaY;
|
containerRef.current.scrollLeft += e.deltaY;
|
||||||
scrollPositionRef.current = containerRef.current.scrollLeft;
|
scrollPosRef.current = containerRef.current.scrollLeft;
|
||||||
lastScrollTimeRef.current = Date.now();
|
lastScrollRef.current = Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Skeleton height={height} />;
|
return <Skeleton height={HEIGHT} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
@@ -242,9 +232,13 @@ function Slider() {
|
|||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
style={{
|
style={{
|
||||||
overflow: "hidden",
|
overflowX: mobile ? "auto" : "hidden",
|
||||||
cursor: "grab",
|
overflowY: "hidden",
|
||||||
|
cursor: mobile ? "default" : "grab",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
|
WebkitOverflowScrolling: "touch",
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
msOverflowStyle: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@@ -259,13 +253,13 @@ function Slider() {
|
|||||||
<Box
|
<Box
|
||||||
key={`${item.id}-${index}`}
|
key={`${item.id}-${index}`}
|
||||||
style={{
|
style={{
|
||||||
flex: `0 0 ${mobile ? "100%" : "calc(33.333% - 1rem)"}`,
|
flex: `0 0 ${mobile ? "90%" : "calc(33.333% - 1rem)"}`,
|
||||||
minWidth: mobile ? "100%" : "calc(33.333% - 1rem)",
|
minWidth: mobile ? "90%" : "calc(33.333% - 1rem)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paper
|
<Paper
|
||||||
h={height}
|
h={HEIGHT}
|
||||||
pos={"relative"}
|
pos="relative"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${item.image?.link})`,
|
backgroundImage: `url(${item.image?.link})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
@@ -280,23 +274,23 @@ function Slider() {
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
}}
|
}}
|
||||||
pos={"absolute"}
|
pos="absolute"
|
||||||
w={"100%"}
|
w="100%"
|
||||||
h={"100%"}
|
h="100%"
|
||||||
bg={colors.trans.dark[2]}
|
bg={colors.trans.dark[2]}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
h={"100%"}
|
h="100%"
|
||||||
gap={0}
|
gap={0}
|
||||||
p={"lg"}
|
p="lg"
|
||||||
pos={"relative"}
|
pos="relative"
|
||||||
>
|
>
|
||||||
<Box p={"lg"}>
|
<Box p="lg">
|
||||||
<Text
|
<Text
|
||||||
fw={"bold"}
|
fw="bold"
|
||||||
c={"white"}
|
c="white"
|
||||||
size={"3.5rem"}
|
fz={{base: "xl", md: "3.5rem"}}
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}}
|
}}
|
||||||
@@ -310,7 +304,7 @@ function Slider() {
|
|||||||
router.push(`/darmasaba/desa/layanan/${item.id}`)
|
router.push(`/darmasaba/desa/layanan/${item.id}`)
|
||||||
}
|
}
|
||||||
px={46}
|
px={46}
|
||||||
radius={"100"}
|
radius="100"
|
||||||
size="md"
|
size="md"
|
||||||
bg={colors["blue-button"]}
|
bg={colors["blue-button"]}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user