Compare commits

...

4 Commits

Author SHA1 Message Date
f6f77d9e35 Fix QC Kak Inno Tgl 11 Des
Fix QC Kak Ayu Tgl 11 Des
Fix font style {font size, color, line height} menu kesehatan
2025-12-12 17:06:33 +08:00
a00481152c Fix Konsisten teks di tampilan mobile dan desktop
Fix QC Kak Inno tgl 10 Des
Fix QC Kak Ayu tgl 10 Des
2025-12-11 17:58:03 +08:00
242ea86f77 Fix konsisten font, menu landing page & PPID 2025-12-10 17:44:31 +08:00
99c2c9c6d7 Fix semua tulisan profile jadi profil, mulai dari navbar, dan route 2025-12-10 14:16:15 +08:00
107 changed files with 4242 additions and 2436 deletions

View File

@@ -11,21 +11,21 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const tabs = [
{
label: "Profile Desa",
value: "profiledesa",
href: "/admin/desa/profile/profile-desa",
label: "Profil Desa",
value: "profildesa",
href: "/admin/desa/profil/profil-desa",
icon: <IconUser size={18} stroke={1.8} />
},
{
label: "Profile Perbekel",
value: "profileperbekel",
href: "/admin/desa/profile/profile-perbekel",
label: "Profil Perbekel",
value: "profilperbekel",
href: "/admin/desa/profil/profil-perbekel",
icon: <IconUsers size={18} stroke={1.8} />
},
{
label: "Profile Perbekel Dari Masa Ke Masa",
value: "profile-perbekel-dari-masa-ke-masa",
href: "/admin/desa/profile/profile-perbekel-dari-masa-ke-masa",
label: "Profil Perbekel Dari Masa Ke Masa",
value: "profilperbekeldarimasakemasa",
href: "/admin/desa/profil/profil-perbekel-dari-masa-ke-masa",
icon: <IconCalendar size={18} stroke={1.8} />
}
];

View File

@@ -12,22 +12,22 @@ function LayoutTabsEdit({ children }: { children: React.ReactNode }) {
{
label: "Sejarah Desa",
value: "sejarahdesa",
href: "/admin/desa/profile/edit/sejarah_desa"
href: "/admin/desa/profil/edit/sejarah_desa"
},
{
label: "Visi Misi Desa",
value: "visimisidesa",
href: "/admin/desa/profile/edit/visi_misi_desa"
href: "/admin/desa/profil/edit/visi_misi_desa"
},
{
label: "Lambang Desa",
value: "lambangdesa",
href: "/admin/desa/profile/edit/lambang_desa"
href: "/admin/desa/profil/edit/lambang_desa"
},
{
label: "Maskot Desa",
value: "maskotdesa",
href: "/admin/desa/profile/edit/maskot_desa"
href: "/admin/desa/profil/edit/maskot_desa"
},
];
const curentTab = tabs.find(tab => tab.href === pathname)

View File

