Compare commits

...

2 Commits

20 changed files with 510 additions and 263 deletions

View File

@@ -55,9 +55,9 @@ function EditProgramKemiskinan() {
useEffect(() => {
if (!id) return;
stateProgram.findUnique
.load(id)
.then(() => {
const loadData = async () => {
try {
await stateProgram.findUnique.load(id);
const data = stateProgram.findUnique.data;
if (data) {
setFormData({
@@ -70,12 +70,16 @@ function EditProgramKemiskinan() {
},
});
}
})
.catch((err) => {
} catch (err) {
console.error('Error load data:', err);
toast.error('Gagal mengambil data program');
});
}, [id, stateProgram.findUnique]);
}
};
loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]); // ✅ hanya trigger saat id berubah
// generic handler untuk field top-level
const handleChange = useCallback(

View File

@@ -62,11 +62,23 @@ function Page() {
Informasi dan Pelayanan Administrasi Digital
</Text>
</Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy"/>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy" />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={"xs"}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.content || '' }} />
<Text
py={20}
fz={{ base: "sm", md: "lg" }}
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif
ta="justify"
style={{
wordBreak: "break-word",
whiteSpace: "normal",
}}
dangerouslySetInnerHTML={{
__html: state.findUnique.data?.content || "",
}}
/>
</Stack>
</Box>
</Stack>

View File

@@ -186,12 +186,11 @@ function Page() {
stateCreate.create.form.kategoriId = '';
}
}}
searchable
// searchable
clearable
nothingFoundMessage="Tidak ditemukan"
required
/>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan
</Button>

View File

