Compare commits
4 Commits
nico/9-des
...
nico/12-de
| Author | SHA1 | Date | |
|---|---|---|---|
| f6f77d9e35 | |||
| a00481152c | |||
| 242ea86f77 | |||
| 99c2c9c6d7 |
@@ -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} />
|
||||
}
|
||||
];
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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');
|
||||
@@ -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"
|
||||
@@ -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');
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
@@ -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 "{kategori}".</Text>
|
||||
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Belum ada berita di kategori "{kategori}".
|
||||
</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',
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
)
|
||||
@@ -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>
|
||||
@@ -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 }}
|
||||
>
|
||||
"Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
|
||||
<Text span fw={800} c="cyan.6">
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 "Ajukan Ide Inovatif" 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 "Ajukan Ide Inovatif" 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;
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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"}}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.name}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text fw="bold">
|
||||
Tanggal:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.tanggal}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text fw="bold">
|
||||
Waktu:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.waktu}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text fw="bold">
|
||||
Lokasi:
|
||||
<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:
|
||||
<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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
257
src/app/darmasaba/(pages)/ppid/profil-ppid/page.tsx
Normal file
257
src/app/darmasaba/(pages)/ppid/profil-ppid/page.tsx
Normal 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;
|
||||
@@ -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
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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}`)}
|
||||
|
||||
@@ -168,6 +168,7 @@ export default function ModernNewsNotification({
|
||||
position: "fixed",
|
||||
bottom: "24px",
|
||||
right: "24px",
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<ActionIcon
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -100,6 +100,7 @@ const NewsReaderLanding = () => {
|
||||
borderBottomRightRadius: '20px',
|
||||
borderTopRightRadius: '20px',
|
||||
transition: 'all 0.3s ease',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
{isPointerMode ? <IconMusicOff /> : <IconMusic />}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user