fix tampilan admin menu inovasi, sisa menu lingkungan

This commit is contained in:
2025-09-22 10:53:48 +08:00
parent 8e25c91e85
commit 0fc47c28ff
52 changed files with 3787 additions and 1723 deletions

View File

@@ -120,7 +120,7 @@ function Page() {
<Box>
<Text fw={600} fz="lg" >Kronologi</Text>
<Text fz="sm" c="dimmed">{data.kronologi || '-'}</Text>
<Text fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
</Box>
<Divider />

View File

@@ -55,27 +55,28 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}>
<Flex justify="space-between" align="center">
<BackButton />
<Flex gap={"xs"} align={"center"}>
<TextInput
placeholder="Cari laporan"
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<Button
onClick={open}
bg={colors['blue-button']}
size="md"
radius="md"
>
<IconPlus size={20} />
</Button>
</Flex>
<TextInput
placeholder="Cari laporan"
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Flex>
</Box>
<Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Laporan Keamanan Lingkungan
</Text>
<Box px={{ base: 'md', md: 100 }}>
<Group justify="space-between">
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Laporan Keamanan Lingkungan
</Text>
<Button
onClick={open}
bg={colors['blue-button']}
size="md"
radius="md"
rightSection={<IconPlus size={20} />}
>
Tambah Laporan
</Button>
</Group>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>

View File

@@ -1,11 +1,129 @@
import React from 'react';
'use client'
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
import { Box, Button, Card, Center, Group, Loader, Paper, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowLeft } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function DetailPencegahanKriminalitas() {
const router = useRouter();
const params = useParams();
const kriminalitasState = useProxy(pencegahanKriminalitasState);
useShallowEffect(() => {
kriminalitasState.findUnique.load(params?.id as string);
}, []);
if (kriminalitasState.findUnique.loading) {
return (
<Center py="xl">
<Loader size="lg" color="blue" />
</Center>
);
}
if (!kriminalitasState.findUnique.data) {
return (
<Center h={400}>
<Stack align="center" gap="sm">
<Text fz="lg" fw="bold" c="dimmed">Data tidak ditemukan</Text>
<Button
variant="light"
color="blue"
leftSection={<IconArrowLeft size={18} />}
onClick={() => router.push("/admin/keamanan/pencegahan-kriminalitas")}
>
Kembali ke daftar
</Button>
</Stack>
</Center>
);
}
const data = kriminalitasState.findUnique.data;
function Page() {
return (
<div>
Page
</div>
<Box py="md" px="md">
<Group mb="md">
<Button
variant="light"
color="blue"
onClick={() => router.back()}
leftSection={<IconArrowLeft size={20} />}
>
Kembali
</Button>
</Group>
<Card
withBorder
radius="xl"
shadow="md"
p="xl"
bg="white"
>
<Stack gap="lg">
<Title order={2} c="blue">Detail Pencegahan Kriminalitas</Title>
<Paper radius="lg" p="lg" withBorder>
<Stack gap="lg">
<Stack gap={4}>
<Text fz="sm" c="dimmed">Judul</Text>
<Text fz="lg" fw={600}>{data?.judul || '-'}</Text>
</Stack>
<Stack gap={4}>
<Text fz="sm" c="dimmed">Deskripsi Singkat</Text>
{data?.deskripsiSingkat ? (
<Text fz="md" dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat }} />
) : (
<Text fz="sm" c="dimmed">Belum ada deskripsi singkat</Text>
)}
</Stack>
<Stack gap={4}>
<Text fz="sm" c="dimmed">Deskripsi Lengkap</Text>
{data?.deskripsi ? (
<Text fz="md" dangerouslySetInnerHTML={{ __html: data.deskripsi }} />
) : (
<Text fz="sm" c="dimmed">Belum ada deskripsi</Text>
)}
</Stack>
<Stack gap={4}>
<Text fz="sm" c="dimmed">Video</Text>
{data?.linkVideo ? (
<Box
component="iframe"
src={convertToEmbedUrl(data.linkVideo)}
width="100%"
h={{ base: 320, md: 450 }}
allowFullScreen
style={{ borderRadius: 12, border: 'none' }}
/>
) : (
<Text fz="sm" c="dimmed">Belum ada video</Text>
)}
</Stack>
</Stack>
</Paper>
</Stack>
</Card>
</Box>
);
function convertToEmbedUrl(youtubeUrl: string): string {
try {
const url = new URL(youtubeUrl);
const videoId = url.searchParams.get("v");
if (!videoId) return youtubeUrl;
return `https://www.youtube.com/embed/${videoId}`;
} catch {
return youtubeUrl;
}
}
}
export default Page;
export default DetailPencegahanKriminalitas;

