Fix QC Kak Inno Tgl 4 & 5 Desember

Fix QC Kak Ayu Tgl 4 & 5 Desember
Fix QC Pak Jun Tgl 5 Desember
This commit is contained in:
2025-12-09 10:28:17 +08:00
parent dcb8017594
commit cc318d4d54
28 changed files with 816 additions and 1124 deletions

View File

@@ -100,7 +100,7 @@ function Page() {
{data.name}
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Box px={{ base: "35", md: 100 }}>
<Stack gap="md">
<Text
dangerouslySetInnerHTML={{ __html: data.deskripsi }}

View File

@@ -1,96 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../../layanan/_com/BackButto';
import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
const dataPengumuman = [
{
id: 1,
judul: 'Sosialisasi Perarem Konservasi Adat di Darmasaba',
tanggal: 'Jumat, 26 April 2025',
jam: '16:00 WITA',
lokasi: 'Wantilan Adat Desa',
deskripsi: 'Para krama diundang hadir dalam sosialisasi perarem (aturan adat) baru yang berkaitan dengan konservasi lingkungan berbasis budaya, termasuk larangan alih fungsi lahan pekarangan suci.'
},
{
id: 2,
judul: 'Sosialisasi Perarem Konservasi Adat di Darmasaba',
tanggal: 'Jumat, 26 April 2025',
jam: '16:00 WITA',
lokasi: 'Wantilan Adat Desa',
deskripsi: 'Para krama diundang hadir dalam sosialisasi perarem (aturan adat) baru yang berkaitan dengan konservasi lingkungan berbasis budaya, termasuk larangan alih fungsi lahan pekarangan suci.'
},
{
id: 3,
judul: 'Sosialisasi Perarem Konservasi Adat di Darmasaba',
tanggal: 'Jumat, 26 April 2025',
jam: '16:00 WITA',
lokasi: 'Wantilan Adat Desa',
deskripsi: 'Para krama diundang hadir dalam sosialisasi perarem (aturan adat) baru yang berkaitan dengan konservasi lingkungan berbasis budaya, termasuk larangan alih fungsi lahan pekarangan suci.'
},
{
id: 4,
judul: 'Sosialisasi Perarem Konservasi Adat di Darmasaba',
tanggal: 'Jumat, 26 April 2025',
jam: '16:00 WITA',
lokasi: 'Wantilan Adat Desa',
deskripsi: 'Para krama diundang hadir dalam sosialisasi perarem (aturan adat) baru yang berkaitan dengan konservasi lingkungan berbasis budaya, termasuk larangan alih fungsi lahan pekarangan suci.'
},
{
id: 5,
judul: 'Sosialisasi Perarem Konservasi Adat di Darmasaba',
tanggal: 'Jumat, 26 April 2025',
jam: '16:00 WITA',
lokasi: 'Wantilan Adat Desa',
deskripsi: 'Para krama diundang hadir dalam sosialisasi perarem (aturan adat) baru yang berkaitan dengan konservasi lingkungan berbasis budaya, termasuk larangan alih fungsi lahan pekarangan suci.'
},
]
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Pengumuman Adat & Budaya
</Text>
<Text ta="center" px="md" pb={10}>
Informasi dan pengumuman resmi terkait pengumuman adat & budaya di Desa Darmasaba.
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{dataPengumuman.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">{v.tanggal}</Text>
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">{v.jam}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text size="sm">{v.lokasi}</Text>
</Group>
</Group>
<Text ta={'justify'}>
{v.deskripsi}
</Text>
</Paper>
)
})}
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,96 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../../layanan/_com/BackButto';
import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
const dataPengumuman = [
{
id: 1,
judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
tanggal: 'Selasa, 30 April 2025',
jam: '09:00 WITA',
lokasi: 'Perpustakaan Desa',
deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
},
{
id: 2,
judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
tanggal: 'Selasa, 30 April 2025',
jam: '09:00 WITA',
lokasi: 'Perpustakaan Desa',
deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
},
{
id: 3,
judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
tanggal: 'Selasa, 30 April 2025',
jam: '09:00 WITA',
lokasi: 'Perpustakaan Desa',
deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
},
{
id: 4,
judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
tanggal: 'Selasa, 30 April 2025',
jam: '09:00 WITA',
lokasi: 'Perpustakaan Desa',
deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
},
{
id: 5,
judul: 'Pelatihan Dasar Komputer untuk Lansia dan Ibu Rumah Tangga',
tanggal: 'Selasa, 30 April 2025',
jam: '09:00 WITA',
lokasi: 'Perpustakaan Desa',
deskripsi: 'Belajar dari nol: cara menyalakan komputer, menulis di Word, membuat email, dan dasar penggunaan HP Android untuk komunikasi dan akses layanan desa online. Terbuka untuk 20 peserta.'
},
]
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Pengumuman Digitalisasi Desa
</Text>
<Text ta="center" px="md" pb={10}>
Informasi dan pengumuman resmi terkait pengumuman digitalisasi desa
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{dataPengumuman.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">{v.tanggal}</Text>
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">{v.jam}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text size="sm">{v.lokasi}</Text>
</Group>
</Group>
<Text ta={'justify'}>
{v.deskripsi}
</Text>
</Paper>
)
})}
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,96 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../../layanan/_com/BackButto';
import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
const dataPengumuman = [
{
id: 1,
judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
tanggal: 'Rabu, 23 April 2025',
jam: '13:00 WITA',
lokasi: 'Aula Kantor Desa',
deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
},
{
id: 2,
judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
tanggal: 'Rabu, 23 April 2025',
jam: '13:00 WITA',
lokasi: 'Aula Kantor Desa',
deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
},
{
id: 3,
judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
tanggal: 'Rabu, 23 April 2025',
jam: '13:00 WITA',
lokasi: 'Aula Kantor Desa',
deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
},
{
id: 4,
judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
tanggal: 'Rabu, 23 April 2025',
jam: '13:00 WITA',
lokasi: 'Aula Kantor Desa',
deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
},
{
id: 5,
judul: 'Pelatihan Digital Marketing untuk UMKM Desa',
tanggal: 'Rabu, 23 April 2025',
jam: '13:00 WITA',
lokasi: 'Aula Kantor Desa',
deskripsi: 'Para pelaku UMKM diundang untuk mengikuti pelatihan dasar digital marketing: mulai dari membuat akun bisnis, copywriting produk, hingga promosi melalui media sosial. Peserta akan mendapat sertifikat dan materi pelatihan.'
},
]
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Pengumuman Ekonomi & UMKM
</Text>
<Text ta="center" px="md" pb={10}>
Informasi dan pengumuman resmi terkait pengumuman ekonomi & umkm
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{dataPengumuman.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">{v.tanggal}</Text>
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">{v.jam}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text size="sm">{v.lokasi}</Text>
</Group>
</Group>
<Text ta={'justify'}>
{v.deskripsi}
</Text>
</Paper>
)
})}
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,96 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../../layanan/_com/BackButto';
import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
const dataPengumuman = [
{
id: 1,
judul: 'Gotong Royong Bersih Sungai dan Drainase',
tanggal: 'Minggu, 21 April 2025',
jam: '06:30 WITA',
lokasi: 'Titik Kumpul: Poskamling RW 02',
deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
},
{
id: 2,
judul: 'Gotong Royong Bersih Sungai dan Drainase',
tanggal: 'Minggu, 21 April 2025',
jam: '06:30 WITA',
lokasi: 'Titik Kumpul: Poskamling RW 02',
deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
},
{
id: 3,
judul: 'Gotong Royong Bersih Sungai dan Drainase',
tanggal: 'Minggu, 21 April 2025',
jam: '06:30 WITA',
lokasi: 'Titik Kumpul: Poskamling RW 02',
deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
},
{
id: 4,
judul: 'Gotong Royong Bersih Sungai dan Drainase',
tanggal: 'Minggu, 21 April 2025',
jam: '06:30 WITA',
lokasi: 'Titik Kumpul: Poskamling RW 02',
deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
},
{
id: 5,
judul: 'Gotong Royong Bersih Sungai dan Drainase',
tanggal: 'Minggu, 21 April 2025',
jam: '06:30 WITA',
lokasi: 'Titik Kumpul: Poskamling RW 02',
deskripsi: 'Seluruh warga RW 02 diimbau ikut serta dalam kegiatan bersih-bersih aliran sungai dan saluran air untuk mencegah banjir saat musim hujan. Disediakan alat kebersihan dan konsumsi ringan.'
},
]
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Pengumuman Lingkungan & Bencana
</Text>
<Text ta="center" px="md" pb={10}>
Informasi dan pengumuman resmi terkait pengumuman lingkungan & bencana
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{dataPengumuman.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">{v.tanggal}</Text>
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">{v.jam}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text size="sm">{v.lokasi}</Text>
</Group>
</Group>
<Text ta={'justify'}>
{v.deskripsi}
</Text>
</Paper>
)
})}
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,96 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../../layanan/_com/BackButto';
import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
const dataPengumuman = [
{
id: 1,
judul: 'Pendaftaran Bimbingan Belajar Gratis untuk Pelajar',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
{
id: 2,
judul: 'Lomba Video Pendek Hari Lingkungan',
tanggal: 'Deadline: 28 April 2025',
jam: '08:00 WITA',
lokasi: 'Online Submission',
deskripsi: 'Karang Taruna Desa mengadakan lomba video pendek bertema "Lingkunganku, Tanggung Jawabku". Pemenang akan diumumkan saat acara Hari Desa Hijau. Total hadiah Rp1.000.000.'
},
{
id: 3,
judul: 'Pendaftaran Bimbingan Belajar Gratis untuk Pelajar',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
{
id: 4,
judul: 'Lomba Video Pendek Hari Lingkungan',
tanggal: 'Deadline: 28 April 2025',
jam: '08:00 WITA',
lokasi: 'Online Submission',
deskripsi: 'Karang Taruna Desa mengadakan lomba video pendek bertema "Lingkunganku, Tanggung Jawabku". Pemenang akan diumumkan saat acara Hari Desa Hijau. Total hadiah Rp1.000.000.'
},
{
id: 5,
judul: 'Pendaftaran Bimbingan Belajar Gratis untuk Pelajar',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
]
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Pengumuman Pendidikan & Kepemudaan
</Text>
<Text ta="center" px="md" pb={10}>
Informasi dan pengumuman resmi terkait pengumuman pendidikan & kepemudaan
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{dataPengumuman.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">{v.tanggal}</Text>
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">{v.jam}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text size="sm">{v.lokasi}</Text>
</Group>
</Group>
<Text ta={'justify'}>
{v.deskripsi}
</Text>
</Paper>
)
})}
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,96 +0,0 @@
import colors from '@/con/colors';
import { Stack, Box, Container, Text, Paper, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../../layanan/_com/BackButto';
import { IconCalendar, IconClock, IconMapPin } from '@tabler/icons-react';
const dataPengumuman = [
{
id: 1,
judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
{
id: 2,
judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
{
id: 3,
judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
{
id: 4,
judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
{
id: 5,
judul: 'Pemeriksaan Kesehatan Gratis Untuk Warga Desa',
tanggal: 'Sabtu, 20 April 2025',
jam: '08:00 WITA',
lokasi: 'Balai Banjar Desa Darmasaba',
deskripsi: 'Dalam rangka meningkatkan kesadaran kesehatan, Pemerintah Desa Darmasaba bekerja sama dengan Puskesmas Abiansemal akan mengadakan pemeriksaan kesehatan gratis meliputi cek tekanan darah, kolesterol, gula darah, dan konsultasi gizi.'
},
]
function Page() {
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
{/* Header */}
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container size="lg" px="md" >
<Stack align="center" gap="0" >
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
Pengumuman Sosial & Kesehatan
</Text>
<Text ta="center" px="md" pb={10}>
Informasi dan pengumuman resmi terkait pengumuman sosial & kesehatan
</Text>
</Stack>
</Container>
<Box px={{ base: 'md', md: 100 }}>
{dataPengumuman.map((v, k) => {
return (
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
<Text fz={'h3'}>{v.judul}</Text>
<Group style={{ color: 'black' }} pb={20}>
<Group gap="xs">
<IconCalendar size={18} />
<Text size="sm">{v.tanggal}</Text>
</Group>
<Group gap="xs">
<IconClock size={18} />
<Text size="sm">{v.jam}</Text>
</Group>
<Group gap="xs">
<IconMapPin size={18} />
<Text size="sm">{v.lokasi}</Text>
</Group>
</Group>
<Text ta={'justify'}>
{v.deskripsi}
</Text>
</Paper>
)
})}
</Box>
</Stack>
);
}
export default Page;

View File

@@ -46,8 +46,8 @@ function Page() {
</Group>
</Group>
<Paper bg={colors["white-1"]} p="md">
<Text id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
<Text px="lg" id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
<Text px="lg" fz={"md"} c={colors["blue-button"]} fw="bold" >
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
weekday: 'long',
day: 'numeric',

View File

@@ -14,6 +14,9 @@ import {
Loader,
Paper,
Stack,
Tabs,
TabsList,
TabsTab,
Text,
TextInput,
Title,
@@ -35,6 +38,7 @@ import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils'
import './struktur.css'
import BackButton from '../_com/BackButto'
import { useMediaQuery } from '@mantine/hooks'
export default function StrukturPerangkatDesa() {
return (
@@ -231,87 +235,121 @@ function StrukturPerangkatDesaNode() {
p="md"
radius="md"
style={{
background: colors['blue-button']
background: colors['blue-button'],
width: '100%', // ⬅️ penting
maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow
}}
>
<Group gap="sm" wrap="wrap" justify="center">
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
<Stack gap="sm">
<Group justify='center'>
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
styles={{
input: {
minWidth: 250,
},
}}
/>
</Group>
<Tabs
defaultValue="zoom-out"
variant="outline"
radius="md"
styles={{
input: {
minWidth: 250,
panel: { display: 'none' },
tab: {
color: colors['blue-button'],
backgroundColor: colors['blue-button-2'],
border: 'none',
fontWeight: 600,
fontSize: '0.875rem',
padding: '6px 12px',
minHeight: 'auto',
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
},
}}
/>
<Group gap="xs">
<Button
variant="light"
bg={colors['blue-button-2']}
size="sm"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
c={colors['blue-button']}
>
Zoom Out
</Button>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={16}
py={8}
>
<TabsList
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '8px',
minWidth: 70,
textAlign: 'center',
display: 'flex',
overflowX: 'auto',
overflowY: 'hidden', // 👈 tambahkan ini
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
}}
>
{Math.round(scale * 100)}%
</Box>
<TabsTab
value="zoom-out"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
>
Zoom Out
</TabsTab>
<Button
bg={colors['blue-button-2']}
c={colors['blue-button']}
variant="light"
size="sm"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
>
Zoom In
</Button>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={12}
py={6}
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '6px',
minWidth: 60,
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
whiteSpace: 'nowrap', // 👈 mencegah text wrap
}}
>
{Math.round(scale * 100)}%
</Box>
<Button
bg={colors['blue-button-2']}
c={colors['blue-button']}
variant="light"
size="sm"
onClick={resetZoom}
>
Reset
</Button>
<TabsTab
value="zoom-in"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
>
Zoom In
</TabsTab>
<Button
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
>
Fullscreen
</Button>
</Group>
</Group>
<TabsTab
value="reset"
onClick={resetZoom}
style={{ flexShrink: 0 }}
>
Reset
</TabsTab>
<TabsTab
value="fullscreen"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
style={{ flexShrink: 0 }}
>
{isFullscreen ? 'Exit' : 'Fullscreen'}
</TabsTab>
</TabsList>
</Tabs>
</Stack>
</Paper>
{/* 🧩 Chart Container */}
@@ -325,15 +363,20 @@ function StrukturPerangkatDesaNode() {
maxWidth: '100%',
padding: '32px 16px',
transition: 'transform 0.2s ease',
transform: `scale(${scale})`,
transformOrigin: 'center top',
}}
>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
<Box style={{
transform: `scale(${scale})`,
transformOrigin: 'center top',
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
}}>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
</Box>
</Box>
</Center>
</Stack>
@@ -345,6 +388,7 @@ function NodeCard({ node, router }: any) {
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const hasId = Boolean(node?.data?.id)
const isMobile = useMediaQuery("(max-width: 768px)");
return (
<Transition mounted transition="pop" duration={300}>
@@ -355,9 +399,10 @@ function NodeCard({ node, router }: any) {
withBorder
style={{
...styles,
width: 240,
minHeight: 280,
padding: 20,
width: '100%',
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
minHeight: isMobile ? 240 : 280,
padding: isMobile ? 16 : 20,
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
borderColor: 'rgba(28, 110, 164, 0.3)',
borderWidth: 2,

View File

@@ -87,7 +87,7 @@ export default function DetailInformasiPublikUser() {
<Divider />
<Stack gap="lg">
<Box>
<Box px="lg">
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
Jenis Informasi
</Text>
@@ -96,7 +96,7 @@ export default function DetailInformasiPublikUser() {
</Text>
</Box>
<Box>
<Box px="lg">
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
Tanggal Publikasi
</Text>
@@ -111,15 +111,19 @@ export default function DetailInformasiPublikUser() {
</Text>
</Box>
<Box>
<Box px="lg">
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
Deskripsi
</Text>
<Box
className="prose max-w-none leading-relaxed"
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
/>
<Box>
<Text
ta={"justify"}
className="prose max-w-none leading-relaxed"
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
fz={{ base: 'md', md: 'lg' }}
/>
</Box>
</Box>
</Stack>
</Stack>

View File

@@ -31,7 +31,11 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Stack align="center" gap="xs">
<Stack
align="center"
gap="xs"
px={{ base: 'md', md: 100 }}
>
<IconBook2 size={42} stroke={1.5} color={colors["blue-button"]} />
<Text
ta="center"
@@ -42,7 +46,7 @@ function Page() {
>
Dasar Hukum
</Text>
<Text ta="center" fz="md" >
<Text ta="center" fz="md" c={"black"}>
Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum
</Text>
</Stack>
@@ -71,12 +75,15 @@ function Page() {
<Stack gap="md">
<Text
ta="center"
c={"black"}
fw="bold"
fz={{ base: 'lg', md: 'xl' }}
style={{ lineHeight: 1.4 }}
dangerouslySetInnerHTML={{ __html: item.judul }}
/>
<Text
c={"black"}
ta={"justify"}
fz={{ base: 'sm', md: 'md' }}
style={{ lineHeight: 1.7, wordBreak: "break-word", whiteSpace: "normal" }}
dangerouslySetInnerHTML={{ __html: item.content }}

View File

@@ -598,7 +598,7 @@ const state = useProxy(indeksKepuasanState.responden);
<TextInput
label="Nama"
type='text'
placeholder="masukkan nama"
placeholder="Masukkan nama"
value={state.create.form.name}
onChange={(val) => {
state.create.form.name = val.currentTarget.value;
@@ -607,7 +607,7 @@ const state = useProxy(indeksKepuasanState.responden);
<TextInput
label="Tanggal Pengisian"
type="date"
placeholder="masukkan tanggal"
placeholder="Masukkan tanggal"
value={state.create.form.tanggal}
onChange={(val) => {
state.create.form.tanggal = val.currentTarget.value;

View File

@@ -53,23 +53,11 @@ function Page() {
const permohonanInformasiPublikState = useProxy(statePermohonanInformasi);
const router = useRouter();
const submitForms = () => {
const submitForms = async () => {
const { create } = permohonanInformasiPublikState.statepermohonanInformasiPublik;
if (
create.form.name &&
create.form.nik &&
create.form.notelp &&
create.form.alamat &&
create.form.email &&
create.form.jenisInformasiDimintaId &&
create.form.caraMemperolehInformasiId &&
create.form.caraMemperolehSalinanInformasiId
) {
create.create();
const hasil = await create.create(); // tunggu hasilnya
if (hasil) {
router.push('/darmasaba/permohonan/berhasil');
} else {
console.log('Validasi gagal, form tidak lengkap');
}
};

View File

@@ -55,17 +55,13 @@ function Page() {
const stateKeberatan = useProxy(permohonanKeberatanInformasi);
const router = useRouter();
const submit = () => {
if (
stateKeberatan.create.form.name &&
stateKeberatan.create.form.email &&
stateKeberatan.create.form.notelp &&
stateKeberatan.create.form.alasan
) {
stateKeberatan.create.create();
const submit = async () => {
const { create } = stateKeberatan;
const hasil = await create.create(); // tunggu hasilnya
if (hasil) {
router.push('/darmasaba/permohonan/berhasil');
} else {
console.log('Formulir belum lengkap');
}
};
@@ -190,7 +186,7 @@ function Page() {
<TextInput
label="Nomor Telepon"
placeholder="Contoh: 0812-3456-7890"
placeholder="Contoh: 081234567890"
radius="md"
size="md"
withAsterisk

View File

@@ -96,14 +96,20 @@ function Page() {
<IconUser size={28} />
<Text fz={{ base: "1.25rem", md: "1.5rem" }} fw="bold">Biografi</Text>
</Flex>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} ta="justify" dangerouslySetInnerHTML={{ __html: item.biodata }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
<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>
<Text fz={{ base: "1rem", md: "1.125rem", lg: "1.25rem" }} dangerouslySetInnerHTML={{ __html: item.riwayat }} style={{ wordBreak: "break-word", whiteSpace: "normal" }} />
<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>

View File

@@ -14,6 +14,9 @@ import {
Loader,
Paper,
Stack,
Tabs,
TabsList,
TabsTab,
Text,
TextInput,
Title,
@@ -35,6 +38,7 @@ import { useEffect, useRef, useState } from 'react'
import { useProxy } from 'valtio/utils'
import BackButton from '../../desa/layanan/_com/BackButto'
import './struktur.css'
import { useMediaQuery } from '@mantine/hooks'
export default function Page() {
return (
@@ -231,87 +235,121 @@ function StrukturOrganisasiPPID() {
p="md"
radius="md"
style={{
background: colors['blue-button']
background: colors['blue-button'],
width: '100%', // ⬅️ penting
maxWidth: '100%', // ⬅️ penting
overflowX: 'auto' // ⬅️ untuk mencegah overflow
}}
>
<Group gap="sm" wrap="wrap" justify="center">
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
<Stack gap="sm">
<Group justify='center'>
<TextInput
placeholder="Cari nama atau jabatan..."
leftSection={<IconSearch size={16} />}
onChange={(e) => debouncedSearch(e.target.value)}
styles={{
input: {
minWidth: 250,
},
}}
/>
</Group>
<Tabs
defaultValue="zoom-out"
variant="outline"
radius="md"
styles={{
input: {
minWidth: 250,
panel: { display: 'none' },
tab: {
color: colors['blue-button'],
backgroundColor: colors['blue-button-2'],
border: 'none',
fontWeight: 600,
fontSize: '0.875rem',
padding: '6px 12px',
minHeight: 'auto',
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
},
}}
/>
<Group gap="xs">
<Button
variant="light"
bg={colors['blue-button-2']}
size="sm"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
c={colors['blue-button']}
>
Zoom Out
</Button>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={16}
py={8}
>
<TabsList
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '8px',
minWidth: 70,
textAlign: 'center',
display: 'flex',
overflowX: 'auto',
overflowY: 'hidden', // 👈 tambahkan ini
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
}}
>
{Math.round(scale * 100)}%
</Box>
<TabsTab
value="zoom-out"
onClick={handleZoomOut}
leftSection={<IconZoomOut size={16} />}
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
>
Zoom Out
</TabsTab>
<Button
bg={colors['blue-button-2']}
c={colors['blue-button']}
variant="light"
size="sm"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
>
Zoom In
</Button>
<Box
bg={colors['blue-button-2']}
c={colors['blue-button']}
px={12}
py={6}
style={{
fontSize: 14,
fontWeight: 700,
borderRadius: '6px',
minWidth: 60,
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
whiteSpace: 'nowrap', // 👈 mencegah text wrap
}}
>
{Math.round(scale * 100)}%
</Box>
<Button
bg={colors['blue-button-2']}
c={colors['blue-button']}
variant="light"
size="sm"
onClick={resetZoom}
>
Reset
</Button>
<TabsTab
value="zoom-in"
onClick={handleZoomIn}
leftSection={<IconZoomIn size={16} />}
style={{ flexShrink: 0 }}
>
Zoom In
</TabsTab>
<Button
bg={colors['blue-button-2']}
c={colors['blue-button']}
size="sm"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
>
Fullscreen
</Button>
</Group>
</Group>
<TabsTab
value="reset"
onClick={resetZoom}
style={{ flexShrink: 0 }}
>
Reset
</TabsTab>
<TabsTab
value="fullscreen"
onClick={toggleFullscreen}
leftSection={
isFullscreen ? (
<IconArrowsMinimize size={16} />
) : (
<IconArrowsMaximize size={16} />
)
}
style={{ flexShrink: 0 }}
>
{isFullscreen ? 'Exit' : 'Fullscreen'}
</TabsTab>
</TabsList>
</Tabs>
</Stack>
</Paper>
{/* 🧩 Chart Container */}
@@ -325,15 +363,20 @@ function StrukturOrganisasiPPID() {
maxWidth: '100%',
padding: '32px 16px',
transition: 'transform 0.2s ease',
transform: `scale(${scale})`,
transformOrigin: 'center top',
}}
>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
<Box style={{
transform: `scale(${scale})`,
transformOrigin: 'center top',
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
}}>
<OrganizationChart
value={chartData}
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
className="p-organizationchart p-organizationchart-horizontal"
/>
</Box>
</Box>
</Center>
</Stack>
@@ -345,6 +388,7 @@ function NodeCard({ node, router }: any) {
const name = node?.data?.name || 'Tanpa Nama'
const title = node?.data?.title || 'Tanpa Jabatan'
const hasId = Boolean(node?.data?.id)
const isMobile = useMediaQuery("(max-width: 768px)");
return (
<Transition mounted transition="pop" duration={300}>
@@ -355,9 +399,10 @@ function NodeCard({ node, router }: any) {
withBorder
style={{
...styles,
width: 240,
minHeight: 280,
padding: 20,
width: '100%',
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
minHeight: isMobile ? 240 : 280,
padding: isMobile ? 16 : 20,
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
borderColor: 'rgba(28, 110, 164, 0.3)',
borderWidth: 2,
@@ -411,6 +456,7 @@ function NodeCard({ node, router }: any) {
c={colors['blue-button']}
lineClamp={2}
style={{
// fontSize: 'clamp(12px, 4vw, 16px)', // 👈 responsif font size
minHeight: 40,
display: 'flex',
alignItems: 'center',

View File

@@ -75,7 +75,7 @@ function Page() {
lh={1.7}
ta="center"
dangerouslySetInnerHTML={{ __html: item.visi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
</Box>
@@ -86,12 +86,15 @@ function Page() {
c={colors['blue-button']} mb="sm">
Misi PPID
</Text>
<Text
fz={{ base: 'md', md: 'lg' }}
lh={1.7}
dangerouslySetInnerHTML={{ __html: item.misi }}
style={{wordBreak: "break-word", whiteSpace: "normal"}}
/>
<Box px={{ base: 'md', md: 100 }}>
<Text
ta={"justify"}
fz={{ base: 'md', md: 'lg' }}
lh={1.7}
dangerouslySetInnerHTML={{ __html: item.misi }}
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
/>
</Box>
</Box>
</Stack>
</Paper>

View File

@@ -220,8 +220,9 @@ export default function ModernNewsNotification({
...styles,
position: "fixed",
bottom: "100px",
right: "24px",
width: "380px",
left: "24px",
width: "90vw",
maxWidth: 380,
maxHeight: "500px",
boxShadow: "0 8px 32px rgba(0,0,0,0.12)",
borderRadius: "16px",
@@ -290,7 +291,7 @@ export default function ModernNewsNotification({
color={item.type === "berita" ? "blue" : "orange"}
variant="light"
>
{item.type === "berita" ? "📰 Berita" : "📢 Pengumuman"}
{item.type === "berita" ? "Berita" : "Pengumuman"}
</Badge>
<IconChevronRight size={16} color="#adb5bd" />
</Group>
@@ -321,8 +322,9 @@ export default function ModernNewsNotification({
...styles,
position: "fixed",
bottom: "100px",
right: "24px",
width: "380px",
left: "24px",
width: "90vw",
maxWidth: 380,
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
borderRadius: "12px",
overflow: "hidden",
@@ -350,7 +352,6 @@ export default function ModernNewsNotification({
size="md"
color={currentNews?.type === "berita" ? "blue" : "orange"}
variant="light"
leftSection={currentNews?.type === "berita" ? "📰" : "📢"}
>
{currentNews?.type === "berita"
? "Berita Terbaru"

View File

@@ -4,7 +4,7 @@ import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/ind
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 { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { useDisclosure, useMediaQuery, useShallowEffect } from "@mantine/hooks";
import { useState } from "react";
import { useProxy } from "valtio/utils";
@@ -25,6 +25,7 @@ function Kepuasan() {
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
const [barChartData, setBarChartData] = useState<Array<{ month: string; Responden: number }>>([]);
const [opened, { open, close }] = useDisclosure(false)
const isMobile = useMediaQuery("(max-width: 768px)");
const resetForm = () => {
state.create.form = {
@@ -41,7 +42,7 @@ function Kepuasan() {
indeksKepuasanState.jenisKelaminResponden.findMany.load()
indeksKepuasanState.pilihanRatingResponden.findMany.load()
indeksKepuasanState.kelompokUmurResponden.findMany.load()
},[])
}, [])
const handleSubmit = async () => {
try {
@@ -82,13 +83,13 @@ function Kepuasan() {
// Update gender chart data
setDonutDataJenisKelamin([
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] },
{ name: 'Laki-laki', value: totalLaki, color: '#52ABE3FF' },
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
]);
// Update rating chart data
setDonutDataRating([
{ name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] },
{ name: 'Sangat Baik', value: totalSangatBaik, color: '#52ABE3FF' },
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
@@ -96,7 +97,7 @@ function Kepuasan() {
// Update age group chart data
setDonutDataKelompokUmur([
{ name: 'Muda', value: totalMuda, color: colors['blue-button'] },
{ name: 'Muda', value: totalMuda, color: '#52ABE3FF' },
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
]);
@@ -220,10 +221,13 @@ function Kepuasan() {
<Box style={{ position: 'relative', width: '100%' }}>
<Center>
<PieChart
withLabels
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="inside" // 👈 ini yang penting!
labelsType="percent"
size={250} // Fixed size in pixels
withLabelsLine
size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile
data={donutDataJenisKelamin}
/>
</Center>
@@ -259,10 +263,10 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="outside"
labelsPosition="inside" // 👈 ini yang penting!
labelsType="percent"
withLabelsLine
size={250}
size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile
data={donutDataRating}
/>
</Center>
@@ -302,10 +306,10 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="outside"
labelsPosition="inside"// 👈 ini yang penting!
labelsType="percent"
withLabelsLine
size={250}
size={isMobile ? 180 : 250} // 👈 kecilkan ukuran di mobile
data={donutDataKelompokUmur}
/>
</Center>
@@ -494,6 +498,8 @@ function Kepuasan() {
<PieChart
withLabels
withTooltip
labelsPosition="inside"
labelsType="percent"
size={200}
data={donutDataJenisKelamin}
@@ -531,7 +537,8 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="outside"
labelsPosition="inside"
labelsType="percent"
withLabelsLine
size={200}
@@ -574,7 +581,8 @@ function Kepuasan() {
withTooltip
tooltipAnimationDuration={200}
withLabels
labelsPosition="outside"
labelsPosition="inside"
labelsType="percent"
withLabelsLine
size={190}
@@ -610,7 +618,7 @@ function Kepuasan() {
<TextInput
label="Nama"
type='text'
placeholder="masukkan nama"
placeholder="Masukkan nama"
value={state.create.form.name}
onChange={(val) => {
state.create.form.name = val.currentTarget.value;
@@ -619,7 +627,7 @@ function Kepuasan() {
<TextInput
label="Tanggal Pengisian"
type="date"
placeholder="masukkan tanggal"
placeholder="Masukkan tanggal"
value={state.create.form.tanggal}
onChange={(val) => {
state.create.form.tanggal = val.currentTarget.value;

View File

@@ -154,7 +154,7 @@ function LandingPage() {
return (
<Stack bg={colors.Bg} p="md" gap="lg">
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }} pb={30}>
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
<Card radius="xl" bg={colors.grey[1]} p="lg" mt={10} shadow="xl">
<Stack gap="xl">

View File

@@ -4,7 +4,7 @@ import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
import { Stack, Box, Container, Button, Text, Loader, Paper, Center, ActionIcon } from "@mantine/core";
import { IconAward, IconArrowRight, IconPlayerPlay } from "@tabler/icons-react";
import { useTransitionRouter } from 'next-view-transitions';
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import { useProxy } from "valtio/utils";
import { useMediaQuery } from "@mantine/hooks";
@@ -15,16 +15,35 @@ function Penghargaan() {
const isMobile = useMediaQuery('(max-width: 768px)');
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [showVideo, setShowVideo] = useState(true);
const [videoError, setVideoError] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
// Opsional: deteksi iOS
const isIOS = typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
// 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(() => {
if (isIOS) {
// Di iOS, jangan andalkan autoplay — tampilkan kontrol
setShowVideo(false);
// 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]);
useEffect(() => {
const loadData = async () => {
@@ -38,42 +57,99 @@ 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);
});
}
}, 100);
};
// kalau mobile ambil 1 data aja, kalau desktop ambil 3
const data = state.findMany.data?.slice(0, isMobile ? 1 : 3);
return (
<Stack pos="relative" h="auto" mih={{ base: 500, md: 720 }}>
{showVideo ? (
<Stack pos="relative" h="auto" mih={{ base: 500, md: 720 }} style={{ overflow: 'hidden' }}>
{/* Video Layer */}
{showVideo && !videoError && (
<video
ref={videoRef}
autoPlay
muted
loop
playsInline
webkit-playsinline="true"
preload="auto"
onLoadedData={() => setIsVideoLoaded(true)}
style={{ opacity: isVideoLoaded ? 1 : 0, transition: 'opacity 0.5s' }}
onError={() => {
console.error("Video load error");
setVideoError(true);
setShowVideo(false);
}}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
opacity: isVideoLoaded ? 1 : 0,
transition: 'opacity 0.5s ease',
zIndex: 0,
}}
>
<source src="/assets/videos/award.mp4" type="video/mp4" />
</video>
) : (
// Fallback: tampilkan poster + play button
)}
{/* Fallback Image + Play Button */}
{(!showVideo || videoError) && (
<Box
onClick={() => setShowVideo(true)}
onClick={handlePlayVideo}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundImage: "url('/mangupuraaward.jpeg')",
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
cursor: 'pointer',
zIndex: 0,
}}
>
<Center h="100%">
<ActionIcon size="lg" radius="xl" color="white">
<IconPlayerPlay size={32} />
<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 */}
<Box
style={{
width: "100%",
@@ -152,4 +228,4 @@ function Penghargaan() {
);
}
export default Penghargaan;
export default Penghargaan;

View File

@@ -38,15 +38,15 @@ export default function Page() {
const [hasNewContent, setHasNewContent] = useState(false);
const [newItemCount, setNewItemCount] = useState(0);
const lastBeritaId = useRef<string | null>(null);
const lastPengumumanId = useRef<string | null>(null);
const lastBeritaTimestamp = useRef<string | null>(null);
const lastPengumumanTimestamp = useRef<string | null>(null);
// Inisialisasi dari localStorage
useEffect(() => {
const savedBerita = localStorage.getItem("lastSeenBeritaId");
const savedPengumuman = localStorage.getItem("lastSeenPengumumanId");
if (savedBerita) lastBeritaId.current = savedBerita;
if (savedPengumuman) lastPengumumanId.current = savedPengumuman;
const savedBeritaTs = localStorage.getItem("lastSeenBeritaTs");
const savedPengumumanTs = localStorage.getItem("lastSeenPengumumanTs");
if (savedBeritaTs) lastBeritaTimestamp.current = savedBeritaTs;
if (savedPengumumanTs) lastPengumumanTimestamp.current = savedPengumumanTs;
}, []);
// Load data utama (untuk card)
@@ -70,28 +70,49 @@ export default function Page() {
if (result.success && Array.isArray(result.news)) {
const news = result.news as NewsItem[];
// Ambil ID terbaru
const latestBerita = news.find((n) => n.type === "berita");
const latestPengumuman = news.find((n) => n.type === "pengumuman");
const isNewBerita = latestBerita && lastBeritaId.current !== null && latestBerita.id !== lastBeritaId.current;
const isNewPengumuman = latestPengumuman && lastPengumumanId.current !== null && latestPengumuman.id !== lastPengumumanId.current;
const latestBeritaTs = latestBerita?.timestamp
? new Date(latestBerita.timestamp).toISOString()
: null;
const latestPengumumanTs = latestPengumuman?.timestamp
? new Date(latestPengumuman.timestamp).toISOString()
: null;
// Simpan ID terbaru ke ref
if (latestBerita) lastBeritaId.current = (latestBerita.id);
if (latestPengumuman) lastPengumumanId.current = (latestPengumuman.id);
// Inisialisasi flag
let isNewBerita = false;
let isNewPengumuman = false;
// Jika ini bukan inisialisasi pertama, tampilkan notifikasi
if (lastBeritaId.current !== null || lastPengumumanId.current !== null) {
if (isNewBerita || isNewPengumuman) {
const count = (isNewBerita ? 1 : 0) + (isNewPengumuman ? 1 : 0);
setNewItemCount(count);
setHasNewContent(true);
// Deteksi berita baru
if (latestBeritaTs) {
if (lastBeritaTimestamp.current === null) {
// Pertama kali: simpan tanpa notifikasi
lastBeritaTimestamp.current = latestBeritaTs;
localStorage.setItem("lastSeenBeritaTs", latestBeritaTs);
} else if (latestBeritaTs > lastBeritaTimestamp.current) {
isNewBerita = true;
lastBeritaTimestamp.current = latestBeritaTs;
}
} else {
// Simpan ke localStorage saat pertama kali
if (latestBerita) localStorage.setItem("lastSeenBeritaId", (latestBerita.id));
if (latestPengumuman) localStorage.setItem("lastSeenPengumumanId", (latestPengumuman.id));
}
// Deteksi pengumuman baru
if (latestPengumumanTs) {
if (lastPengumumanTimestamp.current === null) {
// Pertama kali: simpan tanpa notifikasi
lastPengumumanTimestamp.current = latestPengumumanTs;
localStorage.setItem("lastSeenPengumumanTs", latestPengumumanTs);
} else if (latestPengumumanTs > lastPengumumanTimestamp.current) {
isNewPengumuman = true;
lastPengumumanTimestamp.current = latestPengumumanTs;
}
}
// 🔔 Trigger notifikasi hanya jika ada yang benar-benar BARU
if (isNewBerita || isNewPengumuman) {
const count = (isNewBerita ? 1 : 0) + (isNewPengumuman ? 1 : 0);
setNewItemCount(count);
setHasNewContent(true); // ✅ INI YANG KAMU LUPA!
}
setNotificationNews(news);
@@ -113,13 +134,17 @@ export default function Page() {
}, []);
const handleSeen = () => {
setHasNewContent(false);
setNewItemCount(0);
const latestBerita = notificationNews.find(n => n.type === "berita");
const latestPengumuman = notificationNews.find(n => n.type === "pengumuman");
if (latestBerita) localStorage.setItem("lastSeenBeritaId", String(latestBerita.id));
if (latestPengumuman) localStorage.setItem("lastSeenPengumumanId", String(latestPengumuman.id));
};
setHasNewContent(false);
setNewItemCount(0);
const latestBerita = notificationNews.find(n => n.type === "berita");
const latestPengumuman = notificationNews.find(n => n.type === "pengumuman");
if (latestBerita) {
localStorage.setItem("lastSeenBeritaTs", new Date(latestBerita.timestamp!).toISOString());
}
if (latestPengumuman) {
localStorage.setItem("lastSeenPengumumanTs", new Date(latestPengumuman.timestamp!).toISOString());
}
};
return (
<Box id="page-root">