Compare commits
2 Commits
nico/9-des
...
nico/10-de
| Author | SHA1 | Date | |
|---|---|---|---|
| 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",
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -168,6 +168,7 @@ export default function ModernNewsNotification({
|
||||
position: "fixed",
|
||||
bottom: "24px",
|
||||
right: "24px",
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<ActionIcon
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client';
|
||||
|
||||
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
||||
import { Stack, Box, Container, Button, Text, Loader, Paper, Center, ActionIcon } from "@mantine/core";
|
||||
import {
|
||||
Stack,
|
||||
Box,
|
||||
Container,
|
||||
Button,
|
||||
Text,
|
||||
Loader,
|
||||
Paper,
|
||||
Center,
|
||||
ActionIcon,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { IconAward, IconArrowRight, IconPlayerPlay } from "@tabler/icons-react";
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useTransitionRouter } from "next-view-transitions";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
@@ -12,43 +24,33 @@ function Penghargaan() {
|
||||
const router = useTransitionRouter();
|
||||
const state = useProxy(penghargaanState);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
|
||||
const [showVideo, setShowVideo] = useState(true);
|
||||
const [videoError, setVideoError] = useState(false);
|
||||
const [showPlayButton, setShowPlayButton] = useState(false);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const hasTriedAutoplay = useRef(false);
|
||||
|
||||
// Deteksi iOS dengan lebih akurat
|
||||
const isIOS = typeof window !== 'undefined' && (
|
||||
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) // iPad dengan iPadOS 13+
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Di iOS, coba autoplay dulu, kalau gagal tampilkan fallback
|
||||
if (isIOS && videoRef.current) {
|
||||
const playPromise = videoRef.current.play();
|
||||
|
||||
if (playPromise !== undefined) {
|
||||
playPromise
|
||||
.then(() => {
|
||||
// Autoplay berhasil
|
||||
setShowVideo(true);
|
||||
setIsVideoLoaded(true);
|
||||
})
|
||||
.catch(() => {
|
||||
// Autoplay gagal, tampilkan fallback
|
||||
setShowVideo(false);
|
||||
setVideoError(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [isIOS]);
|
||||
// ---- TYPOGRAPHY SCALE (RESPONSIVE) ----
|
||||
// ukuran dalam px, lh = line-height
|
||||
const TYPO = {
|
||||
// utama / hero title
|
||||
title: { base: 22, md: 36, lh: 1.08 }, // lebih menonjol di desktop
|
||||
// subheading / loader / tagline
|
||||
subtitle: { base: 14, md: 16, lh: 1.35 },
|
||||
// teks body / deskripsi umum
|
||||
body: { base: 14, md: 16, lh: 1.6 },
|
||||
// caption / small notes
|
||||
small: { base: 12, md: 13, lh: 1.4 },
|
||||
// judul dalam kartu (card title)
|
||||
paperTitle: { base: 15, md: 18, lh: 1.25 },
|
||||
};
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findMany.load();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -57,99 +59,134 @@ function Penghargaan() {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const handlePlayVideo = () => {
|
||||
setShowVideo(true);
|
||||
setVideoError(false);
|
||||
|
||||
// Paksa play video setelah user interaction
|
||||
setTimeout(() => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.play().catch(err => {
|
||||
console.error("Video play error:", err);
|
||||
setVideoError(true);
|
||||
});
|
||||
// Attempt autoplay setelah video loaded
|
||||
useEffect(() => {
|
||||
if (isVideoLoaded && videoRef.current && !hasTriedAutoplay.current) {
|
||||
hasTriedAutoplay.current = true;
|
||||
|
||||
const attemptAutoplay = async () => {
|
||||
try {
|
||||
// Pastikan video muted sebelum play
|
||||
videoRef.current!.muted = true;
|
||||
await videoRef.current!.play();
|
||||
setShowPlayButton(false);
|
||||
console.log("✅ Autoplay berhasil");
|
||||
} catch (err) {
|
||||
console.warn("⚠️ Autoplay diblokir browser:", err);
|
||||
// Tampilkan tombol play jika autoplay gagal
|
||||
setShowPlayButton(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Delay sedikit untuk memastikan video siap
|
||||
setTimeout(attemptAutoplay, 100);
|
||||
}
|
||||
}, [isVideoLoaded]);
|
||||
|
||||
// Handle manual play
|
||||
const handlePlayVideo = async () => {
|
||||
if (videoRef.current) {
|
||||
try {
|
||||
videoRef.current.muted = true;
|
||||
await videoRef.current.play();
|
||||
setShowPlayButton(false);
|
||||
setVideoError(false);
|
||||
} catch (err) {
|
||||
console.error("❌ Gagal memutar video:", err);
|
||||
setVideoError(true);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
// kalau mobile ambil 1 data aja, kalau desktop ambil 3
|
||||
// Ambil data terbatas berdasarkan perangkat
|
||||
const data = state.findMany.data?.slice(0, isMobile ? 1 : 3);
|
||||
|
||||
return (
|
||||
<Stack pos="relative" h="auto" mih={{ base: 500, md: 720 }} style={{ overflow: 'hidden' }}>
|
||||
{/* Video Layer */}
|
||||
{showVideo && !videoError && (
|
||||
<Stack pos="relative" h="auto" mih={{ base: 500, md: 720 }} style={{ overflow: "hidden" }}>
|
||||
{/* Video background */}
|
||||
{!videoError && (
|
||||
<video
|
||||
ref={videoRef}
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="auto"
|
||||
webkit-playsinline="true"
|
||||
onLoadedData={() => setIsVideoLoaded(true)}
|
||||
onError={() => {
|
||||
console.error("Video load error");
|
||||
console.error("❌ Video gagal dimuat");
|
||||
setVideoError(true);
|
||||
setShowVideo(false);
|
||||
}}
|
||||
onCanPlayThrough={() => {
|
||||
console.log("✅ Video siap diputar");
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
opacity: isVideoLoaded ? 1 : 0,
|
||||
transition: 'opacity 0.5s ease',
|
||||
transition: "opacity 0.5s ease",
|
||||
zIndex: 0,
|
||||
}}
|
||||
>
|
||||
<source src="/assets/videos/award.mp4" type="video/mp4" />
|
||||
Browser Anda tidak mendukung video.
|
||||
</video>
|
||||
)}
|
||||
|
||||
{/* Fallback Image + Play Button */}
|
||||
{(!showVideo || videoError) && (
|
||||
{/* Fallback background image */}
|
||||
{(videoError || !isVideoLoaded) && (
|
||||
<Box
|
||||
onClick={handlePlayVideo}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundImage: "url('/mangupuraaward.jpeg')",
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
cursor: 'pointer',
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
zIndex: 0,
|
||||
}}
|
||||
>
|
||||
<Center
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'rgba(0,0,0,0.3)', // overlay gelap agar icon terlihat
|
||||
}}
|
||||
>
|
||||
<ActionIcon
|
||||
size={80}
|
||||
radius="xl"
|
||||
variant="filled"
|
||||
color="blue"
|
||||
style={{
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.3)',
|
||||
}}
|
||||
>
|
||||
<IconPlayerPlay size={40} color="var(--mantine-color-blue-6)" />
|
||||
</ActionIcon>
|
||||
</Center>
|
||||
</Box>
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Overlay Gradient + Content */}
|
||||
{/* Tombol Play (muncul jika autoplay gagal atau video error) */}
|
||||
{(showPlayButton || videoError) && (
|
||||
<Center
|
||||
onClick={handlePlayVideo}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
cursor: "pointer",
|
||||
zIndex: 2,
|
||||
pointerEvents: showPlayButton || videoError ? "auto" : "none",
|
||||
}}
|
||||
>
|
||||
<ActionIcon
|
||||
size={isMobile ? 64 : 80}
|
||||
radius="xl"
|
||||
variant="filled"
|
||||
style={{
|
||||
backgroundColor: "rgba(255,255,255,0.95)",
|
||||
boxShadow: "0 8px 32px rgba(0,0,0,0.3)",
|
||||
animation: "pulse 2s infinite",
|
||||
}}
|
||||
aria-label="Play background video"
|
||||
>
|
||||
<IconPlayerPlay size={isMobile ? 34 : 40} color="var(--mantine-color-blue-6)" />
|
||||
</ActionIcon>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
{/* Overlay konten */}
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -161,22 +198,39 @@ function Penghargaan() {
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Container w={{ base: "100%", md: "80%" }} mih={{ base: 500, md: 720 }} p="xl">
|
||||
<Container w={{ base: "100%", md: "80%" }} maw={1100} mih={{ base: 500, md: 720 }} p={{ base: "lg", md: "xl" }}>
|
||||
<Stack justify="center" align="center" gap="xl" h="100%">
|
||||
<Text
|
||||
fw={900}
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
ta="center"
|
||||
variant="gradient"
|
||||
gradient={{ from: "cyan", to: "blue", deg: 60 }}
|
||||
{/* Hero Title - pakai Title agar semantics lebih jelas */}
|
||||
<Title
|
||||
order={2}
|
||||
style={{
|
||||
fontWeight: 800,
|
||||
lineHeight: TYPO.title.lh,
|
||||
// Mantine support fz prop but inline style fallback ok:
|
||||
fontSize: isMobile ? TYPO.title.base : TYPO.title.md,
|
||||
textAlign: "center",
|
||||
// gradient via CSS text-fill technique (ke Mantine gradient prop juga bisa)
|
||||
background: "-webkit-linear-gradient(60deg, #22D3EE 0%, #3B82F6 100%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
}}
|
||||
aria-label="Penghargaan Desa"
|
||||
>
|
||||
Penghargaan Desa
|
||||
</Text>
|
||||
</Title>
|
||||
|
||||
{/* Content area */}
|
||||
{loading ? (
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader color="blue" size="lg" />
|
||||
<Text c="gray.3" fz="lg">Sedang memuat data penghargaan...</Text>
|
||||
<Loader color="blue" size={isMobile ? "md" : "lg"} />
|
||||
<Text
|
||||
c="gray.3"
|
||||
fz={isMobile ? TYPO.subtitle.base : TYPO.subtitle.md}
|
||||
lh={TYPO.subtitle.lh}
|
||||
ta="center"
|
||||
>
|
||||
Sedang memuat data penghargaan...
|
||||
</Text>
|
||||
</Stack>
|
||||
) : data && data.length > 0 ? (
|
||||
<Stack gap="md" w="100%" maw={600}>
|
||||
@@ -185,47 +239,98 @@ function Penghargaan() {
|
||||
key={k}
|
||||
withBorder
|
||||
radius="xl"
|
||||
p="lg"
|
||||
p={isMobile ? "md" : "lg"}
|
||||
shadow="xl"
|
||||
style={{
|
||||
background: "rgba(255,255,255,0.07)",
|
||||
backdropFilter: "blur(12px)",
|
||||
transition: "all 0.3s ease",
|
||||
}}
|
||||
aria-label={`Penghargaan ${v.name}`}
|
||||
>
|
||||
<Stack align="center" gap="xs">
|
||||
<IconAward size={40} color="var(--mantine-color-blue-4)" />
|
||||
<Text fz="lg" fw={700} c="white" ta="center">
|
||||
<IconAward size={isMobile ? 36 : 40} color="var(--mantine-color-blue-4)" />
|
||||
<Text
|
||||
// card title: lebih tegas
|
||||
fz={isMobile ? TYPO.paperTitle.base : TYPO.paperTitle.md}
|
||||
fw={700}
|
||||
c="white"
|
||||
ta="center"
|
||||
lh={TYPO.paperTitle.lh}
|
||||
style={{ wordBreak: "break-word" }}
|
||||
title={v.name}
|
||||
>
|
||||
{v.name}
|
||||
</Text>
|
||||
|
||||
{/* Jika ingin menambahkan deskripsi ringkas di card, gunakan body scale */}
|
||||
{v.description && (
|
||||
<Text
|
||||
fz={isMobile ? TYPO.body.base : TYPO.body.md}
|
||||
c="gray.2"
|
||||
ta="center"
|
||||
lh={TYPO.body.lh}
|
||||
style={{ maxWidth: 520 }}
|
||||
>
|
||||
{v.description}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack align="center" gap="xs">
|
||||
<IconAward size={48} color="var(--mantine-color-gray-5)" />
|
||||
<Text c="gray.4" fz="lg" ta="center">
|
||||
<IconAward size={isMobile ? 40 : 48} color="var(--mantine-color-gray-5)" />
|
||||
<Text
|
||||
c="gray.4"
|
||||
fz={isMobile ? TYPO.body.base : TYPO.body.md}
|
||||
ta="center"
|
||||
lh={TYPO.body.lh}
|
||||
>
|
||||
Belum ada penghargaan yang tercatat
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
size={isMobile ? "md" : "lg"}
|
||||
radius="xl"
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170", deg: 45 }}
|
||||
rightSection={<IconArrowRight size={20} />}
|
||||
rightSection={<IconArrowRight size={isMobile ? 16 : 20} />}
|
||||
onClick={() => router.push("/darmasaba/penghargaan")}
|
||||
aria-label="Lihat semua penghargaan"
|
||||
>
|
||||
Lihat Semua Penghargaan
|
||||
<Text
|
||||
c="white"
|
||||
fz={isMobile ? TYPO.body.base : TYPO.body.md}
|
||||
fw={700}
|
||||
style={{ lineHeight: TYPO.body.lh }}
|
||||
>
|
||||
Lihat Semua Penghargaan
|
||||
</Text>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
{/* CSS untuk animasi tombol play */}
|
||||
<style jsx>{`
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.9;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Penghargaan;
|
||||
export default Penghargaan;
|
||||
|
||||
@@ -50,31 +50,52 @@ function Potensi() {
|
||||
|
||||
return (
|
||||
<Stack p="sm" gap="xl">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||
<Text id="news-title" ta={"center"} fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
{/* HEADER */}
|
||||
<Container w={{ base: "100%", md: "80%" }} p="md">
|
||||
<Text
|
||||
id="news-title"
|
||||
ta="center"
|
||||
fw={800}
|
||||
c={colors["blue-button"]}
|
||||
fz={{ base: "2rem", md: "3.2rem" }}
|
||||
lh={{ base: "2.6rem", md: "3.6rem" }}
|
||||
style={{ letterSpacing: "-0.5px" }}
|
||||
>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text id="news-content" ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
|
||||
<Text
|
||||
id="news-content"
|
||||
ta="center"
|
||||
c="gray.7"
|
||||
fz={{ base: "1rem", md: "1.25rem" }}
|
||||
lh={{ base: "1.5rem", md: "1.9rem" }}
|
||||
style={{ marginTop: 8, maxWidth: 800, marginInline: "auto" }}
|
||||
>
|
||||
{textHeading.des}
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
{/* LOADING STATE */}
|
||||
{loading ? (
|
||||
<Stack align="center" justify="center" h={300}>
|
||||
<Loader size="lg" color={colors["blue-button"]} />
|
||||
<Text c="gray.4">Sedang memuat potensi desa...</Text>
|
||||
<Text c="gray.4" fz="1rem" lh="1.4rem">
|
||||
Sedang memuat potensi desa...
|
||||
</Text>
|
||||
</Stack>
|
||||
) : data.length === 0 ? (
|
||||
<Stack align="center" justify="center" h={300} gap="xs">
|
||||
<IconInfoCircle size={48} color={colors["blue-button"]} />
|
||||
<Text fw={600} c="gray.3">
|
||||
<Text fw={600} c="gray.3" fz="1.2rem" lh="1.4rem">
|
||||
Belum ada potensi tersedia
|
||||
</Text>
|
||||
<Text size="sm" c="gray.5">
|
||||
<Text fz="0.9rem" lh="1.3rem" c="gray.5">
|
||||
Silakan cek kembali nanti untuk pembaruan terbaru.
|
||||
</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
/* CARD LIST */
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }}>
|
||||
{_.take(data, 4).map((v, k) => (
|
||||
<motion.div
|
||||
@@ -84,7 +105,12 @@ function Potensi() {
|
||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<BackgroundImage src={v.image?.link} h={320} radius={20} pos="relative">
|
||||
<BackgroundImage
|
||||
src={v.image?.link}
|
||||
h={320}
|
||||
radius={20}
|
||||
pos="relative"
|
||||
>
|
||||
<Box
|
||||
pos="absolute"
|
||||
w="100%"
|
||||
@@ -92,6 +118,8 @@ function Potensi() {
|
||||
bg={colors.trans.dark[2]}
|
||||
style={{ borderRadius: 20, zIndex: 0 }}
|
||||
/>
|
||||
|
||||
{/* CARD CONTENT */}
|
||||
<Stack
|
||||
justify="end"
|
||||
h="100%"
|
||||
@@ -101,11 +129,24 @@ function Potensi() {
|
||||
style={{ zIndex: 1 }}
|
||||
>
|
||||
<Tooltip label={v.name} position="top-start">
|
||||
<Text fw={700} c="white" fz={{ base: "1.2rem", md: "1.4rem" }} truncate>
|
||||
<Text
|
||||
fw={700}
|
||||
c="white"
|
||||
fz={{ base: "1.25rem", md: "1.45rem" }}
|
||||
lh={{ base: "1.6rem", md: "1.8rem" }}
|
||||
truncate
|
||||
>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
|
||||
<Text
|
||||
lineClamp={2}
|
||||
c="gray.2"
|
||||
fz={{ base: "0.85rem", md: "1rem" }}
|
||||
lh={{ base: "1.2rem", md: "1.4rem" }}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
</motion.div>
|
||||
@@ -113,16 +154,18 @@ function Potensi() {
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
{/* BUTTON */}
|
||||
<Stack align="center">
|
||||
<Group>
|
||||
<Button
|
||||
onClick={() => router.push("/darmasaba/desa/potensi")}
|
||||
color={colors["blue-button"]}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170", }}
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
radius="xl"
|
||||
size="md"
|
||||
rightSection={<IconArrowRight size={18} />}
|
||||
style={{ fontWeight: 600 }}
|
||||
>
|
||||
Lihat Semua Potensi
|
||||
</Button>
|
||||
|
||||
@@ -2,7 +2,19 @@
|
||||
'use client'
|
||||
import prestasiState from "@/app/admin/(dashboard)/_state/landing-page/prestasi-desa";
|
||||
import colors from "@/con/colors";
|
||||
import { BackgroundImage, Box, Button, Center, Container, Group, Loader, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import {
|
||||
BackgroundImage,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Container,
|
||||
Group,
|
||||
Loader,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -32,12 +44,31 @@ function Prestasi() {
|
||||
<Stack p="sm" bg="linear-gradient(180deg, #ffffff 0%, #f8fbff 100%)">
|
||||
<Container w={{ base: "100%", md: "80%" }} p="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<Text c={colors["blue-button"]} ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
||||
Prestasi Desa
|
||||
</Text>
|
||||
<Text fz={{ base: "1rem", md: "1.3rem" }} ta="center" c="dimmed" maw={700}>
|
||||
Kami bangga dengan pencapaian desa hingga saat ini. Semoga prestasi ini menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan bersama.
|
||||
|
||||
{/* TITLE UTAMA */}
|
||||
<Title
|
||||
order={1}
|
||||
c={colors["blue-button"]}
|
||||
ta="center"
|
||||
fz={{ base: "2rem", sm: "2.6rem", md: "3.2rem" }}
|
||||
lh={{ base: "2.4rem", md: "3.5rem" }}
|
||||
>
|
||||
Prestasi Desa
|
||||
</Title>
|
||||
|
||||
{/* SUBTEXT */}
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
lh={{ base: "1.5rem", md: "1.8rem" }}
|
||||
ta="center"
|
||||
c="black"
|
||||
maw={700}
|
||||
>
|
||||
Kami bangga dengan pencapaian desa hingga saat ini. Semoga prestasi ini
|
||||
menjadi inspirasi untuk terus berkarya dan berinovasi demi kemajuan
|
||||
bersama.
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
radius="xl"
|
||||
size="lg"
|
||||
@@ -59,13 +90,13 @@ function Prestasi() {
|
||||
) : data.length === 0 ? (
|
||||
<Center mih={200}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz="1.2rem" fw={500} c="dimmed">
|
||||
<Text fz="1.2rem" fw={500} c="dimmed" ta="center" lh="1.4rem">
|
||||
Belum ada prestasi yang ditampilkan
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" mb={"xl"}>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg" mb="xl">
|
||||
{data.map((v, k) => (
|
||||
<BackgroundImage
|
||||
key={k}
|
||||
@@ -79,26 +110,32 @@ function Prestasi() {
|
||||
bg="linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.7) 100%)"
|
||||
style={{ borderRadius: 27 }}
|
||||
/>
|
||||
|
||||
<Stack justify="space-between" h="100%" pos="relative" p="lg">
|
||||
<Box>
|
||||
<Text
|
||||
c="white"
|
||||
fz={{ base: "1rem", md: "1.25rem" }}
|
||||
ta="center"
|
||||
fw={500}
|
||||
>
|
||||
{v.kategori.name}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* KATEGORI */}
|
||||
<Text
|
||||
c="white"
|
||||
fz={{ base: "1rem", md: "1.15rem" }}
|
||||
lh={{ base: "1.4rem", md: "1.6rem" }}
|
||||
ta="center"
|
||||
fw={500}
|
||||
>
|
||||
{v.kategori.name}
|
||||
</Text>
|
||||
|
||||
{/* DESKRIPSI */}
|
||||
<Text
|
||||
fw={700}
|
||||
c="white"
|
||||
fz={{ base: "1.5rem", md: "2rem", lg: "2.5rem" }}
|
||||
fz={{ base: "1.4rem", md: "1.8rem", lg: "2.2rem" }}
|
||||
lh={{ base: "1.8rem", md: "2.2rem", lg: "2.6rem" }}
|
||||
ta="center"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
lineClamp={5}
|
||||
/>
|
||||
|
||||
<Group justify="center">
|
||||
<Button
|
||||
onClick={() => router.push(`/darmasaba/prestasi-desa/${v.id}`)}
|
||||
|
||||
@@ -20,12 +20,11 @@ export default function SDGS() {
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
||||
const result = await response.json()
|
||||
let data = []
|
||||
|
||||
if (Array.isArray(result.data)) data = result.data
|
||||
else if (Array.isArray(result)) data = result
|
||||
else {
|
||||
setSdgsDesa([])
|
||||
return
|
||||
}
|
||||
else return setSdgsDesa([])
|
||||
|
||||
const top4Sdgs = [...data].sort((a, b) => parseInt(b.jumlah) - parseInt(a.jumlah)).slice(0, 4)
|
||||
setSdgsDesa(top4Sdgs)
|
||||
} catch {
|
||||
@@ -36,24 +35,38 @@ export default function SDGS() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Stack p="sm" my={"xs"}>
|
||||
<Stack p="sm" my="xs">
|
||||
<Container w={{ base: "100%", md: "80%" }} p="xl">
|
||||
|
||||
{/* ========== TITLE SECTION ========== */}
|
||||
<Center>
|
||||
<Title
|
||||
order={1}
|
||||
fz={{ base: "2.4rem", md: "3.6rem" }}
|
||||
fz={{ base: "2.2rem", md: "3.4rem" }}
|
||||
lh={{ base: 1.1, md: 1.1 }}
|
||||
fw={900}
|
||||
c={colors["blue-button"]}
|
||||
ta="center"
|
||||
>
|
||||
SDGs Desa
|
||||
</Title>
|
||||
</Center>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
|
||||
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
c="black"
|
||||
mt="xs"
|
||||
mb="md"
|
||||
>
|
||||
SDGs Desa adalah upaya desa untuk menciptakan pembangunan yang maju, inklusif, dan berkelanjutan melalui 17 tujuan mulai dari pengentasan kemiskinan, pendidikan, kesehatan, hingga pelestarian lingkungan.
|
||||
</Text>
|
||||
|
||||
<Box py="lg">
|
||||
{sdgsDesa && sdgsDesa.length > 0 ? (
|
||||
|
||||
/* ========== LIST GRID ========== */
|
||||
<SimpleGrid cols={{ base: 1, sm: 4 }} spacing="xl" verticalSpacing="xl" pb={30}>
|
||||
{sdgsDesa.map((item) => (
|
||||
<motion.div
|
||||
@@ -70,7 +83,7 @@ export default function SDGS() {
|
||||
background: "linear-gradient(180deg, #FFFFFF, #F6F8FA)",
|
||||
border: "1px solid rgba(0,0,0,0.05)",
|
||||
transition: "all 0.3s ease",
|
||||
height: "100%", // biar tinggi antar card konsisten
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
@@ -101,23 +114,26 @@ export default function SDGS() {
|
||||
</Box>
|
||||
</Center>
|
||||
|
||||
{/* Stack isi teks & angka */}
|
||||
<Stack justify="space-between" align="center" gap="xs" h="100%">
|
||||
|
||||
{/* JUDUL ITEM */}
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: "lg", md: "xl" }}
|
||||
lh={{ base: 1.3, md: 1.3 }}
|
||||
fw={700}
|
||||
mb="xs"
|
||||
style={{ minHeight: mobile ? 60 : 70 }} // biar judulnya punya tinggi tetap
|
||||
style={{ minHeight: mobile ? 60 : 70 }}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
|
||||
{/* ANGKA */}
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
style={{
|
||||
fontSize: mobile ? "2.4rem" : "3.2rem",
|
||||
fontSize: mobile ? "2.2rem" : "3rem",
|
||||
lineHeight: 1.1,
|
||||
fontWeight: 900,
|
||||
letterSpacing: "-0.5px",
|
||||
color: "#124170",
|
||||
@@ -132,14 +148,15 @@ export default function SDGS() {
|
||||
</SimpleGrid>
|
||||
|
||||
) : (
|
||||
|
||||
/* ========== EMPTY STATE ========== */
|
||||
<Center mih={200} style={{ flexDirection: "column" }}>
|
||||
<IconMoodSad size={48} stroke={1.5} style={{ marginBottom: "1rem" }} />
|
||||
<Text fz="lg" c="dimmed">
|
||||
Data SDGs Desa belum tersedia
|
||||
</Text>
|
||||
<Text fz="lg" lh={1.4} c="dimmed">Data SDGs Desa belum tersedia</Text>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
{/* BUTTON */}
|
||||
<Center>
|
||||
<Button
|
||||
component={Link}
|
||||
@@ -152,18 +169,19 @@ export default function SDGS() {
|
||||
style={{
|
||||
boxShadow: "0 6px 14px rgba(18,65,112,0.25)",
|
||||
transition: "all 0.3s ease",
|
||||
transform: "translateY(0)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(-4px)";
|
||||
e.currentTarget.style.boxShadow = "0 10px 20px rgba(18,65,112,0.35)";
|
||||
e.currentTarget.style.transform = "translateY(-4px)"
|
||||
e.currentTarget.style.boxShadow = "0 10px 20px rgba(18,65,112,0.35)"
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(0)";
|
||||
e.currentTarget.style.boxShadow = "0 6px 14px rgba(18,65,112,0.25)";
|
||||
e.currentTarget.style.transform = "translateY(0)"
|
||||
e.currentTarget.style.boxShadow = "0 6px 14px rgba(18,65,112,0.25)"
|
||||
}}
|
||||
>
|
||||
<Text c="white" fz={{ base: "md", md: "lg" }} fw="bold">Jelajahi Semua Tujuan SDGs Desa</Text>
|
||||
<Text c="white" fz={{ base: "md", md: "lg" }} lh={1.3} fw={600}>
|
||||
Jelajahi Semua Tujuan SDGs Desa
|
||||
</Text>
|
||||
</Button>
|
||||
</Center>
|
||||
</Box>
|
||||
|
||||
@@ -158,8 +158,6 @@ export default function Page() {
|
||||
<SDGS />
|
||||
<Apbdes />
|
||||
<Prestasi />
|
||||
</Stack>
|
||||
|
||||
<ScrollToTopButton />
|
||||
<NewsReaderLanding />
|
||||
|
||||
@@ -170,6 +168,8 @@ export default function Page() {
|
||||
onSeen={handleSeen}
|
||||
autoShowDelay={2000}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -5,8 +5,8 @@ const navbarListMenu = [
|
||||
children: [
|
||||
{
|
||||
id: "1.1",
|
||||
name: "Profile PPID",
|
||||
href: "/darmasaba/ppid/profile-ppid"
|
||||
name: "Profil PPID",
|
||||
href: "/darmasaba/ppid/profil-ppid"
|
||||
},
|
||||
{
|
||||
id: "1.2",
|
||||
@@ -53,8 +53,8 @@ const navbarListMenu = [
|
||||
children: [
|
||||
{
|
||||
id: "2.1",
|
||||
name: "Profile",
|
||||
href: "/darmasaba/desa/profile"
|
||||
name: "Profil",
|
||||
href: "/darmasaba/desa/profil"
|
||||
},
|
||||
{
|
||||
id: "2.2",
|
||||
|
||||
Reference in New Issue
Block a user