View File

@@ -1,11 +1,137 @@
import React from 'react';
'use client'
import {
Box,
Card,
Center,
Group,
Pagination,
Skeleton,
Stack,
Text,
Title,
Tooltip,
Button,
Paper,
} from '@mantine/core';
import { IconSearch, IconArrowRight } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
import { useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
function PencegahanKriminalitas() {
const [search, setSearch] = useState("");
function Page() {
return (
<div>
Page
</div>
<Box>
<HeaderSearch
title="Program Pencegahan Kriminalitas"
placeholder="Cari program atau deskripsi..."
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPencegahanKriminalitas search={search} />
</Box>
);
}
export default Page;
function ListPencegahanKriminalitas({ search }: { search: string }) {
const kriminalitasState = useProxy(pencegahanKriminalitasState);
const router = useRouter();
const { data, page, totalPages, loading, load } = kriminalitasState.findMany;
useShallowEffect(() => {
load(page, 6, search);
}, [page, search]);
if (loading || !data) {
return (
<Stack py="lg">
<Skeleton height={300} radius="lg" />
<Skeleton height={300} radius="lg" />
</Stack>
);
}
return (
<Box py="lg">
<Stack>
{data.length > 0 ? (
data.map((item) => (
<Card
key={item.id}
withBorder
radius="lg"
shadow="md"
p="lg"
style={{
background: 'linear-gradient(135deg, #ffffff 0%, #f9fbff 100%)',
}}
>
<Stack gap="xs">
<Title order={4} c="blue" style={{ fontWeight: 600 }}>
{item.judul}
</Title>
<Text
fz="sm"
c="dimmed"
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat || '' }}
/>
<Group justify="flex-end" mt="sm">
<Tooltip label="Lihat detail program" withArrow>
<Button
size="sm"
variant="gradient"
gradient={{ from: 'blue', to: 'cyan' }}
rightSection={<IconArrowRight size={18} />}
onClick={() => router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`)}
>
Lihat Detail
</Button>
</Tooltip>
</Group>
</Stack>
</Card>
))
) : (
<Paper withBorder radius="lg" p="xl" shadow="sm">
<Center>
<Stack align="center" gap="xs">
<Text fz="lg" fw={500} c="dimmed">
Belum ada program pencegahan kriminalitas
</Text>
<Text fz="sm" c="dimmed">
Program akan ditampilkan di sini ketika tersedia
</Text>
</Stack>
</Center>
</Paper>
)}
</Stack>
{totalPages > 1 && (
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 6, search);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
mt="xl"
color="blue"
radius="lg"
/>
</Center>
)}
</Box>
);
}
export default PencegahanKriminalitas;

View File

@@ -3,55 +3,23 @@ import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_k
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import {
ActionIcon,
Box,
Button,
Combobox,
ComboboxChevron,
ComboboxOption,
ComboboxOptions,
ComboboxTarget,
Divider,
Group,
InputBase,
InputPlaceholder,
Paper,
Skeleton,
Stack,
Text,
Textarea,
TextInput,
useCombobox
Text
} from '@mantine/core';
import { DateInput } from '@mantine/dates';
import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconDownload, IconPhone, IconMail, IconUser } from '@tabler/icons-react';
import { IconDownload, IconMail, IconPhone, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import CreatePendaftaran from '../create/page';
const layanan = [
'Penimbangan',
'Imunisasi',
'Vitamin A',
'Konsultasi Gizi',
'Pemeriksaan Kesehatan'
];
function Page() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
onDropdownOpen: (eventSource) => {
if (eventSource === 'keyboard') {
combobox.selectActiveOption();
} else {
combobox.updateSelectedOptionIndex('active');
}
},
});
const [value, setValue] = useState<string | null>('');
const [dateInputOpened, setDateInputOpened] = useState(false);
const params = useParams();
const state = useProxy(jadwalkegiatanState);
@@ -59,20 +27,7 @@ function Page() {
state.findUnique.load(params?.id as string);
}, []);
const options = layanan.map((item) => (
<ComboboxOption value={item} key={item} active={item === value}>
<Group gap="xs">
{item === value && <IconUser size={14} stroke={2} />}
<span>{item}</span>
</Group>
</ComboboxOption>
));
const pickerControl = (
<ActionIcon onClick={() => setDateInputOpened(true)} variant="light" color="blue">
<IconCalendar size={18} />
</ActionIcon>
);
if (!state.findUnique.data) {
return (
@@ -133,56 +88,7 @@ function Page() {
<Text ta="justify" fz="md" dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text>
<Divider />
<Stack gap="md">
<TextInput label="Nama Balita" placeholder="Masukkan nama balita" size="md" />
<DateInput
label="Tanggal Lahir"
placeholder="dd/mm/yyyy"
size="md"
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
popoverProps={{ opened: dateInputOpened, onChange: setDateInputOpened }}
rightSection={pickerControl}
/>
<TextInput label="Nama Orang Tua / Wali" placeholder="Masukkan nama orang tua / wali" size="md" />
<TextInput label="Nomor Telepon" placeholder="Masukkan nomor telepon" size="md" />
<TextInput label="Alamat" placeholder="Masukkan alamat lengkap" size="md" />
<Box w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}>
<Text fz="sm" fw="bold" pb={4}>Pilih Layanan</Text>
<Combobox
store={combobox}
resetSelectionOnOptionHover
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val);
combobox.updateSelectedOptionIndex('active');
}}
>
<ComboboxTarget targetType="button">
<InputBase
component="button"
type="button"
pointer
rightSection={<ComboboxChevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <InputPlaceholder>Pilih layanan</InputPlaceholder>}
</InputBase>
</ComboboxTarget>
<Combobox.Dropdown>
<ComboboxOptions>{options}</ComboboxOptions>
</Combobox.Dropdown>
</Combobox>
</Box>
<Textarea label="Catatan Khusus (Opsional)" placeholder="Masukkan catatan jika ada" size="md" />
<Button size="md" radius="lg" bg={colors['blue-button']}>
Daftar Sekarang
</Button>
</Stack>
</Stack>
<CreatePendaftaran />
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
<Stack gap="xs">

View File

@@ -0,0 +1,90 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import pendaftaranJadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/pendafataranJadwalKegiatan';
import colors from '@/con/colors';
import { Button, Divider, Stack, Text, Textarea, TextInput } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function CreatePendaftaran() {
const stateCreate = useProxy(pendaftaranJadwalKegiatanState);
useEffect(() => {
stateCreate.findMany.load();
}, []);
const resetForm = () => {
stateCreate.create.form = {
name: '',
alamat: '',
tanggal: '',
namaOrangtua: '',
nomor: '',
catatan: '',
};
};
const handleSubmit = async () => {
await stateCreate.create.submit();
resetForm();
};
return (
<Stack gap="sm">
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text>
<Divider />
<Stack gap="md">
<TextInput
label="Nama Balita"
placeholder="Masukkan nama balita"
size="md"
value={stateCreate.create.form.name}
onChange={(e) => stateCreate.create.form.name = e.target.value}
/>
<TextInput
type='date'
label="Tanggal Lahir"
placeholder="dd/mm/yyyy"
size="md"
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
value={stateCreate.create.form.tanggal}
onChange={(e) => stateCreate.create.form.tanggal = e.target.value}
/>
<TextInput
label="Nama Orang Tua / Wali"
placeholder="Masukkan nama orang tua / wali"
size="md"
value={stateCreate.create.form.namaOrangtua}
onChange={(e) => stateCreate.create.form.namaOrangtua = e.target.value}
/>
<TextInput
label="Nomor Telepon"
placeholder="Masukkan nomor telepon"
size="md"
value={stateCreate.create.form.nomor}
onChange={(e) => stateCreate.create.form.nomor = e.target.value}
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat lengkap"
size="md"
value={stateCreate.create.form.alamat}
onChange={(e) => stateCreate.create.form.alamat = e.target.value}
/>
<Textarea
label="Catatan Khusus (Opsional)"
placeholder="Masukkan catatan jika ada"
size="md"
value={stateCreate.create.form.catatan}
onChange={(e) => stateCreate.create.form.catatan = e.target.value}
/>
<Button size="md" radius="lg" bg={colors['blue-button']} onClick={handleSubmit}>
Daftar Sekarang
</Button>
</Stack>
</Stack>
);
}
export default CreatePendaftaran;