@@ -43,7 +43,7 @@ function Page() {
const id = params?.id as string;
if (!id) {
toast.error('ID tidak valid');
router.push('/admin/desa/profile/profile-desa');
router.push('/admin/desa/profil/profil-desa');
return;
}
@@ -106,7 +106,7 @@ function Page() {
if (success) {
toast.success('Data berhasil disimpan');
router.push('/admin/desa/profile/profile-desa');
router.push('/admin/desa/profil/profil-desa');
} else {
toast.error('Gagal menyimpan data');
}
@@ -156,7 +156,7 @@ function Page() {
<Alert icon={<IconAlertCircle size={20} />} color="red" title="Terjadi Kesalahan" radius="md">
{loadError}
</Alert>
<Button onClick={() => router.push('/admin/desa/profile/profile-desa')} variant="outline">
<Button onClick={() => router.push('/admin/desa/profil/profil-desa')} variant="outline">
Kembali ke Halaman Utama
</Button>
</Stack>

View File

@@ -40,7 +40,7 @@ function Page() {
const id = params?.id as string;
if (!id) {
toast.error("ID tidak valid");
router.push("/admin/desa/profile/profile-desa");
router.push("/admin/desa/profil/profil-desa");
return;
}
@@ -157,7 +157,7 @@ function Page() {
if (success) {
toast.success("Maskot berhasil diperbarui!");
router.push("/admin/desa/profile/profile-desa");
router.push("/admin/desa/profil/profil-desa");
}
} catch (error) {
console.error("Error update maskot:", error);

View File

@@ -50,7 +50,7 @@ function Page() {
const id = params?.id as string;
if (!id) {
toast.error('ID tidak valid');
router.push('/admin/desa/profile/profile-desa');
router.push('/admin/desa/profil/profil-desa');
return;
}
@@ -122,7 +122,7 @@ function Page() {
if (success) {
toast.success('Data berhasil disimpan');
router.push('/admin/desa/profile/profile-desa');
router.push('/admin/desa/profil/profil-desa');
} else {
toast.error('Gagal menyimpan data');
}
@@ -179,7 +179,7 @@ function Page() {
{loadError}
</Alert>
<Button
onClick={() => router.push('/admin/desa/profile/profile-desa')}
onClick={() => router.push('/admin/desa/profil/profil-desa')}
variant="outline"
>
Kembali ke Halaman Utama

View File

@@ -42,7 +42,7 @@ function Page() {
const id = params?.id as string;
if (!id) {
toast.error('ID tidak valid');
router.push('/admin/desa/profile/profile-desa');
router.push('/admin/desa/profil/profil-desa');
return;
}
@@ -106,7 +106,7 @@ function Page() {
if (success) {
toast.success('Data berhasil disimpan');
router.push('/admin/desa/profile/profile-desa');
router.push('/admin/desa/profil/profil-desa');
} else {
toast.error('Gagal menyimpan data');
}
@@ -156,7 +156,7 @@ function Page() {
{loadError}
</Alert>
<Button
onClick={() => router.push('/admin/desa/profile/profile-desa')}
onClick={() => router.push('/admin/desa/profil/profil-desa')}
variant="outline"
>
Kembali ke Halaman Utama

View File

@@ -27,7 +27,7 @@ function Page() {
return (
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
<Stack gap="lg">
<Title order={2} c={colors['blue-button']}>Preview Profile Desa</Title>
<Title order={2} c={colors['blue-button']}>Preview Profil Desa</Title>
{/* Sejarah Desa */}
{sejarah && (
@@ -42,7 +42,7 @@ function Page() {
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profile/profile-desa/${sejarah.id}/sejarah_desa`)}
onClick={() => router.push(`/admin/desa/profil/profil-desa/${sejarah.id}/sejarah_desa`)}
>
Edit
</Button>
@@ -87,7 +87,7 @@ function Page() {
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profile/profile-desa/${visiMisi.id}/visi_misi_desa`)}
onClick={() => router.push(`/admin/desa/profil/profil-desa/${visiMisi.id}/visi_misi_desa`)}
>
Edit
</Button>
@@ -135,7 +135,7 @@ function Page() {
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profile/profile-desa/${lambang.id}/lambang_desa`)}
onClick={() => router.push(`/admin/desa/profil/profil-desa/${lambang.id}/lambang_desa`)}
>
Edit
</Button>
@@ -180,7 +180,7 @@ function Page() {
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profile/profile-desa/${maskot.id}/maskot_desa`)}
onClick={() => router.push(`/admin/desa/profil/profil-desa/${maskot.id}/maskot_desa`)}
>
Edit
</Button>

View File

@@ -117,7 +117,7 @@ function EditPerbekelDariMasaKeMasa() {
await state.update.update();
toast.success('Perbekel dari masa ke masa berhasil diperbarui!');
router.push('/admin/desa/profile/profile-perbekel-dari-masa-ke-masa');
router.push('/admin/desa/profil/profil-perbekel-dari-masa-ke-masa');
} catch (error) {
console.error('Error updating perbekel dari masa ke masa:', error);
toast.error('Terjadi kesalahan saat memperbarui perbekel dari masa ke masa');

View File

@@ -25,7 +25,7 @@ function DetailPerbekelDariMasa() {
state.delete.byId(selectedId);
setModalHapus(false);
setSelectedId(null);
router.push("/admin/desa/profile/profile-perbekel-dari-masa-ke-masa");
router.push("/admin/desa/profil/profil-perbekel-dari-masa-ke-masa");
}
};
@@ -113,7 +113,7 @@ function DetailPerbekelDariMasa() {
<Button
color="green"
onClick={() => router.push(`/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/${data.id}/edit`)}
onClick={() => router.push(`/admin/desa/profil/profil-perbekel-dari-masa-ke-masa/${data.id}/edit`)}
variant="light"
radius="md"
size="md"

View File

@@ -46,7 +46,7 @@ function CreatePerbekelDariMasaKeMasa() {
state.create.form.imageId = uploaded.id;
await state.create.create();
resetForm();
router.push('/admin/desa/profile/profile-perbekel-dari-masa-ke-masa');
router.push('/admin/desa/profil/profil-perbekel-dari-masa-ke-masa');
} catch (error) {
console.error(error);
toast.error('Gagal menambahkan perbekel dari masa ke masa');

View File

@@ -53,7 +53,7 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/create')}
onClick={() => router.push('/admin/desa/profil/profil-perbekel-dari-masa-ke-masa/create')}
>
Tambah Baru
</Button>
@@ -90,7 +90,7 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/${item.id}`)}
onClick={() => router.push(`/admin/desa/profil/profil-perbekel-dari-masa-ke-masa/${item.id}`)}
>
Detail
</Button>

View File

@@ -25,7 +25,7 @@ function ProfilePerbekel() {
const id = params?.id as string;
if (!id) {
toast.error("ID tidak valid");
router.push("/admin/desa/profile/profile-perbekel");
router.push("/admin/desa/profil/profil-perbekel");
return;
}
@@ -74,7 +74,7 @@ function ProfilePerbekel() {
const success = await perbekelState.edit.submit()
if (success) {
toast.success("Data berhasil disimpan");
router.push("/admin/desa/profile/profile-perbekel");
router.push("/admin/desa/profil/profil-perbekel");
}
} catch (error) {
console.error("Error update sejarah desa:", error);

View File

@@ -41,7 +41,7 @@ function Page() {
variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md"
onClick={() => router.push(`/admin/desa/profile/profile-perbekel/${perbekel.id}`)}
onClick={() => router.push(`/admin/desa/profil/profil-perbekel/${perbekel.id}`)}
>
Edit
</Button>

View File

@@ -91,8 +91,8 @@ export const devBar = [
children: [
{
id: "Desa_1",
name: "Profile",
path: "/admin/desa/profile/profile-desa"
name: "Profil",
path: "/admin/desa/profil/profil-desa"
},
{
id: "Desa_2",
@@ -495,8 +495,8 @@ export const navBar = [
children: [
{
id: "Desa_1",
name: "Profile",
path: "/admin/desa/profile/profile-desa"
name: "Profil",
path: "/admin/desa/profil/profil-desa"
},
{
id: "Desa_2",
@@ -899,8 +899,8 @@ export const role1 = [
children: [
{
id: "Desa_1",
name: "Profile",
path: "/admin/desa/profile/profile-desa"
name: "Profil",
path: "/admin/desa/profil/profil-desa"
},
{
id: "Desa_2",

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
'use client';
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import {
Badge,
@@ -51,10 +51,14 @@ export default function Content({ kategori }: { kategori: string }) {
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
{/* === Berita Utama === */}
{featuredState.loading ? (
<Center><Skeleton h={400} /></Center>
<Center>
<Skeleton h={400} />
</Center>
) : featured ? (
<Box mb={50}>
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
<Title order={2} mb="md">
Berita Utama
</Title>
<Paper shadow="md" radius="md" withBorder>
<Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}>
@@ -74,13 +78,29 @@ export default function Content({ kategori }: { kategori: string }) {
<Badge color="blue" variant="light" mb="md">
{featured.kategoriBerita?.name || kategori}
</Badge>
<Title order={2} mb="md">{featured.judul}</Title>
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featured.deskripsi }} />
<Title order={3} mb="md">
{featured.judul}
</Title>
<Text
c="dimmed"
lineClamp={3}
mb="md"
style={{ lineHeight: 1.6 }}
dangerouslySetInnerHTML={{ __html: featured.deskripsi }}
/>
</div>
<Group justify="apart" mt="auto">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lh={1.5}
style={{
fontSize: '0.875rem',
lineHeight: '1.5rem',
}}
>
{new Date(featured.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
@@ -91,7 +111,9 @@ export default function Content({ kategori }: { kategori: string }) {
<Button
variant="light"
rightSection={<IconArrowRight size={16} />}
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)}
onClick={() =>
router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)
}
>
Baca Selengkapnya
</Button>
@@ -105,19 +127,29 @@ export default function Content({ kategori }: { kategori: string }) {
{/* === Daftar Berita === */}
<Box mt={50}>
<Title order={2} mb="md">Daftar Berita</Title>
<Title order={2} mb="md">
Daftar Berita
</Title>
<Divider mb="xl" />
{state.findMany.loading ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
{Array(3).fill(0).map((_, i) => (
<Skeleton key={i} h={300} radius="md" />
))}
{Array(3)
.fill(0)
.map((_, i) => (
<Skeleton key={i} h={300} radius="md" />
))}
</SimpleGrid>
) : paginatedNews.length === 0 ? (
<Text c="dimmed" ta="center">Belum ada berita di kategori &quot;{kategori}&quot;.</Text>
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Belum ada berita di kategori &quot;{kategori}&quot;.
</Text>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
<SimpleGrid
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="xl"
verticalSpacing="xl"
>
{paginatedNews.map((item) => (
<Card
key={item.id}
@@ -125,19 +157,51 @@ export default function Content({ kategori }: { kategori: string }) {
p="lg"
radius="md"
withBorder
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)}
onClick={() =>
router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)
}
style={{ cursor: 'pointer' }}
>
<Card.Section>
<Image src={item.image?.link} height={200} alt={item.judul} fit="cover" loading="lazy"/>
<Image
src={item.image?.link}
height={200}
alt={item.judul}
fit="cover"
loading="lazy"
/>
</Card.Section>
<Badge color="blue" variant="light" mt="md">
{item.kategoriBerita?.name || kategori}
</Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Title
order={4}
mt="sm"
fz={{ base: 'sm', md: 'md' }}
style={{ lineHeight: 1.4 }}
lineClamp={2}
>
{item.judul}
</Title>
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lineClamp={3}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
lineHeight: 1.5,
}}
mt="xs"
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
<Group justify="apart" mt="md" gap="xs">
<Text size="xs" c="dimmed">
<Text
fz={{ base: 'xs', md: 'xs' }}
c="dimmed"
lh={1.4}
style={{ fontSize: '0.75rem', lineHeight: '1.125rem' }}
>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'short',

View File

@@ -3,18 +3,16 @@
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
import NewsReader from '@/app/darmasaba/_com/NewsReader';
import colors from '@/con/colors';
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(stateDashboardBerita.berita)
const [loading, setLoading] = useState(true)
const state = useProxy(stateDashboardBerita.berita);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
@@ -27,9 +25,9 @@ function Page() {
} finally {
setLoading(false);
}
}
loadData()
}, [id])
};
loadData();
}, [id]);
if (loading) {
return (
@@ -47,41 +45,49 @@ function Page() {
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} pb={"xl"} gap={"xs"} px={{ base: "md", md: 0 }}>
<Group px={{ base: "md", md: 100 }}>
<Stack pos="relative" bg={colors.Bg} pb="xl" gap="xs" px={{ base: 'md', md: 0 }}>
<Group px={{ base: 'md', md: 100 }}>
<NewsReader />
</Group>
<Container w={{ base: "100%", md: "50%" }} >
<Container w={{ base: '100%', md: '50%' }}>
<Box pb={20}>
<Text id='news-title' ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
{state.findUnique.data?.judul}
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
<Title
id="news-title"
order={1}
ta="center"
c={colors['blue-button']}
fw="bold"
lh={{ base: 1.2, md: 1.25 }}
>
{state.findUnique.data.judul}
</Title>
<Title
order={2}
ta="center"
fw="bold"
fz={{ base: 'md', md: 'lg' }}
lh={{ base: 1.3, md: 1.35 }}
>
Informasi dan Pelayanan Administrasi Digital
</Text>
</Title>
</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"}>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="xs">
<Text
id='news-content'
id="news-content"
py={20}
fz={{ base: "sm", md: "lg" }}
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.8 }}
ta="justify"
style={{
wordBreak: "break-word",
whiteSpace: "normal",
wordBreak: 'break-word',
whiteSpace: 'normal',
}}
dangerouslySetInnerHTML={{
__html: state.findUnique.data?.content || "",
__html: state.findUnique.data.content || '',
}}
/>
</Stack>
@@ -90,4 +96,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -16,35 +16,30 @@ function Semua() {
const searchParams = useSearchParams();
const router = useTransitionRouter();
// Ambil parameter langsung dari URL
const search = searchParams.get('search') || '';
const page = parseInt(searchParams.get('page') || '1');
// Gunakan proxy untuk state global
const state = useProxy(stateDashboardBerita.berita);
const featured = useProxy(stateDashboardBerita.berita.findFirst);
const loadingGrid = state.findMany.loading;
const loadingFeatured = featured.loading;
// Load berita utama sekali saja
useEffect(() => {
if (!featured.data && !loadingFeatured) {
stateDashboardBerita.berita.findFirst.load();
}
}, [featured.data, loadingFeatured]);
// Load berita terbaru tiap page / search berubah
useEffect(() => {
const limit = 3;
state.findMany.load(page, limit, search);
}, [page, search]);
// Handler pagination → langsung update URL
const handlePageChange = (newPage: number) => {
const url = new URLSearchParams(searchParams.toString());
if (search) url.set('search', search);
if (newPage > 1) url.set('page', newPage.toString());
else url.delete('page'); // biar page=1 ga muncul di URL
else url.delete('page');
router.replace(`?${url.toString()}`);
};
@@ -61,7 +56,7 @@ function Semua() {
<Center><Skeleton h={400} /></Center>
) : featuredData ? (
<Box mb={50}>
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
<Title order={2} mb="md">Berita Utama</Title>
<Paper shadow="md" radius="md" withBorder>
<Grid gutter={0}>
<GridCol span={{ base: 12, md: 6 }}>
@@ -81,13 +76,24 @@ function Semua() {
<Badge color="blue" variant="light" mb="md">
{featuredData.kategoriBerita?.name || 'Berita'}
</Badge>
<Title order={2} mb="md">{featuredData.judul}</Title>
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }} />
<Title order={3} mb="md">{featuredData.judul}</Title>
<Text
c="dimmed"
lineClamp={3}
mb="md"
dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }}
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
/>
</div>
<Group justify="apart" mt="auto">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
<Text
c="dimmed"
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
>
{new Date(featuredData.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
@@ -124,7 +130,9 @@ function Semua() {
))}
</SimpleGrid>
) : paginatedNews.length === 0 ? (
<Text c="dimmed" ta="center">Tidak ada berita ditemukan.</Text>
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={{ base: 1.5, md: 1.6 }}>
Tidak ada berita ditemukan.
</Text>
) : (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
{paginatedNews.map((item) => (
@@ -143,11 +151,24 @@ function Semua() {
{item.kategoriBerita?.name || 'Berita'}
</Badge>
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Title order={4} mt="sm" lineClamp={2}>
{item.judul}
</Title>
<Text
c="dimmed"
lineClamp={3}
mt="xs"
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.5, md: 1.6 }}
/>
<Flex align="center" justify="apart" mt="md" gap="xs">
<Text size="xs" c="dimmed">
<Text
c="dimmed"
fz={{ base: 'xs', md: 'xs' }}
lh={{ base: 1.4, md: 1.4 }}
>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'short',
@@ -187,4 +208,4 @@ function Semua() {
);
}
export default Semua;
export default Semua;

View File

@@ -17,17 +17,11 @@ import {
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconPhoto } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
// Komponen kartu foto
function FotoCard({ item }: { item: any }) {
const router = useRouter();
const handleClick = () => {
router.push(`/darmasaba/galeri/foto/${item.id}`);
};
return (
<Grid.Col span={{ base: 12, xs: 6, md: 4 }}>
@@ -35,19 +29,19 @@ function FotoCard({ item }: { item: any }) {
shadow="sm"
radius="md"
p={0}
onClick={handleClick}
style={{ cursor: 'pointer', transition: 'transform 0.2s' }}
style={{ transition: 'transform 0.2s' }}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
>
{item.imageGalleryFoto?.link ? (
<Box
pos="relative"
style={{
paddingBottom: '100%', // ✅ Ubah ke 1:1 (square) — atau sesuaikan
paddingBottom: '100%',
overflow: 'hidden',
borderRadius: '4px 4px 0 0',
backgroundColor: '#f9f9f9', // ✅ background netral
backgroundColor: '#f9f9f9',
}}
>
<Image
@@ -61,8 +55,8 @@ function FotoCard({ item }: { item: any }) {
left: 0,
width: '100%',
height: '100%',
objectFit: 'contain', // ✅ Tampilkan utuh, jangan crop
objectPosition: 'center', // rata tengah
objectFit: 'contain',
objectPosition: 'center',
}}
loading="lazy"
/>
@@ -74,13 +68,23 @@ function FotoCard({ item }: { item: any }) {
)}
<Stack p="md" gap={4}>
<Text fw={600} lineClamp={1}>
<Text fw={600} lineClamp={1} fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
{item.name || 'Tanpa Judul'}
</Text>
{item.deskripsi && (
<Text fz="sm" c="dimmed" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lineClamp={2}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
lh={{ base: '1.4', md: '1.5' }}
/>
)}
<Text fz="xs" c="dimmed">
<Text
fz={{ base: 11, md: 'xs' }}
c="dimmed"
lh={{ base: '1.3', md: '1.4' }}
>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'short',
@@ -99,7 +103,7 @@ export default function GaleriFotoUser() {
return (
<Box py="xl" px={{ base: 'md', md: 'lg' }}>
{/* Header */}
<Title order={2} c={colors['blue-button']} mb="lg">
<Title order={2} c={colors['blue-button']} mb="lg" ta="center">
Galeri Foto Desa Darmasaba
</Title>
@@ -115,7 +119,7 @@ function FotoList({ search }: { search: string }) {
const { data, page, totalPages, loading, load } = FotoState.findMany;
useShallowEffect(() => {
load(page, 3, search); // ✅ 9 item per halaman
load(page, 3, search);
}, [page, search]);
if (loading) {
@@ -135,7 +139,9 @@ function FotoList({ search }: { search: string }) {
<Center py="xl">
<Stack align="center" c="dimmed">
<IconPhoto size={48} />
<Text>Tidak ada foto ditemukan</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
Tidak ada foto ditemukan
</Text>
</Stack>
</Center>
);
@@ -150,19 +156,18 @@ function FotoList({ search }: { search: string }) {
</Grid>
{/* Pagination */}
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 3, search);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
color="blue"
radius="md"
/>
</Center>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 3, search);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
color="blue"
radius="md"
/>
</Center>
</Stack>
);
}

View File

@@ -11,7 +11,8 @@ import {
Paper,
SimpleGrid,
Stack,
Text
Text,
Title
} from '@mantine/core';
import { useTransitionRouter } from 'next-view-transitions';
import { useCallback, useEffect } from 'react';
@@ -19,15 +20,13 @@ import { useSnapshot } from 'valtio';
export default function VideoContent() {
const videoState = useSnapshot(stateGallery.video);
const router = useTransitionRouter()
const router = useTransitionRouter();
const { data, page, totalPages, loading } = videoState.findMany;
// Handle search and pagination changes
const loadData = useCallback((pageNum: number, searchTerm: string) => {
stateGallery.video.findMany.load(pageNum, 3, searchTerm.trim());
}, []);
// Initial load and URL change handler
useEffect(() => {
const handleRouteChange = () => {
const urlParams = new URLSearchParams(window.location.search);
@@ -57,13 +56,14 @@ export default function VideoContent() {
loadData(newPage, search);
};
const dataVideo = data || [];
if (loading && !data) {
return (
<Box py={10}>
<Text>Memuat Video...</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed" ta="center">
Memuat Video...
</Text>
</Box>
);
}
@@ -78,55 +78,71 @@ export default function VideoContent() {
p="md"
radius={26}
bg={colors['white-trans-1']}
w={{ base: '100%', md: '100%' }}
w="100%"
>
<Box>
<Center>
<Box
component="iframe"
src={convertToEmbedUrl(v.linkVideo)}
width="100%"
height={300}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{ borderRadius: 8 }}
/>
</Center>
</Box>
<Box>
<Stack gap="sm" py={10}>
<Text fz="sm" c="dimmed">
{new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
<Text fw="bold" fz="sm" lineClamp={1}>
{v.name}
</Text>
<Text
ta="justify"
fz="sm"
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
lineClamp={3}
truncate="end"
/>
<Group justify={"right"}>
<Button
<Center>
<Box
component="iframe"
src={convertToEmbedUrl(v.linkVideo)}
width="100%"
height={300}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{ borderRadius: 8 }}
/>
</Center>
<Stack gap="sm" py={10}>
{/* Tanggal: Caption */}
<Text
fz={{ base: 12, md: 14 }}
c="dimmed"
ta="left"
>
{new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
{/* Judul Video: Subsection (H3) */}
<Title
order={3}
c="dark"
ta="left"
lh={1.3}
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
>
{v.name}
</Title>
{/* Deskripsi: Body kecil */}
<Text
ta="justify"
fz={{ base: 13, md: 14 }}
c="dimmed"
style={{ wordBreak: 'break-word' }}
lineClamp={3}
>
<span dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Text>
<Group justify="right">
<Button
onClick={() => router.push(`/darmasaba/desa/galery/video/${v.id}`)}
bg={colors['blue-button']}
fz={{ base: 'sm', md: 'md' }}
>
Detail
</Button>
</Group>
</Stack>
</Box>
</Group>
</Stack>
</Paper>
</Box>
))}
</SimpleGrid>
<Center>
<Pagination
value={page}
@@ -140,7 +156,6 @@ export default function VideoContent() {
);
}
// ✅ Fix: convert YouTube URL ke embed
function convertToEmbedUrl(youtubeUrl: string): string {
try {
const url = new URL(youtubeUrl);
@@ -151,4 +166,4 @@ function convertToEmbedUrl(youtubeUrl: string): string {
console.error('Error converting YouTube URL to embed:', err);
return youtubeUrl;
}
}
}

View File

@@ -12,16 +12,17 @@ import {
Stack,
Text,
ThemeIcon,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconInfoCircle, IconVideo } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; // pastikan state bisa dipakai di publik
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import BackButton from '../../../layanan/_com/BackButto';
// Fungsi helper: aman dan tanpa spasi
function convertToEmbedUrl(youtubeUrl: string): string {
try {
const url = new URL(youtubeUrl);
@@ -72,7 +73,9 @@ export default function DetailVideoUser() {
color="red"
radius="md"
>
Video yang Anda cari tidak tersedia.
<Text fz={{ base: 'sm', md: 'md' }} c="red.9">
Video yang Anda cari tidak tersedia.
</Text>
</Alert>
<Button
leftSection={<IconArrowBack size={16} />}
@@ -91,20 +94,20 @@ export default function DetailVideoUser() {
return (
<Box py="xl" px={{ base: 'md', md: 100 }}>
{/* Tombol Kembali */}
<Box >
<Box>
<BackButton />
</Box>
{/* Header */}
<Text
{/* Header - Dijadikan Title */}
<Title
order={1}
ta="center"
fz={{ base: 'xl', md: '2xl' }}
fw={700}
c={colors['blue-button']}
mb="lg"
lh={{ base: 1.2, md: 1.25 }}
>
{data.name || 'Video Galeri Desa'}
</Text>
</Title>
{/* Konten Utama */}
<Card
@@ -118,7 +121,7 @@ export default function DetailVideoUser() {
{embedUrl ? (
<Box
pos="relative"
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }} // 16:9 aspect ratio
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }}
>
<iframe
src={embedUrl}
@@ -144,7 +147,9 @@ export default function DetailVideoUser() {
title="Gagal memuat video"
radius="md"
>
Mohon maaf, video tidak dapat diputar.
<Text fz={{ base: 'xs', md: 'sm' }} c="orange.9">
Mohon maaf, video tidak dapat diputar.
</Text>
</Alert>
) : (
<Alert
@@ -153,7 +158,9 @@ export default function DetailVideoUser() {
title="Tidak ada video"
radius="md"
>
Konten video belum tersedia.
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
Konten video belum tersedia.
</Text>
</Alert>
)}
@@ -163,7 +170,11 @@ export default function DetailVideoUser() {
<ThemeIcon variant="light" size="sm" radius="xl">
<IconInfoCircle size={14} />
</ThemeIcon>
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lh={{ base: 1.4, md: 1.5 }}
>
Diunggah pada{' '}
{new Date(data.createdAt).toLocaleDateString('id-ID', {
weekday: 'long',
@@ -179,8 +190,9 @@ export default function DetailVideoUser() {
{data.deskripsi && (
<Paper p="md" bg="gray.0" radius="md">
<Text
fz="md"
fz={{ base: 'sm', md: 'md' }}
c="dark"
ta={"justify"}
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{
wordBreak: 'break-word',

View File

@@ -3,7 +3,7 @@
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -39,30 +39,38 @@ function PelayananPendudukNonPermanent() {
) : (
<Stack gap="xl">
<Box>
<Text fz={{ base: "xl", md: "2xl" }} fw={700} lh={1.3} c="dark">
<Title
order={1}
fz={{ base: 'lg', md: 'xl' }}
fw={700}
lh={{ base: 1.3, md: 1.3 }}
c="dark"
>
{data?.name || "Judul belum tersedia"}
</Text>
</Title>
</Box>
<Box>
{data?.deskripsi ? (
<Text
fz={{ base: "sm", md: "md" }}
lh={1.7}
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
ta="justify"
c="dimmed"
c="black"
dangerouslySetInnerHTML={{ __html: data?.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
) : (
<Text fz="sm" c="gray">Deskripsi belum tersedia.</Text>
<Text fz="xs" c="gray">
Deskripsi belum tersedia.
</Text>
)}
</Box>
<Divider color={colors["blue-button"]} size="sm" />
<Flex justify="space-between" align="center" wrap="wrap" gap="md">
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">
<Text fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.5 }} c="black">
25 Mei 2021 Darmasaba
</Text>
<Group gap="md">
@@ -96,4 +104,4 @@ function PelayananPendudukNonPermanent() {
);
}
export default PelayananPendudukNonPermanent;
export default PelayananPendudukNonPermanent;

View File

@@ -47,7 +47,7 @@ function PelayananPerizinanBerusaha() {
return (
<Center mih={300}>
<Stack align="center" gap="sm">
<Text fz="lg" fw={500} c="dimmed">
<Text fz={{ base: 'md', md: 'lg' }} fw={500} c="dimmed" lh="sm">
Belum ada informasi layanan yang tersedia
</Text>
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
@@ -67,10 +67,10 @@ function PelayananPerizinanBerusaha() {
) : (
<Stack gap="lg">
<Box>
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
<Title order={2} fw={700} mb="sm">
Perizinan Berusaha Berbasis Risiko melalui OSS
</Title>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
<Text fz={{ base: 'sm', md: 'md' }} c="black" lh="sm">
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
</Text>
</Box>
@@ -83,13 +83,13 @@ function PelayananPerizinanBerusaha() {
/>
<Box>
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>
<Title order={3} fw={600} mb="sm">
Alur pendaftaran NIB:
</Text>
</Title>
<Stepper
active={active}
onStepClick={(step) => {
if (step <= active) { // Only allow clicking on previous or current steps
if (step <= active) {
setActive(step);
}
}}
@@ -102,28 +102,42 @@ function PelayananPerizinanBerusaha() {
}}
>
<StepperStep label="Langkah 1" description="Daftar Akun">
<Text fz="sm">Membuat akun di portal OSS</Text>
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Membuat akun di portal OSS
</Text>
</StepperStep>
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text>
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Lengkapi informasi perusahaan, data pemegang saham, dan alamat
</Text>
</StepperStep>
<StepperStep label="Langkah 3" description="Pilih KBLI">
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text>
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Menentukan kode KBLI sesuai jenis usaha
</Text>
</StepperStep>
<StepperStep label="Langkah 4" description="Unggah Dokumen">
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text>
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Unggah akta pendirian, surat izin, dan dokumen wajib lainnya
</Text>
</StepperStep>
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text>
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Menunggu verifikasi dan persetujuan dari pihak berwenang
</Text>
</StepperStep>
<StepperStep label="Langkah 6" description="Terbit NIB">
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text>
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
Menerima NIB sebagai identitas resmi usaha
</Text>
</StepperStep>
<StepperCompleted>
<Center>
<Stack align="center" gap="xs">
<IconCheck size={40} color="green" />
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text>
<Text fz={{ base: 'xs', md: 'sm' }} fw={500} lh="sm">
Proses pendaftaran selesai
</Text>
</Stack>
</Center>
</StepperCompleted>
@@ -159,7 +173,7 @@ function PelayananPerizinanBerusaha() {
)}
</Box>
<Text fz="sm" ta="justify" c="dimmed" mt="md">
<Text fz={{ base: 'xs', md: 'sm' }} ta="justify" c="black" lh="sm" mt="md">
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
oss.go.id

View File

@@ -2,7 +2,7 @@
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -35,7 +35,7 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
<Center py="xl">
<Stack align="center" gap="xs">
<IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} />
<Text c="dimmed" ta="center">
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh="sm">
Tidak ada layanan surat keterangan yang ditemukan
</Text>
</Stack>
@@ -48,9 +48,9 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
<Group justify="space-between" align="center" mb="md">
<Group gap="xs">
<IconFileDescription size={28} stroke={1.8} />
<Text fz={{ base: "h4", md: "h2" }} fw={700}>
<Title order={2} c="black">
Layanan Surat Keterangan
</Text>
</Title>
</Group>
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
<IconInfoCircle size={22} stroke={1.8} />
@@ -82,15 +82,15 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
style={{ borderRadius: 16 }}
/>
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
<Text
<Title
order={3}
c="white"
fw={600}
fz="lg"
ta="center"
lineClamp={2}
lh="sm"
>
{v.name}
</Text>
</Title>
<Group justify="center">
<Button
size="md"
@@ -128,4 +128,4 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
);
}
export default PelayananSuratKeterangan;
export default PelayananSuratKeterangan;

View File

@@ -42,9 +42,10 @@ function PelayananTelunjukSaktiDesa() {
return (
<Box>
<Title order={2} mb="lg" fz={{ base: 22, md: 28 }} fw={700} style={{ lineHeight: 1.4 }}>
Layanan Telunjuk Sakti Desa <br />
<Text span c="dimmed" fz="lg" fw={400}>
<Title order={2} mb="lg" fw={700} style={{ lineHeight: 1.3 }} ta="left">
Layanan Telunjuk Sakti Desa
<Text span c="black" fz={{ base: 'sm', md: 'md' }} fw={400} style={{ lineHeight: 1.5 }}>
{' '}
Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman
</Text>
</Title>
@@ -53,7 +54,7 @@ function PelayananTelunjukSaktiDesa() {
<Skeleton h={400} radius="lg" />
) : data.length === 0 ? (
<Card shadow="sm" radius="lg" withBorder>
<Text c="dimmed" ta="center" py="xl">
<Text c="black" ta="center" py="xl" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Belum ada layanan tersedia untuk saat ini
</Text>
</Card>
@@ -72,9 +73,9 @@ function PelayananTelunjukSaktiDesa() {
}}
>
<Stack gap="sm">
<Text fw={700} fz="lg" lh={1.4}>
<Title order={3} fw={700} lh={1.3}>
{v.name}
</Text>
</Title>
<Flex gap="xs" align="center">
<IconExternalLink size={18} stroke={1.5} />
<Text
@@ -82,7 +83,7 @@ function PelayananTelunjukSaktiDesa() {
href={v.link}
target="_blank"
rel="noopener noreferrer"
fz="sm"
fz={{ base: 'xs', md: 'sm' }}
c="blue"
td="underline"
style={{ cursor: 'pointer' }}
@@ -100,4 +101,4 @@ function PelayananTelunjukSaktiDesa() {
);
}
export default PelayananTelunjukSaktiDesa;
export default PelayananTelunjukSaktiDesa;

View File

@@ -1,58 +1,94 @@
'use client'
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
import colors from '@/con/colors';
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Container, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import BackButton from '../../../layanan/_com/BackButto';
import NewsReader from '@/app/darmasaba/_com/NewsReader';
import BackButton from '../../../layanan/_com/BackButto';
function Page() {
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique)
const params = useParams()
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique);
const params = useParams();
useShallowEffect(() => {
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string)
}, [])
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string);
}, []);
if (!detail.data) {
return (
<Box>
<Skeleton h={400} />
</Box>
)
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md">
<Group>
<NewsReader />
</Group>
<Stack gap="xs" >
<Group justify={"space-between"} align={"center"}>
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
{detail.data?.judul}
<Stack gap="xs">
<Group justify="space-between" align="flex-start" wrap="wrap">
<Title
order={1}
c={colors['blue-button']}
fz={{ base: 28, md: 36 }}
style={{
wordBreak: 'break-word',
flex: '1 1 auto',
minWidth: 0
}}
>
{detail.data?.judul}
</Title>
<Paper bg={colors['blue-button']} p={8} style={{ flexShrink: 0 }}>
<Text c={colors['white-1']} fz={{ base: 'xs', md: 'sm' }} lh={1.2}>
{detail.data?.CategoryPengumuman?.name}
</Text>
<Group justify='end'>
<Paper bg={colors['blue-button']} p={5}>
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
</Paper>
</Group>
</Group>
<Paper bg={colors["white-1"]} p="md">
<Text px="lg" id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
<Text px="lg" fz={"md"} c={colors["blue-button"]} fw="bold" >
</Paper>
</Group>
<Paper
bg={colors['white-1']}
p="md"
w="100%"
mih={{ base: 200, md: 300 }}
>
<Text
px="lg"
id="news-content"
fz={{ base: 14, md: 16 }}
lh={{ base: 1.6, md: 1.6 }}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
width: '100%'
}}
dangerouslySetInnerHTML={{ __html: detail.data?.content }}
/>
<Text
px="lg"
fz={{ base: 12, md: 14 }}
c={colors['blue-button']}
fw="bold"
lh={{ base: 1.4, md: 1.4 }}
mt="md"
>
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
year: 'numeric',
})}
</Text>
</Paper>
@@ -62,4 +98,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,14 +2,13 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
import colors from '@/con/colors';
import { Box, Container, Group, Paper, Stack, Text } from '@mantine/core';
import { Box, Container, Group, Paper, Stack, Text, Title } from '@mantine/core';
import { IconCalendar } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
import { useEffect } from 'react';
import { useParams } from 'next/navigation';
function Page() {
const unwrappedParams = useParams();
const kategoriState = useProxy(stateDesaPengumuman);
@@ -26,48 +25,85 @@ function Page() {
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
{categoryName.split('-').map(word =>
<Container size="lg" px="md">
<Stack align="center" gap="xs">
<Title
order={1}
c={colors["blue-button"]}
ta="center"
style={{ fontWeight: 'bold' }}
>
{categoryName.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}
</Text>
<Text ta="center" px="md" pb={10}>
</Title>
<Text
ta="center"
px="md"
pb="sm"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
c="dimmed"
>
Informasi dan pengumuman resmi terkait {categoryName.split('-').join(' ')}
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{!kategoriState.pengumuman.findMany.data?.length ? (
<Paper p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
Tidak ada pengumuman yang ditemukan
<Text
fz={{ base: 'sm', md: 'md' }}
ta="center"
c="dimmed"
>
Tidak ada pengumuman yang ditemukan
</Text>
</Paper>
) : kategoriState.pengumuman.findMany.data?.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
) : (
kategoriState.pengumuman.findMany.data?.map((v, k) => (
<Paper
mb="md"
key={k}
withBorder
p="lg"
radius="md"
shadow="md"
bg={colors["white-1"]}
>
<Title order={3}>{v.judul}</Title>
<Group style={{ color: 'black' }} pb="sm">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
{v.createdAt ? new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
}) : 'No date available'}
<Text
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
>
{v.createdAt
? new Date(v.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
: 'No date available'}
</Text>
</Group>
</Group>
<Text ta={'justify'}>
<Text
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
>
{v.deskripsi}
</Text>
</Paper>
)
})}
))
)}
</Box>
</Stack>
);
}
export default Page;
export default Page;

View File

@@ -9,7 +9,6 @@ import {
Center,
Container,
Divider,
Flex,
Grid,
GridCol,
Group,
@@ -22,7 +21,7 @@ import {
Text,
TextInput,
Title,
UnstyledButton,
UnstyledButton
} from '@mantine/core';
import { IconCalendar, IconClock, IconSearch } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
@@ -98,10 +97,14 @@ function Page() {
<Container size="lg" px="md">
<Stack align="center" gap="0">
<Text fz={{ base: '2rem', md: '3.4rem' }} c={colors['blue-button']} fw="bold" ta="center">
<Title
order={1}
c={colors['blue-button']}
ta="center"
>
Pengumuman Desa Darmasaba
</Text>
<Text ta="center" px="md" pb={10}>
</Title>
<Text ta="center" px="md" pb={10} fz={{ base: 'sm', md: 'md' }} lh="sm">
Informasi dan pengumuman resmi terkait kegiatan dan kebijakan Desa Darmasaba
</Text>
</Stack>
@@ -126,17 +129,17 @@ function Page() {
withCloseButton={false}
title={item.CategoryPengumuman?.name || 'Pengumuman'}
>
<Stack gap={"xs"}>
<Text fz="sm" fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
<Stack gap="xs">
<Text fz={{ base: 'sm', md: 'sm' }} fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
{item.judul}
</Text>
<Text ta="justify" fz="sm" c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Text ta="justify" fz={{ base: 'xs', md: 'sm' }} c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Stack>
<Flex pt={20} gap="md" justify="space-between">
<Group pt={20} gap="md" justify="space-between">
<Group style={{ color: 'black' }}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
<Text fz={{ base: 'xs', md: 'sm' }}>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
weekday: 'long',
day: 'numeric',
@@ -147,7 +150,7 @@ function Page() {
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">
<Text fz={{ base: 'xs', md: 'sm' }}>
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit',
@@ -157,11 +160,11 @@ function Page() {
</Group>
</Group>
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
<Text fs="unset" c={colors['blue-button']} fz="sm">
<Text fs="unset" c={colors['blue-button']} fz={{ base: 'xs', md: 'sm' }}>
Baca Selengkapnya
</Text>
</Anchor>
</Flex>
</Group>
</Notification>
))
)}
@@ -169,19 +172,19 @@ function Page() {
<Paper p="md">
<Stack gap="xs">
<Text fw="bold" fz="lg" c={colors['blue-button']}>
<Title order={3} c={colors['blue-button']}>
Kategori
</Text>
</Title>
{stateDesaPengumuman.category.findMany.data?.map((v: any, k) => {
const count = v._count?.pengumumans || 0;
return (
<UnstyledButton component={Link} href={`/darmasaba/desa/pengumuman/${v.name}`} key={k}>
<Paper bg={colors['BG-trans']} p={5}>
<Group px={3} justify="space-between">
<Text fz="md" c="black">
<Text fz={{ base: 'sm', md: 'md' }} c="black">
{v.name}
</Text>
<Text fz="md" c="black">
<Text fz={{ base: 'sm', md: 'md' }} c="black">
{count}
</Text>
</Group>
@@ -200,7 +203,7 @@ function Page() {
<Divider mb={10} color={colors['blue-button']} />
<Grid>
<GridCol span={{ base: 12, md: 8 }}>
<Title order={3}>Daftar Pengumuman</Title>
<Title order={2}>Daftar Pengumuman</Title>
</GridCol>
<GridCol span={{ base: 12, md: 4 }}>
<TextInput
@@ -210,6 +213,7 @@ function Page() {
w="100%"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
fz={{ base: 'sm', md: 'md' }}
/>
</GridCol>
</Grid>
@@ -223,7 +227,9 @@ function Page() {
</SimpleGrid>
) : !state.findMany.data?.length ? (
<Notification withCloseButton={false} h={100}>
Tidak ada pengumuman yang ditemukan
<Text fz={{ base: 'sm', md: 'md' }} ta="center">
Tidak ada pengumuman yang ditemukan
</Text>
</Notification>
) : (
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" verticalSpacing="lg">
@@ -231,26 +237,26 @@ function Page() {
<Paper key={item.id} p="md" withBorder radius="md" h="100%">
<Stack h="100%" justify="space-between">
<div>
<Text fw={600} c={colors['blue-button']} mb={5}>
<Text fw={600} c={colors['blue-button']} mb={5} fz={{ base: 'sm', md: 'md' }}>
{item.CategoryPengumuman?.name || 'Pengumuman'}
</Text>
<Text fz="lg" fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }}>
<Text fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }} fz={{ base: 'sm', md: 'lg' }}>
{item.judul}
</Text>
<Text
fz="sm"
c="dimmed"
lineClamp={4}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
mb="md"
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
fz={{ base: 'xs', md: 'sm' }}
/>
</div>
<div>
<Group mb="sm" c="dimmed">
<Group gap={5}>
<IconCalendar size={16} />
<Text size="xs">
<Text fz={{ base: 'xs', md: 'xs' }}>
{new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'short',
@@ -260,19 +266,19 @@ function Page() {
</Group>
<Group gap={5}>
<IconClock size={16} />
<Text size="xs">
<Text fz={{ base: 'xs', md: 'xs' }}>
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit',
})}
</Text>
</Group>
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
<Text fw={600} c={colors['blue-button']} fz={{ base: 'sm', md: 'sm' }}>
Baca Selengkapnya
</Text>
</Anchor>
</Group>
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
<Text fw={600} c={colors['blue-button']} size="sm">
Baca Selengkapnya
</Text>
</Anchor>
</div>
</Stack>
</Paper>
@@ -289,6 +295,7 @@ function Page() {
siblings={1}
boundaries={1}
withEdges
fz={{ base: 'xs', md: 'sm' }}
/>
</Center>
</Stack>

View File

@@ -9,6 +9,7 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
@@ -35,7 +36,9 @@ function Page() {
<Center h="80vh">
<Stack align="center" gap="md">
<Loader size="lg" color="blue" />
<Text c="dimmed" fz="sm">Sedang memuat informasi...</Text>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
Sedang memuat informasi...
</Text>
</Stack>
</Center>
);
@@ -46,28 +49,31 @@ function Page() {
<Center h="80vh">
<Stack align="center" gap="sm">
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
<Title order={3}>Data Tidak Ditemukan</Title>
<Text c="dimmed" fz="sm">Mohon periksa kembali atau coba beberapa saat lagi</Text>
<Title order={3} ta="center">
Data Tidak Ditemukan
</Title>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
Mohon periksa kembali atau coba beberapa saat lagi
</Text>
</Stack>
</Center>
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: 'md', md: 0 }}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "60%" }}>
<Container w={{ base: '100%', md: '60%' }}>
<Paper radius="2xl" shadow="lg" p="xl" withBorder>
<Stack gap="lg" align="center">
<Title ta="center" fz={{ base: "2rem", md: "3rem" }} c={colors["blue-button"]} fw={800}>
<Title order={1} ta="center" c={colors['blue-button']} fw={800}>
{state.findUnique.data?.name}
</Title>
<Text ta="center" fw={600} fz={{ base: "md", md: "lg" }} c="dimmed">
<Text ta="center" fw={600} fz={{ base: 'md', md: 'lg' }} c="dimmed">
Informasi & Pelayanan Potensi Desa Digital
</Text>
{/* ✅ Bagian gambar dibuat konsisten tanpa CSS manual */}
<Box
w="100%"
h={{ base: 220, md: 400 }}
@@ -87,7 +93,15 @@ function Page() {
radius="lg"
/>
</Box>
<Text py="md" fz={{ base: "sm", md: "md" }} ta="justify" lh={1.8} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || 'Belum ada deskripsi untuk potensi desa ini.' }} />
<Text
py="md"
fz={{ base: 'sm', md: 'md' }}
ta="justify"
lh={{ base: 1.6, md: 1.8 }}
dangerouslySetInnerHTML={{
__html: state.findUnique.data?.content || 'Belum ada deskripsi untuk potensi desa ini.',
}}
/>
</Stack>
</Paper>
</Container>
@@ -95,4 +109,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,7 +2,7 @@
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { BackgroundImage, Box, Button, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconEye } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions';
import { useEffect, useState } from 'react';
@@ -41,10 +41,10 @@ function Page() {
<Box px={{ base: "md", md: 100 }}>
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
<Stack gap="sm" maw={600}>
<Text fz={{ base: "2rem", md: "3rem" }} fw={900} c={colors["blue-button"]} lh={1.2}>
<Title order={1} fz={{ base: 28, md: 36 }} lh={1.2} c={colors["blue-button"]}>
Potensi Desa Darmasaba
</Text>
<Text fz="lg" ta="justify">
</Title>
<Text fz={{ base: 14, md: 16 }} lh={1.6} ta="justify">
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
</Text>
</Stack>
@@ -58,18 +58,18 @@ function Page() {
>
<Flex justify="center" align="center" gap="xl">
<Box>
<Text ta="center" fz="2rem" fw={800} c="white">
<Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
</Text>
<Text ta="center" fz="sm" c="white" fw={500}>
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
Potensi
</Text>
</Box>
<Box>
<Text ta="center" fz="2rem" fw={800} c="white">
<Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0}
</Text>
<Text ta="center" fz="sm" c="white" fw={500}>
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
Wisata
</Text>
</Box>
@@ -91,45 +91,40 @@ function Page() {
radius="xl"
onMouseEnter={() => setHoveredId(v.id)}
onMouseLeave={() => setHoveredId(null)}
style={{
overflow: 'hidden',
style={{
overflow: 'hidden',
position: 'relative',
cursor: 'pointer',
transition: 'transform 0.3s ease'
}}
>
{/* Overlay with smooth transition */}
<Box
pos="absolute"
inset={0}
bg={hoveredId === v.id
? "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.75) 100%)"
bg={hoveredId === v.id
? "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.75) 100%)"
: "linear-gradient(180deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.15) 100%)"
}
style={{
transition: 'background 0.3s ease'
}}
/>
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
{/* Kategori badge - always visible */}
<Group>
<Paper
radius="lg"
py={6}
px={12}
shadow="md"
withBorder
<Paper
radius="lg"
py={6}
px={12}
shadow="md"
withBorder
bg="rgba(255,255,255,0.9)"
style={{
transition: 'all 0.3s ease'
}}
style={{ transition: 'all 0.3s ease' }}
>
<Text fz="sm" fw={600}>{v.kategori?.nama}</Text>
<Text fz={{ base: 11, md: 14 }} fw={600}>{v.kategori?.nama}</Text>
</Paper>
</Group>
{/* Nama potensi - visible on hover */}
<Box
style={{
opacity: hoveredId === v.id ? 1 : 0,
@@ -138,20 +133,20 @@ function Page() {
pointerEvents: hoveredId === v.id ? 'auto' : 'none'
}}
>
<Text
<Title
order={3}
fw={800}
c="white"
fz="xl"
fz={{ base: 18, md: 20 }}
ta="center"
lineClamp={2}
lh={1.3}
>
{v.name}
</Text>
</Title>
</Box>
{/* Button - visible on hover */}
<Group
<Group
justify="center"
style={{
opacity: hoveredId === v.id ? 1 : 0,
@@ -169,23 +164,21 @@ function Page() {
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
>
Lihat Detail
<Text c={'white'} fz={{ base: 12, md: 14 }} fw={500}>Lihat Detail</Text>
</Button>
</Group>
</Stack>
</BackgroundImage>
))
) : (
<Center h={240}>
<Stack align="center" gap="xs">
<Text fz="lg" fw={600} c="dimmed">
Belum ada potensi desa
</Text>
<Text fz="sm" c="dimmed">
Data potensi akan tampil di sini setelah tersedia.
</Text>
</Stack>
</Center>
<Stack align="center" gap="xs">
<Text fz={{ base: 14, md: 16 }} fw={600} c="dimmed">
Belum ada potensi desa
</Text>
<Text fz={{ base: 12, md: 14 }} c="dimmed">
Data potensi akan tampil di sini setelah tersedia.
</Text>
</Stack>
)}
</SimpleGrid>
</Box>
@@ -193,4 +186,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -26,7 +26,6 @@ function DetailPegawaiUser() {
statePegawai.findUnique.load(params?.id as string);
}, []);
if (!statePegawai.findUnique.data) {
return (
<Stack py="lg">
@@ -41,7 +40,7 @@ function DetailPegawaiUser() {
<Box px={{ base: 'md', md: 100 }} py="xl">
{/* Back button */}
<Group mb="lg" px={{ base: 'md', md: 100 }}>
<BackButton/>
<BackButton />
</Group>
<Paper
@@ -69,11 +68,17 @@ function DetailPegawaiUser() {
/>
{/* Nama & Jabatan */}
<Stack align="center" gap={2}>
<Title order={3} fw={700} c={colors['blue-button']}>
<Stack align="center" gap={4}>
{/* Title utama → H2 karena ini judul profil */}
<Title order={2} c={colors['blue-button']} lh={1.2}>
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
</Title>
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'sm', md: 'md' }}
lh={1.4}
c="dimmed"
>
{data.posisi?.nama || 'Posisi tidak tersedia'}
</Text>
</Stack>
@@ -82,7 +87,11 @@ function DetailPegawaiUser() {
<Divider my="lg" />
{/* Informasi Detail */}
<Stack gap="md">
<Stack gap="lg">
<Title order={3} lh={1.3}>
Informasi Pegawai
</Title>
<InfoRow label="Email" value={data.email} />
<InfoRow label="Telepon" value={data.telepon} />
<InfoRow label="Alamat" value={data.alamat} multiline />
@@ -91,10 +100,10 @@ function DetailPegawaiUser() {
value={
data.tanggalMasuk
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
day: '2-digit',
month: 'long',
year: 'numeric',
})
: '-'
}
/>
@@ -123,11 +132,18 @@ function InfoRow({
}) {
return (
<Box>
<Text fz="sm" fw={600} c="dark">
<Text
fz={{ base: 'sm', md: 'md' }}
fw={600}
lh={1.3}
c="dark"
>
{label}
</Text>
<Text
fz="sm"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c={valueColor || 'dimmed'}
style={{
whiteSpace: multiline ? 'normal' : 'nowrap',

View File

@@ -36,11 +36,12 @@ import { useTransitionRouter } from 'next-view-transitions'
import { OrganizationChart } from 'primereact/organizationchart'
import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils'
import './struktur.css'
import BackButton from '../_com/BackButto'
import { useMediaQuery } from '@mantine/hooks'
export default function StrukturPerangkatDesa() {
import './struktur.css'
import { useMediaQuery } from '@mantine/hooks'
import BackButton from '../_com/BackButto'
export default function Page() {
return (
<Box
style={{
@@ -59,10 +60,11 @@ export default function StrukturPerangkatDesa() {
ta="center"
c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }}
lh={{ base: 1.05, md: 1.03 }}
>
Struktur Perangkat Desa
</Title>
<Text ta="center" c="black" maw={800}>
<Text ta="center" c="black" maw={800} fz={{ base: 13, md: 15 }} lh={1.45}>
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
untuk melihat detail atau klik node untuk fokus tampilan.
</Text>
@@ -105,8 +107,8 @@ function StrukturPerangkatDesaNode() {
<Center py={48}>
<Stack align="center" gap="sm">
<Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text>
<Text c="dimmed" size="sm">
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.2}>Memuat struktur organisasi</Text>
<Text c="dimmed" fz={{ base: 12, md: 13 }} lh={1.4}>
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
</Text>
</Stack>
@@ -132,10 +134,10 @@ function StrukturPerangkatDesaNode() {
<Center>
<IconUsers size={56} />
</Center>
<Title order={3} mt="md">
<Title order={3} mt="md" fz={{ base: 16, md: 18 }} lh={1.15}>
Data pegawai belum tersedia
</Title>
<Text c="dimmed" mt="xs">
<Text c="dimmed" mt="xs" fz={{ base: 13, md: 14 }} lh={1.4}>
Belum ada data pegawai yang tercatat untuk PPID.
</Text>
<Group justify="center" mt="lg">
@@ -232,11 +234,18 @@ function StrukturPerangkatDesaNode() {
{/* 🔍 Controls */}
<Paper
shadow="xs"
w={{
base: '100%', // Mobile: 100%
sm: '40%', // Tablet: 95%
md: '39%', // Desktop: 70%
lg: '38%', // Desktop L: 60%
xl: '37%', // 4K: 50%
'2xl': '36%', // Ultra-wide: 45%
}}
p="md"
radius="md"
style={{
background: colors['blue-button'],
width: '100%', // ⬅️ penting
background: colors['blue-button'], // ⬅️ penting
maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow
}}
@@ -269,30 +278,33 @@ function StrukturPerangkatDesaNode() {
fontSize: '0.875rem',
padding: '6px 12px',
minHeight: 'auto',
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
flexShrink: 0,
},
}}
style={{ width: '100%' }} // 👈 penting
>
<TabsList
style={{
display: 'flex',
overflowX: 'auto',
overflowY: 'hidden', // 👈 tambahkan ini
overflowY: 'hidden',
gap: '4px',
paddingBottom: '4px',
flexWrap: 'nowrap',
WebkitOverflowScrolling: 'touch', // 👈 smooth scroll di iOS
scrollbarWidth: 'thin', // 👈 scrollbar tipis di Firefox
msOverflowStyle: '-ms-autohiding-scrollbar', // 👈 untuk IE/Edge
WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'thin',
msOverflowStyle: '-ms-autohiding-scrollbar',
maxWidth: '100%',
scrollBehavior: 'smooth', // 👈 smooth scroll
}}
>
<TabsTab
value="zoom-out"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
style={{ flexShrink: 0 }}
>
Zoom Out
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
</TabsTab>
<Box
@@ -301,7 +313,6 @@ function StrukturPerangkatDesaNode() {
px={12}
py={6}
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '6px',
minWidth: 60,
@@ -310,10 +321,12 @@ function StrukturPerangkatDesaNode() {
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
whiteSpace: 'nowrap', // 👈 mencegah text wrap
whiteSpace: 'nowrap',
}}
>
{Math.round(scale * 100)}%
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
{Math.round(scale * 100)}%
</Text>
</Box>
<TabsTab
@@ -322,7 +335,7 @@ function StrukturPerangkatDesaNode() {
leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
>
Zoom In
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
</TabsTab>
<TabsTab
@@ -330,7 +343,7 @@ function StrukturPerangkatDesaNode() {
onClick={resetZoom}
style={{ flexShrink: 0 }}
>
Reset
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
</TabsTab>
<TabsTab
@@ -345,7 +358,9 @@ function StrukturPerangkatDesaNode() {
}
style={{ flexShrink: 0 }}
>
{isFullscreen ? 'Exit' : 'Fullscreen'}
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
{isFullscreen ? 'Exit' : 'Fullscreen'}
</Text>
</TabsTab>
</TabsList>
</Tabs>
@@ -451,17 +466,17 @@ function NodeCard({ node, router }: any) {
{/* Name */}
<Text
fw={700}
size="sm"
ta="center"
c={colors['blue-button']}
lineClamp={2}
fz={{ base: 13, md: 15 }}
lh={1.2}
style={{
minHeight: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
lineHeight: 1.3,
}}
>
{name}
@@ -469,18 +484,18 @@ function NodeCard({ node, router }: any) {
{/* Title/Position */}
<Text
size="xs"
c="dimmed"
ta="center"
fw={500}
lineClamp={2}
fz={{ base: 12, md: 13 }}
lh={1.3}
style={{
minHeight: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
lineHeight: 1.2,
}}
>
{title}
@@ -496,14 +511,14 @@ function NodeCard({ node, router }: any) {
mt={8}
radius="md"
onClick={() =>
router.push(`/darmasaba/desa/profile/struktur-perangkat-desa/${node.data.id}`)
router.push(`/darmasaba/desa/profil/struktur-perangkat-desa/${node.data.id}`)
}
style={{
height: 32,
fontWeight: 600,
}}
>
Lihat Detail
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
</Button>
)}
</Stack>

View File

@@ -2,7 +2,7 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
import colors from '@/con/colors'
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'
import { useEffect } from 'react'
import { useProxy } from 'valtio/utils'
@@ -26,6 +26,8 @@ function LambangDesa() {
return (
<Box>
<Stack align="center" gap="lg">
{/* HEADER */}
<Box pb="lg">
<Center>
<Image
@@ -36,17 +38,20 @@ function LambangDesa() {
loading="lazy"
/>
</Center>
<Text
{/* TITLE - H1 */}
<Title
order={1}
c={colors['blue-button']}
ta="center"
fw={800}
fz={{ base: 28, md: 40 }}
mt="sm"
style={{ letterSpacing: '-0.5px' }}
>
Lambang Desa
</Text>
</Title>
</Box>
{/* DESKRIPSI */}
<Paper
p="xl"
radius="xl"
@@ -58,15 +63,20 @@ function LambangDesa() {
borderColor: '#e0e9ff',
}}
>
<Text
fz={{ base: '1.125rem', md: '1.375rem' }}
lh={1.8}
c="dark"
ta="justify"
style={{ fontWeight: 400, wordBreak: "break-word", whiteSpace: "normal", }}
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/>
<Text
fz={{ base: 'sm', md: 'md' }} // Body text mobile & desktop
lh={1.7}
c="dark"
ta="justify"
style={{
fontWeight: 400,
wordBreak: "break-word",
whiteSpace: "normal",
}}
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/>
</Paper>
</Stack>
</Box>
)

View File

@@ -2,7 +2,7 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text, Title } from '@mantine/core';
import { IconPhoto } from '@tabler/icons-react';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
@@ -21,7 +21,9 @@ function MaskotDesa() {
<Center mih={500}>
<Stack align="center" gap="sm">
<Loader size="lg" color="blue" />
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
Sedang memuat data maskot desa...
</Text>
</Stack>
</Center>
);
@@ -31,8 +33,21 @@ function MaskotDesa() {
<Box>
<Stack align="center" gap="xl">
<Stack align="center" gap={10}>
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} loading="lazy"/>
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
<Image
src="/pudak-icon.png"
alt="Ikon Desa"
w={{ base: 160, md: 240 }}
loading="lazy"
/>
{/* Page Title */}
<Title
order={1}
ta="center"
c={colors['blue-button']}
>
Maskot Desa
</Title>
</Stack>
<Paper
@@ -42,48 +57,60 @@ function MaskotDesa() {
withBorder
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
>
{/* Body Description */}
<Text
fz={{ base: 'sm', md: 'lg' }}
lh={1.7}
ta="justify"
c="dark"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
<Group justify="center" gap="lg" mt="lg">
{data.images.length > 0 ? (
data.images.map((img, index) => (
<Card
<Card
key={index}
radius="lg"
shadow="md"
withBorder
w={220}
p="sm"
style={{
transition: 'transform 200ms ease, box-shadow 200ms ease',
}}
className="hover:scale-105 hover:shadow-lg"
radius="lg"
shadow="md"
withBorder
w={220}
p="sm"
style={{
transition: 'transform 200ms ease, box-shadow 200ms ease',
}}
className="hover:scale-105 hover:shadow-lg"
>
<Image
src={img.image.link}
alt={img.label}
w="100%"
h={200}
fit="cover"
radius="md"
loading="lazy"
/>
{/* Image Label */}
<Text
ta="center"
mt="sm"
fw={600}
fz={{ base: 'xs', md: 'sm' }}
c="dark"
>
<Image
src={img.image.link}
alt={img.label}
w="100%"
h={200}
fit="cover"
radius="md"
loading="lazy"
/>
<Text ta="center" mt="sm" fw={600} fz="sm" c="dark">
{img.label}
</Text>
</Card>
{img.label}
</Text>
</Card>
))
) : (
<Stack align="center" gap="xs" mt="lg">
<IconPhoto size={48} stroke={1.5} color="gray" />
<Text c="dimmed" fz="sm">Belum ada gambar maskot yang ditambahkan</Text>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
Belum ada gambar maskot yang ditambahkan
</Text>
</Stack>
)}
</Group>

View File

@@ -1,35 +1,15 @@
'use client'
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { motion } from 'framer-motion';
import { IconSparkles } from '@tabler/icons-react';
import colors from '@/con/colors';
const dataText = [
{
id: 1,
title: "Santun",
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
},
{
id: 2,
title: "Adaptif",
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
},
{
id: 3,
title: "Inovatif",
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
},
{
id: 4,
title: "Profesional",
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
},
{
id: 5,
title: "Gesit",
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
},
{ id: 1, title: "Santun", description: "Pelayanan ramah, penuh empati, sopan, dan beretika." },
{ id: 2, title: "Adaptif", description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif." },
{ id: 3, title: "Inovatif", description: "Berani menciptakan pembaruan dan ide-ide kreatif." },
{ id: 4, title: "Profesional", description: "Berpengetahuan luas, terampil, dan bertanggung jawab." },
{ id: 5, title: "Gesit", description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja." },
];
const letters = ["S", "I", "G", "A", "P"];
@@ -38,11 +18,14 @@ function MotoDesa() {
return (
<Box px={{ base: "md", md: "xl" }}>
<Stack align="center" gap="lg">
{/* Page Title */}
<Box>
<Text
<Title
order={1}
ta="center"
fw={800}
fz={{ base: "2rem", md: "2.8rem" }}
fz={{ base: 28, md: 36 }}
lh={{ base: 1.2, md: 1.3 }}
style={{
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
WebkitBackgroundClip: "text",
@@ -50,9 +33,10 @@ function MotoDesa() {
}}
>
Moto Desa Darmasaba
</Text>
</Title>
</Box>
{/* Letter Icons */}
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
{letters.map((letter, i) => (
<motion.div
@@ -71,7 +55,7 @@ function MotoDesa() {
backdropFilter: "blur(6px)",
}}
>
<Text c="white" fw={800} fz="xl">
<Text c="white" fw={800} fz={{ base: 20, md: 24 }}>
{letter}
</Text>
</ActionIcon>
@@ -79,6 +63,7 @@ function MotoDesa() {
))}
</Flex>
{/* Values Card */}
<Paper
radius="lg"
p="xl"
@@ -90,19 +75,22 @@ function MotoDesa() {
>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
{dataText.map((v) => (
<motion.div
key={v.id}
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<motion.div key={v.id} whileHover={{ scale: 1.02 }} transition={{ duration: 0.2 }}>
<Stack gap={4}>
{/* Section Title */}
<Flex align="center" gap="sm">
<IconSparkles size={20} color={colors['blue-button']} />
<Text fw={700} fz={{ base: "lg", md: "xl" }} c={colors['blue-button']}>
<Title
order={3}
fw={700}
fz={{ base: 20, md: 24 }}
c={colors['blue-button']}
>
{v.title}
</Text>
</Title>
</Flex>
<Text fz={{ base: "sm", md: "md" }} c="gray.7">
{/* Body Text */}
<Text fz={{ base: 14, md: 16 }} lh={{ base: 1.5, md: 1.6 }} c="gray.7">
{v.description}
</Text>
</Stack>
@@ -111,16 +99,15 @@ function MotoDesa() {
</SimpleGrid>
</Paper>
{/* Motto Description */}
<Text
ta="center"
fw={700}
fz={{ base: "md", md: "xl" }}
fz={{ base: 15, md: 20 }}
lh={{ base: 1.6, md: 1.8 }}
c="blue.8"
mt="md"
style={{
maxWidth: 720,
lineHeight: 1.6,
}}
style={{ maxWidth: 720 }}
>
&quot;Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
<Text span fw={800} c="cyan.6">

View File

@@ -2,44 +2,45 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function ProfilPerbekel() {
const state = useProxy(stateProfileDesa.profilPerbekel)
const state = useProxy(stateProfileDesa.profilPerbekel);
useEffect(() => {
state.findUnique.load("edit")
}, [])
state.findUnique.load("edit");
}, []);
const { data, loading } = state.findUnique
const { data, loading } = state.findUnique;
if (loading || !data) {
return (
<Box py={20} px="md">
<Skeleton h={500} radius="lg" />
</Box>
)
);
}
return (
<Box px="md">
{/* ===== PAGE TITLE ===== */}
<Stack align="center" gap={0} mb={40}>
<Text
<Title
order={1}
c={colors['blue-button']}
ta="center"
fw="bold"
fz={{ base: "2rem", md: "2.8rem" }}
style={{ letterSpacing: "0.5px" }}
>
Profil Perbekel
</Text>
</Title>
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
</Stack>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
{/* ========== FOTO PERBEKEL ========== */}
<Box>
<Paper
bg={colors['white-trans-1']}
@@ -60,6 +61,8 @@ function ProfilPerbekel() {
}}
loading="lazy"
/>
{/* ===== NAMA DAN JABATAN ===== */}
<Paper
bg={colors['blue-button']}
px="lg"
@@ -67,22 +70,23 @@ function ProfilPerbekel() {
className="glass3"
py={{ base: 20, md: 50 }}
>
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
<Title order={3} c={colors['white-1']}>
Perbekel Desa Darmasaba
</Text>
<Text
</Title>
<Title
order={2}
c={colors['white-1']}
fw="bolder"
fz={{ base: "xl", md: "h2" }}
mt={8}
>
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
</Text>
</Title>
</Paper>
</Stack>
</Paper>
</Box>
{/* ========== BIODATA & PENGALAMAN ========== */}
<Paper
p="xl"
bg={colors['white-trans-1']}
@@ -92,34 +96,39 @@ function ProfilPerbekel() {
withBorder
>
<Stack gap="xl">
{/* ===== BIODATA ===== */}
<Box>
<Stack gap={6}>
<Stack align="center" gap={6}>
<IconUser size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text>
<Title order={3}>Biodata</Title>
</Stack>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
fz={{ base: "sm", md: "md" }}
ta="justify"
lh={1.6}
lh={1.7}
dangerouslySetInnerHTML={{ __html: data.biodata }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: "break-word" }}
/>
</Stack>
</Box>
{/* ===== PENGALAMAN ===== */}
<Box>
<Stack gap={6}>
<Stack align="center" gap={6}>
<IconBriefcase size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
<Title order={3}>Pengalaman</Title>
</Stack>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
fz={{ base: "sm", md: "md" }}
ta="left"
lh={1.6}
lh={1.7}
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: "break-word" }}
/>
</Stack>
</Box>
@@ -127,6 +136,7 @@ function ProfilPerbekel() {
</Paper>
</SimpleGrid>
{/* ========== ORGANISASI & PROGRAM UNGGULAN ========== */}
<Paper
p="xl"
bg={colors['white-trans-1']}
@@ -136,35 +146,41 @@ function ProfilPerbekel() {
withBorder
>
<Stack gap="xl">
{/* ===== PENGALAMAN ORGANISASI ===== */}
<Box>
<Stack align="center" gap={6} >
<Stack align="center" gap={6}>
<IconUsers size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text>
<Title order={3}>Pengalaman Organisasi</Title>
</Stack>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
fz={{ base: "sm", md: "md" }}
ta="justify"
lh={1.6}
lh={1.7}
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: "break-word" }}
/>
</Box>
{/* ===== PROGRAM UNGGULAN ===== */}
<Box>
<Stack align="center" gap={6} mb={6}>
<IconTargetArrow size={22} />
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Program Kerja Unggulan</Text>
<Title order={3}>Program Kerja Unggulan</Title>
</Stack>
<Box px={10}>
<Text
fz={{ base: "1rem", md: "1.2rem" }}
fz={{ base: "sm", md: "md" }}
ta="justify"
lh={1.6}
lh={1.7}
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: "break-word" }}
/>
</Box>
</Box>
</Stack>
</Paper>
</Box>

View File

@@ -2,7 +2,7 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
@@ -26,29 +26,32 @@ function SejarahDesa() {
return (
<Box>
<Stack align="center" gap="xl">
{/* HEADER ICON + TITLE */}
<Stack align="center" gap="sm">
<Center>
<Image
src="/darmasaba-icon.png"
alt="Ikon Desa Darmasaba"
w={{ base: 180, md: 260 }}
w={{ base: 160, md: 240 }}
radius="md"
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
loading="lazy"
/>
</Center>
<Center>
<Text
<Title
order={1}
c={colors['blue-button']}
ta="center"
fw={700}
fz={{ base: '2rem', md: '2.8rem' }}
style={{ letterSpacing: '-0.5px' }}
>
Sejarah Desa
</Text>
</Title>
</Center>
</Stack>
{/* CONTENT */}
<Paper
p="xl"
radius="lg"
@@ -61,10 +64,14 @@ function SejarahDesa() {
>
<Stack gap="md">
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.8}
fz={{ base: 'sm', md: 'md' }}
lh={1.75}
ta="justify"
style={{ color: '#2a2a2a', wordBreak: "break-word", whiteSpace: "normal" }}
c="dark.7"
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
}}
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
/>
</Stack>

View File

@@ -28,8 +28,10 @@ function SemuaPerbekel() {
<Center py="xl">
<Stack align="center" gap="sm">
<IconUser size={48} stroke={1.5} />
<Title fw="bold" order={2}>Belum ada data Perbekel</Title>
<Text c="dimmed" fz="sm" ta="center">Data mantan Perbekel akan muncul di sini ketika sudah tersedia</Text>
<Title order={2} ta="center">Belum ada data Perbekel</Title>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.6 }} ta="center">
Data mantan Perbekel akan muncul di sini ketika sudah tersedia
</Text>
</Stack>
</Center>
);
@@ -38,17 +40,20 @@ function SemuaPerbekel() {
return (
<Box>
<Stack align="center" gap="lg">
<Box>
<Text
ta="center"
fw={900}
fz={{ base: "2rem", md: "2.5rem" }}
variant="gradient"
gradient={{ from: "blue", to: "cyan", deg: 45 }}
>
Perbekel Dari Masa ke Masa
</Text>
</Box>
<Title
order={1}
ta="center"
style={{
background: 'linear-gradient(45deg, blue, cyan)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
fz={{ base: 28, md: 36 }}
lh={{ base: 1.2, md: 1.3 }}
fw={900}
>
Perbekel Dari Masa ke Masa
</Title>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
{data.map((v: any, k: number) => (
@@ -59,9 +64,7 @@ function SemuaPerbekel() {
withBorder
p="lg"
bg="white"
style={{
transition: "all 250ms ease",
}}
style={{ transition: "all 250ms ease" }}
className="hover:shadow-xl hover:scale-[1.02]"
>
<Stack gap="md" align="center">
@@ -77,17 +80,17 @@ function SemuaPerbekel() {
</Box>
<Stack gap={4} align="center">
<Text fw={700} fz="lg" ta="center">
{v.nama}
</Text>
<Title order={3} fz={{ base: 18, md: 20 }} ta="center" fw={700}>
{v.nama}
</Title>
<Text c="dimmed" fz="sm" ta="center">
{v.daerah}
</Text>
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
{v.daerah}
</Text>
<Text c="blue" fw={600} fz="sm" ta="center">
{v.periode}
</Text>
<Text c="blue" fw={600} fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
{v.periode}
</Text>
</Stack>
</Stack>
</Paper>

View File

@@ -2,7 +2,7 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
@@ -34,60 +34,57 @@ function VisiMisiDesa() {
loading="lazy"
/>
{/* VISI */}
<Paper
p="xl"
radius="lg"
shadow="md"
withBorder
w="100%"
style={{
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
}}
style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
>
<Text
<Title
order={1}
c={colors['blue-button']}
ta="center"
fw={700}
fz={{ base: '2rem', md: '2.5rem' }}
mb="md"
>
Visi Desa
</Text>
</Title>
<Text
fz={{ base: '1.125rem', md: '1.375rem' }}
fz={{ base: 'sm', md: 'md' }} // body text responsive
lh={1.7}
ta="center"
fw={500}
lh={1.6}
dangerouslySetInnerHTML={{ __html: data.visi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Paper>
{/* MISI */}
<Paper
p="xl"
radius="lg"
shadow="md"
withBorder
w="100%"
style={{
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
}}
style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
>
<Text
<Title
order={1}
c={colors['blue-button']}
ta="center"
fw={700}
fz={{ base: '2rem', md: '2.5rem' }}
mb="md"
>
Misi Desa
</Text>
</Title>
<Text
fz={{ base: '1.125rem', md: '1.375rem' }}
fw={500}
lh={1.6}
fz={{ base: 'sm', md: 'md' }} // body text responsive
lh={1.7}
ta="left"
dangerouslySetInnerHTML={{ __html: data.misi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Paper>
</Stack>

View File

@@ -1,7 +1,7 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
import { Box, Flex, Group, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
@@ -30,196 +30,265 @@ function Page() {
// Hasil akhir
const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan;
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(value);
};
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
{/* Page Title */}
<Title
ta="center"
c={colors["blue-button"]}
fw="bold"
order={1}
fz={{ base: 28, md: 36 }}
>
Pendapatan Asli Desa
</Text>
</Title>
<Box px={{ base: "md", md: 100 }}>
<Stack gap="lg" justify="center">
<Paper bg={colors['white-1']} p="xl">
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }}>
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
{/* Pendapatan Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
<Stack gap={"xs"}>
<Title order={3}>Pendapatan</Title>
{latestApb?.pendapatan?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
p="md"
style={{
border: '1px solid #e9ecef',
borderRadius: '8px',
height: '100%'
}}
>
<Stack gap="md">
<Title order={3} fz={{ base: 18, md: 20 }} c={colors['blue-button']}>
Pendapatan
</Title>
<Stack gap="sm">
{latestApb?.pendapatan?.map((item) => (
<Box key={item.id}>
<Flex gap={1}>
<Text
fz="md"
fz={{ base: 13, md: 14 }}
fw={500}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textAlign: 'right',
}}
lh={1.4}
c="black"
style={{ wordBreak: 'break-word' }}
>
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(item.value)}
{item.name} {formatCurrency(item.value)}
</Text>
</GridCol>
</Grid>
</Box>
))}
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text style={{
wordBreak: 'break-word',
whiteSpace: 'normal'
}} fz="xl" fw={700} c={colors['blue-button']}>
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(totalPendapatan)}
</Flex>
</Box>
))}
</Stack>
<Box
pt="sm"
mt="auto"
style={{
borderTop: `2px solid ${colors['blue-button']}`
}}
>
<Flex direction="column" gap={4}>
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
Total Pendapatan
</Text>
</GridCol>
</Grid>
<Text
fz={{ base: 18, md: 22 }}
fw={700}
c={colors['blue-button']}
lh={1.4}
>
{formatCurrency(totalPendapatan)}
</Text>
</Flex>
</Box>
</Stack>
</Box>
{/* Belanja Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
<Stack gap={"xs"}>
<Title order={3}>Belanja</Title>
{latestApb?.belanja?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text
fz="md"
<Box
p="md"
style={{
border: '1px solid #e9ecef',
borderRadius: '8px',
height: '100%'
}}
>
<Stack gap="md">
<Title order={3} fz={{ base: 18, md: 20 }} c="orange">
Belanja
</Title>
<Stack gap="sm">
{latestApb?.belanja?.map((item) => (
<Box key={item.id}>
<Group gap={1}>
<Text
fz={{ base: 13, md: 14 }}
fw={500}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textAlign: 'right',
}}
lh={1.4}
c="black"
style={{ wordBreak: 'break-word' }}
>
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(item.value)}
{item.name} {formatCurrency(item.value)}
</Text>
</GridCol>
</Grid>
</Box>
))}
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="xl" fw={700} c="orange">
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(totalBelanja)}
</Group>
</Box>
))}
</Stack>
<Box
pt="sm"
mt="auto"
style={{
borderTop: '2px solid orange'
}}
>
<Flex direction="column" gap={4}>
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
Total Belanja
</Text>
</GridCol>
</Grid>
<Text
fz={{ base: 18, md: 22 }}
fw={700}
c="orange"
lh={1.4}
>
{formatCurrency(totalBelanja)}
</Text>
</Flex>
</Box>
</Stack>
</Box>
{/* Pembiayaan Card */}
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
<Stack gap={"xs"}>
<Title order={3}>Pembiayaan</Title>
{latestApb?.pembiayaan?.map((item) => (
<Box key={item.id}>
<Grid>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text fz="md" fw={500}>{item.name}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Text
fz="md"
<Box
p="md"
style={{
border: '1px solid #e9ecef',
borderRadius: '8px',
height: '100%'
}}
>
<Stack gap="md">
<Title order={3} fz={{ base: 18, md: 20 }} c="green">
Pembiayaan
</Title>
<Stack gap="sm">
{latestApb?.pembiayaan?.map((item) => (
<Box key={item.id}>
<Group gap={1}>
<Text
fz={{ base: 13, md: 14 }}
fw={500}
style={{
wordBreak: 'break-word',
whiteSpace: 'normal',
textAlign: 'right',
}}
lh={1.4}
c="black"
style={{ wordBreak: 'break-word' }}
>
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(item.value)}
{item.name} {formatCurrency(item.value)}
</Text>
</GridCol>
</Grid>
</Box>
))}
<Grid>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
</GridCol>
<GridCol span={{ base: 12, md: 6 }}>
<Text fz="xl" fw={700} c="green">
{new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(totalPembiayaan)}
</Group>
</Box>
))}
</Stack>
<Box
pt="sm"
mt="auto"
style={{
borderTop: '2px solid green'
}}
>
<Flex direction="column" gap={4}>
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
Total Pembiayaan
</Text>
</GridCol>
</Grid>
<Text
fz={{ base: 18, md: 22 }}
fw={700}
c="green"
lh={1.4}
>
{formatCurrency(totalPembiayaan)}
</Text>
</Flex>
</Box>
</Stack>
</Box>
</SimpleGrid>
</Paper>
{/* 🔽 Tambahan Ringkasan Anggaran */}
<Paper bg={colors['white-1']} p="xl" shadow="sm" withBorder>
<Title order={3} mb="md">Ringkasan Anggaran</Title>
{/* Ringkasan Anggaran */}
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="sm" withBorder>
<Title order={3} mb="md" fz={{ base: 18, md: 20 }}>
Ringkasan Anggaran
</Title>
<Table striped highlightOnHover withTableBorder>
<Table.Thead>
<Table.Tr>
<Table.Th>Keterangan</Table.Th>
<Table.Th ta={"right"}>Jumlah</Table.Th>
<Table.Th>
<Text fz={{ base: 13, md: 14 }} fw={600}>Keterangan</Text>
</Table.Th>
<Table.Th ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600}>Jumlah</Text>
</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
<Table.Td>Total Pendapatan</Table.Td>
<Table.Td align="right">
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPendapatan)}
<Table.Td>
<Text fz={{ base: 13, md: 14 }} lh={1.4}>Total Pendapatan</Text>
</Table.Td>
<Table.Td ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4}>
{formatCurrency(totalPendapatan)}
</Text>
</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Td>Total Belanja</Table.Td>
<Table.Td align="right" c="orange">
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalBelanja)}
<Table.Td>
<Text fz={{ base: 13, md: 14 }} lh={1.4} c="orange">Total Belanja</Text>
</Table.Td>
<Table.Td ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="orange">
{formatCurrency(totalBelanja)}
</Text>
</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Td>Total Pembiayaan</Table.Td>
<Table.Td align="right" c="green">
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPembiayaan)}
<Table.Td>
<Text fz={{ base: 13, md: 14 }} lh={1.4} c="green">Total Pembiayaan</Text>
</Table.Td>
<Table.Td ta="right">
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="green">
{formatCurrency(totalPembiayaan)}
</Text>
</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Td><b>Sisa Anggaran</b></Table.Td>
<Table.Td align="right" c={sisaAnggaran >= 0 ? "blue" : "red"}>
<b>
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(sisaAnggaran)}
</b>
<Table.Tr style={{ backgroundColor: '#f8f9fa' }}>
<Table.Td>
<Text fz={{ base: 14, md: 15 }} fw={700} lh={1.4}>Sisa Anggaran</Text>
</Table.Td>
<Table.Td ta="right">
<Text
fz={{ base: 14, md: 15 }}
fw={700}
c={sisaAnggaran >= 0 ? colors['blue-button'] : "red"}
lh={1.4}
>
{formatCurrency(sisaAnggaran)}
</Text>
</Table.Td>
</Table.Tr>
</Table.Tbody>

View File

@@ -76,13 +76,13 @@ function Page() {
</Box>
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Flex gap={{base: 7, md: 5}} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Laki-Laki</Text>
<ColorSwatch color="#5082EE" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Flex gap={{base: 7, md: 5}} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Perempuan</Text>
<ColorSwatch color="#6EDF9C" size={30} />
</Flex>

View File

@@ -1,7 +1,7 @@
'use client'
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
import colors from '@/con/colors';
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react';
import { motion } from 'motion/react';
@@ -14,7 +14,7 @@ function Page() {
const router = useRouter()
const state = useProxy(pasarDesaState.pasarDesa)
const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const {
data,
@@ -28,7 +28,6 @@ function Page() {
pasarDesaState.kategoriProduk.findManyAll.load()
}, [])
// Filter data based on selected category
const filteredData = selectedCategory
? data?.filter(item =>
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
@@ -39,7 +38,6 @@ function Page() {
load(page, 4, debouncedSearch, selectedCategory || undefined)
}, [page, debouncedSearch, selectedCategory])
if (loading || !data) {
return (
<Stack py={10}>
@@ -49,44 +47,38 @@ function Page() {
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Title order={1} c={colors["blue-button"]} fw="bold">
Pasar Desa
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Produk'
radius="lg"
placeholder="Cari Produk"
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
w={"100%"}
/>
</GridCol>
</Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan
</Text>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
dan memperkenalkan produk mereka.
<Text px={{ base: 'md', md: 100 }} pt={20} ta="justify" fz={{ base: 'sm', md: 'md' }}>
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan dan memperkenalkan produk mereka.
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<SimpleGrid
pb={30}
cols={{
base: 1,
md: 2
}}
>
<Stack gap="lg">
<SimpleGrid pb={30} cols={{ base: 1, md: 2 }}>
<Box>
<Select
placeholder="Pilih Kategori"
@@ -103,50 +95,58 @@ function Page() {
/>
</Box>
</SimpleGrid>
<SimpleGrid cols={{ base: 1, md: 4 }}>
{filteredData?.map((v, k) => {
return (
<Stack key={k}>
<motion.div
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.8 }}
>
<Paper p={'lg'}>
<Image
radius={'lg'}
src={v.image?.link || '/placeholder-product.jpg'}
alt={v.nama}
h={200}
w='100%'
style={{ objectFit: 'cover' }}
loading="lazy"
/>
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text>
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text>
<Flex py={10} gap={'md'}>
<IconStarFilled size={20} color='#EBCB09' />
<Text fz={'sm'} ml={2}>{v.rating}</Text>
</Flex>
<Flex justify={'space-between'} align={'center'}>
<Box>
<Flex gap={'md'} align={'center'}>
<IconMapPinFilled size={20} color='red' />
<Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text>
</Flex>
</Box>
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
</Flex>
</Paper>
</motion.div>
</Stack>
)
})}
{filteredData?.map((v, k) => (
<Stack key={k}>
<motion.div
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.8 }}
>
<Paper p="lg">
<Image
radius="lg"
src={v.image?.link || '/placeholder-product.jpg'}
alt={v.nama}
h={200}
w="100%"
style={{ objectFit: 'cover' }}
loading="lazy"
/>
<Text py="sm" fw="bold" fz={{ base: 'md', md: 'lg' }}>
{v.nama}
</Text>
<Text fz={{ base: 'sm', md: 'md' }}>
Rp {v.harga.toLocaleString('id-ID')}
</Text>
<Flex py="sm" gap="md">
<IconStarFilled size={20} color="#EBCB09" />
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
{v.rating}
</Text>
</Flex>
<Flex justify="space-between" align="center">
<Box>
<Flex gap="md" align="center">
<IconMapPinFilled size={20} color="red" />
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
{v.alamatUsaha}
</Text>
</Flex>
</Box>
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
</Flex>
</Paper>
</motion.div>
</Stack>
))}
</SimpleGrid>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
@@ -157,4 +157,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { useState } from 'react';
@@ -32,10 +32,9 @@ interface ProgramKemiskinanData {
function Page() {
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000);
const state = useProxy(programKemiskinanState)
// 🔧 Get valid statistics data with proper type checking
const statistikData = state.findMany.data
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
return !!item?.statistik &&
@@ -43,11 +42,11 @@ function Page() {
item.statistik.jumlah !== undefined;
})
.map(item => ({
tahun: Number(item.statistik.tahun) || 0, // Ensure tahun is a number
jumlah: Number(item.statistik.jumlah) || 0, // Ensure jumlah is a number
tahun: Number(item.statistik.tahun) || 0,
jumlah: Number(item.statistik.jumlah) || 0,
}))
.sort((a, b) => a.tahun - b.tahun)
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah)); // Remove any invalid entries
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah));
const {
data,
@@ -74,12 +73,18 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Box px={{ base: 'md', md: 100 }}>
<Grid align='center'>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Title
order={1}
c={colors["blue-button"]}
fw={"bold"}
fz={{ base: '28px', md: '32px' }}
lh={{ base: '1.2', md: '1.25' }}
>
Program Kemiskinan
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
@@ -92,7 +97,15 @@ function Page() {
/>
</GridCol>
</Grid>
<Text fz={'h4'}>Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat</Text>
<Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
c="black"
ta={{ base: 'left', md: 'left' }}
pt={20}
>
Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
@@ -106,8 +119,22 @@ function Page() {
{state.findMany.data.map(v => {
return (
<Paper p={'xl'} key={v.id}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
<Text fz={'lg'} c={'black'} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
<Title
order={3}
fw={'bold'}
c={colors['blue-button']}
fz={{ base: '18px', md: '20px' }}
lh={{ base: '1.3', md: '1.35' }}
>
{v.nama}
</Title>
<Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
c={'black'}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Paper>
)
})}
@@ -124,7 +151,16 @@ function Page() {
/>
</Center>
<Paper p={'xl'}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text>
<Title
order={3}
fw={'bold'}
c={colors['blue-button']}
fz={{ base: '18px', md: '20px' }}
lh={{ base: '1.3', md: '1.35' }}
mb="md"
>
Statistik Kemiskinan Masyarakat
</Title>
<Box style={{ width: '100%', height: 'auto' }}>
{statistikData.length > 0 ? (
<Box w="100%" style={{ overflowX: 'auto' }}>
@@ -162,7 +198,11 @@ function Page() {
</Box>
) : (
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
<Text c="dimmed">
<Text
fz={{ base: '12px', md: '14px' }}
c="dimmed"
lh={{ base: '1.4', md: '1.5' }}
>
{state.findMany.loading
? 'Memuat data statistik...'
: 'Belum ada data statistik yang tersedia atau data tidak valid'}
@@ -177,4 +217,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -53,6 +53,7 @@ function Page() {
Ton: item.value,
}));
const chartWidth = Math.max(600, chartData.length * 150); // contoh: 150px per bar
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
@@ -78,7 +79,7 @@ function Page() {
<Box style={{ width: '100%', overflowX: 'auto' }}>
<Paper p="xl">
<Text pb={10} fw="bold" fz="h4">Statistik Sektor Unggulan Darmasaba</Text>
<Box style={{ width: '100%', minWidth: '600px' }}>
<Box style={{ width: '100%', overflowX: 'auto', maxWidth: `${chartWidth}px` }}>
<BarChart
p={10}
h={300}
@@ -90,11 +91,14 @@ function Page() {
tickLine="y"
tooltipAnimationDuration={200}
withTooltip
style={{
fontFamily: 'inherit',
}}
withXAxis
withYAxis
xAxisLabel="Sektor"
yAxisLabel="Ton"
style={{
fontFamily: 'inherit',
fontSize: '12px', // ukuran font lebih kecil di mobile
}}
/>
</Box>
</Paper>

View File

@@ -14,6 +14,9 @@ import {
Loader,
Paper,
Stack,
Tabs,
TabsList,
TabsTab,
Text,
TextInput,
Title,
@@ -33,6 +36,8 @@ import { OrganizationChart } from 'primereact/organizationchart'
import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils'
import BackButton from '../../desa/layanan/_com/BackButto'
import { useMediaQuery } from '@mantine/hooks'
import { useTransitionRouter } from 'next-view-transitions'
export default function Page() {
return (
@@ -51,11 +56,11 @@ export default function Page() {
order={1}
ta="center"
c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }}
fz={{ base: 28, md: 36 }}
>
Struktur Organisasi & SK Pengurus BumDes
</Title>
<Text ta="center" c="black" maw={800}>
<Text ta="center" c="black" maw={800} fz={{ base: 14, md: 16 }} lh={1.6}>
Gambaran visual peran dan pengurus yang ditugaskan. Gunakan kontrol
di bawah untuk mencari, memperbesar, atau melihat lebih jelas.
</Text>
@@ -70,13 +75,14 @@ export default function Page() {
}
function StrukturOrganisasiBumDes() {
const router = useTransitionRouter()
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
const chartContainerRef = useRef<HTMLDivElement>(null)
const [scale, setScale] = useState(1)
const [isFullscreen, setFullscreen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const debouncedSearch = useRef(
debounce((value: string) => setSearchQuery(value), 400)
debounce((value: string) => setSearchQuery(value), 1000)
).current
useEffect(() => {
@@ -92,8 +98,10 @@ function StrukturOrganisasiBumDes() {
<Center py={48}>
<Stack align="center" gap="sm">
<Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text>
<Text c="dimmed" size="sm">
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.4}>
Memuat struktur organisasi
</Text>
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={1.4}>
Mengambil data pengurus dan posisi. Mohon tunggu sebentar.
</Text>
</Stack>
@@ -119,10 +127,10 @@ function StrukturOrganisasiBumDes() {
<Center>
<IconUsers size={56} />
</Center>
<Title order={3} mt="md">
<Title order={3} mt="md" ta="center">
Data pengurus belum tersedia
</Title>
<Text c="dimmed" mt="xs">
<Text c="dimmed" mt="xs" fz={{ base: 12, md: 14 }} lh={1.4}>
Belum ada data pengurus yang tercatat untuk BumDes.
</Text>
<Group justify="center" mt="lg">
@@ -218,155 +226,299 @@ function StrukturOrganisasiBumDes() {
return (
<Stack align="center" mt="xl">
{/* 🧭 Kontrol atas */}
<Paper shadow="xs" p="md" radius="md" bg={colors['blue-button']}>
<Group gap="sm" wrap="wrap" justify="center">
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
<Paper
shadow="xs"
w={{
base: '100%', // Mobile: 100%
sm: '40%', // Tablet: 95%
md: '39%', // Desktop: 70%
lg: '38%', // Desktop L: 60%
xl: '37%', // 4K: 50%
'2xl': '36%', // Ultra-wide: 45%
}}
p="md"
radius="md"
style={{
background: colors['blue-button'], // ⬅️ penting
maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow
}}
>
<Stack gap="sm">
<Group justify='center'>
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
styles={{
input: {
minWidth: 250,
},
}}
/>
</Group>
<Tabs
defaultValue="zoom-out"
variant="outline"
radius="md"
styles={{
input: {
minWidth: 250,
panel: { display: 'none' },
tab: {
color: colors['blue-button'],
backgroundColor: colors['blue-button-2'],
border: 'none',
fontWeight: 600,
fontSize: '0.875rem',
padding: '6px 12px',
minHeight: 'auto',
flexShrink: 0,
},
}}
/>
<Group gap="xs">
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
>
Zoom Out
</Button>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={16}
py={8}
style={{ width: '100%' }} // 👈 penting
>
<TabsList
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '8px',
minWidth: 70,
textAlign: 'center',
display: 'flex',
overflowX: 'auto',
overflowY: 'hidden',
gap: '4px',
paddingBottom: '4px',
flexWrap: 'nowrap',
WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'thin',
msOverflowStyle: '-ms-autohiding-scrollbar',
maxWidth: '100%',
scrollBehavior: 'smooth', // 👈 smooth scroll
}}
>
{Math.round(scale * 100)}%
</Box>
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
>
Zoom In
</Button>
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={resetZoom}
>
Reset
</Button>
<Button
variant="light"
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
>
Fullscreen
</Button>
</Group>
</Group>
<TabsTab
value="zoom-out"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
</TabsTab>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={12}
py={6}
style={{
fontWeight: 700,
borderRadius: '6px',
minWidth: 60,
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
whiteSpace: 'nowrap',
}}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
{Math.round(scale * 100)}%
</Text>
</Box>
<TabsTab
value="zoom-in"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
</TabsTab>
<TabsTab
value="reset"
onClick={resetZoom}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
</TabsTab>
<TabsTab
value="fullscreen"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
style={{ flexShrink: 0 }}
>
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
{isFullscreen ? 'Exit' : 'Fullscreen'}
</Text>
</TabsTab>
</TabsList>
</Tabs>
</Stack>
</Paper>
{/* 📊 Chart Container */}
<Center style={{ width: '100%' }}>
<Box
ref={chartContainerRef}
style={{
overflowX: 'auto',
overflowY: 'auto',
width: '100%',
padding: '32px 16px',
transition: 'transform 0.2s ease',
transform: `scale(${scale})`,
transformOrigin: 'center top',
}}
>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
</Box>
</Center>
</Stack>
)
}
function NodeCard({ node }: any) {
const imageSrc = node?.data?.image || '/img/default.png'
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const description = node?.data?.description || ''
return (
<Transition mounted transition="pop" duration={300}>
{(styles) => (
<Card
shadow="md"
radius="xl"
withBorder
style={{
...styles,
width: 240,
padding: 20,
background:
'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
borderColor: 'rgba(28, 110, 164, 0.3)',
transition: 'all 0.3s ease',
}}
>
<Stack align="center" gap={10}>
<Box
style={{
width: 90,
height: 90,
borderRadius: '50%',
overflow: 'hidden',
border: '3px solid rgba(28, 110, 164, 0.4)',
}}
>
<Image src={imageSrc} alt={name} fit="cover" loading="lazy" />
</Box>
<Text fw={700} size="sm" ta="center" c={colors['blue-button']}>
{name}
</Text>
<Text size="xs" c="dimmed" ta="center">
{title}
</Text>
<Text size="xs" c="dimmed" ta="center" lineClamp={3}>
{description || 'Belum ada deskripsi.'}
</Text>
{/* 🧩 Chart Container */}
<Center style={{ width: '100%' }}>
<Box
ref={chartContainerRef}
style={{
overflowX: 'auto',
overflowY: 'auto',
width: '100%',
maxWidth: '100%',
padding: '32px 16px',
transition: 'transform 0.2s ease',
}}
>
<Box style={{
transform: `scale(${scale})`,
transformOrigin: 'center top',
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
}}>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
</Box>
</Box>
</Center>
</Stack>
</Card>
)}
</Transition>
)
}
)
}
function NodeCard({ node, router }: any) {
const imageSrc = node?.data?.image || '/img/default.png'
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const hasId = Boolean(node?.data?.id)
const isMobile = useMediaQuery("(max-width: 768px)");
return (
<Transition mounted transition="pop" duration={300}>
{(styles) => (
<Card
shadow="md"
radius="xl"
withBorder
style={{
...styles,
width: '100%',
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
minHeight: isMobile ? 240 : 280,
padding: isMobile ? 16 : 20,
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
borderColor: 'rgba(28, 110, 164, 0.3)',
borderWidth: 2,
transition: 'all 0.3s ease',
cursor: hasId ? 'pointer' : 'default',
}}
onMouseEnter={(e) => {
if (hasId) {
e.currentTarget.style.transform = 'translateY(-4px)'
e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)'
}
}}
onMouseLeave={(e) => {
if (hasId) {
e.currentTarget.style.transform = 'translateY(0)'
e.currentTarget.style.boxShadow = ''
}
}}
>
<Stack align="center" gap={12}>
{/* Photo */}
<Box
style={{
width: 96,
height: 96,
borderRadius: '50%',
overflow: 'hidden',
border: '3px solid rgba(28, 110, 164, 0.4)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
background: 'white',
}}
>
<Image
src={imageSrc}
alt={name}
width={96}
height={96}
fit="cover"
loading="lazy"
style={{
objectFit: 'cover',
}}
/>
</Box>
{/* Name */}
<Text
fw={700}
ta="center"
c={colors['blue-button']}
lineClamp={2}
fz={{ base: 13, md: 15 }}
lh={1.2}
style={{
minHeight: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
}}
>
{name}
</Text>
{/* Title/Position */}
<Text
c="dimmed"
ta="center"
fw={500}
lineClamp={2}
fz={{ base: 12, md: 13 }}
lh={1.3}
style={{
minHeight: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
}}
>
{title}
</Text>
{/* Detail Button */}
{hasId && (
<Button
variant="gradient"
gradient={{ from: 'blue', to: 'cyan' }}
size="xs"
fullWidth
mt={8}
radius="md"
onClick={() =>
router.push(`/darmasaba/ppid/struktur-ppid/${node.data.id}`)
}
style={{
height: 32,
fontWeight: 600,
}}
>
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
</Button>
)}
</Stack>
</Card>
)}
</Transition>
)
}

View File

@@ -1,20 +1,18 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react';
import { IconBulbFilled } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
function Page() {
const [opened, { open, close }] = useDisclosure(false);
const ideInovatif = useProxy(ajukanIdeInovatifState)
const ideInovatif = useProxy(ajukanIdeInovatifState);
const resetForm = () => {
// Reset state di valtio
ideInovatif.create.form = {
name: "",
deskripsi: "",
@@ -23,53 +21,66 @@ function Page() {
masalah: "",
benefit: "",
};
// Reset state lokal
};
const handleSubmit = async () => {
// Submit data berita
await ideInovatif.create.create();
// Reset form setelah submit
resetForm();
close();
};
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
style={{ fontSize: 'clamp(1.75rem, 4vw, 2.25rem)' }}
>
Ajukan Ide Inovatif
</Title>
<Text ta="center" fz={{ base: 'sm', md: 'md' }} c="black" lh="1.6">
Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform &quot;Ajukan Ide Inovatif&quot; hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.
</Text>
<Text ta={'center'} fz={'h4'}>Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform &quot;Ajukan Ide Inovatif&quot; hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} p={'lg'}>
<SimpleGrid
cols={{
base: 1,
md: 2,
}}
>
<Paper p={'xl'} >
<Stack gap={"xs"}>
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Tujuan Ide Inovatif Ini</Text>
<List>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mendorong partisipasi aktif masyarakat</ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memfasilitasi inovasi berbasis lokal</ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memecahkan tantangan komunal</ListItem>
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mengembangkan potensi kreativitas warga</ListItem>
</List>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg" p="lg">
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Paper p="xl">
<Stack gap="xs">
<Title order={2} c={colors['blue-button']} fw="bold">
Tujuan Ide Inovatif Ini
</Title>
<List>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Mendorong partisipasi aktif masyarakat
</ListItem>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Memfasilitasi inovasi berbasis lokal
</ListItem>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Memecahkan tantangan komunal
</ListItem>
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
Mengembangkan potensi kreativitas warga
</ListItem>
</List>
</Stack>
</Paper>
<Paper p={'xl'} >
<Flex align={'center'} justify={'space-between'}>
<Paper p="xl">
<Flex align="center" justify="space-between" direction={{ base: 'column', md: 'row' }} gap="md">
<Box>
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar Di Samping</Text>
<IconArrowRight size={30} color={colors['blue-button']} />
<Title order={3} c={colors['blue-button']} fw="bold" ta={{ base: 'center', md: 'start' }}>
Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar
</Title>
</Box>
<Box px={{ base: 5, md: 10 }} py={5}>
<ActionIcon variant="transparent" size={150} onClick={open}>
@@ -88,32 +99,46 @@ function Page() {
radius={0}
transitionProps={{ transition: 'fade', duration: 200 }}
>
<Paper p={"md"} withBorder>
<Stack gap={"xs"}>
<Paper p="md" withBorder>
<Stack gap="xs">
<Title order={3}>Ajukan Ide Inovatif</Title>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
label={
<Text fz="sm" fw="bold">
Nama
</Text>
}
placeholder="masukkan nama"
onChange={(val) => {
ideInovatif.create.form.name = val.target.value
ideInovatif.create.form.name = val.target.value;
}}
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
label={
<Text fz="sm" fw="bold">
Alamat
</Text>
}
placeholder="masukkan alamat"
onChange={(val) => {
ideInovatif.create.form.alamat = val.target.value
ideInovatif.create.form.alamat = val.target.value;
}}
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama Ide</Text>}
label={
<Text fz="sm" fw="bold">
Nama Ide
</Text>
}
placeholder="masukkan nama ide"
onChange={(val) => {
ideInovatif.create.form.namaIde = val.target.value
ideInovatif.create.form.namaIde = val.target.value;
}}
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
<Text fz="sm" fw="bold">
Deskripsi
</Text>
<CreateEditor
value={ideInovatif.create.form.deskripsi}
onChange={(htmlContent) => {
@@ -122,26 +147,35 @@ function Page() {
/>
</Box>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Masalah</Text>}
label={
<Text fz="sm" fw="bold">
Masalah
</Text>
}
placeholder="masukkan masalah"
onChange={(val) => {
ideInovatif.create.form.masalah = val.target.value
ideInovatif.create.form.masalah = val.target.value;
}}
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Benefit</Text>}
label={
<Text fz="sm" fw="bold">
Benefit
</Text>
}
placeholder="masukkan benefit"
onChange={(val) => {
ideInovatif.create.form.benefit = val.target.value
ideInovatif.create.form.benefit = val.target.value;
}}
/>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan
</Button>
</Stack>
</Paper>
</Modal>
</Stack>
);
}
export default Page;
export default Page;

View File

@@ -58,7 +58,7 @@ function Page() {
/>
</GridCol>
</Grid>
<Text fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text>
<Text pt={20} fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text>
<Text fz={'md'}>dan peningkatan ekonomi berbasis teknologi.</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>

View File

@@ -1,17 +1,17 @@
'use client'
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
import { IconSearch } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const [search, setSearch] = useState("")
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000);
const state = useProxy(infoTeknoState)
const {
data,
@@ -34,17 +34,24 @@ function Page() {
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }} >
<Box px={{ base: 'md', md: 100 }}>
<Grid align='center'>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Title
order={1}
c={colors["blue-button"]}
fw={"bold"}
ta={{ base: 'center', md: 'left' }}
>
Info Teknologi Tepat Guna
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
@@ -53,13 +60,19 @@ function Page() {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
w={{ base: "100%", md: "100%" }}
/>
</GridCol>
</Grid>
<Text fz={'md'}>Desa Darmasaba berkomitmen mengembangkan teknologi tepat guna yang sesuai dengan kebutuhan masyarakat,</Text>
<Text fz={'md'}>mendukung pembangunan berkelanjutan, dan meningkatkan kualitas hidup warga.</Text>
<Text pt={20} fz={{ base: 'sm', md: 'md' }} ta={{ base: 'center', md: 'left' }} lh={1.5}>
Desa Darmasaba berkomitmen mengembangkan teknologi tepat guna yang sesuai dengan kebutuhan masyarakat,
</Text>
<Text fz={{ base: 'sm', md: 'md' }} ta={{ base: 'center', md: 'left' }} lh={1.5}>
mendukung pembangunan berkelanjutan, dan meningkatkan kualitas hidup warga.
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} p={'lg'}>
<SimpleGrid
@@ -74,12 +87,14 @@ function Page() {
<Paper p={'xl'} key={k}>
<Stack gap={"xs"}>
<Image src={v.image.link || ''} pb={10} radius={10} alt='' loading="lazy" />
<Text fz={'h3'} fw={'bold'}>{v.name}</Text>
<Title order={3} fw={'bold'} ta="left">
{v.name}
</Title>
<Box pr={'lg'} pb={10}>
<Text
size="md"
fz={{ base: 'xs', md: 'sm' }}
ta="justify"
lh={1} // line height biar enak dibaca
lh={1.5}
style={{
wordBreak: "break-word",
whiteSpace: "normal",
@@ -94,10 +109,11 @@ function Page() {
</SimpleGrid>
</Stack>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
@@ -106,4 +122,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -58,7 +58,7 @@ function Page() {
/>
</GridCol>
</Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" mt={4} >
<Text px={{ base: 'md', md: 100 }} pt={20} ta={"justify"} fz="md" mt={4} >
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
</Text>
</Box>

View File

@@ -71,14 +71,14 @@ function Page() {
<Stack gap="md">
<Box>
<Text fw={600} fz="lg" >Judul</Text>
<Text fz="sm" c="dimmed">{data.judul || '-'}</Text>
<Text fz="sm" c="black">{data.judul || '-'}</Text>
</Box>
<Divider />
<Box>
<Text fw={600} fz="lg" >Tanggal</Text>
<Text fz="sm" c="dimmed">
<Text fz="sm" c="black">
{data.tanggalWaktu
? new Date(data.tanggalWaktu).toLocaleString('id-ID', { dateStyle: 'full', timeStyle: 'short' })
: '-'}
@@ -89,7 +89,7 @@ function Page() {
<Box>
<Text fw={600} fz="lg" >Lokasi</Text>
<Text fz="sm" c="dimmed">{data.lokasi || '-'}</Text>
<Text fz="sm" c="black">{data.lokasi || '-'}</Text>
</Box>
<Divider />
@@ -120,7 +120,7 @@ function Page() {
<Box>
<Text fw={600} fz="lg" >Kronologi</Text>
<Text fz="sm" c="dimmed" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
<Text fz="sm" c="black" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
</Box>
<Divider />
@@ -136,11 +136,11 @@ function Page() {
radius="md"
shadow="xs"
withBorder
bg="dark.5"
bg={colors['blue-button-1']}
>
<Text
fz="sm"
c="dimmed"
c="black"
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
/>

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
@@ -8,11 +8,10 @@ import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import { IconSearch } from '@tabler/icons-react';
function Page() {
const state = useProxy(tipsKeamananState)
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const state = useProxy(tipsKeamananState);
const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000);
const {
data,
page,
@@ -22,84 +21,114 @@ function Page() {
} = state.findMany;
useShallowEffect(() => {
load(page, 3, debouncedSearch)
}, [page, debouncedSearch])
load(page, 3, debouncedSearch);
}, [page, debouncedSearch]);
if (loading || !data) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Skeleton h={500} />
</Stack>
)
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
<Title
order={1}
c={colors['blue-button']}
style={{ lineHeight: '1.2' }}
>
Tips Keamanan
</Text>
</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Tips'
radius="lg"
placeholder="Cari Tips"
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
w={'100%'}
/>
</GridCol>
</Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
<Text
px={{ base: 'md', md: 100 }}
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
mt="sm"
>
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal).
</Text>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
<Text
px={{ base: 'md', md: 100 }}
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
mt="xs"
>
Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
<SimpleGrid
pb={10}
cols={{
base: 1,
md: 3,
}}>
{data.map((v, k) => {
return (
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center p={10}>
<Image src={v.image?.link} radius={10} loading="lazy"
alt='' />
</Center>
<Box px={'xl'}>
<Box pb={20}>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.judul}
</Text>
<Box>
<Text pb={10} fz={"md"} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Box>
pb="10"
cols={{ base: 1, md: 3 }}
>
{data.map((v, k) => (
<Paper radius={10} key={k} bg={colors['white-trans-1']}>
<Stack gap="xs">
<Center p="10">
<Image
src={v.image?.link}
radius={10}
loading="lazy"
alt=""
/>
</Center>
<Box px="xl">
<Box pb="20">
<Title
order={3}
c={colors['blue-button']}
style={{ lineHeight: '1.3' }}
>
{v.judul}
</Title>
<Box>
<Text
pb="10"
fz={{ base: 'xs', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Box>
</Box>
</Stack>
</Paper>
)
})}
</Box>
</Stack>
</Paper>
))}
</SimpleGrid>
</Stack>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
@@ -108,4 +137,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,7 +2,7 @@
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Divider, Flex, 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, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
@@ -37,9 +37,9 @@ function Page() {
<Stack gap="lg">
<Paper radius="xl" shadow="md" withBorder>
<Box style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} bg={colors['blue-button']}>
<Text p="md" fz={{ base: 'h3', md: 'h2' }} c={colors['white-1']} fw="bold">
<Title order={1} p="md" c={colors['white-1']} fw="bold">
{state.findUnique.data.title || 'Detail Artikel Kesehatan'}
</Text>
</Title>
</Box>
<Box p="lg">
@@ -64,7 +64,7 @@ function Page() {
<Stack gap="lg">
<Group gap="xs">
<IconCalendar size={18} color={colors['blue-button']} />
<Text c="dimmed" fz="sm">
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.5}>
{new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
@@ -74,48 +74,47 @@ function Page() {
</Group>
<Stack gap="lg">
<Box>
<Text fz="h4" fw="bold">Pendahuluan</Text>
<Title order={2} fw="bold">Pendahuluan</Title>
<Divider my="xs" />
<Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
</Box>
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text>
<Title order={2} fw="bold">{state.findUnique.data.symptom?.title}</Title>
<Divider my="xs" />
<Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
</Box>
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.prevention?.title}</Text>
<Title order={2} fw="bold">{state.findUnique.data.prevention?.title}</Title>
<Divider my="xs" />
<Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
</Box>
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.firstaid?.title}</Text>
<Title order={2} fw="bold">{state.findUnique.data.firstaid?.title}</Title>
<Divider my="xs" />
<Box pl={20}>
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
</Box>
</Box>
<Box>
<Text fz="h4" fw="bold">{state.findUnique.data.mythvsfact?.title}</Text>
<Title order={2} fw="bold">{state.findUnique.data.mythvsfact?.title}</Title>
<Divider my="xs" />
<Box pb="md">
<Table highlightOnHover withTableBorder withColumnBorders striped>
<TableThead>
<TableTr>
<TableTh fz="sm" fw="bold">Mitos</TableTh>
<TableTh fz="sm" fw="bold">Fakta</TableTh>
<TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Mitos</TableTh>
<TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Fakta</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -123,12 +122,12 @@ function Page() {
<TableTr>
<TableTd>
<Box pl={20}>
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
</Box>
</TableTd>
<TableTd>
<Box pl={20}>
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
</Box>
</TableTd>
</TableTr>
@@ -143,34 +142,35 @@ function Page() {
</Box>
<Box>
<Text fz="h4" fw="bold">Kapan Harus ke Dokter?</Text>
<Title order={2} fw="bold">Kapan Harus ke Dokter?</Title>
<Divider my="xs" />
<Flex justify={'flex-start'} gap={"xs"} align={"center"} mb="xs">
<IconAlertCircle size={18} color="red" />
<Text fz="md">Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
</Flex>
<Box pl={20}>
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} /></Box>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} />
</Box>
</Box>
<Box>
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
<Group gap="xs" mb="sm">
<IconInfoCircle size={20} color={colors['white-1']} />
<Text fz="h4" c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Text>
<Title order={3} c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Title>
</Group>
<Stack gap={4}>
<Text fz="sm" c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
<Text fz="sm" c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
<Text fz="sm" c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
</Stack>
</Paper>
</Box>
<Box>
<Text fz="h4" fw="bold">Referensi</Text>
<Title order={2} fw="bold">Referensi</Title>
<Divider my="xs" />
<List spacing="xs" size="sm" type="ordered">
<List spacing="xs" fz={{ base: 'xs', md: 'sm' }} type="ordered">
<ListItem>Kementerian Kesehatan RI. (2024). Pedoman Pencegahan dan Pengendalian DBD.</ListItem>
<ListItem>World Health Organization. (2024). Dengue Guidelines for Diagnosis, Treatment, Prevention and Control.</ListItem>
<ListItem>Dinas Kesehatan Kabupaten Badung. (2025). Laporan Surveilans DBD Triwulan I 2025.</ListItem>
@@ -186,4 +186,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
import { Box, Button, Card, Divider, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
import { Box, Button, Card, Divider, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -17,9 +17,8 @@ function ArtikelKesehatanPage() {
if (!state.findMany.data) {
return (
<Box py="xl" ta="center">
<Loader size="lg" color={colors['blue-button']} />
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
<Box py="lg">
<Skeleton h={500} radius="lg" />
</Box>
)
}
@@ -28,13 +27,13 @@ function ArtikelKesehatanPage() {
<Box>
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
<Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
<Title ta="center" order={1} c={colors['blue-button']}>
Artikel Kesehatan
</Text>
</Title>
<Divider size="sm" color={colors['blue-button']} />
{state.findMany.data.length === 0 ? (
<Box py="xl" ta="center">
<Text fz="lg" c="dimmed">
<Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
Belum ada artikel kesehatan yang tersedia
</Text>
</Box>
@@ -51,17 +50,26 @@ function ArtikelKesehatanPage() {
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
>
<Card.Section>
<Image style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }} src={item.image?.link} alt={item.title} height={200} fit="cover" loading="lazy" />
<Image
style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }}
src={item.image?.link}
alt={item.title}
height={200}
fit="cover"
loading="lazy"
/>
</Card.Section>
<Stack gap="xs" mt="md">
<Text fw="bold" fz="xl" c={colors['blue-button']}>{item.title}</Text>
<Title order={3} c={colors['blue-button']}>
{item.title}
</Title>
<Group gap="xs">
<IconCalendar size={16} color='gray' />
<Text fz="sm" c="dimmed">
<Text fz={{ base: 'xs', sm: 'sm' }} c="dimmed">
{new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} Dinas Kesehatan
</Text>
</Group>
<Text fz="md" lineClamp={3}>
<Text fz={{ base: 'sm', sm: 'md' }} lh={{ base: 'sm', sm: 'md' }} lineClamp={3}>
{item.content}
</Text>
<Group justify="flex-start">
@@ -85,4 +93,4 @@ function ArtikelKesehatanPage() {
);
}
export default ArtikelKesehatanPage;
export default ArtikelKesehatanPage;

View File

@@ -16,7 +16,6 @@ interface Kontak {
email: string;
}
interface Lokasi {
mapsEmbed: string;
}
@@ -35,7 +34,7 @@ function Page() {
state.findUnique.load(params?.id as string);
}, []);
const data = state.findUnique.data as any; // Temporary any to fix type issues
const data = state.findUnique.data as any;
const nama = data?.name || 'Fasilitas Kesehatan';
const prosedur = data?.prosedurpendaftaran.content || '';
@@ -111,11 +110,11 @@ function Page() {
<Group gap="md" wrap="wrap">
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconMapPin size={18} /></ThemeIcon>
<Text>{alamat}</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{alamat}</Text>
</Group>
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconDeviceLandlinePhone size={18} /></ThemeIcon>
<Text>{kontak.telepon}</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.telepon}</Text>
<CopyButton value={kontak.telepon}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin nomor'}>
@@ -126,7 +125,7 @@ function Page() {
</Group>
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconBrandWhatsapp size={18} /></ThemeIcon>
<Text>{kontak.whatsapp}</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.whatsapp}</Text>
<CopyButton value={kontak.whatsapp}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin WhatsApp'}>
@@ -137,7 +136,7 @@ function Page() {
</Group>
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconMail size={18} /></ThemeIcon>
<Text>{kontak.email}</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.email}</Text>
<CopyButton value={kontak.email}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Disalin' : 'Salin email'}>
@@ -163,33 +162,43 @@ function Page() {
<Divider />
<Group gap="xl" align="start">
<Stack gap={2}>
<Text c="dimmed" fz="sm">Nama Fasilitas</Text>
<Text fw={600}>{nama}</Text>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>Nama Fasilitas</Text>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.5}>{nama}</Text>
</Stack>
<Stack gap={2}>
<Text c="dimmed" fz="sm">Jam Operasional</Text>
<Text fw={600}>{jam}</Text>
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>Jam Operasional</Text>
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.5}>{jam}</Text>
</Stack>
</Group>
<Divider />
<Title order={4}>Layanan Unggulan</Title>
<Divider />
{layananUnggulan ? (
<Box pl={"lg"}>
<Text fz="md" style={{ lineHeight: 1.7, wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: layananUnggulan }} />
<Box pl="lg">
<Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: layananUnggulan }}
/>
</Box>
) : (
<Paper withBorder radius="md" p="md">
<Group gap="sm">
<IconMoodEmpty />
<Text>Belum ada informasi fasilitas pendukung.</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada informasi layanan unggulan.</Text>
</Group>
</Paper>
)}
<Divider />
<Title order={4}>Peta Lokasi</Title>
<AspectRatio ratio={16 / 9}>
<iframe src={lokasi.mapsEmbed} style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }} loading="lazy" aria-label="Peta Lokasi" />
<iframe
src={lokasi.mapsEmbed}
style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }}
loading="lazy"
aria-label="Peta Lokasi"
/>
</AspectRatio>
</Stack>
</Card>
@@ -201,9 +210,15 @@ function Page() {
<Stack gap="md">
<Title order={4}>Kontak Cepat</Title>
<Group gap="sm" wrap="wrap">
<Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">Telepon</Button>
<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>
<Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">
<Text fz={{ base: 'xs', md: 'sm' }}>Telepon</Text>
</Button>
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">
<Text fz={{ base: 'xs', md: 'sm' }}>WhatsApp</Text>
</Button>
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">
<Text fz={{ base: 'xs', md: 'sm' }}>Email</Text>
</Button>
</Group>
</Stack>
</Card>
@@ -214,9 +229,15 @@ function Page() {
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Dokter">
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Spesialisasi</TableTh>
<TableTh>Jadwal</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Nama</Text>
</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Spesialisasi</Text>
</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Jadwal</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -226,11 +247,15 @@ function Page() {
<TableTd>
<Group gap="xs">
<IconUser size={16} />
<Text>{dokter.name || '-'}</Text>
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.name || '-'}</Text>
</Group>
</TableTd>
<TableTd>{dokter.specialist || '-'}</TableTd>
<TableTd>{dokter.jadwal || '-'}</TableTd>
<TableTd>
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.specialist || '-'}</Text>
</TableTd>
<TableTd>
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.jadwal || '-'}</Text>
</TableTd>
</TableTr>
))
) : (
@@ -238,7 +263,7 @@ function Page() {
<TableTd colSpan={3}>
<Group justify="center" gap="xs" c="dimmed">
<IconSearch size={18} />
<Text>Tidak ada data tenaga medis.</Text>
<Text fz={{ base: 'sm', md: 'md' }}>Tidak ada data tenaga medis.</Text>
</Group>
</TableTd>
</TableTr>
@@ -254,13 +279,18 @@ function Page() {
<Divider />
{fasilitasPendukungHtml ? (
<Box pl="lg">
<Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} />
<Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }}
/>
</Box>
) : (
<Paper withBorder radius="md" p="md">
<Group gap="sm">
<IconMoodEmpty />
<Text>Belum ada informasi fasilitas pendukung.</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada informasi fasilitas pendukung.</Text>
</Group>
</Paper>
)}
@@ -274,16 +304,24 @@ function Page() {
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
<TableThead>
<TableTr>
<TableTh>Layanan</TableTh>
<TableTh>Tarif</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Layanan</Text>
</TableTh>
<TableTh>
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Tarif</Text>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{Array.isArray(data?.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? (
data.tarifdanlayanan.map((item: any) => (
<TableTr key={item.id}>
<TableTd>{item.layanan || '-'}</TableTd>
<TableTd>{formatRupiah(item.tarif)}</TableTd>
<TableTd>
<Text fz={{ base: 'sm', md: 'md' }}>{item.layanan || '-'}</Text>
</TableTd>
<TableTd>
<Text fz={{ base: 'sm', md: 'md' }}>{formatRupiah(item.tarif)}</Text>
</TableTd>
</TableTr>
))
) : (
@@ -291,7 +329,7 @@ function Page() {
<TableTd colSpan={2}>
<Group justify="center" gap="xs" c="dimmed">
<IconSearch size={18} />
<Text>Tidak ada data tarif.</Text>
<Text fz={{ base: 'sm', md: 'md' }}>Tidak ada data tarif.</Text>
</Group>
</TableTd>
</TableTr>
@@ -301,7 +339,7 @@ function Page() {
{gratisBpjs && (
<Group gap="xs">
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text>
<Text fz={{ base: 'sm', md: 'md' }} fw={600}>Gratis dengan BPJS Kesehatan</Text>
</Group>
)}
</Stack>
@@ -317,9 +355,16 @@ function Page() {
<Title order={3}>Prosedur Pendaftaran</Title>
<Divider />
{prosedur ? (
<Box pl="lg"><Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: prosedur }} /></Box>
<Box pl="lg">
<Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.6, md: 1.7 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: prosedur }}
/>
</Box>
) : (
<Text fz="md" c="dimmed">Belum ada prosedur pendaftaran</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada prosedur pendaftaran</Text>
)}
</Stack>
</Paper>
@@ -328,4 +373,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -17,12 +17,8 @@ function FasilitasKesehatanPage() {
if (!state.findMany.data) {
return (
<Box py="xl" px="md">
<Stack gap="md">
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
<Skeleton height={80} radius="lg" />
</Stack>
<Box py="lg">
<Skeleton h={500} radius="lg" />
</Box>
);
}
@@ -31,14 +27,24 @@ function FasilitasKesehatanPage() {
<Box>
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%">
<Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
<Title
order={1}
ta="center"
fw={700}
c={colors['blue-button']}
style={{ lineHeight: '1.2' }}
>
Fasilitas Kesehatan
</Text>
</Title>
<Divider size="sm" color={colors['blue-button']} />
<Stack gap="lg">
{state.findMany.data.length === 0 ? (
<Box py="xl" ta="center">
<Text fz="lg" c="dimmed">
<Text
fz={{ base: 'sm', sm: 'md' }}
c={colors['blue-button']}
lh={{ base: '1.5', sm: '1.6' }}
>
Belum ada fasilitas kesehatan yang tersedia
</Text>
</Box>
@@ -65,22 +71,36 @@ function FasilitasKesehatanPage() {
>
<Stack gap="sm">
<Group justify="space-between" align="center">
<Text fw={700} fz="lg" c={colors['blue-button']}>
<Title
order={3}
fw={700}
c={colors['blue-button']}
fz={{ base: 'sm', sm: 'md' }}
lh={{ base: '1.3', sm: '1.3' }}
>
{item.name}
</Text>
<Badge color="blue" radius="sm" variant="light" fz="xs">
</Title>
<Badge color="blue" radius="sm" variant="light" size="xs">
Aktif
</Badge>
</Group>
<Group gap="xs">
<IconMapPin size={18} stroke={1.5} />
<Text fz="sm">
<Text
fz={{ base: 'xs', sm: 'sm' }}
lh={{ base: '1.5', sm: '1.5' }}
c="text"
>
{item.informasiumum.alamat}
</Text>
</Group>
<Group gap="xs">
<IconClock size={18} stroke={1.5} />
<Text fz="sm">
<Text
fz={{ base: 'xs', sm: 'sm' }}
lh={{ base: '1.5', sm: '1.5' }}
c="text"
>
{item.informasiumum.jamOperasional}
</Text>
</Group>
@@ -110,4 +130,4 @@ function FasilitasKesehatanPage() {
);
}
export default FasilitasKesehatanPage;
export default FasilitasKesehatanPage;

View File

@@ -11,7 +11,8 @@ import {
Paper,
Skeleton,
Stack,
Text
Text,
Title
} from '@mantine/core';
import { useDisclosure, useShallowEffect } from '@mantine/hooks';
import { IconMail, IconPhone, IconUser } from '@tabler/icons-react';
@@ -51,55 +52,97 @@ function Page() {
style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
bg={colors['blue-button']}
>
<Text p="md" fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw="bold">
<Title
p="md"
order={1}
c={colors['white-1']}
fw="bold"
ta={{ base: 'center', md: 'left' }}
>
Detail & Pendaftaran Kegiatan
</Text>
</Title>
</Box>
<Box p="lg">
<Stack gap="xl">
<Stack gap="sm">
<Text fz="lg" fw="bold">Informasi Kegiatan</Text>
<Title order={2} fw="bold">Informasi Kegiatan</Title>
<Divider />
<Text fz="md" fw="bold">Nama Kegiatan: <Text span>{state.findUnique.data.informasijadwalkegiatan.name}</Text></Text>
<Text fz="md" fw="bold">Tanggal: <Text span>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text></Text>
<Text fz="md" fw="bold">Waktu: <Text span>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text></Text>
<Text fz="md" fw="bold">Lokasi: <Text span>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text></Text>
<Text fw="bold">
Nama Kegiatan:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.name}
</Text>
</Text>
<Text fw="bold">
Tanggal:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.tanggal}
</Text>
</Text>
<Text fw="bold">
Waktu:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.waktu}
</Text>
</Text>
<Text fw="bold">
Lokasi:&nbsp;
<Text span fw="normal">
{state.findUnique.data.informasijadwalkegiatan.lokasi}
</Text>
</Text>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
<Title order={2} fw="bold">Deskripsi Kegiatan</Title>
<Divider />
<Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
<Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }}
/>
</Box>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
<Title order={2} fw="bold">Layanan yang Tersedia</Title>
<Divider />
<Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
<Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }}
/>
</Box>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
<Title order={2} fw="bold">Syarat & Ketentuan</Title>
<Divider />
<Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
<Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }}
/>
</Box>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
<Title order={2} fw="bold">Dokumen yang Perlu Dibawa</Title>
<Divider />
<Box pl={20}>
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
<Text
ta="justify"
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }}
/>
</Box>
</Stack>
<Stack gap="sm">
<Text fz="lg" fw="bold">Pendaftaran Kegiatan</Text>
<Title order={2} fw="bold">Pendaftaran Kegiatan</Title>
<Divider />
<Group>
<Button onClick={open}>Buat Pendaftaran</Button>
@@ -112,18 +155,21 @@ function Page() {
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
<Stack gap="xs">
<Text fz="lg" c={colors['white-1']} fw="bold">Informasi Kontak</Text>
<Group gap="xs">
<Title order={3} c={colors['white-1']} fw="bold">Informasi Kontak</Title>
<Group gap="xs" justify="flex-start">
<IconUser size={18} color="white" />
<Text fz="md" c={colors['white-1']}>Penanggung Jawab: <Text span fw="bold">Bidan Komang Ayu</Text></Text>
<Text c={colors['white-1']}>
Penanggung Jawab:&nbsp;
<Text span fw="bold">Bidan Komang Ayu</Text>
</Text>
</Group>
<Group gap="xs">
<IconPhone size={18} color="white" />
<Text fz="md" c={colors['white-1']}>081234567890</Text>
<Text c={colors['white-1']}>081234567890</Text>
</Group>
<Group gap="xs">
<IconMail size={18} color="white" />
<Text fz="md" c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text>
<Text c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text>
</Group>
</Stack>
</Paper>
@@ -136,4 +182,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -2,15 +2,14 @@
'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 { Button, Divider, Stack, Text, Textarea, TextInput, Title } from '@mantine/core';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function CreatePendaftaran() {
const stateCreate = useProxy(pendaftaranJadwalKegiatanState);
useEffect(() => {
useEffect(() => {
stateCreate.findMany.load();
}, []);
@@ -32,15 +31,19 @@ useEffect(() => {
return (
<Stack gap="sm">
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text>
<Title order={2} ta="left">Formulir Pendaftaran</Title>
<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}
label="Nama Balita"
placeholder="Masukkan nama balita"
size="md"
value={stateCreate.create.form.name}
onChange={(e) => stateCreate.create.form.name = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/>
<TextInput
type='date'
@@ -50,41 +53,63 @@ useEffect(() => {
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
value={stateCreate.create.form.tanggal}
onChange={(e) => stateCreate.create.form.tanggal = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/>
<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}
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}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/>
<TextInput
label="Nomor Telepon"
placeholder="Masukkan nomor telepon"
size="md"
value={stateCreate.create.form.nomor}
onChange={(e) => stateCreate.create.form.nomor = e.target.value}
label="Nomor Telepon"
placeholder="Masukkan nomor telepon"
size="md"
value={stateCreate.create.form.nomor}
onChange={(e) => stateCreate.create.form.nomor = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/>
<TextInput
label="Alamat"
placeholder="Masukkan alamat lengkap"
size="md"
value={stateCreate.create.form.alamat}
onChange={(e) => stateCreate.create.form.alamat = e.target.value}
label="Alamat"
placeholder="Masukkan alamat lengkap"
size="md"
value={stateCreate.create.form.alamat}
onChange={(e) => stateCreate.create.form.alamat = e.target.value}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/>
<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}
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}
styles={{
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
input: { fontSize: '16px', lineHeight: 1.5 },
}}
/>
<Button size="md" radius="lg" bg={colors['blue-button']} onClick={handleSubmit}>
Daftar Sekarang
<Text fz={{ base: 'sm', md: 'md' }} fw={600} c="white">
Daftar Sekarang
</Text>
</Button>
</Stack>
</Stack>
);
}
export default CreatePendaftaran;
export default CreatePendaftaran;

View File

@@ -1,7 +1,7 @@
'use client'
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconChevronRight, IconClockHour4, IconMapPin } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -27,13 +27,13 @@ function JadwalKegiatanPage() {
<Box>
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="auto" mih="100vh">
<Stack gap="lg">
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
<Title ta="center" order={1} c={colors['blue-button']} fw={700}>
Jadwal Kegiatan Warga
</Text>
</Title>
<Divider size="sm" color={colors['blue-button']} />
{state.findMany.data.length === 0 ? (
<Box py="xl" ta="center">
<Text fz="lg" c="dimmed">
<Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
Belum ada jadwal kegiatan yang tersedia
</Text>
</Box>
@@ -48,11 +48,11 @@ function JadwalKegiatanPage() {
style={{ backdropFilter: 'blur(8px)' }}
>
<Stack gap="sm">
<Group justify="space-between">
<Text fw={700} fz="xl" c={colors['blue-button']}>
<Group justify="space-between" wrap="nowrap">
<Title order={2} c={colors['blue-button']} fw={700} fz={{ base: 'md', sm: 'xl' }}>
{item.informasijadwalkegiatan.name}
</Text>
<Text fw={600} fz="sm" c={colors['blue-button']}>
</Title>
<Text fw={600} fz={{ base: 'xs', sm: 'sm' }} c={colors['blue-button']} lh="1.4">
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
@@ -63,12 +63,16 @@ function JadwalKegiatanPage() {
<Group gap="xs">
<IconClockHour4 size={18} />
<Text fz="sm">{item.informasijadwalkegiatan.waktu}</Text>
<Text fz={{ base: 'xs', sm: 'sm' }} lh="1.5">
{item.informasijadwalkegiatan.waktu}
</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text fz="sm">{item.informasijadwalkegiatan.lokasi}</Text>
<Text fz={{ base: 'xs', sm: 'sm' }} lh="1.5">
{item.informasijadwalkegiatan.lokasi}
</Text>
</Group>
<Divider my="sm" />
@@ -98,4 +102,4 @@ function JadwalKegiatanPage() {
);
}
export default JadwalKegiatanPage;
export default JadwalKegiatanPage;

View File

@@ -6,9 +6,7 @@ import { Box, Center, ColorSwatch, Flex, Paper, SimpleGrid, Skeleton, Stack, Tex
import { useEffect, useState } from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
// import { useRouter } from 'next/navigation';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import { useProxy } from 'valtio/utils';
@@ -17,7 +15,6 @@ import GrafikPenyakit from './grafik-penyakit/page';
import JadwalKegiatan from './jadwal-kegiatan-page/page';
import ArtikelKesehatanPage from './artikel-kesehatan-page/page';
function Page() {
type DataTahunan = {
tahun: string;
@@ -31,7 +28,6 @@ function Page() {
}>;
};
// Count occurrences per year
const countByYear = (data: any[], dateField: string) => {
const counts: Record<string, number> = {};
data?.forEach(item => {
@@ -43,28 +39,23 @@ function Page() {
const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<DataTahunan[]>([]);
const isTablet = useMediaQuery('(max-width: 1024px)');
const isMobile = useMediaQuery('(max-width: 768px)');
useShallowEffect(() => {
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data
statePersentase.kelahiran.findMany.load(1, 1000);
statePersentase.kematian.findMany.load(1, 1000);
}, []);
useEffect(() => {
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
// Count kelahiran and kematian by year
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
// Get all unique years
const allYears = new Set([
...Object.keys(kelahiranByYear),
...Object.keys(kematianByYear)
]);
// Create data structure for the chart
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
acc[year] = {
tahun: year,
@@ -93,32 +84,44 @@ function Page() {
</Stack>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
{/* Page Title */}
<Title
order={1}
ta="center"
c={colors['blue-button']}
fw="bold"
lh={1.2}
>
Data Kesehatan Masyarakat Puskesmas Darmasaba
</Text>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
</Title>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
{/* Bar Chart Kematian Kelahiran */}
<Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Paper p="xl" bg={colors['white-trans-1']}>
<Box pb={30}>
<Title order={2} mb="md">Data Kematian dan Kelahiran</Title>
<Title order={2} mb="md" ta="center">
Data Kematian dan Kelahiran
</Title>
{chartData.length === 0 ? (
<Text c="dimmed" ta="center" py="xl">
<Text c="dimmed" ta="center" py="xl" size="md">
Belum ada data yang tersedia untuk ditampilkan
</Text>
) : (
<>
{/* Main Chart */}
<Center>
<Box h={400}>
<Box style={{
width: isMobile ? '90vw' : isTablet ? '700px' : '800px',
width: isMobile ? '90vw' : '800px',
maxWidth: '100%',
margin: '0 auto'
}}>
@@ -137,16 +140,21 @@ function Page() {
</Center>
</>
)}
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
<Flex pb={30} justify="center" gap="xl" align="center" wrap="wrap">
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text>
<ColorSwatch color="#EF3E3E" size={30} />
<Flex gap={{ base: 'xs', md: 'sm' }} align="center">
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
Angka Kematian
</Text>
<ColorSwatch color="#EF3E3E" size={30} />
</Flex>
</Box>
<Box>
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran</Text>
<Flex gap={{ base: 'xs', md: 'sm' }} align="center">
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
Angka Kelahiran
</Text>
<ColorSwatch color="#3290CA" size={30} />
</Flex>
</Box>
@@ -154,20 +162,13 @@ function Page() {
</Box>
</Paper>
</Box>
<GrafikPenyakit />
{/* Artikel Kesehatan */}
<Box>
<SimpleGrid
cols={{
base: 1,
md: 3,
}}
>
{/* Fasilitas Kesehatan */}
<SimpleGrid cols={{ base: 1, md: 3 }}>
<FasilitasKesehatan />
{/* Jadwal Kegiatan */}
<JadwalKegiatan />
{/* Artikel Kesehatan */}
<ArtikelKesehatanPage />
</SimpleGrid>
</Box>
@@ -177,4 +178,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Button, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -51,10 +51,15 @@ function DetailInfoWabahPenyakitUser() {
shadow="sm"
>
<Stack gap="lg">
{/* Judul */}
<Text fz="xl" fw="bold" c={colors['blue-button']} ta="center">
{/* Judul — H1 */}
<Title
order={1}
fw="bold"
c={colors['blue-button']}
ta="center"
>
{data.name || 'Kontak Darurat'}
</Text>
</Title>
{/* Gambar */}
{data.image?.link && (
@@ -69,20 +74,26 @@ function DetailInfoWabahPenyakitUser() {
)}
{/* Deskripsi */}
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Stack gap={"xs"}>
{/* Section Title — H2 */}
<Title order={3} fw="bold" ta="left">
Deskripsi
</Title>
<Box pl={20}>
<Text
fz="md"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: '1.5', md: '1.6' }}
c="dark"
ta="justify"
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
</Box>
</Box>
</Stack>
</Stack>
</Paper>
</Box>
);
}
export default DetailInfoWabahPenyakitUser;
export default DetailInfoWabahPenyakitUser;

View File

@@ -17,7 +17,8 @@ import {
Skeleton,
Stack,
Text,
TextInput
TextInput,
Title
} from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconInfoCircle, IconSearch } from '@tabler/icons-react';
@@ -30,7 +31,7 @@ function Page() {
const state = useProxy(infoWabahPenyakit);
const router = useTransitionRouter();
const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000)
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
@@ -53,15 +54,19 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 8 }}>
<Text
fz={{ base: '1.8rem', md: '2.8rem' }}
<Title
order={1}
c={colors['blue-button']}
fw="bold"
lh={1.2}
lh={{ base: 1.2, md: 1.2 }}
>
Informasi Wabah & Penyakit
</Text>
<Text fz="md" mt={4}>
</Title>
<Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.5 }}
mt={4}
>
Dapatkan informasi terbaru mengenai wabah dan penyakit yang sedang
diawasi.
</Text>
@@ -84,9 +89,9 @@ function Page() {
<Center py="6xl">
<Stack align="center" gap="sm">
<IconInfoCircle size={50} color={colors['blue-button']} />
<Text fz="lg" fw={500} >
<Title order={2} fz={{ base: 'md', md: 'lg' }} fw={500}>
Tidak ada data yang cocok dengan pencarian Anda.
</Text>
</Title>
</Stack>
</Center>
) : (
@@ -131,15 +136,24 @@ function Page() {
{/* Judul dan badge */}
<Group justify="space-between" mt="sm">
<Text fw={700} fz="lg" c={colors['blue-button']}>
<Title
order={3}
c={colors['blue-button']}
fw={700}
fz={{ base: 'sm', md: 'lg' }}
>
{v.name}
</Text>
</Title>
<Badge color="blue" variant="light" radius="sm">
Wabah
</Badge>
</Group>
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
lh={{ base: 1.4, md: 1.4 }}
>
Diposting:{' '}
{new Date(v.createdAt).toLocaleDateString('id-ID', {
day: '2-digit',
@@ -153,8 +167,8 @@ function Page() {
{/* Bagian deskripsi dan tombol */}
<Box style={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
<Text
fz="sm"
lh={1.5}
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.5, md: 1.5 }}
lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsiSingkat }}
style={{ flexGrow: 1 }}
@@ -174,14 +188,11 @@ function Page() {
</Box>
</Stack>
</Paper>
))}
</SimpleGrid>
)}
</Box>
<Center>
<Pagination
value={page}
@@ -192,9 +203,8 @@ function Page() {
mt="lg"
/>
</Center>
</Stack>
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'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 { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconBrandWhatsapp } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -27,15 +27,16 @@ function Page() {
const data = state.findUnique.data;
return (
<Box px={{base: 'md', md: 100}} py={10}>
<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}
style={{ lineHeight: 1.2 }}
>
Kembali
<Text fz={{ base: 'sm', md: 'md' }} fw={500}>Kembali</Text>
</Button>
{/* Wrapper Detail */}
@@ -48,34 +49,38 @@ function Page() {
shadow="sm"
>
<Stack gap="md">
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
<Title order={1} c={colors['blue-button']}>
Detail Kontak Darurat
</Text>
</Title>
<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>
<Title order={3}>Judul</Title>
<Text fz={{ base: 'sm', md: 'md' }} c={data.name ? 'black' : 'dimmed'}>
{data.name || '-'}
</Text>
</Box>
<Box>
<Text fz="lg" fw="bold">Whatsapp</Text>
<Text fz="md" c="dimmed">{data.whatsapp || '-'}</Text>
<Title order={3}>Whatsapp</Title>
<Text fz={{ base: 'sm', md: 'md' }} c={data.whatsapp ? 'black' : 'dimmed'}>
{data.whatsapp || '-'}
</Text>
</Box>
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Title order={3}>Deskripsi</Title>
<Text
fz="md"
c="dimmed"
fz={{ base: 'sm', md: 'md' }}
c={data.deskripsi ? 'black' : 'dimmed'}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal', lineHeight: 1.5 }}
/>
</Box>
<Box>
<Text fz="lg" fw="bold">Gambar</Text>
<Title order={3}>Gambar</Title>
{data.image?.link ? (
<Image
src={data.image.link}
@@ -85,9 +90,10 @@ function Page() {
loading="lazy"
/>
) : (
<Text fz="md" c="dimmed">-</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">-</Text>
)}
</Box>
<Group>
<Button
variant="light"
@@ -96,6 +102,7 @@ function Page() {
href={`https://wa.me/${data.whatsapp.replace(/\D/g, '')}`}
target="_blank"
aria-label="Hubungi WhatsApp"
fz={{ base: 'sm', md: 'md' }}
>
WhatsApp
</Button>
@@ -108,4 +115,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -16,6 +16,7 @@ import {
Stack,
Text,
TextInput,
Title,
Tooltip
} from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
@@ -52,10 +53,21 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} gutter="lg">
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{ base: '2rem', md: '2.8rem' }} c={colors['blue-button']} fw={800}>
<Title
order={1}
c={colors['blue-button']}
fw={800}
fz={{ base: '28px', md: '32px' }}
lh={{ base: 1.2, md: 1.25 }}
>
Kontak Darurat
</Text>
<Text fz="md" mt={4}>
</Title>
<Text
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
mt={4}
c="dark.9"
>
Hubungi layanan penting dengan cepat dan mudah
</Text>
</GridCol>
@@ -79,10 +91,20 @@ function Page() {
<Center mih={300}>
<Stack align="center" gap="xs">
<IconSearch size={50} color={colors['blue-button']} />
<Text fz="lg" fw={600} c={colors['blue-button']}>
<Title
order={2}
fw={600}
c={colors['blue-button']}
fz={{ base: '20px', md: '24px' }}
lh={{ base: 1.3, md: 1.35 }}
>
Tidak ada kontak ditemukan
</Text>
<Text fz="sm" c="dimmed">
</Title>
<Text
fz={{ base: 'xs', md: 'sm' }}
lh={{ base: 1.4, md: 1.5 }}
c="dark.7"
>
Coba kata kunci lain untuk pencarian
</Text>
</Stack>
@@ -102,8 +124,8 @@ function Page() {
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between', // ✅ biar button selalu di bawah
height: '100%', // ✅ bikin tinggi seragam
justifyContent: 'space-between',
height: '100%',
}}
>
<Stack align="center" gap="sm" style={{ flexGrow: 1 }}>
@@ -131,27 +153,24 @@ function Page() {
/>
</Box>
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}>
<Text ta="center" fw={700} fz={{ base: '18px', md: '20px' }} lh={1.3} c={colors['blue-button']}>
{v.name}
</Text>
<Text
fz="sm"
fz={{ base: 'xs', md: 'sm' }}
ta="center"
lineClamp={3}
lh={1.6}
lh={{ base: 1.5, md: 1.6 }}
style={{
minHeight: '4.8em', // tinggi tetap 3 baris
minHeight: '4.8em',
wordBreak: 'break-word',
whiteSpace: 'normal',
}}
>
<span
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Text>
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Stack>
{/* ✅ Tombol selalu di bagian bawah card */}
<Group mt="md" justify='center'>
<Button
bg={colors['blue-button']}
@@ -161,8 +180,6 @@ function Page() {
</Button>
</Group>
</Paper>
))}
</SimpleGrid>
)}
@@ -186,4 +203,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client';
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
import colors from '@/con/colors';
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
@@ -31,48 +31,55 @@ function DetailPenangananDaruratUser() {
<Box py={20}>
{/* Tombol Back */}
<Box px={{ base: 'md', md: 100 }}>
<BackButton/>
<BackButton />
</Box>
{/* Wrapper Detail */}
<Paper
withBorder
w={{ base: '100%', md: '70%', lg: '60%' }}
mx="auto"
bg={colors['white-1']}
p="xl"
radius="lg"
shadow="sm"
>
<Stack gap="md" align="center" ta="center">
<Text fz="xl" fw={700} c={colors['blue-button']}>
{data.name || 'Penanganan Darurat'}
</Text>
<Box pt={20} px={{ base: 'md', md: 100 }}>
<Paper
withBorder
w={'100%'}
bg={colors['white-1']}
p="xl"
radius="lg"
shadow="sm"
>
<Stack gap="md">
<Title
ta={"center"}
order={1}
fw={700}
c={colors['blue-button']}
style={{ wordBreak: 'break-word' }}
>
{data.name || 'Penanganan Darurat'}
</Title>
<Center>
{data.image?.link && (
<Image
src={data.image.link}
alt={data.name}
radius="md"
mah={300}
fit="contain"
loading="lazy"
mb="md"
/>
)}
</Center>
{data.image?.link && (
<Image
src={data.image.link}
alt={data.name}
radius="md"
mah={300}
fit="contain"
loading="lazy"
mb="md"
/>
)}
<Box>
<Text
fz="md"
ta="justify"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</Stack>
</Paper>
<Box >
<Text
ta="justify"
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</Stack>
</Paper>
</Box>
</Box>
);
}
export default DetailPenangananDaruratUser;
export default DetailPenangananDaruratUser;

View File

@@ -15,6 +15,7 @@ import {
Stack,
Text,
TextInput,
Title,
Tooltip
} from '@mantine/core'
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'
@@ -49,10 +50,19 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} mb="lg">
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: 30, md: 40 }} c={colors['blue-button']} fw={800} lh={1.2}>
<Title
order={1}
c={colors['blue-button']}
fw={800}
lh={{ base: 1.3, md: 1.2 }}
>
Penanganan Darurat
</Text>
<Text fz="md" mt={4}>
</Title>
<Text
fz={{ base: 'sm', md: 'md' }}
mt={4}
lh={{ base: 1.5, md: 1.5 }}
>
Informasi cepat dan jelas untuk situasi darurat kesehatan
</Text>
</GridCol>
@@ -74,10 +84,10 @@ function Page() {
{data.length === 0 ? (
<Center py={100}>
<Stack align="center" gap="xs">
<Text fz="lg" fw={600} c={colors['blue-button']}>
<Title order={2} fw={600} c={colors['blue-button']}>
Tidak ada data ditemukan
</Text>
<Text fz="sm" c="dimmed">
</Title>
<Text fz={{ base: 'xs', md: 'sm' }}>
Coba gunakan kata kunci lain
</Text>
</Stack>
@@ -128,18 +138,21 @@ function Page() {
</Box>
<Stack gap={4} w="100%">
<Text
fz="lg"
<Title
order={3}
fw={700}
c={colors['blue-button']}
ta="center"
lineClamp={2}
fz={{ base: 'sm', md: 'lg' }}
lh={{ base: 1.4, md: 1.4 }}
>
{v.name}
</Text>
</Title>
<Box>
<Text
fz="md"
fz={{ base: 'xs', md: 'md' }}
lh={{ base: 1.5, md: 1.5 }}
lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
@@ -177,12 +190,10 @@ function Page() {
'&:hover': { backgroundColor: colors['blue-button'], color: 'white' },
},
}}
/>
</Center>
</Stack>
)
}
export default Page
export default Page

View File

@@ -1,7 +1,18 @@
'use client';
import colors from '@/con/colors';
import { Button, Center, Flex, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import {
Button,
Center,
Flex,
Group,
Image,
Paper,
Skeleton,
Stack,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -53,15 +64,14 @@ export default function DetailPosyanduUser() {
mx="auto"
>
<Stack gap="md">
{/* Header */}
<Text
{/* Header — Dijadikan Title */}
<Title
ta="center"
fz={{ base: '1.8rem', md: '2.2rem' }}
fw={700}
order={1}
c={colors['blue-button']}
>
{data.name || 'Posyandu Desa'}
</Text>
</Title>
{/* Gambar */}
{data.image?.link ? (
@@ -78,7 +88,11 @@ export default function DetailPosyanduUser() {
</Center>
) : (
<Center>
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'xs', md: 'sm' }}
c="dimmed"
ta="center"
>
Tidak ada gambar
</Text>
</Center>
@@ -88,7 +102,11 @@ export default function DetailPosyanduUser() {
<Stack gap="sm" mt="md">
<Flex align="center" gap="xs">
<IconPhone size={18} stroke={1.5} />
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'sm', md: 'md' }}
c="black"
lh={{ base: 1.5, md: 1.6 }}
>
{data.nomor || 'Nomor tidak tersedia'}
</Text>
</Flex>
@@ -96,8 +114,9 @@ export default function DetailPosyanduUser() {
<Flex align="center" gap="xs">
<IconCalendar size={18} stroke={1.5} />
<Text
fz="sm"
c="dimmed"
fz={{ base: 'sm', md: 'md' }}
c="black"
lh={{ base: 1.5, md: 1.6 }}
dangerouslySetInnerHTML={{ __html: data.jadwalPelayanan || '-' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
@@ -106,9 +125,9 @@ export default function DetailPosyanduUser() {
<Flex align="center" gap="xs">
<IconInfoCircle size={18} stroke={1.5} />
<Text
fz="sm"
c="dimmed"
lh={1.6}
fz={{ base: 'sm', md: 'md' }}
c="black"
lh={{ base: 1.5, md: 1.6 }}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
@@ -118,4 +137,4 @@ export default function DetailPosyanduUser() {
</Paper>
</Stack>
);
}
}

View File

@@ -1,7 +1,7 @@
'use client'
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
import colors from "@/con/colors";
import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from "@mantine/core";
import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
import { useState } from "react";
@@ -12,8 +12,8 @@ import { useTransitionRouter } from "next-view-transitions";
export default function Page() {
const state = useProxy(posyandustate);
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const router = useTransitionRouter()
const [debouncedSearch] = useDebouncedValue(search, 1000);
const router = useTransitionRouter();
const { data, page, totalPages, loading, load } = state.findMany;
@@ -35,14 +35,13 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }}>
<BackButton />
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
<Text
<Title
order={1}
ta="left"
fz={{ base: "1.8rem", md: "2.5rem" }}
c={colors["blue-button"]}
fw="bold"
>
Posyandu Desa Darmasaba
</Text>
</Title>
<TextInput
placeholder="Cari posyandu berdasarkan nama..."
aria-label="Pencarian Posyandu"
@@ -55,6 +54,7 @@ export default function Page() {
/>
</Flex>
</Box>
<Title c={"dimmed"} order={3} ta={"center"}>Belum ada posyandu yang terdaftar</Title>
</Stack>
);
}
@@ -64,14 +64,13 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }}>
<BackButton />
<Flex mt="md" justify="space-between" align="center" wrap="wrap" gap="md">
<Text
<Title
order={1}
ta="left"
fz={{ base: "1.8rem", md: "2.5rem" }}
c={colors["blue-button"]}
fw="bold"
>
Posyandu Desa Darmasaba
</Text>
</Title>
<TextInput
placeholder="Cari posyandu berdasarkan nama..."
aria-label="Pencarian Posyandu"
@@ -116,9 +115,9 @@ export default function Page() {
>
<Stack gap="sm">
<Group justify="space-between" align="center">
<Text c={colors["blue-button"]} fw="bold" fz="lg" lineClamp={1}>
<Title order={3} c={colors["blue-button"]} fw="bold" lineClamp={1}>
{v.name}
</Text>
</Title>
<Badge color="blue" variant="light" size="sm" radius="sm">
Aktif
</Badge>
@@ -136,39 +135,45 @@ export default function Page() {
</Center>
<Flex align="flex-start" gap="xs">
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
<Box>
<Text fz="sm" c="dimmed" lh={1.4}>
{v.nomor || "Tidak tersedia"}
</Text>
</Box>
<Text
fz={{ base: "sm", md: "md" }}
c="black"
lh={{ base: 1.4, md: 1.5 }}
>
{v.nomor || "Tidak tersedia"}
</Text>
</Flex>
<Flex align="flex-start" gap="xs">
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
<Box>
<Text fz="sm" c="dimmed" lh={1.4}>
<strong>Jadwal:</strong>{" "}
<span
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }}
/>
</Text>
</Box>
<Text
fz={{ base: "sm", md: "md" }}
c="black"
lh={{ base: 1.4, md: 1.5 }}
>
<strong>Jadwal:</strong>{" "}
<span
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }}
/>
</Text>
</Flex>
<Flex align="flex-start" gap="xs">
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
<Text
fz="sm"
lh={1.5}
c="dimmed"
fz={{ base: "sm", md: "md" }}
lh={{ base: 1.4, md: 1.5 }}
c="black"
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
lineClamp={3}
truncate="end"
/>
</Flex>
<Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>Detail</Button>
<Button radius="lg" size="md" variant="outline" onClick={() => router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>
Detail
</Button>
</Stack>
</Paper>
))}
@@ -191,11 +196,11 @@ export default function Page() {
<Stack gap="sm">
<Flex align="center" gap="xs">
<IconInfoCircle size={22} color={colors["blue-button"]} />
<Text fz="lg" fw="bold" c={colors["blue-button"]}>
<Title order={2} c={colors["blue-button"]}>
Layanan Utama Posyandu
</Text>
</Title>
</Flex>
<List spacing="xs" size="sm" center>
<List spacing="xs" fz={{ base: "sm", md: "md" }} lh={{ base: 1.4, md: 1.5 }} c="black">
<ListItem>Penimbangan bayi dan balita</ListItem>
<ListItem>Pemantauan status gizi</ListItem>
<ListItem>Imunisasi dasar lengkap</ListItem>
@@ -207,4 +212,4 @@ export default function Page() {
</Box>
</Stack>
);
}
}

View File

@@ -1,7 +1,7 @@
'use client'
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
import colors from '@/con/colors';
import { Box, Center, Group, Image, Loader, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
import { Box, Center, Group, Image, Loader, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCalendar, IconUser } from '@tabler/icons-react';
import { useParams } from 'next/navigation';
@@ -9,12 +9,12 @@ import { useProxy } from 'valtio/utils';
import BackButton from '../../../desa/layanan/_com/BackButto';
function Page() {
const state = useProxy(programKesehatan)
const params = useParams()
const state = useProxy(programKesehatan);
const params = useParams();
useShallowEffect(() => {
state.findUnique.load(params.id as string)
}, [params.id])
state.findUnique.load(params.id as string);
}, [params.id]);
if (!state.findUnique.data) {
return (
@@ -24,7 +24,7 @@ function Page() {
<Text c="dimmed" fz="sm">Memuat data program kesehatan...</Text>
</Stack>
</Center>
)
);
}
return (
@@ -33,68 +33,73 @@ function Page() {
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Paper
px={{ base: 'md', md: 100 }}
py="xl"
radius="xl"
shadow="md"
bg={colors["white-trans-1"]}
>
<Stack gap="lg">
<Center>
{state.findUnique.data.image?.link ? (
<Image
radius="xl"
src={state.findUnique.data.image?.link}
alt={state.findUnique.data.name}
w="100%"
maw={800}
fit="cover"
loading="lazy"
<Paper
px={{ base: 'md', md: 100 }}
py="xl"
radius="xl"
shadow="md"
bg={colors["white-trans-1"]}
>
<Stack gap="lg">
<Center>
{state.findUnique.data.image?.link ? (
<Image
src={state.findUnique.data.image?.link}
alt={state.findUnique.data.name}
radius="md"
mah={300}
fit="contain"
loading="lazy"
/>
) : (
<Skeleton h={300} w="100%" radius="xl" />
)}
</Center>
<Box pl={20}>
<Title
order={1}
pb="sm"
c={colors["blue-button"]}
fw="bold"
lh={{ base: 1.2, md: 1.15 }}
>
{state.findUnique.data.name}
</Title>
<Text
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={{ base: 1.5, md: 1.6 }}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
) : (
<Skeleton h={300} w="100%" radius="xl" />
)}
</Center>
<Box>
<Text pb="sm" c={colors["blue-button"]} fw="bold" fz={{ base: 24, md: 32 }} lh={1.2}>
{state.findUnique.data.name}
</Text>
<Text
ta="justify"
fz="md"
lh={1.6}
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
/>
</Box>
<Group gap="xl">
<Group gap="xs">
<Tooltip label="Tanggal dibuat" withArrow>
<IconCalendar color='gray' size={20} stroke={1.5} />
</Tooltip>
<Text size="sm" c="dimmed">
{state.findUnique.data.createdAt
? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
</Box>
<Group gap="xl">
<Group gap="xs">
<Tooltip label="Tanggal dibuat" withArrow>
<IconCalendar color="gray" size={20} stroke={1.5} />
</Tooltip>
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
{state.findUnique.data.createdAt
? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
: 'Tanggal tidak tersedia'}
</Text>
: 'Tanggal tidak tersedia'}
</Text>
</Group>
<Group gap="xs">
<Tooltip label="Dibuat oleh" withArrow>
<IconUser color="gray" size={20} stroke={1.5} />
</Tooltip>
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">Admin Desa</Text>
</Group>
</Group>
<Group gap="xs">
<Tooltip label="Dibuat oleh" withArrow>
<IconUser color='gray' size={20} stroke={1.5} />
</Tooltip>
<Text size="sm" c="dimmed">Admin Desa</Text>
</Group>
</Group>
</Stack>
</Paper>
</Stack>
</Paper>
</Box>
</Stack>
);
}
export default Page;
export default Page;

View File

@@ -16,6 +16,7 @@ import {
Stack,
Text,
TextInput,
Title,
Transition
} from "@mantine/core";
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
@@ -57,7 +58,7 @@ export default function Page() {
const state = useProxy(programKesehatan);
const router = useRouter();
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
@@ -80,14 +81,19 @@ export default function Page() {
<Grid px={{ base: "md", md: 100 }} align="center" gutter="lg">
<GridCol span={{ base: 12, md: 8 }}>
<Text
fz={{ base: "2rem", md: "2.8rem" }}
<Title
order={1}
c={colors["blue-button"]}
fw="bold"
fz={{ base: '28px', md: '32px' }}
>
Program Kesehatan Desa
</Text>
<Text fz="lg" mt="xs">
</Title>
<Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
mt="xs"
>
Temukan berbagai program kesehatan untuk mendukung kualitas hidup
masyarakat Darmasaba.
</Text>
@@ -129,11 +135,9 @@ export default function Page() {
<Box
style={{
width: '100%',
height: 180, // 🔥 tinggi fix biar semua seragam
aspectRatio: '16/9', // thumbnail landscape aman untuk portrait/landscape
borderRadius: 12,
overflow: 'hidden',
position: 'relative',
backgroundColor: '#f0f2f5', // fallback kalau gambar loading
}}
>
<Image
@@ -142,32 +146,28 @@ export default function Page() {
fit="cover"
width="100%"
height="100%"
style={{ objectFit: 'cover', objectPosition: 'center' }}
loading="lazy"
style={{
objectFit: 'cover',
objectPosition: 'center',
transition: 'transform 0.4s ease',
}}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
/>
</Box>
</Center>
<Box px="lg" pb="lg">
<Text
fw="bold"
fz="xl"
<Title
order={3}
c={colors["blue-button"]}
fw="bold"
fz={{ base: '18px', md: '20px' }}
mb="xs"
>
{v.name}
</Text>
</Title>
<Text
fz="sm"
ta={"justify"}
fz={{ base: '13px', md: '14px' }}
lh="1.6"
ta="justify"
lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
@@ -175,7 +175,10 @@ export default function Page() {
<Group justify="space-between" mt="md">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">
<Text
fz={{ base: '12px', md: '14px' }}
lh="1.5"
>
{v.createdAt
? new Date(v.createdAt).toLocaleDateString(
"id-ID",
@@ -190,25 +193,30 @@ export default function Page() {
</Group>
<Group gap="xs">
<IconUser size={18} />
<Text size="sm">Admin Desa</Text>
<Text
fz={{ base: '12px', md: '14px' }}
lh="1.5"
>
Admin Desa
</Text>
</Group>
</Group>
<Button
mt="lg"
fullWidth
radius="lg"
size="md"
fw="bold"
variant="gradient"
gradient={{ from: colors["blue-button"], to: "#4dabf7" }}
onClick={() =>
router.push(
`/darmasaba/kesehatan/program-kesehatan/${v.id}`
)
}
>
Lihat Detail
</Button>
<Button
mt="lg"
fullWidth
radius="lg"
size="md"
fw="bold"
variant="gradient"
gradient={{ from: colors["blue-button"], to: "#4dabf7" }}
onClick={() =>
router.push(
`/darmasaba/kesehatan/program-kesehatan/${v.id}`
)
}
>
Lihat Detail
</Button>
</Box>
</Stack>
</Paper>
@@ -239,14 +247,19 @@ export default function Page() {
<Box px={{ base: "md", md: 100 }} py="xl">
<Stack gap="sm" mb="lg">
<Text
fz={{ base: "2rem", md: "2.5rem" }}
<Title
order={2}
c={colors["blue-button"]}
fw="bold"
fz={{ base: '24px', md: '28px' }}
>
Manfaat Program Kesehatan
</Text>
<Text fz="lg" maw={700}>
</Title>
<Text
fz={{ base: '14px', md: '16px' }}
lh={{ base: '1.5', md: '1.6' }}
maw={700}
>
Program kesehatan Desa Darmasaba berperan penting dalam meningkatkan
kesejahteraan dan kualitas hidup warganya.
</Text>
@@ -273,10 +286,20 @@ export default function Page() {
>
<Center>{v.icon}</Center>
</Paper>
<Text ta="center" fw="bold" fz="xl" c={colors["blue-button"]}>
<Title
order={3}
ta="center"
fw="bold"
c={colors["blue-button"]}
fz={{ base: '18px', md: '20px' }}
>
{v.title}
</Text>
<Text ta="center" fz="sm">
</Title>
<Text
ta="center"
fz={{ base: '13px', md: '14px' }}
lh="1.5"
>
{v.desc}
</Text>
</Stack>
@@ -286,4 +309,4 @@ export default function Page() {
</Box>
</Stack>
);
}
}

View File

@@ -59,12 +59,16 @@ function Page() {
left={20}
gap={6}
>
<Text fw="bold" fz={{ base: 'lg', md: 'h2' }} c={colors['white-1']}>
<Title
order={1}
c={colors['white-1']}
fz={{ base: 'lg', md: 'xl' }}
>
{data.name}
</Text>
</Title>
<Group gap={6}>
<IconMapPin size={20} color="white" />
<Text fz={{ base: 'sm', md: 'md' }} c={colors['white-1']}>
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>
{data.alamat}
</Text>
</Group>
@@ -75,37 +79,45 @@ function Page() {
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="lg">
<Box>
<Title order={3} mb={10}>Informasi Kontak</Title>
<Title order={2} mb="md">Informasi Kontak</Title>
<Stack gap={8}>
<Group gap={8}>
<IconPhone size={18} />
<Text fz="md">{data.kontak.kontakPuskesmas || '-'}</Text>
<Text fz={{ base: 'sm', md: 'md' }}>
{data.kontak.kontakPuskesmas || '-'}
</Text>
</Group>
<Group gap={8}>
<IconMail size={18} />
<Text fz="md">{data.kontak.email || '-'}</Text>
<Text fz={{ base: 'sm', md: 'md' }}>
{data.kontak.email || '-'}
</Text>
</Group>
</Stack>
</Box>
<Divider />
<Stack gap={"xs"}>
<Title order={3} mb={10}>Jam Operasional</Title>
<Text fw="bold" fz="md">Senin - Jumat</Text>
<Stack gap="xs">
<Title order={2} mb="md">Jam Operasional</Title>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Senin - Jumat</Text>
<Group gap={8}>
<IconClock size={18} />
<Text fw="bold" fz="md">{data.jam.workDays} - {data.jam.weekDays}</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
{data.jam.workDays} {data.jam.weekDays}
</Text>
<Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip>
</Group>
<Text fw="bold" fz="md">Sabtu - Minggu / Hari Libur</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Sabtu Minggu / Hari Libur</Text>
<Group gap={8}>
<IconClock size={18} />
<Text fw="bold" fz="md">{data.jam.holiday}</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
{data.jam.holiday}
</Text>
<Tooltip label="Hari aktif pelayanan puskesmas" position="top" withArrow>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
<Badge size="sm" variant="light" color="blue">Aktif</Badge>
</Tooltip>
</Group>
</Stack>
@@ -114,20 +126,24 @@ function Page() {
<GridCol span={{ base: 12, md: 6 }}>
<Paper p="xl" radius="lg" bg="linear-gradient(135deg, #EAF0FB, #BFD4F5)" shadow="sm">
<Title order={3} mb="lg" ta="center" c="dark">Layanan Unggulan</Title>
<Title order={2} mb="lg" ta="center" c="dark">Layanan Unggulan</Title>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poliklinik Umum</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Poliklinik Umum</Text>
<Text fz={{ base: 'h1', md: 'h1' }} fw="bold" c={colors['blue-button']} lh={1}>
26
</Text>
</Stack>
</Paper>
<Paper p="lg" radius="lg" withBorder bg={colors['white-trans-1']} shadow="xs">
<Stack align="center" gap={8}>
<IconBuildingHospital size={36} color={colors['blue-button']} />
<Text fw="bold" fz="lg">Poli Gigi</Text>
<Text fz={{ base: 36, md: 48 }} fw="bold" c={colors['blue-button']}>26</Text>
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>Poli Gigi</Text>
<Text fz={{ base: 'h1', md: 'h1' }} fw="bold" c={colors['blue-button']} lh={1}>
26
</Text>
</Stack>
</Paper>
</SimpleGrid>
@@ -141,4 +157,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -1,7 +1,7 @@
'use client'
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
import colors from '@/con/colors';
import { Anchor, Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Badge, Group } from '@mantine/core';
import { Anchor, Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Badge, Group, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSearch, IconMapPin, IconPhone, IconMail } from '@tabler/icons-react';
import { useState } from 'react';
@@ -42,10 +42,10 @@ function Page() {
<Grid align="center" px={{ base: 'md', md: 100 }} mb="md">
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{ base: "2rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
<Title order={1} c={colors["blue-button"]}>
Daftar Puskesmas
</Text>
<Text fz="md">
</Title>
<Text fz={{ base: "sm", md: "md" }} ta="start">
Temukan informasi lengkap mengenai layanan, kontak, dan lokasi Puskesmas Darmasaba
</Text>
</GridCol>
@@ -65,8 +65,8 @@ function Page() {
{data.length === 0 ? (
<Center py="xl">
<Stack align="center" gap="xs">
<Text fz="lg" fw={500} c="dimmed">Tidak ada data ditemukan</Text>
<Text fz="sm" c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text>
<Title order={2} fw={500} c="dimmed">Tidak ada data ditemukan</Title>
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">Coba gunakan kata kunci pencarian yang berbeda</Text>
</Stack>
</Center>
) : (
@@ -92,29 +92,29 @@ function Page() {
loading="lazy"
/>
<Group justify="space-between">
<Text fw={600} fz="lg" lineClamp={1}>{v.name}</Text>
<Title order={3} fw={600} lineClamp={1}>{v.name}</Title>
<Badge color="blue" variant="light" radius="sm" fz="xs">Aktif</Badge>
</Group>
<Stack gap={6}>
<Group gap="xs" align="flex-start" wrap="nowrap">
<Box pt={2}><IconMapPin size={20} /></Box>
<Text fz="sm" c="dimmed">{v.alamat}</Text>
<Text fz={{ base: "sm", md: "md" }}>{v.alamat}</Text>
</Group>
<Group gap="xs" align="flex-start" wrap="nowrap">
<Box pt={2}><IconPhone size={20} /></Box>
<Text fz="sm" c="dimmed">{v.kontak.kontakPuskesmas}</Text>
<Text fz={{ base: "sm", md: "md" }}>{v.kontak.kontakPuskesmas}</Text>
</Group>
<Group gap="xs" align="flex-start" wrap="nowrap">
<Box pt={2}><IconMail size={20} /></Box>
<Text fz="sm" c="dimmed">{v.kontak.email}</Text>
<Text fz={{ base: "sm", md: "md" }}>{v.kontak.email}</Text>
</Group>
</Stack>
<Anchor
href={`/darmasaba/kesehatan/puskesmas/${v.id}`}
fz="sm"
fz={{ base: "sm", md: "md" }}
fw={500}
c={colors['blue-button']}
>
@@ -143,4 +143,4 @@ function Page() {
);
}
export default Page;
export default Page;

View File

@@ -64,14 +64,14 @@ function Page() {
</Text>
<TextInput
radius="xl"
w={'30%'}
w={{base: "100%", md: "30%"}}
placeholder="Cari Data Lingkungan Desa"
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Group>
<Text fz="md" >
<Text fz="md" pt={20}>
Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya.
</Text>
<Text fz="md">

View File

@@ -12,6 +12,7 @@ import {
Skeleton,
Stack,
Text,
Title,
} from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -41,9 +42,9 @@ export default function DetailInformasiPublikUser() {
return (
<Center py="xl">
<Stack align="center" gap="sm">
<Text fz="lg" fw="bold">
<Title order={4} fz={{ base: 'lg', md: 'xl' }} lh={1.3}>
Informasi tidak ditemukan
</Text>
</Title>
<Button variant="light" onClick={() => router.push('/informasi-publik')}>
Kembali ke Daftar
</Button>
@@ -75,53 +76,60 @@ export default function DetailInformasiPublikUser() {
shadow="xs"
>
<Stack gap="xl">
<Text
fz={{ base: 'xl', md: '2xl' }}
fw="bold"
{/* MAIN TITLE */}
<Title
order={2}
lh={1.2}
ta="center"
c={colors['blue-button']}
>
Detail Informasi Publik
</Text>
</Title>
<Divider />
{/* CONTENT */}
<Stack gap="lg">
{/* Jenis Informasi */}
<Box px="lg">
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
<Title order={5} fz={{ base: 'lg', md: 'xl' }} lh={1.3} mb={4}>
Jenis Informasi
</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
</Title>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5} c="black">
{data.jenisInformasi || '-'}
</Text>
</Box>
{/* Tanggal Publikasi */}
<Box px="lg">
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
<Title order={5} fz={{ base: 'lg', md: 'xl' }} lh={1.3} mb={4}>
Tanggal Publikasi
</Text>
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
</Title>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5} c="black">
{data.tanggal
? new Date(data.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
day: '2-digit',
month: 'long',
year: 'numeric',
})
: '-'}
</Text>
</Box>
{/* Deskripsi */}
<Box px="lg">
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
<Title order={5} fz={{ base: 'lg', md: 'xl' }} lh={1.3} mb={4}>
Deskripsi
</Text>
</Title>
<Box>
<Text
ta={"justify"}
className="prose max-w-none leading-relaxed"
ta="justify"
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
fz={{ base: 'md', md: 'lg' }}
fz={{ base: 'sm', md: 'md' }}
lh={1.6}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
className="prose max-w-none"
/>
</Box>
</Box>
@@ -130,4 +138,4 @@ export default function DetailInformasiPublikUser() {
</Paper>
</Box>
);
}
}

View File

@@ -21,7 +21,8 @@ import {
TableTr,
Text,
TextInput,
Tooltip
Tooltip,
Title
} from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconBrandWhatsapp, IconDeviceImacCog, IconFileInfo, IconMail, IconSearch } from '@tabler/icons-react';
@@ -33,7 +34,7 @@ import { useTransitionRouter } from 'next-view-transitions';
function Page() {
const listData = useProxy(daftarInformasiPublik)
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
const [debouncedSearch] = useDebouncedValue(search, 1000); // 1000ms delay
const router = useTransitionRouter()
const {
data,
@@ -65,20 +66,49 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Center>
<Image src="/darmasaba-icon.png" w={{ base: 70, md: 100 }} alt="Logo Desa Darmasaba" loading="lazy" />
<Image
src="/darmasaba-icon.png"
w={{ base: 70, md: 100 }}
alt="Logo Desa Darmasaba"
loading="lazy"
/>
</Center>
<Text ta="center" fz={{ base: "1.8rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold" lh={1.4}>
<Title
order={2}
ta="center"
fz={{ base: '1.6rem', md: '2.4rem' }}
c={colors['blue-button']}
lh={1.35}
style={{ fontWeight: 700 }}
>
Daftar Informasi Publik
</Text>
<Box px={{ base: "md", md: 100 }}>
</Title>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg">
<Paper p="lg" radius="xl" shadow="sm" withBorder>
<Stack gap="sm">
<Text ta={"center"} fz={{ base: 'lg', md: 'xl' }} fw="bold" c={colors["blue-button"]}>
<Title
order={4}
ta="center"
fz={{ base: 'lg', md: 'xl' }}
c={colors['blue-button']}
lh={1.2}
style={{ fontWeight: 700 }}
>
Tentang Informasi Publik
</Text>
<Text ta={"center"} fz={{ base: 'sm', md: 'md' }} c="dimmed">
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
c="black"
lh={1.6}
style={{ maxWidth: 900, margin: '0 auto' }}
>
Daftar Informasi Publik Desa Darmasaba adalah kumpulan data yang dapat diakses oleh masyarakat sesuai dengan ketentuan peraturan yang berlaku.
</Text>
</Stack>
@@ -97,8 +127,8 @@ function Page() {
{data.length === 0 ? (
<Center py="xl">
<Stack align="center" gap="sm">
<IconFileInfo size={48} stroke={1.5} color={colors["blue-button"]} />
<Text fz="md" c="dimmed">Tidak ada informasi publik yang ditemukan.</Text>
<IconFileInfo size={48} stroke={1.5} color={colors['blue-button']} />
<Text fz="md" c="dimmed" lh={1.5}>Tidak ada informasi publik yang ditemukan.</Text>
</Stack>
</Center>
) : (
@@ -113,27 +143,42 @@ function Page() {
<TableTh fz="sm" ta="center" w="15%">Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody bg={colors['white-1']}>
{data.map((item, index) => (
<TableTr key={item.id}>
<TableTd ta="center">{(page - 1) * 5 + index + 1}</TableTd>
<TableTd ta="center">
<Text fz="sm" lh={1.4}>
{(page - 1) * 5 + index + 1}
</Text>
</TableTd>
<TableTd>
<Box>
<Badge variant="light" size="lg" color="blue">
<Text fw={650} fz={"sm"} c={'blue'} lineClamp={1}>
<Text fw={650} fz="sm" c="blue" lineClamp={1} lh={1.2}>
{item.jenisInformasi}
</Text>
</Badge>
</Box>
</TableTd>
<TableTd>
<Box>
<Text lineClamp={1} fz="sm" c="dark" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Text
lineClamp={1}
fz={{ base: 'sm', md: 'md' }}
c="dark"
lh={1.5}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</Box>
</TableTd>
<TableTd ta="center">
<Box>
<Text ta={"center"}>
<Text ta="center" fz="sm" lh={1.4}>
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
@@ -142,6 +187,7 @@ function Page() {
</Text>
</Box>
</TableTd>
<TableTd style={{ textAlign: 'center' }}>
<Box>
<Tooltip label="Lihat Detail" withArrow>
@@ -152,8 +198,9 @@ function Page() {
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/darmasaba/ppid/daftar-informasi-publik/${item.id}`)}
aria-label={`Detail ${item.jenisInformasi}`}
>
Detail
<Text fz="xs" lh={1.2}>Detail</Text>
</Button>
</Tooltip>
</Box>
@@ -178,17 +225,27 @@ function Page() {
<Paper p="lg" radius="xl" shadow="xs" withBorder>
<Stack gap="xs">
<Text fz="lg" fw="bold" c={colors["blue-button"]}>Kontak PPID</Text>
<Title order={5} fz={{ base: 'lg', md: 'xl' }} fw="bold" c={colors['blue-button']} lh={1.2}>
Kontak PPID
</Title>
<Group>
<IconMail color='gray' size={16} style={{ marginRight: 6 }} />
<Text c={"dimmed"} fz="sm" lh={1.6}>
Email: <Text c={"dimmed"} span fw="500">ppid@desadarmasaba.id</Text>
<IconMail color="gray" size={16} style={{ marginRight: 6 }} />
<Text c="dimmed" fz="sm" lh={1.6}>
Email:{' '}
<Text c="dimmed" span fw={500} fz="sm" lh={1.6}>
ppid@desadarmasaba.id
</Text>
</Text>
</Group>
<Group>
<IconBrandWhatsapp color='gray' size={16} style={{ marginRight: 6 }} />
<Text c={"dimmed"} fz="sm" lh={1.6}>
WhatsApp: <Text c={"dimmed"} span fw="500">081-xxx-xxx-xxx</Text>
<IconBrandWhatsapp color="gray" size={16} style={{ marginRight: 6 }} />
<Text c="dimmed" fz="sm" lh={1.6}>
WhatsApp:{' '}
<Text c="dimmed" span fw={500} fz="sm" lh={1.6}>
081-xxx-xxx-xxx
</Text>
</Text>
</Group>
</Stack>

View File

@@ -1,7 +1,7 @@
'use client'
import stateDasarHukum from '@/app/admin/(dashboard)/_state/ppid/dasar_hukum/dasarHukum';
import colors from '@/con/colors';
import { Box, Paper, Skeleton, Stack, Text, Transition } from '@mantine/core';
import { Box, Paper, Skeleton, Stack, Text, Transition, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
import { IconBook2 } from '@tabler/icons-react';
@@ -31,31 +31,39 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Stack
align="center"
gap="xs"
px={{ base: 'md', md: 100 }}
>
{/* HEADER */}
<Stack align="center" gap="xs" px={{ base: 'md', md: 100 }}>
<IconBook2 size={42} stroke={1.5} color={colors["blue-button"]} />
<Text
<Title
order={1}
ta="center"
fz={{ base: "2rem", md: "2.5rem" }}
c={colors["blue-button"]}
fw="bold"
fz={{ base: "1.8rem", md: "2.3rem" }}
lh={1.2}
style={{ letterSpacing: "-0.5px" }}
>
Dasar Hukum
</Text>
<Text ta="center" fz="md" c={"black"}>
</Title>
<Text
ta="center"
fz={{ base: "sm", md: "md" }}
lh={1.6}
c="black"
>
Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum
</Text>
</Stack>
{/* CONTENT */}
<Box px={{ base: "md", md: 100 }}>
<Stack gap="lg">
{dataArray.map((item, k) => (
<Transition
key={k}
mounted={true}
mounted
transition="fade-up"
duration={400}
timingFunction="ease"
@@ -73,19 +81,27 @@ function Page() {
}}
>
<Stack gap="md">
<Text
{/* JUDUL */}
<Title
order={3}
ta="center"
c={"black"}
fw="bold"
fz={{ base: 'lg', md: 'xl' }}
style={{ lineHeight: 1.4 }}
c="black"
fz={{ base: "lg", md: "xl" }}
lh={1.3}
dangerouslySetInnerHTML={{ __html: item.judul }}
/>
{/* CONTENT */}
<Text
c={"black"}
ta={"justify"}
fz={{ base: 'sm', md: 'md' }}
style={{ lineHeight: 1.7, wordBreak: "break-word", whiteSpace: "normal" }}
c="black"
ta="justify"
fz={{ base: "sm", md: "md" }}
lh={1.7}
style={{
wordBreak: "break-word",
whiteSpace: "normal",
}}
dangerouslySetInnerHTML={{ __html: item.content }}
/>
</Stack>

View File

@@ -3,7 +3,22 @@
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, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
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";
@@ -15,16 +30,14 @@ interface ChartDataItem {
label?: string;
}
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; Responden: number }>>([]);
const [opened, { open, close }] = useDisclosure(false)
const [opened, { open, close }] = useDisclosure(false);
const resetForm = () => {
state.create.form = {
@@ -34,14 +47,14 @@ const state = useProxy(indeksKepuasanState.responden);
jenisKelaminId: "",
ratingId: "",
kelompokUmurId: "",
}
}
};
};
useShallowEffect(() => {
indeksKepuasanState.jenisKelaminResponden.findMany.load()
indeksKepuasanState.pilihanRatingResponden.findMany.load()
indeksKepuasanState.kelompokUmurResponden.findMany.load()
},[])
indeksKepuasanState.jenisKelaminResponden.findMany.load();
indeksKepuasanState.pilihanRatingResponden.findMany.load();
indeksKepuasanState.kelompokUmurResponden.findMany.load();
}, []);
const handleSubmit = async () => {
try {
@@ -51,11 +64,11 @@ const state = useProxy(indeksKepuasanState.responden);
await state.findUnique.load(idStr);
}
resetForm();
close()
close();
} catch (error) {
console.error('Error submitting form:', error);
}
}
};
// Load data on component mount
useShallowEffect(() => {
@@ -154,33 +167,52 @@ const state = useProxy(indeksKepuasanState.responden);
if (data.length === 0) {
return (
<Stack p="sm">
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
<Container w={{ base: "100%", md: "80%" }} p="xl">
<Center>
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
{/* Main page title — converted to Title, use order (don't set fz according to rules) */}
<Title order={2} ta="center" c="dark">
Indeks Kepuasan Masyarakat
</Title>
</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>
{/* Body lead text — responsive fz & lh */}
<Text ta="center" fz={{ base: "1rem", md: "1.25rem" }} lh={{ base: 1.4, md: 1.6 }} c="dimmed" mt="sm">
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"}
radius="lg"
onClick={open}
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
>Ajukan Responden</Button>
>
Ajukan Responden
</Button>
</Center>
</Container>
<Box px={"xl"}>
<Paper p={"lg"} bg={colors.Bg}>
<Paper p={"lg"}>
<Stack gap={"xs"}>
<Flex justify={"space-between"} align={"center"}>
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
<Box px="xl">
<Paper p="lg" bg={colors.Bg}>
<Paper p="lg">
<Stack gap="xs">
<Flex justify="space-between" align="center">
{/* Section heading — use Title order for hierarchy */}
<Title order={4}>
Pelayanan Terhadap Publik Desa Darmasaba
</Title>
<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')}
<Text fz={{ base: "0.9rem", md: "1rem" }} fw="bold" c={colors["blue-button"]}>
Total Responden
</Text>
{/* Big number — use Title for emphasis */}
<Title order={3} ta="end" c={colors["blue-button"]} fw="bold" mt="xs">
{state.findMany.total.toLocaleString('id-ID')}
</Title>
</Box>
</Flex>
<BarChart
h={window.innerWidth < 480 ? 200 : 300}
data={barChartData}
@@ -194,18 +226,16 @@ const state = useProxy(indeksKepuasanState.responden);
/>
</Stack>
</Paper>
<Box py={"xl"}>
<SimpleGrid
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="md"
verticalSpacing="md"
>
<Box py="xl">
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md" verticalSpacing="md">
{/* Chart Jenis Kelamin */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Jenis Kelamin</Title>
<Title order={5}>Jenis Kelamin</Title>
{donutDataJenisKelamin.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
<Text c="dimmed" ta="center" my="md" fz={{ base: "0.95rem", md: "1rem" }}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
@@ -218,7 +248,7 @@ const state = useProxy(indeksKepuasanState.responden);
withTooltip
labelsPosition="inside"
labelsType="percent"
size={250} // Fixed size in pixels
size={250}
data={donutDataJenisKelamin}
/>
</Center>
@@ -227,7 +257,7 @@ const state = useProxy(indeksKepuasanState.responden);
{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>
<Text fz={{ base: "0.95rem", md: "1rem" }}>{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
@@ -240,9 +270,10 @@ const state = useProxy(indeksKepuasanState.responden);
{/* Chart Rating */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Ulasan</Title>
<Title order={5}>Ulasan</Title>
{donutDataRating.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
<Text c="dimmed" ta="center" my="md" fz={{ base: "0.95rem", md: "1rem" }}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
@@ -267,7 +298,7 @@ const state = useProxy(indeksKepuasanState.responden);
{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' }}>
<Text fz={{ base: "0.85rem", md: "0.95rem" }} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -283,9 +314,10 @@ const state = useProxy(indeksKepuasanState.responden);
{/* Chart Kelompok Umur */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Umur</Title>
<Title order={5}>Umur</Title>
{donutDataKelompokUmur.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
<Text c="dimmed" ta="center" my="md" fz={{ base: "0.95rem", md: "1rem" }}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
@@ -310,7 +342,7 @@ const state = useProxy(indeksKepuasanState.responden);
{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' }}>
<Text fz={{ base: "0.85rem", md: "0.95rem" }} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -326,18 +358,21 @@ const state = useProxy(indeksKepuasanState.responden);
</Box>
</Paper>
</Box>
{/* Modal */}
<Modal opened={opened} onClose={close} title="Ajukan Responden" centered>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<TextInput
label="Nama"
type='text'
type="text"
placeholder="Masukkan nama"
value={state.create.form.name}
onChange={(val) => {
state.create.form.name = val.currentTarget.value;
}}
// label typography
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<TextInput
label="Tanggal"
@@ -347,10 +382,11 @@ const state = useProxy(indeksKepuasanState.responden);
onChange={(val) => {
state.create.form.tanggal = val.currentTarget.value;
}}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Select
key={"jenisKelamin"}
label={"Jenis Kelamin"}
key="jenisKelamin"
label="Jenis Kelamin"
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
value={state.create.form.jenisKelaminId || ""}
onChange={(val) => {
@@ -358,17 +394,19 @@ const state = useProxy(indeksKepuasanState.responden);
}}
data={
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.filter(Boolean)
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
// label typography
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Select
key={"rating_responden"}
label={"Rating"}
key="rating_responden"
label="Rating"
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
value={state.create.form.ratingId || ""}
onChange={(val) => {
@@ -376,17 +414,18 @@ const state = useProxy(indeksKepuasanState.responden);
}}
data={
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.filter(Boolean)
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Select
key={"kelompokUmur"}
label={"Kelompok Umur"}
key="kelompokUmur"
label="Kelompok Umur"
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
value={state.create.form.kelompokUmurId || ""}
onChange={(val) => {
@@ -394,19 +433,16 @@ const state = useProxy(indeksKepuasanState.responden);
}}
data={
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.filter(Boolean)
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
<Button mt={10} bg={colors['blue-button']} onClick={handleSubmit}>
Submit
</Button>
</Stack>
@@ -415,36 +451,47 @@ const state = useProxy(indeksKepuasanState.responden);
</Stack>
);
}
return (
<Stack p={"sm"}>
<Container size="lg" px="md">
<Stack p="sm">
<Container size="lg" px="md">
<Center>
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
{/* Main page title — Title with order */}
<Title order={2} ta="center" c="dark">
Indeks Kepuasan Masyarakat
</Title>
</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>
<Text fz={{ base: "1rem", md: "1.125rem" }} lh={{ base: 1.4, md: 1.6 }} ta="center" c="dimmed" mt="sm">
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" bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
</Center>
</Container>
<Box px={"xl"}>
<Paper p={"lg"} bg={colors.Bg}>
<Paper p={"lg"}>
<Stack gap={"xs"}>
<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" }}>
<Title order={4} ta={{ base: "center", sm: "left" }}>
Pelayanan Terhadap Publik Desa Darmasaba
</Text>
</Title>
<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"]}>
<Text fz={{ base: "0.9rem", md: "1rem" }} fw="bold" c={colors["blue-button"]}>Total Responden</Text>
<Title order={3} ta="end" c={colors["blue-button"]} fw="bold" mt="xs">
{state.findMany.total.toLocaleString('id-ID')}
</Text>
</Title>
</Box>
</Flex>
<BarChart
h={300}
data={barChartData}
@@ -458,21 +505,18 @@ const state = useProxy(indeksKepuasanState.responden);
/>
</Stack>
</Paper>
<Box py={"xl"}>
<Box py="xl">
<SimpleGrid
cols={{
base: 1,
md: 1,
lg: 1,
xl: 3
}}
cols={{ base: 1, md: 1, lg: 1, xl: 3 }}
>
{/* Chart Jenis Kelamin */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Jenis Kelamin</Title>
<Title order={5}>Jenis Kelamin</Title>
{donutDataJenisKelamin.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
<Text c="dimmed" ta="center" my="md" fz={{ base: "0.95rem", md: "1rem" }}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
@@ -494,7 +538,7 @@ const state = useProxy(indeksKepuasanState.responden);
{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>
<Text fz={{ base: "0.95rem", md: "1rem" }}>{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
@@ -507,9 +551,10 @@ const state = useProxy(indeksKepuasanState.responden);
{/* Chart Rating */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Ulasan</Title>
<Title order={5}>Ulasan</Title>
{donutDataRating.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
<Text c="dimmed" ta="center" my="md" fz={{ base: "0.95rem", md: "1rem" }}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
@@ -534,7 +579,7 @@ const state = useProxy(indeksKepuasanState.responden);
{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' }}>
<Text fz={{ base: "0.85rem", md: "0.95rem" }} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -550,9 +595,10 @@ const state = useProxy(indeksKepuasanState.responden);
{/* Chart Kelompok Umur */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Umur</Title>
<Title order={5}>Umur</Title>
{donutDataKelompokUmur.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
<Text c="dimmed" ta="center" my="md" fz={{ base: "0.95rem", md: "1rem" }}>
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
@@ -577,7 +623,7 @@ const state = useProxy(indeksKepuasanState.responden);
{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' }}>
<Text fz={{ base: "0.85rem", md: "0.95rem" }} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -593,18 +639,20 @@ const state = useProxy(indeksKepuasanState.responden);
</Box>
</Paper>
</Box>
{/* Modal */}
<Modal opened={opened} onClose={close} title="Ajukan Responden" centered>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<TextInput
label="Nama"
type='text'
type="text"
placeholder="Masukkan nama"
value={state.create.form.name}
onChange={(val) => {
state.create.form.name = val.currentTarget.value;
}}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<TextInput
label="Tanggal Pengisian"
@@ -614,10 +662,11 @@ const state = useProxy(indeksKepuasanState.responden);
onChange={(val) => {
state.create.form.tanggal = val.currentTarget.value;
}}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Select
key={"jenisKelamin"}
label={"Jenis Kelamin"}
key="jenisKelamin"
label="Jenis Kelamin"
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
value={state.create.form.jenisKelaminId || ""}
onChange={(val) => {
@@ -625,17 +674,18 @@ const state = useProxy(indeksKepuasanState.responden);
}}
data={
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.filter(Boolean)
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Select
key={"rating_responden"}
label={"Rating"}
key="rating_responden"
label="Rating"
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
value={state.create.form.ratingId || ""}
onChange={(val) => {
@@ -643,17 +693,18 @@ const state = useProxy(indeksKepuasanState.responden);
}}
data={
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.filter(Boolean)
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Select
key={"kelompokUmur"}
label={"Kelompok Umur"}
key="kelompokUmur"
label="Kelompok Umur"
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
value={state.create.form.kelompokUmurId || ""}
onChange={(val) => {
@@ -661,19 +712,16 @@ const state = useProxy(indeksKepuasanState.responden);
}}
data={
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.filter(Boolean)
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
labelProps={{ style: { fontSize: '0.95rem', lineHeight: '1.4' } } as any}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
<Button mt={10} bg={colors['blue-button']} onClick={handleSubmit}>
Submit
</Button>
</Stack>

View File

@@ -12,7 +12,8 @@ import {
SimpleGrid,
Stack,
Text,
TextInput
TextInput,
Title
} from '@mantine/core';
import { IconSend2 } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -55,7 +56,7 @@ function Page() {
const submitForms = async () => {
const { create } = permohonanInformasiPublikState.statepermohonanInformasiPublik;
const hasil = await create.create(); // tunggu hasilnya
const hasil = await create.create();
if (hasil) {
router.push('/darmasaba/permohonan/berhasil');
}
@@ -67,14 +68,17 @@ function Page() {
<BackButton />
</Box>
<Text
{/* MAIN PAGE TITLE */}
<Title
order={1}
ta="center"
fz={{ base: '2rem', md: '2.5rem' }}
fz={{ base: '1.8rem', sm: '2.2rem', md: '2.6rem' }}
lh={1.2}
c={colors['blue-button']}
fw="bold"
style={{ fontWeight: 700 }}
>
Permohonan Informasi Publik
</Text>
</Title>
<Box px={{ base: 'md', md: 100 }}>
<Stack gap="xl">
@@ -85,15 +89,18 @@ function Page() {
shadow="sm"
bg={colors['white-trans-1']}
>
<Text
{/* SUBTITLE */}
<Title
order={2}
pb={30}
ta="center"
fw="bold"
fz={{ base: 'h4', md: 'h3' }}
fz={{ base: '1.4rem', md: '1.8rem' }}
lh={1.3}
c={colors['blue-button']}
style={{ fontWeight: 700 }}
>
Tata Cara Permohonan
</Text>
</Title>
<SimpleGrid pb={30} cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
{steps.map((v) => (
@@ -116,27 +123,38 @@ function Page() {
c={colors['blue-button']}
ta="center"
fw="bold"
fz="h3"
fz="h2"
lh={1}
>
{v.number}
</Text>
</ActionIcon>
</Center>
<Title
order={4}
ta="center"
c={colors['white-1']}
fz="lg"
lh={1.3}
style={{ fontWeight: 700 }}
>
{v.title}
</Title>
<Text
ta="center"
c={colors['white-1']}
fw="bold"
fz="lg"
fz="sm"
lh={1.4}
>
{v.title}
</Text>
<Text ta="center" c={colors['white-1']} fz="sm">
{v.desc}
</Text>
</Stack>
</Paper>
))}
</SimpleGrid>
<Group justify="center">
<Paper
p="xl"
@@ -148,15 +166,20 @@ function Page() {
maw={800}
>
<Stack gap="md">
<Text
fw="bold"
fz={{ base: 'h4', md: 'h3' }}
{/* FORM TITLE */}
<Title
order={2}
ta="center"
fz={{ base: '1.4rem', md: '1.8rem' }}
lh={1.3}
c={colors['blue-button']}
style={{ fontWeight: 700 }}
>
Formulir Permohonan Informasi
</Text>
</Title>
{/* INPUTS */}
<TextInput
label="Nama Lengkap"
placeholder="Masukkan nama lengkap Anda"
@@ -166,6 +189,7 @@ function Page() {
val.target.value;
}}
/>
<TextInput
label="Nomor Induk Kependudukan (NIK)"
placeholder="Masukkan NIK"
@@ -175,6 +199,7 @@ function Page() {
val.target.value;
}}
/>
<TextInput
label="Nomor Telepon"
placeholder="Masukkan nomor telepon aktif"
@@ -184,6 +209,7 @@ function Page() {
val.target.value;
}}
/>
<TextInput
label="Alamat Lengkap"
placeholder="Masukkan alamat sesuai identitas"
@@ -193,6 +219,7 @@ function Page() {
val.target.value;
}}
/>
<TextInput
label="Alamat Email"
placeholder="Masukkan alamat email aktif"
@@ -209,12 +236,14 @@ function Page() {
val.id;
}}
/>
<MemperolehInformasi
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.caraMemperolehInformasiId =
val.id;
}}
/>
<MemperolehSalinan
onChange={(val) => {
permohonanInformasiPublikState.statepermohonanInformasiPublik.create.form.caraMemperolehSalinanInformasiId =
@@ -241,6 +270,7 @@ function Page() {
Kirim Permohonan
</Button>
</Group>
</Stack>
</Paper>
</Group>

View File

@@ -12,6 +12,7 @@ import {
Stack,
Text,
TextInput,
Title,
Tooltip,
} from '@mantine/core';
import {
@@ -56,13 +57,8 @@ function Page() {
const router = useRouter();
const submit = async () => {
const { create } = stateKeberatan;
const hasil = await create.create(); // tunggu hasilnya
if (hasil) {
router.push('/darmasaba/permohonan/berhasil');
}
const hasil = await stateKeberatan.create.create();
if (hasil) router.push('/darmasaba/permohonan/berhasil');
};
return (
@@ -72,15 +68,16 @@ function Page() {
</Box>
<Stack align="center" px={{ base: 'md', md: 100 }}>
<Text
<Title
order={1}
ta="center"
fz={{ base: '2rem', md: '2.8rem' }}
fz={{ base: '1.8rem', md: '2.6rem' }}
lh={1.2}
c={colors['blue-button']}
fw={800}
style={{ letterSpacing: '-0.5px' }}
style={{ letterSpacing: -0.5 }}
>
Permohonan Keberatan Informasi Publik
</Text>
</Title>
<Paper
p="xl"
@@ -90,26 +87,36 @@ function Page() {
withBorder
>
<Stack gap="xl">
{/* Tentang */}
<Box>
<Text fw={700} fz={{ base: 'lg', md: 'xl' }} mb={8}>
<Title order={3} fz={{ base: 'lg', md: 'xl' }} lh={1.3} mb={8}>
Tentang Permohonan Keberatan
</Text>
<Text ta="justify" fz={{ base: 'sm', md: 'md' }} lh={1.6}>
</Title>
<Text
ta="justify"
fz={{ base: 'sm', md: 'md' }}
lh={1.7}
c="black"
>
Jika Anda merasa permohonan informasi tidak ditanggapi dengan
baik atau ditolak, Anda berhak mengajukan keberatan melalui
formulir berikut.
</Text>
</Box>
{/* Alur */}
<Stack>
<Text
<Title
order={3}
ta="center"
fw={700}
fz={{ base: 'xl', md: '2xl' }}
style={{ letterSpacing: '-0.5px' }}
lh={1.2}
style={{ letterSpacing: -0.5 }}
>
Alur Pengajuan Keberatan
</Text>
</Title>
<SimpleGrid cols={{ base: 1, md: 4 }} spacing="lg">
{data.map((v) => (
@@ -124,15 +131,23 @@ function Page() {
<Center>
<v.icon size={48} color={colors['white-1']} />
</Center>
<Text
ta="center"
c={colors['white-1']}
fw={700}
fz="lg"
lh={1.3}
>
{v.title}
</Text>
<Text ta="center" c={colors['white-1']} fz="sm">
<Text
ta="center"
c={colors['white-1']}
fz="sm"
lh={1.6}
>
{v.desc}
</Text>
</Stack>
@@ -141,6 +156,7 @@ function Page() {
</SimpleGrid>
</Stack>
{/* Form */}
<Group justify="center">
<Paper
p="xl"
@@ -152,14 +168,16 @@ function Page() {
w="100%"
>
<Stack gap="md">
<Text
<Title
order={3}
ta="center"
fw={700}
fz={{ base: 'lg', md: 'xl' }}
ta="center"
lh={1.3}
mb={4}
>
Formulir Keberatan
</Text>
</Title>
<TextInput
label="Nama Lengkap"
@@ -196,7 +214,7 @@ function Page() {
/>
<Box>
<Text fw={600} fz="sm" mb={6}>
<Text fw={600} fz="sm" lh={1.4} mb={6}>
Alasan Keberatan
</Text>
<PPIDTextEditor
@@ -222,11 +240,13 @@ function Page() {
</Paper>
</Group>
{/* Kontak */}
<Stack gap={4} pt="lg" align="center">
<Text fw={700} fz="lg">
<Title order={4} fw={700} fz="lg" lh={1.3}>
Kontak PPID
</Text>
<Text fz="sm" c="dimmed">
</Title>
<Text fz="sm" lh={1.5} c="dimmed" ta="center">
Email: desadarmasaba@badungkab.go.id | WhatsApp: 081-xxx-xxx-xxx
</Text>
</Stack>

View File

@@ -0,0 +1,257 @@
'use client'
import stateProfilePPID from '@/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID';
import colors from '@/con/colors';
import {
Box,
Center,
Divider,
Flex,
Image,
List,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import {
IconBuildingCommunity,
IconTargetArrow,
IconTimeline,
IconUser,
} from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import ScrollToTopButton from '@/app/darmasaba/_com/scrollToTopButton';
function Page() {
const allList = useProxy(stateProfilePPID);
useShallowEffect(() => {
allList.profile.load('edit');
}, []);
// LOADING SKELETON
if (!allList.profile.data)
return (
<Stack bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={80} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Paper p="xl" bg={colors['white-trans-1']}>
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={i} h={40} mb="sm" />
))}
</Paper>
</Box>
</Stack>
);
const dataArray = Array.isArray(allList.profile.data)
? allList.profile.data
: [allList.profile.data];
return (
<Box>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
{/* Back Button */}
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
{/* Page Title */}
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors['blue-button']}
fz={{ base: '2rem', md: '2.7rem', lg: '3.2rem', xl: '3.6rem' }}
lh={{ base: 1.1, md: 1.1 }}
fw={900}
>
Profil PPID Desa Darmasaba
</Title>
</Box>
{dataArray.map((item) => (
<Box key={item.id} px={{ base: 'md', md: 100 }}>
<Paper p="xl" bg={colors['white-trans-1']} radius="lg" shadow="xl">
{/* LOGO & TITLE */}
<Box px={{ base: 'md', md: 100 }}>
<Center>
<Image
loading="lazy"
src="/darmasaba-icon.png"
h={{ base: 70, md: 120 }}
w={{ base: 70, md: 120 }}
alt="Logo Desa"
/>
</Center>
<Title
order={2}
ta="center"
fz={{ base: '1.4rem', md: '2.2rem', lg: '2.6rem', xl: '3rem' }}
lh={1.1}
fw={800}
mt="md"
>
Pejabat Pengelola Informasi dan Dokumentasi
</Title>
</Box>
<Divider my="lg" />
{/* GRID BLOCK */}
<Box px={{ base: 0, md: 50 }} pb={40}>
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
{/* FOTO + NAMA */}
<Box px={{ base: 0, md: 50 }}>
<Paper bg={colors['white-trans-1']} radius="xl" shadow="md" withBorder>
<Stack gap={0}>
<Image
pt={{ base: 0, md: 100 }}
px="lg"
src={
item.image?.link
? `${item.image.link}?t=${Date.now()}`
: '/perbekel.png'
}
alt="Foto Pimpinan"
radius="lg"
onError={(e) => (e.currentTarget.src = '/perbekel.png')}
loading="lazy"
/>
<Paper
bg={colors['blue-button']}
px="lg"
radius="0 0 var(--mantine-radius-xl) var(--mantine-radius-xl)"
className="glass3"
py={{ base: 20, md: 50 }}
>
<Title
order={3}
ta="center"
c={colors['white-1']}
fz={{ base: '1.4rem', md: '2.2rem' }}
lh={1.1}
fw={900}
>
{item.name}
</Title>
</Paper>
</Stack>
</Paper>
</Box>
{/* BIOGRAFI & RIWAYAT */}
<Box>
<Stack gap="xl">
{/* BIO */}
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconUser size={28} />
<Title order={3} fz={{ base: '1.3rem', md: '1.6rem' }} lh={1.2} fw={800}>
Biografi
</Title>
</Flex>
<Box px={20}>
<Text
fz={{ base: '1rem', md: '1.1rem', lg: '1.2rem' }}
lh={1.6}
ta="justify"
dangerouslySetInnerHTML={{ __html: item.biodata }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</Box>
{/* RIWAYAT */}
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconTimeline size={28} />
<Title order={3} fz={{ base: '1.3rem', md: '1.6rem' }} lh={1.2} fw={800}>
Riwayat Karir
</Title>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text
fz={{ base: '1rem', md: '1.1rem', lg: '1.2rem' }}
lh={1.6}
ta="justify"
dangerouslySetInnerHTML={{ __html: item.riwayat }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</List>
</Box>
</Stack>
</Box>
</SimpleGrid>
</Box>
{/* ORGANISASI */}
<Box pb={40}>
<Flex align="center" gap="sm" mb="sm">
<IconBuildingCommunity size={28} />
<Title order={3} fz={{ base: '1.3rem', md: '1.6rem' }} lh={1.2} fw={800}>
Pengalaman Organisasi
</Title>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text
fz={{ base: '1rem', md: '1.1rem' }}
lh={1.6}
ta="justify"
dangerouslySetInnerHTML={{ __html: item.pengalaman }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</List>
</Box>
{/* PROGRAM UNGGULAN */}
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconTargetArrow size={28} />
<Title order={3} fz={{ base: '1.3rem', md: '1.6rem' }} lh={1.2} fw={800}>
Program Unggulan
</Title>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text
fz={{ base: '1rem', md: '1.1rem' }}
lh={1.6}
ta="justify"
dangerouslySetInnerHTML={{ __html: item.unggulan }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</List>
</Box>
</Paper>
</Box>
))}
</Stack>
{/* tombol scroll */}
<ScrollToTopButton />
</Box>
);
}
export default Page;

View File

@@ -1,152 +0,0 @@
'use client'
import stateProfilePPID from '@/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID';
import colors from '@/con/colors';
import { Box, Center, Divider, Flex, Image, List, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconBuildingCommunity, IconTargetArrow, IconTimeline, IconUser } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import ScrollToTopButton from '@/app/darmasaba/_com/scrollToTopButton';
function Page() {
const allList = useProxy(stateProfilePPID)
useShallowEffect(() => {
allList.profile.load("edit")
}, [])
if (!allList.profile.data) return (
<Stack bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={80} />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Paper p="xl" bg={colors['white-trans-1']}>
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={i} h={40} mb="sm" />
))}
</Paper>
</Box>
</Stack>
)
const dataArray = Array.isArray(allList.profile.data)
? allList.profile.data
: [allList.profile.data]
return (
<Box>
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Text ta="center" fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }} c={colors["blue-button"]} fw="bold">
Profil PPID Desa Darmasaba
</Text>
</Box>
{dataArray.map((item) => (
<Box key={item.id} px={{ base: "md", md: 100 }}>
<Paper p="xl" bg={colors['white-trans-1']} radius="lg" shadow="xl">
<Box px={{ base: "md", md: 100 }}>
<Center>
<Image loading='lazy' src="/darmasaba-icon.png" h={{ base: 70, md: 120 }} w={{ base: 70, md: 120 }} alt="Logo Desa" />
</Center>
<Text ta="center" fz={{ base: "1.2rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
Pejabat Pengelola Informasi dan Dokumentasi
</Text>
</Box>
<Divider my="lg" />
<Box px={{ base: 0, md: 50 }} pb={40}>
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
<Box px={{ base: 0, md: 50 }}>
<Paper bg={colors['white-trans-1']} radius="xl" shadow="md" withBorder>
<Stack gap={0}>
<Image
pt={{ base: 0, md: 100 }}
px="lg"
src={item.image?.link ? `${item.image.link}?t=${Date.now()}` : "/perbekel.png"}
alt="Foto Pimpinan"
radius="lg"
onError={(e) => e.currentTarget.src = "/perbekel.png"}
loading="lazy"
/>
<Paper
bg={colors['blue-button']}
px="lg"
radius="0 0 var(--mantine-radius-xl) var(--mantine-radius-xl)"
className="glass3"
py={{ base: 20, md: 50 }}
>
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "xl", md: "h2" }}>
{item.name}
</Text>
</Paper>
</Stack>
</Paper>
</Box>
<Box>
<Stack gap="xl">
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconUser size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Biografi</Text>
</Flex>
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.biodata }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
</Box>
</Box>
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconTimeline size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Riwayat Karir</Text>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} dangerouslySetInnerHTML={{ __html: item.riwayat }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
</Box>
</List>
</Box>
</Stack>
</Box>
</SimpleGrid>
</Box>
<Box pb={40}>
<Flex align="center" gap="sm" mb="sm">
<IconBuildingCommunity size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.125rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.pengalaman }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
</Box>
</List>
</Box>
<Box>
<Flex align="center" gap="sm" mb="sm">
<IconTargetArrow size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Program Unggulan</Text>
</Flex>
<List spacing="xs" size="sm">
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.125rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.unggulan }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
</Box>
</List>
</Box>
</Paper>
</Box>
))}
</Stack>
{/* Tombol Scroll ke Atas */}
<ScrollToTopButton />
</Box>
)
}
export default Page

View File

@@ -27,7 +27,6 @@ function DetailPegawaiUser() {
statePegawai.findUnique.load(params?.id as string);
}, []);
if (!statePegawai.findUnique.data) {
return (
<Stack py="lg">
@@ -52,7 +51,7 @@ function DetailPegawaiUser() {
}}
>
<IconArrowBack size={22} color={colors['blue-button']} />
<Text c={colors['blue-button']} fw={500}>
<Text fz={{ base: 'sm', md: 'md' }} lh="1.4" fw={500} c={colors['blue-button']}>
Kembali
</Text>
</Box>
@@ -65,9 +64,7 @@ function DetailPegawaiUser() {
radius="lg"
shadow="sm"
bg="white"
style={{
border: '1px solid #eaeaea',
}}
style={{ border: '1px solid #eaeaea' }}
>
<Stack align="center" gap="md">
{/* Foto Profil */}
@@ -84,10 +81,23 @@ function DetailPegawaiUser() {
{/* Nama & Jabatan */}
<Stack align="center" gap={2}>
<Title order={3} fw={700} c={colors['blue-button']}>
<Title
order={2}
c={colors['blue-button']}
fw={700}
fz={{ base: 'xl', md: '28px' }}
lh="1.2"
ta="center"
>
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
</Title>
<Text fz="sm" c="dimmed">
<Text
fz={{ base: 'sm', md: 'md' }}
lh="1.4"
c="dimmed"
ta="center"
>
{data.posisi?.nama || 'Posisi tidak tersedia'}
</Text>
</Stack>
@@ -105,10 +115,10 @@ function DetailPegawaiUser() {
value={
data.tanggalMasuk
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
day: '2-digit',
month: 'long',
year: 'numeric',
})
: '-'
}
/>
@@ -123,7 +133,7 @@ function DetailPegawaiUser() {
);
}
/* Komponen kecil untuk menampilkan baris informasi */
/* Komponen Baris Informasi */
function InfoRow({
label,
value,
@@ -137,11 +147,18 @@ function InfoRow({
}) {
return (
<Box>
<Text fz="sm" fw={600} c="dark">
<Text
fz={{ base: 'sm', md: 'md' }}
fw={600}
lh="1.3"
c="dark"
>
{label}
</Text>
<Text
fz="sm"
fz={{ base: 'sm', md: 'md' }}
lh="1.5"
c={valueColor || 'dimmed'}
style={{
whiteSpace: multiline ? 'normal' : 'nowrap',

View File

@@ -59,10 +59,11 @@ export default function Page() {
ta="center"
c={colors['blue-button']}
fz={{ base: 28, md: 36, lg: 44 }}
lh={{ base: 1.05, md: 1.03 }}
>
Struktur Organisasi PPID
</Title>
<Text ta="center" c="black" maw={800}>
<Text ta="center" c="black" maw={800} fz={{ base: 13, md: 15 }} lh={1.45}>
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
untuk melihat detail atau klik node untuk fokus tampilan.
</Text>
@@ -90,7 +91,7 @@ function StrukturOrganisasiPPID() {
const debouncedSearch = useRef(
debounce((value: string) => {
setSearchQuery(value)
}, 400)
}, 1000)
).current
useEffect(() => {
@@ -105,8 +106,8 @@ function StrukturOrganisasiPPID() {
<Center py={48}>
<Stack align="center" gap="sm">
<Loader size="lg" />
<Text fw={600}>Memuat struktur organisasi</Text>
<Text c="dimmed" size="sm">
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.2}>Memuat struktur organisasi</Text>
<Text c="dimmed" fz={{ base: 12, md: 13 }} lh={1.4}>
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
</Text>
</Stack>
@@ -132,10 +133,10 @@ function StrukturOrganisasiPPID() {
<Center>
<IconUsers size={56} />
</Center>
<Title order={3} mt="md">
<Title order={3} mt="md" fz={{ base: 16, md: 18 }} lh={1.15}>
Data pegawai belum tersedia
</Title>
<Text c="dimmed" mt="xs">
<Text c="dimmed" mt="xs" fz={{ base: 13, md: 14 }} lh={1.4}>
Belum ada data pegawai yang tercatat untuk PPID.
</Text>
<Group justify="center" mt="lg">
@@ -232,11 +233,18 @@ function StrukturOrganisasiPPID() {
{/* 🔍 Controls */}
<Paper
shadow="xs"
w={{
base: '100%', // Mobile: 100%
sm: '40%', // Tablet: 95%
md: '39%', // Desktop: 70%
lg: '38%', // Desktop L: 60%
xl: '37%', // 4K: 50%
'2xl': '36%', // Ultra-wide: 45%
}}
p="md"
radius="md"
style={{
background: colors['blue-button'],
width: '100%', // ⬅️ penting
background: colors['blue-button'], // ⬅️ penting
maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow
}}
@@ -269,30 +277,33 @@ function StrukturOrganisasiPPID() {
fontSize: '0.875rem',
padding: '6px 12px',
minHeight: 'auto',
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
flexShrink: 0,
},
}}
style={{ width: '100%' }} // 👈 penting
>
<TabsList
style={{
display: 'flex',
overflowX: 'auto',
overflowY: 'hidden', // 👈 tambahkan ini
overflowY: 'hidden',
gap: '4px',
paddingBottom: '4px',
flexWrap: 'nowrap',
WebkitOverflowScrolling: 'touch', // 👈 smooth scroll di iOS
scrollbarWidth: 'thin', // 👈 scrollbar tipis di Firefox
msOverflowStyle: '-ms-autohiding-scrollbar', // 👈 untuk IE/Edge
WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'thin',
msOverflowStyle: '-ms-autohiding-scrollbar',
maxWidth: '100%',
scrollBehavior: 'smooth', // 👈 smooth scroll
}}
>
<TabsTab
value="zoom-out"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
style={{ flexShrink: 0 }}
>
Zoom Out
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
</TabsTab>
<Box
@@ -301,7 +312,6 @@ function StrukturOrganisasiPPID() {
px={12}
py={6}
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '6px',
minWidth: 60,
@@ -310,10 +320,12 @@ function StrukturOrganisasiPPID() {
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
whiteSpace: 'nowrap', // 👈 mencegah text wrap
whiteSpace: 'nowrap',
}}
>
{Math.round(scale * 100)}%
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
{Math.round(scale * 100)}%
</Text>
</Box>
<TabsTab
@@ -322,7 +334,7 @@ function StrukturOrganisasiPPID() {
leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
>
Zoom In
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
</TabsTab>
<TabsTab
@@ -330,7 +342,7 @@ function StrukturOrganisasiPPID() {
onClick={resetZoom}
style={{ flexShrink: 0 }}
>
Reset
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
</TabsTab>
<TabsTab
@@ -345,7 +357,9 @@ function StrukturOrganisasiPPID() {
}
style={{ flexShrink: 0 }}
>
{isFullscreen ? 'Exit' : 'Fullscreen'}
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
{isFullscreen ? 'Exit' : 'Fullscreen'}
</Text>
</TabsTab>
</TabsList>
</Tabs>
@@ -451,18 +465,17 @@ function NodeCard({ node, router }: any) {
{/* Name */}
<Text
fw={700}
size="sm"
ta="center"
c={colors['blue-button']}
lineClamp={2}
fz={{ base: 13, md: 15 }}
lh={1.2}
style={{
// fontSize: 'clamp(12px, 4vw, 16px)', // 👈 responsif font size
minHeight: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
lineHeight: 1.3,
}}
>
{name}
@@ -470,18 +483,18 @@ function NodeCard({ node, router }: any) {
{/* Title/Position */}
<Text
size="xs"
c="dimmed"
ta="center"
fw={500}
lineClamp={2}
fz={{ base: 12, md: 13 }}
lh={1.3}
style={{
minHeight: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
wordBreak: 'break-word',
lineHeight: 1.2,
}}
>
{title}
@@ -504,7 +517,7 @@ function NodeCard({ node, router }: any) {
fontWeight: 600,
}}
>
Lihat Detail
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
</Button>
)}
</Stack>

View File

@@ -1,7 +1,18 @@
'use client'
import stateVisiMisiPPID from '@/app/admin/(dashboard)/_state/ppid/visi_misi_ppid/visimisiPPID';
import colors from '@/con/colors';
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Divider, Transition } from '@mantine/core';
import {
Box,
Center,
Image,
Paper,
Skeleton,
Stack,
Text,
Divider,
Transition,
Title
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
import { IconSparkles } from '@tabler/icons-react';
@@ -9,6 +20,7 @@ import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const allList = useProxy(stateVisiMisiPPID);
useShallowEffect(() => {
allList.findById.load("1");
}, []);
@@ -35,7 +47,7 @@ function Page() {
{dataArray.map((item) => (
<Box key={item.id} px={{ base: 'md', md: 100 }}>
<Transition mounted={true} transition="fade" duration={500} timingFunction="ease">
<Transition mounted transition="fade" duration={500} timingFunction="ease">
{(styles) => (
<Paper
style={styles}
@@ -46,56 +58,93 @@ function Page() {
withBorder
>
<Stack gap="xl">
{/* ==== MOTTO SECTION ==== */}
<Box>
<Center mb="md">
<Image src="/darmasaba-icon.png" w={{ base: 80, md: 130 }} alt="Logo Desa Darmasaba" loading='lazy' />
<Image
src="/darmasaba-icon.png"
w={{ base: 80, md: 130 }}
alt="Logo Desa Darmasaba"
loading="lazy"
/>
</Center>
<Text
<Title
order={2}
ta="center"
fz={{ base: 28, md: 36 }}
fw={800}
fz={{ base: 26, md: 34 }}
lh={1.2}
c={colors['blue-button']}
>
Moto PPID Desa Darmasaba
</Text>
<Text ta="center" fz={{ base: 16, md: 20 }} mt="xs">
</Title>
<Text
ta="center"
fz={{ base: 15, md: 18 }}
lh={1.5}
c={"black"}
mt="xs"
>
Memberikan informasi yang cepat, mudah, tepat, dan transparan
</Text>
</Box>
<Divider my="sm" labelPosition="center" label={<IconSparkles size={18} />} />
<Divider
my="sm"
labelPosition="center"
label={<IconSparkles size={18} />}
/>
{/* ==== VISI SECTION ==== */}
<Box>
<Text ta="center" fz={{ base: 24, md: 30 }} fw={800}
c={colors['blue-button']} mb="sm">
Visi PPID
</Text>
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.7}
<Title
order={3}
ta="center"
fz={{ base: 22, md: 28 }}
lh={1.2}
c={colors['blue-button']}
mb="sm"
>
Visi PPID
</Title>
<Text
ta="center"
fz={{ base: 15, md: 18 }}
lh={1.7}
dangerouslySetInnerHTML={{ __html: item.visi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
<Divider my="sm" />
{/* ==== MISI SECTION ==== */}
<Box>
<Text ta="center" fz={{ base: 24, md: 30 }} fw={800}
c={colors['blue-button']} mb="sm">
<Title
order={3}
ta="center"
fz={{ base: 22, md: 28 }}
lh={1.2}
c={colors['blue-button']}
mb="sm"
>
Misi PPID
</Text>
</Title>
<Box px={{ base: 'md', md: 100 }}>
<Text
ta={"justify"}
fz={{ base: 'md', md: 'lg' }}
ta="justify"
fz={{ base: 15, md: 18 }}
lh={1.7}
dangerouslySetInnerHTML={{ __html: item.misi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/>
</Box>
</Box>
</Stack>
</Paper>
)}

View File

@@ -2,7 +2,7 @@
'use client';
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import colors from "@/con/colors";
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text } from "@mantine/core";
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text, Title } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import { IconArrowRight, IconAward } from "@tabler/icons-react";
import { useTransitionRouter } from "next-view-transitions";
@@ -20,11 +20,21 @@ export default function Page() {
<Stack align="center" gap="sm">
<Group gap="xs">
<IconAward size={40} color={colors["blue-button"]} />
<Text fz={{ base: "2rem", md: "3.2rem" }} fw={800} variant="gradient" gradient={{ from: "#1C6EA4", to: "#69BFF8" }}>
<Title
order={1}
fw={800}
c={colors["blue-button"]}
ta="center"
>
Penghargaan Desa
</Text>
</Title>
</Group>
<Text fz="lg" c="dimmed" ta="center">
<Text
fz={{ base: "sm", md: "md" }}
lh={{ base: "1.5", md: "1.6" }}
c="black"
ta="center"
>
Desa Darmasaba berhasil meraih beragam penghargaan bergengsi yang mencerminkan dedikasi dan kerja keras masyarakat dalam membangun desa yang maju dan berkelanjutan.
</Text>
</Stack>
@@ -61,10 +71,8 @@ function Slider() {
const data = state.findMany.data || [];
const loading = state.findMany.loading;
// Triple data untuk infinite loop (desktop only)
const slidesData = mobile ? data : [...data, ...data, ...data];
// Auto-scroll animation untuk desktop
useEffect(() => {
if (loading || !containerRef.current || data.length === 0 || mobile) return;
@@ -72,7 +80,6 @@ function Slider() {
const slideWidth = container.scrollWidth / slidesData.length;
const originalLength = data.length;
// Start dari middle set
scrollPosRef.current = slideWidth * originalLength;
container.scrollLeft = scrollPosRef.current;
@@ -88,7 +95,6 @@ function Slider() {
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
scrollPosRef.current += speed;
// Reset untuk infinite loop
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
scrollPosRef.current -= slideWidth * originalLength;
}
@@ -100,7 +106,6 @@ function Slider() {
} else {
scrollPosRef.current = container.scrollLeft;
// Momentum untuk drag release
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
scrollPosRef.current += velocityRef.current;
velocityRef.current *= VELOCITY_DECAY;
@@ -185,7 +190,7 @@ function Slider() {
return (
<Stack align="center" py="xl">
<IconAward size={56} color={colors["blue-button"]} />
<Text fz="lg" fw={600} c="dimmed">
<Text fz={{ base: "sm", md: "md" }} fw={600} c="dimmed" ta="center">
Belum ada penghargaan yang ditambahkan
</Text>
</Stack>
@@ -213,7 +218,6 @@ function Slider() {
msOverflowStyle: "none",
}}
>
{/* Blur edges - hanya untuk desktop */}
{!mobile && (
<>
<Box
@@ -291,8 +295,8 @@ function Slider() {
style={{ borderRadius: 16 }}
/>
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
<Text
fz={{ base: "lg", sm: "xl", md: "1.5rem" }}
<Title
order={3}
fw={700}
ta="center"
c="white"
@@ -300,7 +304,7 @@ function Slider() {
style={{ textShadow: "0 2px 8px rgba(0,0,0,0.8)" }}
>
{item.name}
</Text>
</Title>
<Group justify="center">
<Button
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}

View File

@@ -168,6 +168,7 @@ export default function ModernNewsNotification({
position: "fixed",
bottom: "24px",
right: "24px",
zIndex: 1
}}
>
<ActionIcon

View File

@@ -74,7 +74,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
<Tooltip label="Profil Saya" position="bottom" withArrow>
<ActionIcon
onClick={() => {
next.push("/admin/landing-page/profile/program-inovasi")
next.push("/admin/landing-page/profil/program-inovasi")
}}
color={colors["blue-button"]}
radius="xl"

View File

@@ -100,6 +100,7 @@ const NewsReaderLanding = () => {
borderBottomRightRadius: '20px',
borderTopRightRadius: '20px',
transition: 'all 0.3s ease',
zIndex: 1
}}
>
{isPointerMode ? <IconMusicOff /> : <IconMusic />}

View File

@@ -5,7 +5,20 @@ import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
import APBDesProgress from '@/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress'
import { transformAPBDesData } from '@/app/darmasaba/(tambahan)/apbdes/lib/types'
import colors from '@/con/colors'
import { ActionIcon, BackgroundImage, Box, Button, Center, Group, Loader, Select, SimpleGrid, Stack, Text } from '@mantine/core'
import {
ActionIcon,
BackgroundImage,
Box,
Button,
Center,
Group,
Loader,
Select,
SimpleGrid,
Stack,
Text,
Title,
} from '@mantine/core'
import { IconDownload } from '@tabler/icons-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
@@ -38,17 +51,15 @@ function Apbdes() {
const dataAPBDes = state.findMany.data || []
const years = Array.from(new Set(dataAPBDes.map((item: any) => item.tahun)))
.sort((a, b) => b - a) // urutkan descending
.sort((a, b) => b - a)
.map(year => ({ value: year.toString(), label: `Tahun ${year}` }))
// Pilih tahun pertama sebagai default jika belum ada yang dipilih
useEffect(() => {
if (years.length > 0 && !selectedYear) {
setSelectedYear(years[0].value)
}
}, [years, selectedYear])
// Transform and filter data based on selected year
const currentApbdes = dataAPBDes.length > 0
? transformAPBDesData(dataAPBDes.find(item => item?.tahun?.toString() === selectedYear) || dataAPBDes[0])
: null
@@ -57,17 +68,31 @@ function Apbdes() {
return (
<Stack p="sm" gap="xl" bg={colors.Bg}>
<Box mt={"xl"}>
{/* 📌 HEADING */}
<Box mt="xl">
<Stack gap="sm">
<Text c={colors["blue-button"]} ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
<Title
order={1}
ta="center"
c={colors['blue-button']}
fz={{ base: '2rem', md: '3.6rem' }}
lh={{ base: 1.2, md: 1.1 }}
>
{textHeading.title}
</Text>
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
</Title>
<Text
ta="center"
fz={{ base: '1rem', md: '1.25rem' }}
lh={{ base: 1.5, md: 1.55 }}
c="black"
>
{textHeading.des}
</Text>
</Stack>
</Box>
{/* Button Lihat Semua */}
<Group justify="center">
<Button
component={Link}
@@ -81,32 +106,39 @@ function Apbdes() {
</Button>
</Group>
{/* 🔥 COMBOBOX UNTUK PILIH TAHUN */}
{/* COMBOBOX */}
<Box px={{ base: 'md', md: 100 }}>
<Select
label="Pilih Tahun APBDes"
label={<Text fw={600} fz="sm">Pilih Tahun APBDes</Text>}
placeholder="Pilih tahun"
value={selectedYear}
onChange={setSelectedYear}
data={years}
w={{ base: '100%', sm: 200 }}
w={{ base: '100%', sm: 220 }}
searchable
clearable
nothingFoundMessage="Tidak ada tahun tersedia"
/>
</Box>
{/* Progress */}
{currentApbdes ? (
<>
<APBDesProgress apbdesData={currentApbdes} />
</>
<APBDesProgress apbdesData={currentApbdes} />
) : (
<Box px={{ base: 'md', md: 100 }} py="md">
<Text c="dimmed">Tidak ada data APBDes untuk tahun yang dipilih.</Text>
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
Tidak ada data APBDes untuk tahun yang dipilih.
</Text>
</Box>
)}
<SimpleGrid mx={{ base: 'md', md: 100 }} cols={{ base: 1, sm: 3 }} spacing="lg" pb={"xl"}>
{/* GRID */}
<SimpleGrid
mx={{ base: 'md', md: 100 }}
cols={{ base: 1, sm: 3 }}
spacing="lg"
pb="xl"
>
{loading ? (
<Center mih={200}>
<Loader size="lg" color="blue" />
@@ -114,10 +146,10 @@ function Apbdes() {
) : data.length === 0 ? (
<Center mih={200}>
<Stack align="center" gap="xs">
<Text fz="lg" c="dimmed">
<Text fz="lg" c="dimmed" lh={1.4}>
Belum ada data APBDes yang tersedia
</Text>
<Text fz="sm" c="dimmed">
<Text fz="sm" c="dimmed" lh={1.4}>
Data akan ditampilkan di sini setelah diunggah
</Text>
</Stack>
@@ -133,25 +165,30 @@ function Apbdes() {
style={{ overflow: 'hidden' }}
>
<Box pos="absolute" inset={0} bg="rgba(0,0,0,0.45)" style={{ borderRadius: 16 }} />
<Stack gap={"xs"} justify="space-between" h="100%" p="xl" pos="relative">
<Stack gap="xs" justify="space-between" h="100%" p="xl" pos="relative">
<Text
c="white"
fw={600}
fz="lg"
fz={{ base: 'lg', md: 'xl' }}
ta="center"
lh={1.35}
lineClamp={2}
>
{v.name}
</Text>
<Text
fw="bold"
fw={700}
c="white"
fz="3rem"
fz={{ base: '2.4rem', md: '3.2rem' }}
ta="center"
lh={1}
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
>
{v.jumlah}
</Text>
<Center>
<ActionIcon
component={Link}
@@ -163,29 +200,12 @@ function Apbdes() {
>
<IconDownload size={20} color="white" />
</ActionIcon>
</Center>
{/* <Group justify="center">
<ActionIcon
component={Link}
href={v.file?.link || ''}
radius="xl"
size="lg"
variant="gradient"
gradient={{ from: '#1C6EA4', to: '#1C6EA4' }}
>
<Group align="center" gap="xs" px="md" py={6}>
<IconDownload size={25} color="white" />
</Group>
</ActionIcon>
</Group> */}
</Stack>
</BackgroundImage>
))
)}
</SimpleGrid>
</Stack>
)
}

View File

@@ -2,7 +2,16 @@
'use client'
import korupsiState from "@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi";
import colors from "@/con/colors";
import { Button, Center, Container, Flex, Paper, SimpleGrid, Stack, Text } from "@mantine/core";
import {
Button,
Center,
Container,
Flex,
Paper,
SimpleGrid,
Stack,
Text
} from "@mantine/core";
import { IconClipboardText } from "@tabler/icons-react";
import Link from "next/link";
import { useEffect, useState } from "react";
@@ -11,7 +20,6 @@ import { useProxy } from "valtio/utils";
function DesaAntiKorupsi() {
const state = useProxy(korupsiState);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadData = async () => {
@@ -19,30 +27,64 @@ function DesaAntiKorupsi() {
setLoading(true);
await state.desaAntikorupsi.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
console.error("Error loading data:", error);
} finally {
setLoading(false);
}
}
};
loadData();
}, [])
}, []);
const data = (state.desaAntikorupsi.findMany.data || []).slice(0, 6);
return (
<Stack gap={"0"} bg={colors.Bg} p={"sm"} my={"xs"}>
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
<Stack gap="0" bg={colors.Bg} p="sm" my="xs">
{/* ===================== HEADER ===================== */}
<Container w={{ base: "100%", md: "80%" }} p="md">
<Center>
<Text fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
<Text
fw={700}
ta="center"
c={colors["blue-button"]}
fz={{ base: "1.8rem", md: "3.2rem" }}
lh={{ base: "2.2rem", md: "3.4rem" }}
>
Desa Anti Korupsi
</Text>
</Center>
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
<Center py={20}>
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desa-anti-korupsi/detail"}>Selengkapnya</Button>
<Text
ta="center"
c="black"
fz={{ base: "1rem", md: "1.25rem" }}
lh={{ base: "1.5rem", md: "1.8rem" }}
mt="sm"
>
Desa antikorupsi mendorong pemerintahan jujur dan transparan.
Keuangan desa dikelola secara terbuka dengan melibatkan warga
dalam pengawasan anggaran, sehingga digunakan tepat sasaran dan
sesuai kebutuhan masyarakat.
</Text>
<Center py={25}>
<Button
radius="lg"
fz={{ base: "md", md: "lg" }}
bg={colors["blue-button"]}
component={Link}
href="/darmasaba/desa-anti-korupsi/detail"
style={{ paddingInline: "2rem" }}
>
Selengkapnya
</Button>
</Center>
</Container>
{/* ===================== LIST ===================== */}
<Container w="100%" maw="80rem" px="md">
{loading ? (
<Center mih={200}>
<Text fz="lg">Memuat Data...</Text>
<Text fz={{ base: "md", md: "lg" }}>Memuat Data...</Text>
</Center>
) : (
<SimpleGrid
@@ -64,26 +106,35 @@ function DesaAntiKorupsi() {
<IconClipboardText
color={colors["blue-button"]}
size={40}
style={{ flexShrink: 0 }} // biar icon nggak ketekan
style={{ flexShrink: 0 }}
/>
<Stack gap={2} style={{ flex: 1, minWidth: 0 }}>
<Stack gap={6} style={{ flex: 1, minWidth: 0 }}>
{/* Title */}
<Text
fz={{ base: "sm", sm: "md", md: "lg", lg: "xl" }} // lebih besar di desktop
fw={700}
c={colors["blue-button"]}
fw={600}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
fz={{ base: "1rem", sm: "1.1rem", md: "1.25rem" }}
lh={{ base: "1.3rem", md: "1.5rem" }}
style={{
wordBreak: "break-word",
whiteSpace: "normal"
}}
>
{v.kategori?.name || "Kategori"}
</Text>
{/* Description */}
<Text
dangerouslySetInnerHTML={{
__html: v.name || "Name",
__html: v.name || "Name"
}}
fz={{ base: "sm", sm: "md", md: "lg", lg: "xl" }} // sama, scaling responsif
c="dark"
fz={{ base: "0.9rem", sm: "1rem", md: "1.15rem" }}
lh={{ base: "1.3rem", md: "1.6rem" }}
style={{
wordBreak: "break-word",
whiteSpace: "normal",
whiteSpace: "normal"
}}
/>
</Stack>
@@ -91,7 +142,6 @@ function DesaAntiKorupsi() {
</Paper>
))}
</SimpleGrid>
)}
</Container>
</Stack>

View File

@@ -15,8 +15,6 @@ interface ChartDataItem {
label?: string;
}
function Kepuasan() {
const state = useProxy(indeksKepuasanState.responden);
const { data, loading } = state.findMany;
@@ -154,67 +152,118 @@ function Kepuasan() {
if (data.length === 0) {
return (
<Stack p="sm" my={"xs"}>
<Container w={{ base: "100%", md: "80%" }} p={"sm"}>
<Stack p="sm" my="xs">
<Container w={{ base: "100%", md: "80%" }} p="sm">
<Center>
<Text
<Title
order={2}
ta="center"
fz={{ base: '2rem', md: '2.8rem' }}
lh={{ base: 1.05, md: 1.04 }}
c={colors['blue-button']}
fw={800}
style={{ letterSpacing: '-0.5px' }}
>Indeks Kepuasan Masyarakat</Text>
>
Indeks Kepuasan Masyarakat
</Title>
</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}>
<Text
ta="center"
fz={{ base: "0.95rem", md: "1.25rem" }}
lh={{ base: 1.45, md: 1.5 }}
c="black"
mt="sm"
>
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={12}>
<Button
radius={"lg"}
radius="lg"
onClick={open}
variant="gradient"
gradient={{ from: "#26667F", to: "#124170" }}
>Ajukan Responden</Button>
style={{ paddingLeft: 20, paddingRight: 20, fontWeight: 600 }}
>
<Text fz={{ base: "0.95rem", md: "1rem" }} ta="center" c="white">Ajukan Responden</Text>
</Button>
</Center>
</Container>
<Box px={"sm"}>
<Paper p={"lg"} bg={colors.Bg}>
<Paper p={"lg"}>
<Stack gap={"xs"}>
<Flex justify={"space-between"} align={"center"}>
<Text fw={"bold"}>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"]}>
<Box px="sm">
<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" }}
gap={{ base: "xs", sm: "md" }}
>
<Text
fw={700}
ta={{ base: "center", sm: "left" }}
fz={{ base: "0.95rem", sm: "1rem" }}
lh={1.3}
>
Pelayanan Terhadap Publik Desa Darmasaba
</Text>
<Box
mt={{ base: "sm", sm: 0 }}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
textAlign: 'right',
}}
>
<Text fz={{ base: "0.8rem", sm: "0.95rem" }} fw={700} c={colors["blue-button"]} lh={1.2}>
Total Responden
</Text>
<Text
ta="end"
fz={{ base: "1.6rem", sm: "2rem" }}
fw={800}
c={colors["blue-button"]}
lh={1.02}
>
{state.findMany.total.toLocaleString('id-ID')}
</Text>
</Box>
</Flex>
<BarChart
h={window.innerWidth < 480 ? 200 : 300}
data={barChartData}
dataKey="month"
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
withTooltip
tooltipAnimationDuration={200}
/>
<Box style={{ overflowX: 'auto', width: '100%' }}>
<BarChart
h={300}
data={barChartData}
dataKey="month"
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
withTooltip
tooltipAnimationDuration={200}
xAxisProps={{
angle: -45,
textAnchor: 'end',
fontSize: 12,
}}
style={{ minWidth: 'fit-content' }}
/>
</Box>
</Stack>
</Paper>
<Box py={"xl"}>
<SimpleGrid
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="md"
verticalSpacing="md"
>
<Box py="xl">
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md" verticalSpacing="md">
{/* Chart Jenis Kelamin */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Jenis Kelamin</Title>
<Title order={4} fz={{ base: "1rem", md: "1.1rem" }} lh={1.2}>Jenis Kelamin</Title>
{donutDataJenisKelamin.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
<Text c="dimmed" ta="center" my="md" fz="sm">Belum ada data untuk ditampilkan dalam grafik</Text>
) : (
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -224,19 +273,20 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="inside" // 👈 ini yang penting!
labelsPosition="inside"
labelsType="percent"
withLabelsLine
size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile
size={isMobile ? 180 : 250}
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>
<Text fz="sm" lh={1.25}>{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
@@ -249,11 +299,9 @@ function Kepuasan() {
{/* Chart Rating */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Ulasan</Title>
<Title order={4} fz={{ base: "1rem", md: "1.1rem" }} lh={1.2}>Ulasan</Title>
{donutDataRating.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
<Text c="dimmed" ta="center" my="md" fz="sm">Belum ada data untuk ditampilkan dalam grafik</Text>
) : (
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -263,20 +311,21 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="inside" // 👈 ini yang penting!
labelsPosition="inside"
labelsType="percent"
withLabelsLine
size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile
size={isMobile ? 180 : 250}
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' }}>
<Text fz="xs" lh={1.2} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -292,11 +341,9 @@ function Kepuasan() {
{/* Chart Kelompok Umur */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Umur</Title>
<Title order={4} fz={{ base: "1rem", md: "1.1rem" }} lh={1.2}>Umur</Title>
{donutDataKelompokUmur.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
<Text c="dimmed" ta="center" my="md" fz="sm">Belum ada data untuk ditampilkan dalam grafik</Text>
) : (
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -306,20 +353,21 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="inside"// 👈 ini yang penting!
labelsPosition="inside"
labelsType="percent"
withLabelsLine
size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile
size={isMobile ? 180 : 250}
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' }}>
<Text fz="xs" lh={1.2} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -331,17 +379,19 @@ function Kepuasan() {
)}
</Stack>
</Paper>
</SimpleGrid>
</Box>
</Paper>
</Box>
{/* Modal */}
<Modal opened={opened} onClose={close} title="Ajukan Responden" centered>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<TextInput
label="Nama"
type='text'
type="text"
placeholder="Masukkan nama"
value={state.create.form.name}
onChange={(val) => {
@@ -415,8 +465,9 @@ function Kepuasan() {
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
style={{ fontWeight: 700 }}
>
Submit
<Text fz="sm" ta="center" c="white">Submit</Text>
</Button>
</Stack>
</Paper>
@@ -424,72 +475,108 @@ function Kepuasan() {
</Stack>
);
}
return (
<Stack p={"sm"} my={"xs"}>
<Stack p="sm" my="xs">
<Container size="lg" px="sm">
<Center>
<Text
<Title
order={2}
ta="center"
fz={{ base: '2rem', md: '2.8rem' }}
lh={{ base: 1.05, md: 1.04 }}
c={colors['blue-button']}
fw={800}
style={{ letterSpacing: '-0.5px' }}
>Indeks Kepuasan Masyarakat</Text>
>
Indeks Kepuasan Masyarakat
</Title>
</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>
<Text fz={{ base: "1rem", md: "1.25rem" }} ta="center" c="black" lh={1.5} mt="sm">
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={12}>
<Button radius="lg" bg={colors["blue-button"]} onClick={open} style={{ paddingLeft: 20, paddingRight: 20, fontWeight: 600 }}>
<Text fz={{ base: "0.95rem", md: "1rem" }} ta="center" c="white">Ajukan Responden</Text>
</Button>
</Center>
</Container>
<Box px={"md"}>
<Paper p={"lg"} bg={colors.Bg}>
<Paper p={"lg"}>
<Stack gap={"xs"}>
<Box px="md">
<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" }}
gap={{ base: "xs", sm: "md" }}
>
<Text fw="bold" ta={{ base: "center", sm: "left" }}>
<Text
fw={700}
ta={{ base: "center", sm: "left" }}
fz={{ base: "0.95rem", sm: "1rem" }}
lh={1.3}
>
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"]}>
<Box
mt={{ base: "sm", sm: 0 }}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
textAlign: 'right',
}}
>
<Text fz={{ base: "0.8rem", sm: "0.95rem" }} fw={700} c={colors["blue-button"]} lh={1.2}>
Total Responden
</Text>
<Text
ta="end"
fz={{ base: "1.6rem", sm: "2rem" }}
fw={800}
c={colors["blue-button"]}
lh={1.02}
>
{state.findMany.total.toLocaleString('id-ID')}
</Text>
</Box>
</Flex>
<BarChart
h={300}
data={barChartData}
dataKey="month"
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
withTooltip
tooltipAnimationDuration={200}
/>
<Box style={{ overflowX: 'auto', width: '100%' }} pb={50}>
<BarChart
h={300}
data={barChartData}
dataKey="month"
series={[{ name: 'Responden', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel=""
yAxisLabel="Jumlah Responden"
withTooltip
tooltipAnimationDuration={200}
xAxisProps={{
angle: -45,
textAnchor: 'end',
fontSize: 12,
}}
style={{ minWidth: 'fit-content' }}
/>
</Box>
</Stack>
</Paper>
<Box py={"xl"}>
<SimpleGrid
cols={{
base: 1,
md: 1,
lg: 1,
xl: 3
}}
>
<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>
<Title order={4}>Jenis Kelamin</Title>
<Title order={4} fz={{ base: "1rem", md: "1.1rem" }} lh={1.2}>Jenis Kelamin</Title>
{donutDataJenisKelamin.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
<Text c="dimmed" ta="center" my="md" fz="sm">Belum ada data untuk ditampilkan dalam grafik</Text>
) : (
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -499,18 +586,18 @@ function Kepuasan() {
withLabels
withTooltip
labelsPosition="inside"
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>
<Text fz="sm" lh={1.25}>{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
@@ -523,11 +610,9 @@ function Kepuasan() {
{/* Chart Rating */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Ulasan</Title>
<Title order={4} fz={{ base: "1rem", md: "1.1rem" }} lh={1.2}>Ulasan</Title>
{donutDataRating.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
<Text c="dimmed" ta="center" my="md" fz="sm">Belum ada data untuk ditampilkan dalam grafik</Text>
) : (
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -537,7 +622,6 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="inside"
labelsType="percent"
withLabelsLine
@@ -546,12 +630,13 @@ function Kepuasan() {
/>
</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' }}>
<Text fz="xs" lh={1.2} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -567,11 +652,9 @@ function Kepuasan() {
{/* Chart Kelompok Umur */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Umur</Title>
<Title order={4} fz={{ base: "1rem", md: "1.1rem" }} lh={1.2}>Umur</Title>
{donutDataKelompokUmur.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
<Text c="dimmed" ta="center" my="md" fz="sm">Belum ada data untuk ditampilkan dalam grafik</Text>
) : (
<Paper p="md" radius="md" withBorder>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -581,7 +664,6 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="inside"
labelsType="percent"
withLabelsLine
@@ -590,12 +672,13 @@ function Kepuasan() {
/>
</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' }}>
<Text fz="xs" lh={1.2} lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{entry.name}: {entry.value}
</Text>
</Flex>
@@ -607,13 +690,15 @@ function Kepuasan() {
)}
</Stack>
</Paper>
</SimpleGrid>
</Box>
</Paper>
</Box>
{/* Modal */}
<Modal opened={opened} onClose={close} title="Ajukan Responden" centered>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<TextInput
label="Nama"
@@ -691,8 +776,9 @@ function Kepuasan() {
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
style={{ fontWeight: 700 }}
>
Submit
<Text fz="sm" ta="center" c="white">Submit</Text>
</Button>
</Stack>
</Paper>
@@ -701,4 +787,4 @@ function Kepuasan() {
);
}
export default Kepuasan;
export default Kepuasan;

View File

@@ -53,14 +53,23 @@ function ModuleItem({ data }: { data: ProgramInovasiItem }) {
) : (
<Stack align="center" gap="xs">
<IconPhotoOff size={38} stroke={1.5} />
<Text size="sm" c="dimmed">
{/* ❗ Caption konsisten */}
<Text fz={{ base: 13, md: 14 }} c="dimmed">
Belum ada gambar
</Text>
</Stack>
)}
</Center>
<Box mt="md">
<Text fw={600} ta="center" size="md">
{/* ❗ Responsive Title */}
<Text
fw={600}
ta="center"
fz={{ base: 16, md: 18 }} // mobile → desktop
lh={1.3}
>
{data.name}
</Text>
</Box>
@@ -91,10 +100,14 @@ function ModuleView() {
<Center h={320}>
<Stack align="center" gap="sm">
<IconPhotoOff size={54} stroke={1.5} />
<Text size="lg" fw={600}>
{/* ❗ Empty title lebih besar */}
<Text fw={600} fz={{ base: 18, md: 22 }}>
Belum ada program inovasi
</Text>
<Text size="sm" c="dimmed">
{/* ❗ Deskripsi kecil & lembut */}
<Text fz={{ base: 14, md: 16 }} c="dimmed" ta="center" lh={1.4}>
Tambahkan program inovasi untuk ditampilkan di sini
</Text>
</Stack>
@@ -103,11 +116,12 @@ function ModuleView() {
}
return (
<ScrollArea h={280} // ✅ tinggi fixed, bisa disesuaikan
<ScrollArea
h={280}
scrollbarSize={2}
offsetScrollbars
styles={{
viewport: { paddingRight: 8 }, // kasih jarak biar scroll nggak dempet
viewport: { paddingRight: 8 },
}}
>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" mt="lg">

View File

@@ -13,10 +13,23 @@ export default function ProfileView({ data }: ProfileViewProps) {
<Card radius="2xl" className="glass3" py="xl" px="lg" withBorder>
<Stack align="center" gap="sm">
<IconUserCircle size={72} stroke={1.4} />
<Text fw={500} c="dimmed">
{/* TITLE EMPTY */}
<Text
fw={600}
c="dimmed"
fz={{ base: 'lg', sm: 'xl', md: 'xl' }}
ta="center"
>
Profil belum tersedia
</Text>
<Text fz="sm" c="dimmed">
{/* DESCRIPTION EMPTY */}
<Text
fz={{ base: 'sm', sm: 'md' }}
c="dimmed"
ta="center"
>
Data pejabat desa akan muncul di sini
</Text>
</Stack>
@@ -30,12 +43,12 @@ export default function ProfileView({ data }: ProfileViewProps) {
align="end"
pos="relative"
w={{
base: '100%', // mobile: full width
xs: '100%', // small mobile
sm: '85%', // tablet: 85%
md: '60%', // laptop: 60%
lg: '55%', // laptop large: 55%
xl: '50%' // extra large (4K): 50%
base: '100%',
xs: '100%',
sm: '85%',
md: '60%',
lg: '55%',
xl: '50%',
}}
px={{ base: 'md', sm: 'lg', md: 'xl', xl: '2xl' }}
h={{ base: 'auto', sm: '500px', md: '600px', lg: '650px', xl: '700px' }}
@@ -67,13 +80,17 @@ export default function ProfileView({ data }: ProfileViewProps) {
) : (
<Stack align="center" gap="xs" w="100%" py="xl">
<IconUserCircle size={96} stroke={1.5} />
<Text c="dimmed" fz="sm">
<Text
c="dimmed"
fz={{ base: 'sm', sm: 'md' }}
ta="center"
>
Belum ada foto
</Text>
</Stack>
)}
{/* Box nama dan jabatan - responsive positioning */}
{/* Box nama & jabatan */}
<Box
pos="absolute"
bottom={{ base: -30, sm: -25, md: -20 }}
@@ -94,17 +111,21 @@ export default function ProfileView({ data }: ProfileViewProps) {
backgroundColor: 'rgba(255, 255, 255, 0.95)',
}}
>
<Text
fz={{ base: 'xs', sm: 'sm' }}
c="dimmed"
lineClamp={1}
>
{data.position || 'Tidak ada jabatan'}
</Text>
{/* POSITION / JABATAN */}
<Text
fz={{ base: 'xs', sm: 'sm', md: 'md' }}
c="dimmed"
lineClamp={1}
>
{data.position || 'Tidak ada jabatan'}
</Text>
{/* NAME */}
<Text
c={colors['blue-button']}
fw={700}
fz={{ base: 'lg', sm: 'xl' }}
fz={{ base: 'lg', sm: 'xl', md: 'xl', lg: '2xl' }}
mt={4}
lineClamp={2}
>
@@ -114,4 +135,4 @@ export default function ProfileView({ data }: ProfileViewProps) {
</Box>
</Stack>
);
}
}

View File

@@ -26,7 +26,11 @@ function SosmedView({
data.map((item, k) => (
<Tooltip
key={k}
label={item.name || "Tautan Sosial"}
label={
<Text fz={{ base: 12, md: 14 }}>
{item.name || "Tautan Sosial"}
</Text>
}
withArrow
position="top"
transitionProps={{ transition: "pop", duration: 150 }}
@@ -57,7 +61,7 @@ function SosmedView({
);
}
return <Box bg={colors['blue-button']} w="100%" h="100%" />;
return <Box bg={colors["blue-button"]} w="100%" h="100%" />;
})()}
</ActionIcon>
</Tooltip>
@@ -72,7 +76,12 @@ function SosmedView({
background: "linear-gradient(135deg, #1C6EA4 0%, #000 100%)",
}}
>
<Text ta="center" c="dimmed" size="sm">
<Text
ta="center"
c="dimmed"
fz={{ base: 13, md: 15 }}
lh={1.4}
>
Belum ada media sosial yang terhubung
</Text>
</Card>

View File

@@ -59,7 +59,7 @@ const getWorkStatus = (day: string, currentTime: string): { status: string; mess
: { status: "Tutup", message: "08:00 - 17:00" };
};
// Skeleton component untuk Social Media
// 🟦 Skeleton component untuk Social Media
const SosmedSkeleton = () => (
<Flex gap="md" justify="center" wrap="wrap">
{[1, 2, 3, 4].map((i) => (
@@ -68,7 +68,7 @@ const SosmedSkeleton = () => (
</Flex>
);
// Skeleton component untuk Profile
// 🟦 Skeleton component untuk Profile
const ProfileSkeleton = () => (
<Card
radius="xl"
@@ -158,6 +158,8 @@ function LandingPage() {
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
<Card radius="xl" bg={colors.grey[1]} p="lg" mt={10} shadow="xl">
<Stack gap="xl">
{/* Header Logo */}
<Flex gap="md" wrap="wrap">
<Group>
<Box bg="white" w={72} h={72} p="sm" style={{ borderRadius: 24 }}>
@@ -167,6 +169,8 @@ function LandingPage() {
<Image loading="lazy" src="/pudak-icon.png" alt="Logo Pudak" fit="contain" />
</Box>
</Group>
{/* Jam Operasional */}
<Grid w="100%">
<Grid.Col span={12}>
<Paper
@@ -177,36 +181,58 @@ function LandingPage() {
style={{ position: "relative", overflow: "hidden" }}
>
<Grid gutter="md">
{/* Kolom 1 */}
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="xs">
<Flex align="center" gap="xs">
<IconCalendarTime size={16} color="white" />
<Text c="white" fz="sm">Jam Operasional</Text>
<Text c="white" fz={{ base: "xs", md: "sm" }}>
Jam Operasional
</Text>
</Flex>
<Paper p="sm" radius="md" bg="white">
<Tooltip label="Status saat ini berdasarkan jam operasional kantor">
<Badge
color={workStatus.status === "Buka" ? "green" : "red"}
radius="sm"
variant="filled"
size="md"
>
{workStatus.status}
</Badge>
</Tooltip>
<Text fw="bold" fz="lg">{workStatus.message}</Text>
<Text
fw={700}
fz={{ base: "md", md: "lg" }}
mt={4}
>
{workStatus.message}
</Text>
</Paper>
</Stack>
</GridCol>
{/* Kolom 2 */}
<GridCol span={{ base: 12, md: 6 }}>
<Stack gap="xs">
<Flex align="center" gap="xs">
<IconInfoCircle size={16} color="white" />
<Text c="white" fz="sm">Hari Ini</Text>
<Text c="white" fz={{ base: "xs", md: "sm" }}>
Hari Ini
</Text>
</Flex>
<Paper p="sm" radius="md" bg="white">
<Text fz="sm">Status Kantor</Text>
<Text fw="bold" fz="lg">
{workStatus.status === "Buka" ? "Sedang Beroperasi" : "Tidak Beroperasi"}
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">
Status Kantor
</Text>
<Text fw={700} fz={{ base: "md", md: "lg" }}>
{workStatus.status === "Buka"
? "Sedang Beroperasi"
: "Tidak Beroperasi"}
</Text>
</Paper>
</Stack>
@@ -217,19 +243,29 @@ function LandingPage() {
</Grid>
</Flex>
{/* MODULE VIEW */}
<ModuleView />
{/* Sosmed */}
{isLoadingSosmed ? (
<SosmedSkeleton />
) : socialMedia.length > 0 ? (
<SosmedView data={socialMedia} />
) : (
<Center>
<Text c="dimmed">Belum ada tautan media sosial yang tersedia</Text>
<Text fz={{ base: "sm", md: "md" }} c="dimmed">
Belum ada tautan media sosial yang tersedia
</Text>
</Center>
)}
<Text ta="center" c={colors.trans.dark[2]}>
{/* CTA Text */}
<Text
ta="center"
c={colors.trans.dark[2]}
fz={{ base: "sm", md: "md" }}
lh={1.5}
>
Bagikan ide, kritik, atau saran Anda untuk mendukung pembangunan desa.
Semua lebih mudah dengan fitur interaktif yang kami sediakan.
</Text>
@@ -237,6 +273,7 @@ function LandingPage() {
</Card>
</Stack>
{/* PROFIL */}
{isLoadingProfile ? (
<ProfileSkeleton />
) : profile ? (
@@ -251,7 +288,9 @@ function LandingPage() {
style={{ height: "fit-content" }}
>
<Center h={300}>
<Text c="dimmed">Informasi profil belum tersedia</Text>
<Text fz={{ base: "sm", md: "md" }} c="dimmed">
Informasi profil belum tersedia
</Text>
</Center>
</Card>
)}
@@ -260,4 +299,4 @@ function LandingPage() {
);
}
export default LandingPage;
export default LandingPage;

View File

@@ -28,20 +28,41 @@ const textHeading = {
const HEIGHT = 720;
function Layanan() {
// responsive breakpoints: base = mobile, md = desktop/tablet landscape
return (
<Stack pos="relative" bg={colors.grey[1]} gap="xl" py="md">
<Container w={{ base: "100%", md: "80%" }} p="md">
<Stack align="center" gap="0">
{/* Main title - semantic h1 */}
<Text
fw="bold"
component="h1"
fw={700}
c={colors["blue-button"]}
fz={{ base: "1.8rem", md: "3.4rem" }}
ta="center"
// responsive sizes: mobile ~28px, desktop ~48px
fz={{ base: "1.75rem", md: "3rem" }}
// tighter line-height for large headings, slightly more compact on desktop
style={{ lineHeight: "1.05" }}
>
{textHeading.title}
</Text>
<Text ta="center" fz={{ base: "1rem", md: "1.3rem" }}>
{/* Description - readable line-height and constrained width on desktop */}
<Text
component="p"
ta="center"
fz={{ base: "0.95rem", md: "1.15rem" }}
// more comfortable line-height for paragraphs
style={{
lineHeight: "1.6",
maxWidth: "70ch",
marginTop: 8,
}}
c="black"
>
{textHeading.des}
</Text>
<Box p="md">
<Button
component={Link}
@@ -49,6 +70,14 @@ function Layanan() {
variant="filled"
bg={colors["blue-button"]}
radius={100}
// accessible sizing: slightly smaller on mobile, comfortable on desktop
style={{
paddingLeft: 20,
paddingRight: 20,
fontSize: "md",
// ensure button text doesn't overflow on very narrow screens
whiteSpace: "nowrap",
}}
>
Detail
</Button>
@@ -175,7 +204,7 @@ function Slider() {
startXRef.current = e.pageX - containerRef.current.offsetLeft;
scrollLeftRef.current = containerRef.current.scrollLeft;
velocityRef.current = 0;
containerRef.current.style.cursor = 'grabbing';
containerRef.current.style.cursor = "grabbing";
};
const handleMouseMove = (e: React.MouseEvent) => {
@@ -196,7 +225,7 @@ function Slider() {
if (!containerRef.current || mobile) return;
isDraggingRef.current = false;
containerRef.current.style.cursor = 'grab';
containerRef.current.style.cursor = "grab";
};
const handleWheel = (e: React.WheelEvent) => {
@@ -215,7 +244,7 @@ function Slider() {
if (data.length === 0) {
return (
<Container>
<Text ta="center" c="dimmed">
<Text ta="center" c="dimmed" fz={{ base: "0.95rem", md: "1rem" }}>
Tidak ada layanan tersedia
</Text>
</Container>
@@ -240,6 +269,8 @@ function Slider() {
scrollbarWidth: "none",
msOverflowStyle: "none",
}}
// ensure keyboard accessibility: allow focus outline when focused
tabIndex={0}
>
<Box
style={{
@@ -287,26 +318,56 @@ function Slider() {
pos="relative"
>
<Box p="lg">
{/* slide title - semantic h2 */}
<Text
fw="bold"
component="h2"
fw={700}
c="white"
fz={{base: "xl", md: "3.5rem"}}
fz={{ base: "1.25rem", md: "2.4rem" }}
// tighter heading line-height but ensure readability on mobile
style={{
textAlign: "center",
lineHeight: mobile ? "1.15" : "1.02",
// clamp long names visually
display: "-webkit-box",
WebkitLineClamp: 2,
WebkitBoxOrient: "vertical",
overflow: "hidden",
}}
title={_.startCase(item.name)}
>
{_.startCase(item.name)}
</Text>
{/* optional short description - rendered if exists */}
{item.description ? (
<Text
component="p"
mt="sm"
c="white"
fz={{ base: "0.9rem", md: "1rem" }}
style={{ lineHeight: "1.5", textAlign: "center" }}
>
{item.description}
</Text>
) : null}
</Box>
<Group justify="center">
<Group justify="center" mb="lg">
<Button
onClick={() =>
router.push(`/darmasaba/desa/layanan/${item.id}`)
}
px={46}
px={mobile ? 20 : 46}
radius="100"
size="md"
size={mobile ? "sm" : "md"}
bg={colors["blue-button"]}
// ensure button text readable on all sizes
style={{
fontSize: mobile ? "0.95rem" : "1rem",
whiteSpace: "nowrap",
}}
aria-label={`Detail layanan ${_.startCase(item.name)}`}
>
Detail
</Button>
@@ -320,4 +381,4 @@ function Slider() {
);
}
export default Layanan;
export default Layanan;

Some files were not shown because too many files have changed in this diff Show More