@@ -1,14 +1,14 @@
'use client'
import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap';
import programKreatifState from '@/app/admin/(dashboard)/_state/inovasi/program-kreatif';
import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { Box, Button, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
import React, { useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import { IconSearch } from '@tabler/icons-react';
import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap';
// const data = [
// {
@@ -75,17 +75,23 @@ function Page() {
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Group justify="space-between" mb="md" align='center'>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Program Kreatif Desa
</Text>
<TextInput
placeholder="Cari program kreatif..."
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Group>
<Grid align='center'>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Program Kreatif Desa
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Program Kreatif'
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
/>
</GridCol>
</Grid>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>

View File

@@ -3,9 +3,9 @@
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { ActionIcon, Anchor, AspectRatio, Badge, Box, Button, Card, Chip, CopyButton, Divider, Grid, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title, Tooltip } from '@mantine/core';
import { ActionIcon, AspectRatio, Badge, Box, Button, Card, CopyButton, Divider, Grid, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconBrandWhatsapp, IconCheck, IconCopy, IconDeviceLandlinePhone, IconHeart, IconInfoCircle, IconMail, IconMapPin, IconMoodEmpty, IconSearch, IconStethoscope, IconUser, IconUsersGroup, IconWallet } from '@tabler/icons-react';
import { IconBrandWhatsapp, IconCheck, IconCopy, IconDeviceLandlinePhone, IconHeart, IconInfoCircle, IconMail, IconMapPin, IconMoodEmpty, IconSearch, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useMemo } from 'react';
import { useProxy } from 'valtio/utils';
@@ -149,11 +149,6 @@ function Page() {
</CopyButton>
</Group>
</Group>
<Group gap="xs" mt="sm" wrap="wrap">
<Chip defaultChecked radius="xl" variant="light" icon={<IconStethoscope size={16} />}>Layanan Medis</Chip>
<Chip radius="xl" variant="light" icon={<IconUsersGroup size={16} />}>Ramah Keluarga</Chip>
<Chip radius="xl" variant="light" icon={<IconWallet size={16} />}>Pembayaran Non-Tunai</Chip>
</Group>
</Stack>
</Card>
</Box>
@@ -210,7 +205,6 @@ function Page() {
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">WhatsApp</Button>
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">Email</Button>
</Group>
<Anchor target="_blank" underline="hover">Kunjungi situs resmi</Anchor>
</Stack>
</Card>
@@ -246,15 +240,8 @@ function Page() {
</Table>
</Stack>
</Card>
</Stack>
</Grid.Col>
</Grid>
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Grid gutter="lg">
<Grid.Col span={{ base: 12, md: 8 }}>
<Card radius="xl" p="lg" withBorder>
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={3}>Fasilitas Pendukung</Title>
<Divider />
@@ -270,8 +257,7 @@ function Page() {
)}
</Stack>
</Card>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 4 }}>
<Card radius="xl" p="lg" withBorder>
<Stack gap="md">
<Title order={3}>Layanan & Tarif</Title>
@@ -309,10 +295,11 @@ function Page() {
)}
</Stack>
</Card>
</Stack>
</Grid.Col>
</Grid>
</Box>
<Box px={{ base: 'md', md: 100 }} pb="xl">
<Paper radius="xl" p="lg" withBorder>
<Stack gap="md">

View File

@@ -4,14 +4,16 @@ import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import {
Box,
Button,
Divider,
Group,
Modal,
Paper,
Skeleton,
Stack,
Text
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useDisclosure, useShallowEffect } from '@mantine/hooks';
import { IconMail, IconPhone, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
@@ -21,6 +23,7 @@ import CreatePendaftaran from '../create/page';
function Page() {
const params = useParams();
const state = useProxy(jadwalkegiatanState);
const [opened, { open, close }] = useDisclosure(false);
useShallowEffect(() => {
state.findUnique.load(params?.id as string);
@@ -66,28 +69,38 @@ function Page() {
<Stack gap="sm">
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
<Divider />
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
<Divider />
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
<Divider />
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
<Divider />
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
</Stack>
<CreatePendaftaran />
<Stack gap="sm">
<Text fz="lg" fw="bold">Pendaftaran Kegiatan</Text>
<Divider />
<Group>
<Button onClick={open}>Buat Pendaftaran</Button>
</Group>
</Stack>
<Modal opened={opened} onClose={close}>
<CreatePendaftaran />
</Modal>
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
<Stack gap="xs">

View File

@@ -108,20 +108,23 @@ function Page() {
<Box
style={{
width: '100%',
aspectRatio: '16/9',
borderRadius: '12px',
height: 180, // 🔥 tinggi fix biar semua seragam
borderRadius: 12,
overflow: 'hidden',
position: 'relative',
backgroundColor: '#f0f2f5', // fallback kalau gambar loading
}}
>
<Image
src={v.image.link}
src={v.image?.link || '/img/default.png'}
alt={v.name}
fit="cover"
width="100%"
height="100%"
loading="lazy"
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'center',
transition: 'transform 0.4s ease',
}}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
@@ -129,6 +132,7 @@ function Page() {
/>
</Box>
</Center>
<Stack gap={4} w="100%">
<Text

View File

@@ -49,12 +49,10 @@ function Page() {
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md" style={{ width: '100%', display: 'flex', flexDirection: 'column' }}>
<Stack gap="md">
<Box>
<Tooltip label={tujuan.data?.judul} position="top" withArrow>
<Tooltip label={<Text fz={"sm"} c={"white"} dangerouslySetInnerHTML={{ __html: tujuan.data?.judul || '' }} /> } position="top" withArrow>
<Stack gap={4} align="center">
<IconLeaf size={28} color={colors['blue-button']} />
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
{tujuan.data?.judul}
</Text>
<Text dangerouslySetInnerHTML={{ __html: tujuan.data?.judul || '' }} fz="h3" fw="bold" c={colors['blue-button']} ta="center" />
</Stack>
</Tooltip>
</Box>

View File

@@ -74,7 +74,7 @@ function Page() {
<Title fz={55} fw={900} c={colors['blue-button']}>
Wujudkan Mimpi Pendidikanmu di Desa Darmasaba
</Title>
<Text fz="lg" mt="md" c="dimmed">
<Text fz="lg" mt="md" fw={"bold"}>
Program beasiswa untuk mendukung pendidikan berkualitas bagi generasi muda Desa Darmasaba.
</Text>
<Group mt="xl">

View File

@@ -15,7 +15,7 @@ import {
Timeline,
Title
} from '@mantine/core';
import { IconArrowLeft } from '@tabler/icons-react';
import { IconArrowLeft, IconChecklist, IconInfoCircle, IconQuote, IconSchool, IconTimeline, IconUserPlus } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useDisclosure } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
@@ -69,10 +69,11 @@ export default function BeasiswaPage() {
{/* Hero Section */}
<Container size="lg" py="xl">
<Stack gap="md" maw={600}>
<Title order={2} c="blue.9">
Program Beasiswa Pendidikan Desa Darmasaba
</Title>
<Text c="dimmed">
<Group>
<IconSchool size={30} color={colors["blue-button"]} />
<Title order={2}>Program Beasiswa Pendidikan Desa Darmasaba</Title>
</Group>
<Text>
Program ini bertujuan untuk mendukung pendidikan generasi muda di Desa Darmasaba
agar dapat melanjutkan studi ke jenjang lebih tinggi dengan dukungan finansial dan pendampingan.
</Text>
@@ -81,21 +82,35 @@ export default function BeasiswaPage() {
{/* Tentang Program */}
<Container size="lg" py="xl">
<Title order={3} mb="sm">
Tentang Program
</Title>
<Group mb="sm">
<IconInfoCircle size={24} color={colors["blue-button"]} />
<Title order={3}>Tentang Program</Title>
</Group>
<Text>
Program Beasiswa Desa Darmasaba adalah inisiatif pemerintah desa untuk meningkatkan akses
pendidikan bagi siswa berprestasi dan kurang mampu. Melalui program ini, desa memberikan bantuan
biaya sekolah, bimbingan akademik, serta pelatihan soft skill bagi peserta terpilih.
</Text>
{/* Tambahkan info tahun berjalan di sini */}
<Paper mt="md" p="md" radius="lg" shadow="xs" bg="#f8fbff" withBorder>
<Text fw={500} c={colors["blue-button"]}>
📅 Periode Beasiswa Tahun 2025
</Text>
<Text fz="sm" c="dimmed">
Pendaftaran beasiswa dibuka mulai <strong>1 Januari 2025</strong> dan ditutup pada
<strong>31 Mei 2025</strong>.
Pengumuman hasil seleksi akan diumumkan pada pertengahan Juni 2025 melalui website resmi Desa Darmasaba.
</Text>
</Paper>
</Container>
{/* Syarat dan Ketentuan */}
<Container size="lg" py="xl">
<Title order={3} mb="sm">
Syarat Pendaftaran
</Title>
<Group mb="sm">
<IconChecklist size={24} color={colors["blue-button"]} />
<Title order={3}>Syarat Pendaftaran</Title>
</Group>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
<Paper shadow="sm" p="md" radius="lg" withBorder>
@@ -123,42 +138,61 @@ export default function BeasiswaPage() {
{/* Proses Seleksi */}
<Container size="lg" py="xl">
<Title order={3} mb="sm">
Proses Seleksi
</Title>
<Group mb="sm">
<IconTimeline size={24} color={colors["blue-button"]} />
<Title order={3}>Proses Seleksi</Title>
</Group>
<Timeline active={4} bulletSize={24} lineWidth={2}>
<Timeline.Item title="Pendaftaran Online">
<Text c="dimmed" size="sm">
Calon peserta mengisi formulir pendaftaran dan mengunggah dokumen pendukung.
</Text>
<Text size="sm" fw={500} c={colors["blue-button"]} mt={4}>
Estimasi waktu: 1 Februari 31 Mei 2025
</Text>
</Timeline.Item>
<Timeline.Item title="Seleksi Administrasi">
<Text c="dimmed" size="sm">
Panitia memverifikasi kelengkapan dan validitas berkas.
</Text>
<Text size="sm" fw={500} c={colors["blue-button"]} mt={4}>
Estimasi waktu: 57 hari kerja setelah penutupan pendaftaran
</Text>
</Timeline.Item>
<Timeline.Item title="Wawancara dan Penilaian">
<Text c="dimmed" size="sm">
Peserta yang lolos administrasi akan diundang untuk wawancara langsung dengan tim seleksi.
</Text>
<Text size="sm" fw={500} c={colors["blue-button"]} mt={4}>
Estimasi waktu: 710 hari kerja setelah pengumuman seleksi administrasi
</Text>
</Timeline.Item>
<Timeline.Item title="Pengumuman Penerima">
<Text c="dimmed" size="sm">
Daftar penerima beasiswa diumumkan melalui website resmi Desa Darmasaba.
</Text>
<Text size="sm" fw={500} c={colors["blue-button"]} mt={4}>
Estimasi waktu: 5 hari kerja setelah tahap wawancara selesai
</Text>
</Timeline.Item>
</Timeline>
<Text c="dimmed" size="sm" mt="lg" ta="center">
🗓 Total estimasi keseluruhan proses: sekitar 34 minggu setelah penutupan pendaftaran
</Text>
</Container>
{/* Testimoni */}
<Container size="lg" py="xl">
<Title order={3} mb="sm">
Cerita Sukses Penerima Beasiswa
</Title>
<Group mb="sm">
<IconQuote size={24} color={colors["blue-button"]} />
<Title order={3}>Cerita Sukses Penerima Beasiswa</Title>
</Group>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
<Paper shadow="md" p="lg" radius="lg">
@@ -183,7 +217,10 @@ export default function BeasiswaPage() {
{/* CTA Akhir */}
<Container size="lg" py="xl" ta="center">
<Title order={3}>Siap Bergabung dengan Program Ini?</Title>
<Group justify="center" mb="sm">
<IconUserPlus size={28} color={colors["blue-button"]} />
<Title order={3}>Siap Bergabung dengan Program Ini?</Title>
</Group>
<Text c="dimmed" mb="md">
Segera daftar dan wujudkan mimpimu bersama Desa Darmasaba.
</Text>

View File

@@ -43,7 +43,7 @@ function Page() {
Pendidikan Non Formal
</Title>
<Text ta="center" fz="lg" lh={1.6} c="black" maw={800} mx="auto">
Bentuk pendidikan di luar sekolah yang terstruktur, bertujuan memberikan keterampilan, pengetahuan, dan pengembangan karakter masyarakat dari berbagai usia serta latar belakang.
Pendidikan non formal merupakan bentuk pendidikan di luar sekolah yang terstruktur, bertujuan untuk memberikan keterampilan, pengetahuan, serta pengembangan karakter masyarakat dari berbagai usia dan latar belakang.
</Text>
</Box>
<SimpleGrid

View File

@@ -16,16 +16,11 @@ import {
TextInput,
} from '@mantine/core';
import { DateInput } from '@mantine/dates';
import {
IconArrowRight,
IconBook2,
IconUser
} from '@tabler/icons-react';
import { IconArrowRight, IconBook2, IconUser } from '@tabler/icons-react';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { useSnapshot } from 'valtio';
export interface ModalPeminjamanProps {
opened: boolean;
onClose: () => void;
@@ -45,11 +40,12 @@ export default function ModalPeminjaman({
}: ModalPeminjamanProps) {
const snap = useSnapshot(perpustakaanDigitalState.peminjamanBuku);
// reset form setiap modal dibuka
const BATAS_HARI_PINJAM = 4;
// Reset form setiap modal dibuka
useEffect(() => {
if (opened && buku) {
perpustakaanDigitalState.peminjamanBuku.create.form = {
...perpustakaanDigitalState.peminjamanBuku.create.form,
bukuId: buku.id,
nama: '',
noTelp: '',
@@ -99,7 +95,14 @@ export default function ModalPeminjaman({
</Badge>
)}
<Text fz="sm" c="dimmed" lineClamp={3} dangerouslySetInnerHTML={{ __html: buku.deskripsi || 'Tidak ada deskripsi' }} />
<Text
fz="sm"
c="dimmed"
lineClamp={3}
dangerouslySetInnerHTML={{
__html: buku.deskripsi || 'Tidak ada deskripsi',
}}
/>
</Stack>
</Group>
@@ -112,7 +115,8 @@ export default function ModalPeminjaman({
leftSection={<IconUser size={16} />}
value={snap.create.form.nama}
onChange={(e) =>
(perpustakaanDigitalState.peminjamanBuku.create.form.nama = e.currentTarget.value)
(perpustakaanDigitalState.peminjamanBuku.create.form.nama =
e.currentTarget.value)
}
required
/>
@@ -123,7 +127,8 @@ export default function ModalPeminjaman({
leftSection={<IconUser size={16} />}
value={snap.create.form.noTelp}
onChange={(e) =>
(perpustakaanDigitalState.peminjamanBuku.create.form.noTelp = e.currentTarget.value)
(perpustakaanDigitalState.peminjamanBuku.create.form.noTelp =
e.currentTarget.value)
}
required
/>
@@ -134,11 +139,13 @@ export default function ModalPeminjaman({
leftSection={<IconUser size={16} />}
value={snap.create.form.alamat}
onChange={(e) =>
(perpustakaanDigitalState.peminjamanBuku.create.form.alamat = e.currentTarget.value)
(perpustakaanDigitalState.peminjamanBuku.create.form.alamat =
e.currentTarget.value)
}
required
/>
{/* === OTOMATIS SET BATAS DAN TANGGAL KEMBALI === */}
<DateInput
label="Tanggal Pinjam"
placeholder="Pilih tanggal pinjam"
@@ -148,64 +155,83 @@ export default function ModalPeminjaman({
: null
}
onChange={(date) => {
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam =
date ? new Date(date).toISOString() : '';
if (date) {
const tanggalPinjam = new Date(date);
// simpan tanggal pinjam
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam =
tanggalPinjam.toISOString();
// hitung batas +4 hari
const batasKembali = new Date(tanggalPinjam);
batasKembali.setDate(batasKembali.getDate() + BATAS_HARI_PINJAM);
// set batas & tanggal kembali otomatis
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali =
batasKembali.toISOString();
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali =
batasKembali.toISOString();
toast.info(
`Batas pengembalian otomatis diset ke ${batasKembali.toLocaleDateString('id-ID')} (+${BATAS_HARI_PINJAM} hari).`
);
} else {
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam = '';
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali = '';
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali = '';
}
}}
required
/>
<Box>
<Text>Catatan</Text>
<Text fw={500}>Catatan</Text>
<CreateEditor
value={snap.create.form.catatan}
onChange={(e) =>
(perpustakaanDigitalState.peminjamanBuku.create.form.catatan = e)
onChange={(val) =>
(perpustakaanDigitalState.peminjamanBuku.create.form.catatan =
val)
}
/>
</Box>
<DateInput
label="Tanggal Kembali"
placeholder="Pilih tanggal kembali"
value={
snap.create.form.tanggalKembali
? new Date(snap.create.form.tanggalKembali)
: null
}
onChange={(date) => {
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali =
date ? new Date(date).toISOString() : '';
}}
required
/>
<DateInput
label="Batas Pengembalian"
placeholder="Pilih tanggal kembali"
placeholder="Otomatis diatur +4 hari dari tanggal pinjam"
value={
snap.create.form.batasKembali
? new Date(snap.create.form.batasKembali)
: null
}
onChange={(date) => {
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali =
date ? new Date(date).toISOString() : '';
}}
required
disabled
readOnly
/>
<DateInput
label="Tanggal Kembali"
placeholder="Otomatis sama dengan batas pengembalian"
value={
snap.create.form.tanggalKembali
? new Date(snap.create.form.tanggalKembali)
: null
}
disabled
readOnly
/>
<Button
onClick={handleSubmit}
loading={snap.create.loading}
disabled={
!snap.create.form.nama ||
!snap.create.form.tanggalPinjam ||
!snap.create.form.batasKembali ||
!snap.create.form.tanggalKembali
!snap.create.form.nama || !snap.create.form.tanggalPinjam
}
rightSection={<IconArrowRight size={16} />}
radius="xl"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Pinjam Buku
</Button>

View File

@@ -3,8 +3,8 @@
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan";
import colors from "@/con/colors";
import { BarChart, PieChart } from '@mantine/charts';
import { Box, Button, Center, Container, Flex, Group, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
import { useDisclosure, useMediaQuery, useShallowEffect } from "@mantine/hooks";
import { Box, Button, Center, Container, Flex, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { useState } from "react";
import { useProxy } from "valtio/utils";
@@ -18,14 +18,13 @@ interface ChartDataItem {
function Kepuasan() {
const state = useProxy(indeksKepuasanState.responden);
const state = useProxy(indeksKepuasanState.responden);
const { data, loading } = state.findMany;
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
const [opened, { open, close }] = useDisclosure(false);
const isMobile = useMediaQuery("(max-width: 768px)");
const [barChartData, setBarChartData] = useState<Array<{ month: string; Responden: number }>>([]);
const [opened, { open, close }] = useDisclosure(false)
const resetForm = () => {
state.create.form = {
@@ -122,18 +121,18 @@ function Kepuasan() {
// Convert map to array and sort by date
const barData = Array.from(monthYearMap.entries())
.map(([key, count]) => {
.map(([key, Responden]) => {
const [year, month] = key.split('-');
const monthName = new Date(Number(year), Number(month) - 1, 1)
.toLocaleString('id-ID', { month: 'long' });
return {
month: `${monthName} ${year}`,
count,
Responden,
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
};
})
.sort((a, b) => a.sortKey - b.sortKey)
.map(({ month, count }) => ({ month, count }));
.map(({ month, Responden }) => ({ month, Responden }));
setBarChartData(barData);
}
@@ -141,12 +140,12 @@ function Kepuasan() {
if ((loading && !data) || !data) {
return (
<Stack py={10} px="sm">
<Skeleton height={200} mb="md" />
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="md">
<Skeleton height={200} />
<Skeleton height={200} />
<Skeleton height={200} />
<Stack py={10} px="xl">
<Skeleton height={300} mb="md" />
<SimpleGrid cols={{ base: 1, md: 3 }}>
<Skeleton height={300} />
<Skeleton height={300} />
<Skeleton height={300} />
</SimpleGrid>
</Stack>
);
@@ -157,10 +156,16 @@ function Kepuasan() {
<Stack p="sm">
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
<Center>
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
</Center>
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
<Center mt={10}>
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
<Button
radius={"lg"}
onClick={open}
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
>Ajukan Responden</Button>
</Center>
</Container>
<Box px={"xl"}>
@@ -177,10 +182,10 @@ function Kepuasan() {
</Box>
</Flex>
<BarChart
h={300}
h={window.innerWidth < 480 ? 200 : 300}
data={barChartData}
dataKey="month"
series={[{ name: 'count', color: colors['blue-button'] }]}
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
@@ -191,12 +196,9 @@ function Kepuasan() {
</Paper>
<Box py={"xl"}>
<SimpleGrid
cols={{
base: 1,
md: 1,
lg: 1,
xl: 3
}}
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="md"
verticalSpacing="md"
>
{/* Chart Jenis Kelamin */}
<Paper bg={colors['white-1']} p="md" radius="md">
@@ -215,7 +217,7 @@ function Kepuasan() {
withLabels
withTooltip
labelsType="percent"
size={200}
size={250} // Fixed size in pixels
data={donutDataJenisKelamin}
/>
</Center>
@@ -254,7 +256,7 @@ function Kepuasan() {
labelsPosition="outside"
labelsType="percent"
withLabelsLine
size={200}
size={250}
data={donutDataRating}
/>
</Center>
@@ -297,7 +299,7 @@ function Kepuasan() {
labelsPosition="outside"
labelsType="percent"
withLabelsLine
size={190}
size={250}
data={donutDataKelompokUmur}
/>
</Center>
@@ -330,7 +332,7 @@ function Kepuasan() {
<TextInput
label="Nama"
type='text'
placeholder="masukkan nama"
placeholder="Masukkan nama"
defaultValue={state.create.form.name}
onChange={(val) => {
state.create.form.name = val.currentTarget.value;
@@ -413,41 +415,57 @@ function Kepuasan() {
);
}
return (
<Stack p="sm">
<Container w={{ base: "100%", md: "80%" }} p={isMobile ? "md" : "xl"}>
<Stack gap="xs">
<Text ta="center" fz={{ base: "2rem", md: "3rem" }}>Indeks Kepuasan Masyarakat</Text>
<Group justify="center">
<Button radius="lg" bg={colors["blue-button"]} onClick={open}>
Ajukan Responden
</Button>
</Group>
</Stack>
<Stack p={"sm"}>
<Container size="lg" px="md">
<Center>
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
</Center>
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
<Center mt={10}>
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
</Center>
</Container>
<Box px={isMobile ? "sm" : "xl"}>
<Paper p="lg" bg={colors.Bg}>
<Paper p={isMobile ? "sm" : "lg"}>
<Stack gap="xs">
<Flex direction={isMobile ? "column" : "row"} justify="space-between" align={isMobile ? "start" : "center"}>
<Text fw="bold" mb={isMobile ? "sm" : 0}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
<Box>
<Text fz="sm" fw="bold" c={colors["blue-button"]}>Total Responden</Text>
<Text ta="end" fz="h1" fw="bold" c={colors["blue-button"]}>
{state.findMany.total.toLocaleString("id-ID")}
<Box px={"xl"}>
<Paper p={"lg"} bg={colors.Bg}>
<Paper p={"lg"}>
<Stack gap={"xs"}>
<Flex
direction={{ base: "column", sm: "row" }}
justify="space-between"
align={{ base: "flex-start", sm: "center" }}
>
<Text fw="bold" ta={{ base: "center", sm: "left" }}>
Pelayanan Terhadap Publik Desa Darmasaba
</Text>
<Box mt={{ base: "sm", sm: 0 }}>
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
{state.findMany.total.toLocaleString('id-ID')}
</Text>
</Box>
</Flex>
<BarChart
h={isMobile ? 200 : 300}
h={300}
data={barChartData}
dataKey="month"
series={[{ name: "count", color: colors["blue-button"] }]}
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
withTooltip
tooltipAnimationDuration={200}
/>
</Stack>
</Paper>
<Box py="xl">
<SimpleGrid cols={{ base: 1, sm: 2, xl: 3 }} spacing="lg">
<Box py={"xl"}>
<SimpleGrid
cols={{
base: 1,
md: 1,
lg: 1,
xl: 3
}}
>
{/* Chart Jenis Kelamin */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
@@ -457,17 +475,28 @@ function Kepuasan() {
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
<Paper p="md" radius="md">
<Stack>
<Center>
<PieChart
size={isMobile ? 150 : 200}
withLabels
data={donutDataJenisKelamin}
withTooltip
/>
</Center>
</Stack>
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Box style={{ position: 'relative', width: '100%' }}>
<Center>
<PieChart
withLabels
withTooltip
labelsType="percent"
size={200}
data={donutDataJenisKelamin}
/>
</Center>
</Box>
<Stack gap="sm" mt="md">
{donutDataJenisKelamin.map((entry) => (
<Flex key={entry.name} gap="md" align="center">
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
<Text size="sm">{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
</Box>
</Paper>
)}
</Stack>
@@ -482,18 +511,35 @@ function Kepuasan() {
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
<Paper p="md" radius="md">
<Stack>
<Center>
<PieChart
size={isMobile ? 150 : 200}
withLabels
labelsPosition="outside"
withLabelsLine
data={donutDataRating}
/>
</Center>
</Stack>
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Box style={{ position: 'relative', width: '100%' }}>
<Center>
<PieChart
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="outside"
labelsType="percent"
withLabelsLine
size={200}
data={donutDataRating}
/>
</Center>
</Box>
<Box mt="md" style={{ width: '100%' }}>
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
{donutDataRating.map((entry) => (
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
))}
</SimpleGrid>
</Box>
</Box>
</Paper>
)}
</Stack>
@@ -508,18 +554,35 @@ function Kepuasan() {
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
<Paper p="md" radius="md">
<Stack>
<Center>
<PieChart
size={isMobile ? 150 : 200}
withLabels
labelsPosition="outside"
withLabelsLine
data={donutDataKelompokUmur}
/>
</Center>
</Stack>
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Box style={{ position: 'relative', width: '100%' }}>
<Center>
<PieChart
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="outside"
labelsType="percent"
withLabelsLine
size={190}
data={donutDataKelompokUmur}
/>
</Center>
</Box>
<Box mt="md" style={{ width: '100%' }}>
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
{donutDataKelompokUmur.map((entry) => (
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
))}
</SimpleGrid>
</Box>
</Box>
</Paper>
)}
</Stack>
@@ -542,7 +605,7 @@ function Kepuasan() {
}}
/>
<TextInput
label="Tanggal"
label="Tanggal Pengisian"
type="date"
placeholder="masukkan tanggal"
defaultValue={state.create.form.tanggal}

View File

@@ -233,21 +233,42 @@ function StrukturOrganisasiPPID() {
return (
<Stack align="center" mt="xl">
{/* 🔍 Search + Zoom + Fullscreen controls */}
<Group mb="md" justify="center" gap="sm">
<Group mb="md" justify="center" gap="sm" align="center">
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
/>
<Button variant="light" size="sm" onClick={handleZoomOut}>
<IconZoomOut size={16} />
</Button>
<Button variant="light" size="sm" onClick={resetZoom}>
100%
</Button>
{/* 🔍 Tambahkan indikator zoom di sini */}
{/* Floating Zoom Indicator */}
<Box
bg="#C3D0E8"
c="blue"
px={9}
py={8}
style={{
fontSize: 14,
fontWeight: 600,
borderRadius: '5px',
}}
>
{Math.round(scale * 100)}%
</Box>
<Button variant="light" size="sm" onClick={handleZoomIn}>
<IconZoomIn size={16} />
</Button>
<Button variant="light" size="sm" onClick={resetZoom}>
Reset
</Button>
<Button
variant="light"
size="sm"
@@ -260,6 +281,7 @@ function StrukturOrganisasiPPID() {
</Button>
</Group>
{/* Chart Container */}
<Box
ref={chartContainerRef}

View File

@@ -88,7 +88,6 @@ function Page() {
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.7}
ta="center"
dangerouslySetInnerHTML={{ __html: item.misi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
/>

View File

@@ -2,11 +2,11 @@
'use client';
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import colors from "@/con/colors";
import { Carousel, CarouselSlide } from "@mantine/carousel";
import { Box, Button, Container, Group, Paper, Stack, Text, useMantineTheme, Skeleton } from "@mantine/core";
import { Carousel } from "@mantine/carousel";
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text, useMantineTheme } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import { IconArrowRight, IconAward } from "@tabler/icons-react";
import Autoplay from "embla-carousel-autoplay";
import { IconAward, IconArrowRight } from "@tabler/icons-react";
import { useTransitionRouter } from "next-view-transitions";
import { useEffect, useRef } from "react";
import { useProxy } from "valtio/utils";
@@ -18,7 +18,8 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "60%" }}>
<Container w={{ base: "100%", md: "90%", lg: "60%" }}>
<Stack align="center" gap="sm">
<Group gap="xs">
<IconAward size={40} color={colors["blue-button"]} />
@@ -37,11 +38,10 @@ export default function Page() {
}
function Slider() {
const height = 500;
const width = 1200;
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const autoplay = useRef(Autoplay({ delay: 3000 }));
const tablet = useMediaQuery(`(max-width: ${theme.breakpoints.md})`);
const autoplay = useRef(Autoplay({ delay: 3000, stopOnInteraction: false }));
const state = useProxy(penghargaanState);
const router = useTransitionRouter();
@@ -54,7 +54,7 @@ function Slider() {
if (loading) {
return (
<Group justify="center" py="xl">
<Group justify="center" py="xl" gap="md">
<Skeleton w={300} h={200} radius="lg" />
<Skeleton w={300} h={200} radius="lg" visibleFrom="sm" />
<Skeleton w={300} h={200} radius="lg" visibleFrom="md" />
@@ -74,31 +74,49 @@ function Slider() {
}
const slides = data.map((item) => (
<CarouselSlide key={item.id}>
<Carousel.Slide key={item.id}>
<Paper
h="100%"
radius="lg"
shadow="md"
pos="relative"
style={{
height: "100%",
backgroundImage: `url(${item.image?.link})`,
backgroundSize: "cover",
backgroundPosition: "center",
transition: "transform 0.3s ease, box-shadow 0.3s ease",
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "translateY(-4px)";
e.currentTarget.style.boxShadow = "0 8px 20px rgba(0,0,0,0.2)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "translateY(0)";
e.currentTarget.style.boxShadow = "none";
}}
>
<Box
pos="absolute"
inset={0}
bg="linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0.3))"
bg="linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0.2))"
style={{ borderRadius: 16 }}
/>
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
<Text fz="xl" fw={700} ta="center" c="white">
<Text
fz={{ base: "md", sm: "lg", md: "xl" }}
fw={700}
ta="center"
c="white"
lineClamp={3}
style={{ textShadow: "0 2px 4px rgba(0,0,0,0.6)" }}
>
{item.name}
</Text>
<Group justify="center">
<Button
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}
onClick={() =>
router.push(`/darmasaba/penghargaan/${item.id}`)
}
size="md"
radius="xl"
rightSection={<IconArrowRight size={18} />}
@@ -110,24 +128,83 @@ function Slider() {
</Group>
</Stack>
</Paper>
</CarouselSlide>
</Carousel.Slide>
));
return (
<Carousel
py="xl"
plugins={[autoplay.current]}
onMouseEnter={autoplay.current.stop}
onMouseLeave={autoplay.current.reset}
w={{ base: "100%", sm: "90%", md: "80%", lg: width }}
h={height}
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
slideGap="md"
loop
align="start"
slidesToScroll={mobile ? 1 : 2}
<Box
pos="relative"
w="100%"
mx="auto"
px={{ base: "md", sm: "xl", md: "2rem", lg: "3rem" }}
style={{
maxWidth: 1300,
}}
>
{slides}
</Carousel>
<Carousel
py="xl"
w="100%"
h={{ base: 320, sm: 380, md: 420, lg: 450 }}
slideSize={{
base: "100%", // Mobile: 1
sm: "50%", // Tablet kecil (≥768): 2
md: "50%", // 1024px: tetap 2
lg: "33.333%", // Desktop besar: 3
}}
slideGap={{ base: "md", sm: "md", md: "lg" }}
loop
align="start"
slidesToScroll={mobile ? 1 : tablet ? 2 : 3}
plugins={[autoplay.current]}
onMouseEnter={autoplay.current.stop}
onMouseLeave={autoplay.current.reset}
withControls={data.length > 3}
draggable={data.length > 1}
styles={{
root: {
position: "relative",
},
viewport: {
overflow: "hidden",
},
container: {
alignItems: "stretch",
},
control: {
zIndex: 20,
backgroundColor: "rgba(255,255,255,0.95)",
color: colors["blue-button"],
border: `2px solid ${colors["blue-button"]}`,
width: 46,
height: 46,
borderRadius: "50%",
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
transition: "all 0.2s ease",
'&:hover': {
backgroundColor: colors["blue-button"],
color: "white",
transform: "scale(1.1)",
},
'&[data-inactive]': {
opacity: 0,
cursor: 'default',
},
},
controls: {
position: "absolute",
top: mobile ? "70%" : tablet ? "65%" : "60%",
transform: "translateY(-50%)",
width: mobile ? "100%" : tablet ? "calc(100% + 60px)" : "calc(100% + 100px)",
left: mobile ? "0" : tablet ? "-30px" : "-50px",
right: mobile ? "0" : tablet ? "-30px" : "-50px",
padding: "0",
justifyContent: "space-between",
zIndex: 30,
},
}}
>
{slides}
</Carousel>
</Box>
);
}
}

View File

@@ -41,7 +41,7 @@ export function Navbar() {
stateNav.mobileOpen = false;
}}
>
<Tooltip label="Go to homepage" position="bottom" withArrow>
<Tooltip label="Kembali ke Beranda" position="bottom" withArrow>
<Image
src="/darmasaba-icon.png"
alt="Village Logo"

View File

@@ -26,7 +26,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
<Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
<Container pos="relative" w={{ base: '100%', md: '80%' }} fluid>
<Flex align="center" justify="space-between" wrap={{ base: "wrap", md: "nowrap" }}>
<Tooltip label="Go to Homepage" position="bottom" withArrow>
<Tooltip label="Kembali ke Beranda" position="bottom" withArrow>
<ActionIcon
radius="xl"
variant="transparent"
@@ -50,12 +50,12 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
<MenuItemCom
key={k}
item={item}
isActive={pathname === item.href ||
(item.children?.some(child => child.href === pathname))}
isActive={item.href && pathname.startsWith(item.href) ||
(item.children?.some(child => child.href && pathname.startsWith(child.href)))}
/>
))}
<Tooltip label="Search content" position="bottom" withArrow>
<Tooltip label="Cari Konten" position="bottom" withArrow>
<ActionIcon
variant="transparent"
c={isSearch ? 'gray' : colors["blue-button"]}
@@ -71,7 +71,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
{/* hanya tampil kalau role = admin */}
{stateAuth.role === "admin" && (
<Tooltip label="My Profile" position="bottom" withArrow>
<Tooltip label="Profil Saya" position="bottom" withArrow>
<ActionIcon
onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")

View File

@@ -38,7 +38,7 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
justify="space-between"
size="lg"
radius="md"
color={pathname === link.href ? 'blue' : 'gray'}
color={link.href && pathname.startsWith(link.href) ? 'blue' : 'gray'}
onClick={() => {
if (link.href) {
router.push(link.href);
@@ -49,12 +49,12 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
rightSection={<IconArrowRight size={18} />}
styles={(theme) => ({
root: {
background: pathname === link.href ? theme.colors.blue[0] : 'transparent',
color: pathname === link.href ? theme.colors.blue[7] : colors['blue-button'],
fontWeight: pathname === link.href ? 600 : 500,
background: link.href && pathname.startsWith(link.href) ? theme.colors.blue[0] : 'transparent',
color: link.href && pathname.startsWith(link.href) ? theme.colors.blue[7] : colors['blue-button'],
fontWeight: link.href && pathname.startsWith(link.href) ? 600 : 500,
transition: "all 0.2s ease",
"&:hover": {
background: pathname === link.href ? theme.colors.blue[1] : theme.colors.gray[0],
background: link.href && pathname.startsWith(link.href) ? theme.colors.blue[1] : theme.colors.gray[0],
}
},
})}

View File

@@ -23,7 +23,7 @@ function Kepuasan() {
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
const [barChartData, setBarChartData] = useState<Array<{ month: string; Responden: number }>>([]);
const [opened, { open, close }] = useDisclosure(false)
const resetForm = () => {
@@ -121,18 +121,18 @@ function Kepuasan() {
// Convert map to array and sort by date
const barData = Array.from(monthYearMap.entries())
.map(([key, count]) => {
.map(([key, Responden]) => {
const [year, month] = key.split('-');
const monthName = new Date(Number(year), Number(month) - 1, 1)
.toLocaleString('id-ID', { month: 'long' });
return {
month: `${monthName} ${year}`,
count,
Responden,
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
};
})
.sort((a, b) => a.sortKey - b.sortKey)
.map(({ month, count }) => ({ month, count }));
.map(({ month, Responden }) => ({ month, Responden }));
setBarChartData(barData);
}
@@ -185,7 +185,7 @@ function Kepuasan() {
h={window.innerWidth < 480 ? 200 : 300}
data={barChartData}
dataKey="month"
series={[{ name: 'count', color: colors['blue-button'] }]}
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
@@ -448,7 +448,7 @@ function Kepuasan() {
h={300}
data={barChartData}
dataKey="month"
series={[{ name: 'count', color: colors['blue-button'] }]}
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
@@ -605,7 +605,7 @@ function Kepuasan() {
}}
/>
<TextInput
label="Tanggal"
label="Tanggal Pengisian"
type="date"
placeholder="masukkan tanggal"
defaultValue={state.create.form.tanggal}