Fix QC Kak Ayu Tgl 12

Fix QC Kak Ino Tgl 12
Fix UI Mobile Menu Keamanan
Fix UI Mobile Admin Menu Landing Page
This commit is contained in:
2025-12-16 10:19:15 +08:00
parent f6f77d9e35
commit 342e9bbc65
43 changed files with 1715 additions and 1344 deletions

View File

@@ -3,11 +3,12 @@ module.exports = {
'postcss-preset-mantine': {}, 'postcss-preset-mantine': {},
'postcss-simple-vars': { 'postcss-simple-vars': {
variables: { variables: {
'mantine-breakpoint-xs': '36em', /* Mobile first */
'mantine-breakpoint-sm': '48em', 'mantine-breakpoint-xs': '30em', // 480px → mobile kecilnormal
'mantine-breakpoint-md': '62em', 'mantine-breakpoint-sm': '48em', // 768px → tablet / mobile landscape
'mantine-breakpoint-lg': '75em', 'mantine-breakpoint-md': '64em', // 1024px → laptop & desktop kecil
'mantine-breakpoint-xl': '88em', 'mantine-breakpoint-lg': '80em', // 1280px → desktop standar
'mantine-breakpoint-xl': '90em', // 1440px+ → desktop besar
}, },
}, },
}, },

View File

@@ -44,20 +44,58 @@ function CreatePolsekTerdekat() {
}; };
}; };
const isValidGoogleMapsEmbed = (url: string): boolean => {
try {
const u = new URL(url);
return (
u.hostname === 'www.google.com' &&
u.pathname === '/maps/embed' &&
u.searchParams.has('pb')
);
} catch {
return false;
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
const { embedMapUrl } = polsekState.create.form;
// ✅ Validasi Google Maps Embed URL (jika diisi)
if (embedMapUrl && !isValidGoogleMapsEmbed(embedMapUrl)) {
toast.error("URL embed peta tidak valid. Harap paste iframe dari Google Maps.");
return;
}
try { try {
setIsSubmitting(true); setIsSubmitting(true);
await polsekState.create.create(); await polsekState.create.create();
resetForm(); resetForm();
router.push("/admin/keamanan/polsek-terdekat"); router.push("/admin/keamanan/polsek-terdekat");
} catch (error) { } catch (error) {
console.error(error) console.error(error);
toast.error("Gagal menambah polsek terdekat"); toast.error("Gagal menambah polsek terdekat");
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
}; };
const extractEmbedUrl = (input: string): string => {
// Jika sudah berupa URL embed yang valid
if (input.startsWith('https://www.google.com/maps/embed?')) {
return input.trim();
}
// Coba parse sebagai HTML string (iframe)
const iframeRegex = /<iframe[^>]*src=["']([^"']*)["'][^>]*>/i;
const match = input.match(iframeRegex);
if (match && match[1]?.startsWith('https://www.google.com/maps/embed?')) {
return match[1].trim();
}
// Jika tidak cocok, kembalikan input asli (atau string kosong)
return input.trim();
};
const fetchLayanan = async () => { const fetchLayanan = async () => {
try { try {
const res = await fetch("/api/keamanan/layanan-polsek/find-many"); const res = await fetch("/api/keamanan/layanan-polsek/find-many");
@@ -190,9 +228,14 @@ function CreatePolsekTerdekat() {
/> />
<TextInput <TextInput
value={polsekState.create.form.embedMapUrl} value={polsekState.create.form.embedMapUrl}
onChange={(val) => (polsekState.create.form.embedMapUrl = val.target.value)} onChange={(e) => {
const rawValue = e.currentTarget.value;
const cleanUrl = extractEmbedUrl(rawValue);
polsekState.create.form.embedMapUrl = cleanUrl;
}}
description="Contoh: https://www.google.com/maps/embed?pb=..."
label={<Text fw="bold" fz="sm">Embed Map URL</Text>} label={<Text fw="bold" fz="sm">Embed Map URL</Text>}
placeholder="Masukkan embed map url" placeholder="Paste iframe dari Google Maps atau URL embed langsung"
/> />
<TextInput <TextInput
value={polsekState.create.form.namaTempatMaps} value={polsekState.create.form.namaTempatMaps}

View File

@@ -9,7 +9,6 @@ import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
import sdgsDesa from '../../_state/landing-page/sdgs-desa'; import sdgsDesa from '../../_state/landing-page/sdgs-desa';
function SdgsDesa() { function SdgsDesa() {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
return ( return (
@@ -27,7 +26,7 @@ function SdgsDesa() {
} }
function ListSdgsDesa({ search }: { search: string }) { function ListSdgsDesa({ search }: { search: string }) {
const listState = useProxy(sdgsDesa) const listState = useProxy(sdgsDesa);
const router = useRouter(); const router = useRouter();
const { const {
@@ -39,10 +38,10 @@ function ListSdgsDesa({ search }: { search: string }) {
} = listState.findMany; } = listState.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, search);
}, [page, search]) }, [page, search]);
const filteredData = data || [] const filteredData = data || [];
// Handle loading state // Handle loading state
if (loading || !data) { if (loading || !data) {
@@ -53,12 +52,15 @@ function ListSdgsDesa({ search }: { search: string }) {
); );
} }
if (data.length === 0) { const isEmpty = data.length === 0;
return ( return (
<Box py={10}> <Box py={{ base: 'sm', md: 'lg' }}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Sdgs Desa</Title> <Title order={2} lh={1.2}>
Daftar Sdgs Desa
</Title>
<Button <Button
leftSection={<IconPlus size={18} />} leftSection={<IconPlus size={18} />}
color={colors['blue-button']} color={colors['blue-button']}
@@ -68,63 +70,52 @@ function ListSdgsDesa({ search }: { search: string }) {
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table highlightOnHover striped verticalSpacing="sm"> <Table highlightOnHover striped verticalSpacing="sm">
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '60%' }}>Nama Sdgs Desa</TableTh> <TableTh style={{ width: '60%' }}>
<TableTh style={{ width: '20%' }}>Jumlah</TableTh> <Text fz="sm" fw={600} c="dark.7" ta="left">
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh> Nama Sdgs Desa
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} c="dark.7" ta="left">
Jumlah
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fz="sm" fw={600} c="dark.7" ta="center">
Aksi
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{isEmpty ? (
<TableTr> <TableTr>
<TableTd colSpan={3} style={{ textAlign: 'center', padding: '2rem' }}> <TableTd colSpan={3} ta="center" py="xl">
<Text c="dimmed">Tidak ada data Sdgs Desa</Text> <Text c="dimmed" fz="sm" lh={1.5}>
Tidak ada data Sdgs Desa
</Text>
</TableTd> </TableTd>
</TableTr> </TableTr>
</TableTbody> ) : (
</Table> filteredData.map((item) => (
</Box>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
<Group justify="space-between" mb="md">
<Title order={4}>Daftar Sdgs Desa</Title>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light"
onClick={() => router.push('/admin/landing-page/SDGs/create')}
>
Tambah Baru
</Button>
</Group>
<Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover>
<TableThead>
<TableTr>
<TableTh style={{ width: '60%' }}>Nama Sdgs Desa</TableTh>
<TableTh style={{ width: '20%' }}>Jumlah</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd style={{ width: '60%' }}> <TableTd style={{ width: '60%' }}>
<Text fw={500} truncate="end" lineClamp={1}> <Text fz="md" fw={500} truncate="end" lineClamp={1} lh={1.5}>
{item.name} {item.name}
</Text> </Text>
</TableTd> </TableTd>
<TableTd style={{ width: '20%' }}> <TableTd style={{ width: '20%' }}>
<Text fz="sm" c="dimmed"> <Text fz="sm" c="dark.6" lh={1.5}>
{item.jumlah || '0'} {item.jumlah || '0'}
</Text> </Text>
</TableTd> </TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}> <TableTd style={{ width: '20%' }} ta="center">
<Button <Button
size="xs" size="xs"
radius="md" radius="md"
@@ -137,12 +128,53 @@ function ListSdgsDesa({ search }: { search: string }) {
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
))} ))
)}
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
{isEmpty ? (
<Center py="xl">
<Text c="dimmed" fz="sm" lh={1.5} ta="center">
Tidak ada data Sdgs Desa
</Text>
</Center>
) : (
<Stack gap="sm">
{filteredData.map((item) => (
<Paper key={item.id} withBorder p="md" radius="md">
<Stack gap={4}>
<Text fz="sm" fw={600} lh={1.4}>
{item.name}
</Text>
<Text fz="xs" c="dark.6" lh={1.4}>
Jumlah: {item.jumlah || '0'}
</Text>
<Group justify="flex-end" mt="xs">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/landing-page/SDGs/${item.id}`)}
>
Detail
</Button>
</Group>
</Stack>
</Paper> </Paper>
<Center mt="lg"> ))}
</Stack>
)}
</Box>
</Paper>
{!isEmpty && (
<Center mt={{ base: 'md', md: 'lg' }}>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => { onChange={(newPage) => {
@@ -154,8 +186,9 @@ function ListSdgsDesa({ search }: { search: string }) {
radius="md" radius="md"
/> />
</Center> </Center>
)}
</Box> </Box>
) );
} }
export default SdgsDesa; export default SdgsDesa;

View File

@@ -3,6 +3,7 @@
import colors from "@/con/colors"; import colors from "@/con/colors";
import { import {
Box,
ScrollArea, ScrollArea,
Stack, Stack,
Tabs, Tabs,
@@ -68,6 +69,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false} keepMounted={false}
> >
{/* ✅ Scroll horizontal wrapper */} {/* ✅ Scroll horizontal wrapper */}
<Box visibleFrom='md'>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -90,7 +92,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
fontWeight: 600, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ mencegah tab mengecil flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{tab.label} {tab.label}
@@ -98,7 +100,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </ScrollArea>
</Box>
<Box hiddenFrom='md'>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel
key={i} key={i}

View File

@@ -82,7 +82,7 @@ export default function EditKategoriDesaAntiKorupsi() {
// 🧩 UI // 🧩 UI
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -43,7 +43,7 @@ export default function CreateKategoriDesaAntiKorupsi() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -1,6 +1,23 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import {
Box,
Button,
Center,
Group,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -10,9 +27,8 @@ import HeaderSearch from '../../../_com/header';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi'; import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
function KategoriDesaAntiKorupsi() { function KategoriDesaAntiKorupsi() {
const [search, setSearch] = useState("") const [search, setSearch] = useState('');
return ( return (
<Box> <Box>
<HeaderSearch <HeaderSearch
@@ -28,62 +44,102 @@ function KategoriDesaAntiKorupsi() {
} }
function ListKategoriKegiatan({ search }: { search: string }) { function ListKategoriKegiatan({ search }: { search: string }) {
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi) const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi);
const [modalHapus, setModalHapus] = useState(false) const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null) const [selectedId, setSelectedId] = useState<string | null>(null);
const router = useRouter() const router = useRouter();
const { const { data, page, totalPages, loading, load } = stateKategori.findMany;
data,
page,
totalPages,
loading,
load,
} = stateKategori.findMany;
const handleHapus = () => { const handleHapus = () => {
if (selectedId) { if (selectedId) {
stateKategori.delete.byId(selectedId) stateKategori.delete.byId(selectedId);
setModalHapus(false) setModalHapus(false);
setSelectedId(null) setSelectedId(null);
}
} }
};
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, search);
}, [page, search]) }, [page, search]);
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py="xl">
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( // Mobile cards
<Box py={10}> const renderMobileCards = () => (
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Stack gap="md">
<Group justify="space-between" mb="md"> {filteredData.length > 0 ? (
<Title order={4}>Daftar Kategori Kegiatan</Title> filteredData.map((item) => (
<Paper key={item.id} p="md" withBorder>
<Group justify="space-between" align="flex-start">
<Box flex={1}>
<Text fw={500} fz={{ base: 'sm', md: 'md' }} lh={1.45} lineClamp={2}>
{item.name}
</Text>
</Box>
<Group gap="xs" wrap="nowrap">
<Button <Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light" variant="light"
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create')} color="green"
size="xs"
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}
> >
Tambah Baru <IconEdit size={16} />
</Button>
<Button
variant="light"
color="red"
size="xs"
onClick={() => {
setSelectedId(item.id);
setModalHapus(true);
}}
>
<IconTrash size={16} />
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: "auto" }}> </Group>
<Table highlightOnHover striped verticalSpacing="sm"> </Paper>
))
) : (
<Paper p="xl" ta="center">
<Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kategori yang ditemukan
</Text>
</Paper>
)}
</Stack>
);
// Desktop table
const renderDesktopTable = () => (
<Box>
<Table highlightOnHover striped verticalSpacing="sm" miw={300}>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh>Nama Kategori</TableTh> <TableTh>
<TableTh>Edit</TableTh> <Text fw={600} fz="sm" c="dimmed">
<TableTh>Hapus</TableTh> Nama Kategori
</Text>
</TableTh>
<TableTh>
<Text fw={600} fz="sm" c="dimmed">
Edit
</Text>
</TableTh>
<TableTh>
<Text fw={600} fz="sm" c="dimmed">
Hapus
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
@@ -91,11 +147,11 @@ function ListKategoriKegiatan({ search }: { search: string }) {
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Box w={200}> <Text fw={500} fz="md" lh={1.45} lineClamp={1}>
<Text fw={500} lineClamp={1}>{item.name}</Text> {item.name}
</Box> </Text>
</TableTd> </TableTd>
<TableTd> <TableTd w={60}>
<Button <Button
variant="light" variant="light"
color="green" color="green"
@@ -105,7 +161,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
<IconEdit size={18} /> <IconEdit size={18} />
</Button> </Button>
</TableTd> </TableTd>
<TableTd> <TableTd w={60}>
<Button <Button
variant="light" variant="light"
color="red" color="red"
@@ -122,18 +178,41 @@ function ListKategoriKegiatan({ search }: { search: string }) {
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={2}> <TableTd colSpan={3} ta="center" py="xl">
<Center py={20}> <Text c="dimmed" fz="sm" lh={1.4}>
<Text c="dimmed">Tidak ada data kategori yang ditemukan</Text> Tidak ada data kategori yang ditemukan
</Center> </Text>
</TableTd> </TableTd>
</TableTr> </TableTr>
)} )}
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
);
return (
<Box py={{ base: 'xl', md: 'xl' }}>
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="md" radius="md">
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
<Title order={2} lh={1.2}>
Daftar Kategori Kegiatan
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create')}
>
Tambah Baru
</Button>
</Group>
<Box visibleFrom="md">{renderDesktopTable()}</Box>
<Box hiddenFrom="md">{renderMobileCards()}</Box>
</Paper> </Paper>
<Center>
{totalPages > 1 && (
<Center mt="xl">
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => { onChange={(newPage) => {
@@ -141,13 +220,12 @@ function ListKategoriKegiatan({ search }: { search: string }) {
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });
}} }}
total={totalPages} total={totalPages}
mt="md"
mb="md"
color="blue" color="blue"
radius="md" radius="md"
/> />
</Center> </Center>
{/* Modal Konfirmasi Hapus */} )}
<ModalKonfirmasiHapus <ModalKonfirmasiHapus
opened={modalHapus} opened={modalHapus}
onClose={() => setModalHapus(false)} onClose={() => setModalHapus(false)}
@@ -158,4 +236,4 @@ function ListKategoriKegiatan({ search }: { search: string }) {
); );
} }
export default KategoriDesaAntiKorupsi export default KategoriDesaAntiKorupsi;

View File

@@ -150,7 +150,7 @@ export default function EditDesaAntiKorupsi() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -42,7 +42,7 @@ export default function DetailKegiatanDesa() {
const data = detailState.findUnique.data; const data = detailState.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
<Button <Button
variant="subtle" variant="subtle"
onClick={() => router.back()} onClick={() => router.back()}
@@ -53,7 +53,7 @@ export default function DetailKegiatanDesa() {
</Button> </Button>
<Paper <Paper
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -85,7 +85,7 @@ export default function CreateDesaAntiKorupsi() {
} }
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -38,7 +38,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py="md"> <Stack py={{ base: 'sm', md: 'md' }}>
<Skeleton height={650} radius="lg" /> <Skeleton height={650} radius="lg" />
</Stack> </Stack>
); );
@@ -46,11 +46,13 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
if (data.length === 0) { if (data.length === 0) {
return ( return (
<Box py="md"> <Box py={{ base: 'sm', md: 'md' }}>
<Paper p="lg" radius="lg" shadow="md" withBorder> <Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Title order={4}>Data Program Desa Anti Korupsi</Title> <Title order={2} lh={1.2}>
<Text c="dimmed" ta="center"> Data Program Desa Anti Korupsi
</Title>
<Text c="dimmed" ta="center" fz={{ base: 'xs', md: 'sm' }} lh={1.5}>
Belum ada data program yang tersedia Belum ada data program yang tersedia
</Text> </Text>
</Stack> </Stack>
@@ -61,48 +63,56 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
return ( return (
<Box> <Box>
<Stack gap="md"> <Stack gap={'md'}>
<Paper p="lg" radius="lg" shadow="md" withBorder> <Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
<Title order={4}>Daftar Program Desa Anti Korupsi</Title> <Title order={2} lh={1.2}>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" Daftar Program Desa Anti Korupsi
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create')} </Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() =>
router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create')
}
> >
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: 'auto' }}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Table <Table
striped striped
highlightOnHover highlightOnHover
withRowBorders withRowBorders
verticalSpacing="sm" verticalSpacing="sm"
> >
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '50%' }}>Nama Program</TableTh> <TableTh w="50%">Nama Program</TableTh>
<TableTh style={{ width: '30%' }}>Kategori</TableTh> <TableTh w="30%">Kategori</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh> <TableTh w="20%" ta="center">
Aksi
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd style={{ width: '50%' }}> <TableTd w="50%">
<Text fw={500} lineClamp={1}> <Text fw={500} lineClamp={1} fz="md" lh={1.5}>
{item.name || '-'} {item.name || '-'}
</Text> </Text>
</TableTd> </TableTd>
<TableTd style={{ width: '30%' }}> <TableTd w="30%">
<Box w={200}> <Text fz="sm" c="dimmed" lineClamp={1} lh={1.5}>
<Text fz="sm" c="dimmed" lineClamp={1}>
{item.kategori?.name || '-'} {item.kategori?.name || '-'}
</Text> </Text>
</Box>
</TableTd> </TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}> <TableTd w="20%" ta="center">
<Button <Button
size="xs" size="xs"
radius="md" radius="md"
@@ -123,7 +133,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={3}> <TableTd colSpan={3}>
<Text ta="center" c="dimmed"> <Text ta="center" c="dimmed" fz="sm" lh={1.5}>
Tidak ditemukan data dengan kata kunci pencarian Tidak ditemukan data dengan kata kunci pencarian
</Text> </Text>
</TableTd> </TableTd>
@@ -132,6 +142,48 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} p="sm" radius="md" withBorder shadow="xs">
<Stack gap="xs">
<Text fw={500} fz="sm" lh={1.5} lineClamp={1}>
{item.name || '-'}
</Text>
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1}>
Kategori: {item.kategori?.name || '-'}
</Text>
<Group justify="flex-end">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(
`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${item.id}`
)
}
>
Detail
</Button>
</Group>
</Stack>
</Paper>
))
) : (
<Paper p="sm" radius="md" withBorder>
<Text ta="center" c="dimmed" fz="xs" lh={1.5}>
Tidak ditemukan data dengan kata kunci pencarian
</Text>
</Paper>
)}
</Stack>
</Box>
</Paper> </Paper>
<Center> <Center>
@@ -144,7 +196,6 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
}} }}
size="md" size="md"
radius="md" radius="md"
mt="md"
/> />
</Center> </Center>
</Stack> </Stack>

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconChartBar, IconUsers } from '@tabler/icons-react'; import { IconChartBar, IconUsers } from '@tabler/icons-react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -53,7 +53,9 @@ function LayoutTabsKepuasan({ children }: { children: React.ReactNode }) {
radius="lg" radius="lg"
keepMounted={false} keepMounted={false}
> >
{/* ✅ Scroll horizontal wrapper */} {/* ✅ Scroll horizontal wrapper */}
<Box>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -67,22 +69,25 @@ function LayoutTabsKepuasan({ children }: { children: React.ReactNode }) {
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
}} }}
> >
{tabs.map((e, i) => ( {tabs.map((tab, i) => (
<TabsTab <TabsTab
key={i} key={i}
value={e.value} value={tab.value}
leftSection={e.icon} leftSection={tab.icon}
style={{ style={{
fontWeight: 500, fontWeight: 600,
fontSize: "0.9rem", fontSize: "0.9rem",
transition: "all 0.2s ease", transition: "all 0.2s ease",
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}} }}
> >
{e.label} {tab.label}
</TabsTab> </TabsTab>
))} ))}
</TabsList> </TabsList>
</ScrollArea> </ScrollArea>
</Box>
{tabs.map((e, i) => ( {tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}> <TabsPanel key={i} value={e.value}>
<></> <></>

View File

@@ -149,7 +149,7 @@ function EditResponden() {
); );
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -38,7 +38,7 @@ export default function DetailResponden() {
) )
} }
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
<Button <Button
variant="subtle" variant="subtle"
onClick={() => router.back()} onClick={() => router.back()}
@@ -50,7 +50,7 @@ export default function DetailResponden() {
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "60%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -60,7 +60,7 @@ function ListResponden({ search }: ListRespondenProps) {
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py="md"> <Stack py={{ base: 'md', md: 'lg' }}>
<Skeleton height={650} radius="lg" /> <Skeleton height={650} radius="lg" />
</Stack> </Stack>
); );
@@ -68,11 +68,13 @@ function ListResponden({ search }: ListRespondenProps) {
if (data.length === 0) { if (data.length === 0) {
return ( return (
<Box py="md"> <Box py={{ base: 'md', md: 'lg' }}>
<Paper p="lg" radius="lg" shadow="md" withBorder> <Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
<Stack align="center" gap="sm"> <Stack align="center" gap="sm">
<Title order={4}>Data Responden</Title> <Title order={2} lh={1.2}>
<Text c="dimmed" ta="center"> Data Responden
</Title>
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Belum ada data responden yang tersedia Belum ada data responden yang tersedia
</Text> </Text>
</Stack> </Stack>
@@ -83,12 +85,13 @@ function ListResponden({ search }: ListRespondenProps) {
return ( return (
<Box> <Box>
<Stack gap="md"> <Stack gap={'lg'}>
{/* Desktop Table */}
<Box visibleFrom="md">
<Paper p="lg" radius="lg" shadow="md" withBorder> <Paper p="lg" radius="lg" shadow="md" withBorder>
<Title order={4} mb="sm"> <Title order={2} size="lg" mb="md" lh={1.2}>
Daftar Responden Daftar Responden
</Title> </Title>
<Box style={{ overflowX: 'auto' }}>
<Table <Table
striped striped
highlightOnHover highlightOnHover
@@ -97,18 +100,18 @@ function ListResponden({ search }: ListRespondenProps) {
> >
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '5%' }}>No</TableTh> <TableTh fz="sm" fw={600} w={60}>No</TableTh>
<TableTh style={{ width: '25%' }}>Nama</TableTh> <TableTh fz="sm" fw={600}>Nama</TableTh>
<TableTh style={{ width: '20%' }}>Tanggal</TableTh> <TableTh fz="sm" fw={600}>Tanggal</TableTh>
<TableTh style={{ width: '20%' }}>Jenis Kelamin</TableTh> <TableTh fz="sm" fw={600}>Jenis Kelamin</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh> <TableTh fz="sm" fw={600} w={120}>Aksi</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length === 0 ? ( {filteredData.length === 0 ? (
<TableTr> <TableTr>
<TableTd colSpan={5}> <TableTd colSpan={5}>
<Text ta="center" c="dimmed"> <Text ta="center" c="dimmed" fz="sm" lh={1.5}>
Tidak ditemukan data dengan kata kunci pencarian Tidak ditemukan data dengan kata kunci pencarian
</Text> </Text>
</TableTd> </TableTd>
@@ -116,10 +119,9 @@ function ListResponden({ search }: ListRespondenProps) {
) : ( ) : (
filteredData.map((item, index) => ( filteredData.map((item, index) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd>{index + 1}</TableTd> <TableTd fz="md" lh={1.5}>{index + 1}</TableTd>
<TableTd>{item.name}</TableTd> <TableTd fz="md" lh={1.5}>{item.name}</TableTd>
<TableTd> <TableTd fz="md" lh={1.5}>
<Box w={150}>
{item.tanggal {item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID', { ? new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit', day: '2-digit',
@@ -127,13 +129,8 @@ function ListResponden({ search }: ListRespondenProps) {
year: 'numeric', year: 'numeric',
}) })
: '-'} : '-'}
</Box>
</TableTd>
<TableTd>
<Box w={100}>
{item.jenisKelamin.name}
</Box>
</TableTd> </TableTd>
<TableTd fz="md" lh={1.5}>{item.jenisKelamin.name}</TableTd>
<TableTd> <TableTd>
<Button <Button
size="xs" size="xs"
@@ -155,8 +152,64 @@ function ListResponden({ search }: ListRespondenProps) {
)} )}
</TableTbody> </TableTbody>
</Table> </Table>
</Box>
</Paper> </Paper>
</Box>
{/* Mobile Cards */}
<Box hiddenFrom="md">
<Stack gap="sm">
<Title order={2} size="md" lh={1.2} px="md">
Daftar Responden
</Title>
{filteredData.length === 0 ? (
<Paper p="md" radius="lg" shadow="sm" mx="md">
<Text ta="center" c="dimmed" fz="sm" lh={1.5}>
Tidak ditemukan data dengan kata kunci pencarian
</Text>
</Paper>
) : (
filteredData.map((item) => (
<Paper key={item.id} p="md" radius="lg" shadow="sm" mx="md">
<Stack gap={4}>
<Text fz="sm" c="dimmed" lh={1.4}>Nama</Text>
<Text fz="md" lh={1.5}>{item.name}</Text>
<Text fz="sm" c="dimmed" lh={1.4}>Tanggal</Text>
<Text fz="md" lh={1.5}>
{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
: '-'}
</Text>
<Text fz="sm" c="dimmed" lh={1.4}>Jenis Kelamin</Text>
<Text fz="md" lh={1.5}>{item.jenisKelamin.name}</Text>
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
)
}
mt="xs"
>
Detail
</Button>
</Stack>
</Paper>
))
)}
</Stack>
</Box>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
@@ -167,7 +220,7 @@ function ListResponden({ search }: ListRespondenProps) {
}} }}
size="md" size="md"
radius="md" radius="md"
mt="md" mt={{ base: 'md', md: 'lg' }}
/> />
</Center> </Center>
</Stack> </Stack>

View File

@@ -2,6 +2,7 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { import {
Box,
ScrollArea, ScrollArea,
Stack, Stack,
Tabs, Tabs,
@@ -74,6 +75,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
keepMounted={false} keepMounted={false}
> >
{/* ✅ Scroll horizontal wrapper */} {/* ✅ Scroll horizontal wrapper */}
<Box visibleFrom='md'>
<ScrollArea type="auto" offsetScrollbars> <ScrollArea type="auto" offsetScrollbars>
<TabsList <TabsList
p="sm" p="sm"
@@ -104,6 +106,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
))} ))}
</TabsList> </TabsList>
</ScrollArea> </ScrollArea>
</Box>
<Box hiddenFrom='md'>
<ScrollArea
type="auto"
offsetScrollbars={false}
w="100%"
>
<TabsList
p="xs" // lebih kecil
style={{
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
borderRadius: "1rem",
display: "flex",
flexWrap: "nowrap",
gap: "0.5rem",
width: "max-content", // ⬅️ kunci
maxWidth: "100%", // ⬅️ penting
}}
>
{tabs.map((tab, i) => (
<TabsTab
key={i}
value={tab.value}
leftSection={tab.icon}
style={{
fontWeight: 600,
fontSize: "0.9rem",
paddingInline: "0.75rem", // ⬅️ lebih ramping
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
}}
>
{tab.label}
</TabsTab>
))}
</TabsList>
</ScrollArea>
</Box>
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (
<TabsPanel <TabsPanel

View File

@@ -177,7 +177,7 @@ function EditMediaSosial() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'xs' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -50,7 +50,7 @@ function DetailMediaSosial() {
const data = stateMediaSosial.findUnique.data; const data = stateMediaSosial.findUnique.data;
return ( return (
<Box py={10}> <Box px={{ base: 0, md: 'xs' }} py="xs">
<Button <Button
variant="subtle" variant="subtle"
onClick={() => router.back()} onClick={() => router.back()}
@@ -62,7 +62,7 @@ function DetailMediaSosial() {
<Paper <Paper
withBorder withBorder
w={{ base: "100%", md: "50%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -25,7 +25,6 @@ import { useProxy } from 'valtio/utils';
import profileLandingPageState from '../../../../_state/landing-page/profile'; import profileLandingPageState from '../../../../_state/landing-page/profile';
import SelectSosialMedia from '@/app/admin/(dashboard)/_com/selectSocialMedia'; import SelectSosialMedia from '@/app/admin/(dashboard)/_com/selectSocialMedia';
// ⭐ Tambah type SosmedKey // ⭐ Tambah type SosmedKey
type SosmedKey = type SosmedKey =
| 'facebook' | 'facebook'
@@ -88,7 +87,6 @@ export default function CreateMediaSosial() {
stateMediaSosial.create.form.imageId = null; stateMediaSosial.create.form.imageId = null;
stateMediaSosial.create.form.icon = sosmedMap[selectedSosmed].src!; stateMediaSosial.create.form.icon = sosmedMap[selectedSosmed].src!;
await stateMediaSosial.create.create(); await stateMediaSosial.create.create();
resetForm(); resetForm();
router.push('/admin/landing-page/profil/media-sosial'); router.push('/admin/landing-page/profil/media-sosial');
@@ -129,13 +127,13 @@ export default function CreateMediaSosial() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
{/* Header */} {/* Header */}
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />
</Button> </Button>
<Title order={4} ml="sm" c="dark"> <Title order={2} ml="sm" c="dark" lh={1.2} fz={{ base: 'md', md: 'lg' }}>
Tambah Media Sosial Tambah Media Sosial
</Title> </Title>
</Group> </Group>
@@ -155,7 +153,7 @@ export default function CreateMediaSosial() {
{/* Custom icon uploader */} {/* Custom icon uploader */}
{selectedSosmed === 'custom' && ( {selectedSosmed === 'custom' && (
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz={{ base: 'sm', md: 'md' }} lh={1.45} mb={6}>
Upload Custom Icon Upload Custom Icon
</Text> </Text>
@@ -185,8 +183,10 @@ export default function CreateMediaSosial() {
</Dropzone.Idle> </Dropzone.Idle>
<Stack align="center" gap="xs"> <Stack align="center" gap="xs">
<Text fw={500}>Seret gambar atau klik untuk pilih</Text> <Text fw={500} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
<Text size="sm" c="dimmed"> Seret gambar atau klik untuk pilih
</Text>
<Text fz={{ base: 12, md: 'sm' }} c="dimmed" lh={1.4}>
Maksimal 5MB, format .png, .jpg, .jpeg, webp Maksimal 5MB, format .png, .jpg, .jpeg, webp
</Text> </Text>
</Stack> </Stack>
@@ -229,7 +229,11 @@ export default function CreateMediaSosial() {
{/* Input name */} {/* Input name */}
<TextInput <TextInput
label="Nama Media Sosial" label={
<Text fw={500} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
Nama Media Sosial
</Text>
}
placeholder="Masukkan nama media sosial" placeholder="Masukkan nama media sosial"
value={stateMediaSosial.create.form.name ?? ''} value={stateMediaSosial.create.form.name ?? ''}
onChange={(e) => (stateMediaSosial.create.form.name = e.target.value)} onChange={(e) => (stateMediaSosial.create.form.name = e.target.value)}
@@ -238,7 +242,11 @@ export default function CreateMediaSosial() {
{/* Input link */} {/* Input link */}
<TextInput <TextInput
label="Link / Kontak" label={
<Text fw={500} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
Link / Kontak
</Text>
}
placeholder="Masukkan link atau nomor" placeholder="Masukkan link atau nomor"
value={stateMediaSosial.create.form.iconUrl ?? ''} value={stateMediaSosial.create.form.iconUrl ?? ''}
onChange={(e) => (stateMediaSosial.create.form.iconUrl = e.target.value)} onChange={(e) => (stateMediaSosial.create.form.iconUrl = e.target.value)}

View File

@@ -1,7 +1,25 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Group, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import {
Box,
Button,
Center,
Group,
Image,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react'; import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -28,7 +46,7 @@ function MediaSosial() {
} }
function ListMediaSosial({ search }: { search: string }) { function ListMediaSosial({ search }: { search: string }) {
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial) const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
const router = useRouter(); const router = useRouter();
const getIconSource = (item: any) => { const getIconSource = (item: any) => {
@@ -48,70 +66,95 @@ function ListMediaSosial({ search }: { search: string }) {
} = stateMediaSosial.findMany; } = stateMediaSosial.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 10, search) load(page, 10, search);
}, [page, search]) }, [page, search]);
const filteredData = data || [] const filteredData = data || [];
if (loading || !data) { if (loading || !data) {
return ( return (
<Stack py={10}> <Stack py={{ base: 'sm', sm: 'md' }}>
<Skeleton height={600} radius="md" /> <Skeleton height={600} radius="md" />
</Stack> </Stack>
); );
} }
return ( return (
<Box py={10}> <Box py={{ base: 'sm', sm: 'md' }}>
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md"> <Paper withBorder bg={colors['white-1']} p={{ base: 'md', sm: 'lg' }} shadow="md" radius="md">
<Group justify="space-between" mb="md"> <Group justify="space-between" mb={{ base: 'sm', sm: 'md' }}>
<Title order={4}>Daftar Media Sosial</Title> <Title order={4} lh={1.15}>
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/landing-page/profil/media-sosial/create')}> Daftar Media Sosial
</Title>
<Button
leftSection={<IconPlus size={18} />}
color="blue"
variant="light"
onClick={() => router.push('/admin/landing-page/profil/media-sosial/create')}
fz={{ base: 'xs', sm: 'sm' }}
>
Tambah Baru Tambah Baru
</Button> </Button>
</Group> </Group>
<Box style={{ overflowX: "auto" }}>
<Box>
{/* Desktop: Table | Mobile: Card-based vertical layout */}
<Box visibleFrom="md">
<Table highlightOnHover> <Table highlightOnHover>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh style={{ width: '25%' }}>Nama Media Sosial / Kontak</TableTh> <TableTh style={{ width: '25%' }}>
<TableTh style={{ width: '20%' }}>Gambar</TableTh> <Text fw={600} fz="md" lh={1.45}>
<TableTh style={{ width: '20%' }}>Link / No. Telepon</TableTh> Nama Media Sosial / Kontak
<TableTh style={{ width: '15%' }}>Aksi</TableTh> </Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fw={600} fz="md" lh={1.45}>
Gambar
</Text>
</TableTh>
<TableTh style={{ width: '20%' }}>
<Text fw={600} fz="md" lh={1.45}>
Link / No. Telepon
</Text>
</TableTh>
<TableTh style={{ width: '15%' }}>
<Text fw={600} fz="md" lh={1.45}>
Aksi
</Text>
</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd style={{ width: '25%', }}> <TableTd style={{ width: '25%' }}>
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text> <Text fw={500} fz="md" lh={1.5} truncate="end" lineClamp={1}>
{item.name}
</Text>
</TableTd> </TableTd>
<TableTd style={{ width: '20%' }}> <TableTd style={{ width: '20%' }}>
<Box w={50} h={50} style={{ borderRadius: 8, overflow: 'hidden' }}> <Box w={50} h={50} style={{ borderRadius: 8, overflow: 'hidden' }}>
{(() => { {(() => {
const src = getIconSource(item); const src = getIconSource(item);
if (src) { if (src) {
return ( return (
<Image <Image
loading="lazy" loading="lazy"
src={src} src={src}
alt={item.name} alt={item.name}
fit={item.image?.link ? "cover" : "contain"} fit={item.image?.link ? 'cover' : 'contain'}
/> />
); );
} }
return <Box bg={colors['blue-button']} w="100%" h="100%" />; return <Box bg={colors['blue-button']} w="100%" h="100%" />;
})()} })()}
</Box> </Box>
</TableTd> </TableTd>
<TableTd style={{ width: '20%', }}> <TableTd style={{ width: '20%' }}>
<Box w={250}> <Box w={250}>
<Text truncate fz="sm" c="dimmed" lineClamp={1}> <Text truncate fz="sm" lh={1.5} c="dimmed" lineClamp={1}>
{item.iconUrl || item.noTelp || '-'} {item.iconUrl || item.noTelp || '-'}
</Text> </Text>
</Box> </Box>
@@ -123,7 +166,9 @@ function ListMediaSosial({ search }: { search: string }) {
variant="light" variant="light"
color="blue" color="blue"
leftSection={<IconDeviceImacCog size={16} />} leftSection={<IconDeviceImacCog size={16} />}
onClick={() => router.push(`/admin/landing-page/profil/media-sosial/${item.id}`)} onClick={() =>
router.push(`/admin/landing-page/profil/media-sosial/${item.id}`)
}
> >
Detail Detail
</Button> </Button>
@@ -134,7 +179,9 @@ function ListMediaSosial({ search }: { search: string }) {
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={4}>
<Center py={20}> <Center py={20}>
<Text color="dimmed">Tidak ada data media sosial yang cocok</Text> <Text c="dimmed" fz="md" lh={1.5}>
Tidak ada data media sosial yang cocok
</Text>
</Center> </Center>
</TableTd> </TableTd>
</TableTr> </TableTr>
@@ -142,7 +189,78 @@ function ListMediaSosial({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
{/* Mobile layout */}
<Stack hiddenFrom="md" gap="xs">
{filteredData.length > 0 ? (
filteredData.map((item) => (
<Paper key={item.id} withBorder p="sm" radius="md">
<Group justify="space-between" wrap="nowrap" align='center'>
<Box>
<Text fw={600} fz="sm" lh={1.45}>
{item.name}
</Text>
</Box>
<Box w={40} h={40} style={{ borderRadius: 6, overflow: 'hidden' }}>
{(() => {
const src = getIconSource(item);
if (src) {
return (
<Image
loading="lazy"
src={src}
alt={item.name}
fit={item.image?.link ? 'cover' : 'contain'}
/>
);
}
return <Box bg={colors['blue-button']} w="100%" h="100%" />;
})()}
</Box>
</Group>
<Box>
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: 'underline' }}
>
<Text
fz="sm"
c="blue"
truncate
>
{item.iconUrl || item.noTelp || '-'}
</Text>
</a>
</Box>
<Group mt="sm" justify="flex-end">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(`/admin/landing-page/profil/media-sosial/${item.id}`)
}
>
Detail
</Button>
</Group>
</Paper> </Paper>
))
) : (
<Center py={24}>
<Text c="dimmed" fz="sm" lh={1.5}>
Tidak ada data media sosial yang cocok
</Text>
</Center>
)}
</Stack>
</Box>
</Paper>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}

View File

@@ -178,7 +178,7 @@ function EditPejabatDesa() {
} }
return ( return (
<Box> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Stack gap="xs"> <Stack gap="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={handleBack} p="xs" radius="md"> <Button variant="subtle" onClick={handleBack} p="xs" radius="md">

View File

@@ -3,7 +3,6 @@ import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -36,9 +35,9 @@ function Page() {
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 1 }}> <GridCol span={{ base: 12, md: 1 }}>
<Button <Button
style={{fontSize: 15, fontWeight: "bold"}}
c="green" c="green"
variant="light" variant="light"
leftSection={<IconEdit size={18} stroke={2} />}
radius="md" radius="md"
onClick={() => router.push(`/admin/landing-page/profil/pejabat-desa/${allList.findUnique.data?.id}`)} onClick={() => router.push(`/admin/landing-page/profil/pejabat-desa/${allList.findUnique.data?.id}`)}
> >
@@ -93,7 +92,7 @@ function Page() {
</Paper> </Paper>
<Box mt="lg"> <Box mt="lg">
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Jabatan</Text> <Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Jabatan</Text>
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']}> <Text fz={{ base: "1rem", md: "1.4rem" }} ta="left" c={colors['blue-button']}>
{item.position} {item.position}
</Text> </Text>
</Box> </Box>

View File

@@ -130,7 +130,7 @@ function EditProgramInovasi() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -40,13 +40,15 @@ function DetailProgramInovasi() {
const data = stateProgramInovasi.findUnique.data const data = stateProgramInovasi.findUnique.data
return ( return (
<Box px={{ base: 'md', md: 'xl' }} py="lg"> <Box px={{ base: 0, md: 'xs' }} py="xs">
<Box pb="20">
<Button variant="subtle" onClick={() => router.back()} leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}> <Button variant="subtle" onClick={() => router.back()} leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}>
Kembali Kembali
</Button> </Button>
</Box>
<Paper <Paper
w={{ base: "100%", md: "60%" }} w={{ base: "100%", md: "70%" }}
bg={colors['white-1']} bg={colors['white-1']}
p="lg" p="lg"
radius="md" radius="md"

View File

@@ -76,7 +76,7 @@ function CreateProgramInovasi() {
}; };
return ( return (
<Box px={{ base: 'sm', md: 'lg' }} py="md"> <Box px={{ base: 0, md: 'lg' }} py="xs">
<Group mb="md"> <Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md"> <Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} /> <IconArrowBack color={colors['blue-button']} size={24} />

View File

@@ -13,7 +13,7 @@ function ProgramInovasi() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
return ( return (
<Box px="md" py="lg"> <Box px={{base: 0, md: "md"}} py="lg">
<HeaderSearch <HeaderSearch
title="Program Inovasi" title="Program Inovasi"
placeholder="Cari program inovasi..." placeholder="Cari program inovasi..."
@@ -61,6 +61,7 @@ function ListProgramInovasi({ search }: { search: string }) {
Tambah Program Tambah Program
</Button> </Button>
</Group> </Group>
<Box visibleFrom='md'>
<Box style={{ overflowX: 'auto' }}> <Box style={{ overflowX: 'auto' }}>
<Table highlightOnHover striped verticalSpacing="sm"> <Table highlightOnHover striped verticalSpacing="sm">
<TableThead> <TableThead>
@@ -121,6 +122,67 @@ function ListProgramInovasi({ search }: { search: string }) {
</TableTbody> </TableTbody>
</Table> </Table>
</Box> </Box>
</Box>
<Box hiddenFrom="md" pt={20}>
<Stack gap="sm">
{filteredData.map((item) => (
<Paper
key={item.id}
withBorder
radius="md"
p="md"
shadow="xs"
>
<Stack gap={6}>
{/* Title */}
<Text fw={600}>{item.name}</Text>
{/* Description */}
<Text fz="sm" c="gray.7" lineClamp={2}>
{item.description || '-'}
</Text>
{/* Link */}
<Box>
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: 'underline' }}
>
<Text
fz="sm"
c="blue"
truncate
>
{item.link}
</Text>
</a>
</Box>
{/* Action */}
<Group justify="flex-end" mt="xs">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImacCog size={16} />}
onClick={() =>
router.push(
`/admin/landing-page/profil/program-inovasi/${item.id}`
)
}
>
Detail
</Button>
</Group>
</Stack>
</Paper>
))}
</Stack>
</Box>
{filteredData.length > 0 && ( {filteredData.length > 0 && (
<Center mt="md"> <Center mt="md">
<Pagination <Pagination

View File

@@ -1,399 +1,3 @@
// 'use client'
// import colors from "@/con/colors";
// import { authStore } from "@/store/authStore";
// import {
// ActionIcon,
// AppShell,
// AppShellHeader,
// AppShellMain,
// AppShellNavbar,
// Burger,
// Center,
// Flex,
// Group,
// Image,
// Loader,
// NavLink,
// ScrollArea,
// Text,
// Tooltip,
// rem
// } from "@mantine/core";
// import { useDisclosure } from "@mantine/hooks";
// import {
// IconChevronLeft,
// IconChevronRight,
// IconLogout2
// } from "@tabler/icons-react";
// import _ from "lodash";
// import Link from "next/link";
// import { useRouter, useSelectedLayoutSegments } from "next/navigation";
// import { useEffect, useState } from "react";
// // import { useSnapshot } from "valtio";
// import { getNavbar } from "./(dashboard)/user&role/_com/dynamicNavbar";
// export default function Layout({ children }: { children: React.ReactNode }) {
// const [opened, { toggle }] = useDisclosure();
// const [loading, setLoading] = useState(true);
// const [isLoggingOut, setIsLoggingOut] = useState(false);
// const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
// const router = useRouter();
// const segments = useSelectedLayoutSegments().map((s) => _.lowerCase(s));
// // const { user } = useSnapshot(authStore);
// // console.log("Current user in store:", user);
// // ✅ FIX: Selalu fetch user data setiap kali komponen mount
// useEffect(() => {
// const fetchUser = async () => {
// try {
// const res = await fetch('/api/auth/me');
// const data = await res.json();
// if (data.user) {
// // ✅ Check if user is NOT active → redirect to waiting room
// if (!data.user.isActive) {
// authStore.setUser(null);
// router.replace('/waiting-room');
// return;
// }
// // ✅ Fetch menuIds
// const menuRes = await fetch(`/api/admin/user-menu-access?userId=${data.user.id}`);
// const menuData = await menuRes.json();
// const menuIds = menuData.success && Array.isArray(menuData.menuIds)
// ? [...menuData.menuIds]
// : null;
// // ✅ Set user dengan menuIds yang fresh
// authStore.setUser({
// id: data.user.id,
// name: data.user.name,
// roleId: Number(data.user.roleId),
// menuIds,
// isActive: data.user.isActive
// });
// // ✅ TAMBAHKAN INI: Redirect ke dashboard sesuai roleId
// const currentPath = window.location.pathname;
// const expectedPath = getRedirectPath(Number(data.user.roleId));
// // Jika user di halaman /admin tapi bukan di path yang sesuai roleId
// if (currentPath === '/admin' || !currentPath.startsWith(expectedPath)) {
// router.replace(expectedPath);
// }
// } else {
// authStore.setUser(null);
// router.replace('/login');
// }
// } catch (error) {
// console.error('Gagal memuat data pengguna:', error);
// authStore.setUser(null);
// router.replace('/login');
// } finally {
// setLoading(false);
// }
// };
// fetchUser();
// }, [router]);
// // ✅ Fungsi helper untuk get redirect path
// const getRedirectPath = (roleId: number): string => {
// switch (roleId) {
// case 0: // DEVELOPER
// case 1: // SUPERADMIN
// case 2: // ADMIN_DESA
// return '/admin/landing-page/profil/program-inovasi';
// case 3: // ADMIN_KESEHATAN
// return '/admin/kesehatan/posyandu';
// case 4: // ADMIN_PENDIDIKAN
// return '/admin/pendidikan/info-sekolah/jenjang-pendidikan';
// default:
// return '/admin';
// }
// };
// if (loading) {
// return (
// <AppShell>
// <AppShellMain>
// <Center h="100vh">
// <Loader />
// </Center>
// </AppShellMain>
// </AppShell>
// );
// }
// // ✅ Ambil menu berdasarkan roleId dan menuIds
// const currentNav = authStore.user
// ? getNavbar({ roleId: authStore.user.roleId, menuIds: authStore.user.menuIds })
// : [];
// const handleLogout = async () => {
// try {
// setIsLoggingOut(true);
// // ✅ Panggil API logout untuk clear session di server
// const response = await fetch('/api/auth/logout', { method: 'POST' });
// const result = await response.json();
// if (result.success) {
// // Clear user data dari store
// authStore.setUser(null);
// // Clear localStorage
// localStorage.removeItem('auth_nomor');
// localStorage.removeItem('auth_kodeId');
// // Force reload untuk reset semua state
// window.location.href = '/login';
// } else {
// console.error('Logout failed:', result.message);
// // Tetap redirect meskipun gagal
// authStore.setUser(null);
// window.location.href = '/login';
// }
// } catch (error) {
// console.error('Error during logout:', error);
// // Tetap clear store dan redirect jika error
// authStore.setUser(null);
// window.location.href = '/login';
// } finally {
// setIsLoggingOut(false);
// }
// };
// return (
// <AppShell
// suppressHydrationWarning
// header={{ height: 64 }}
// navbar={{
// width: { base: 260, sm: 280, lg: 300 },
// breakpoint: 'sm',
// collapsed: {
// mobile: !opened,
// desktop: !desktopOpened,
// },
// }}
// padding="md"
// >
// <AppShellHeader
// style={{
// background: "linear-gradient(90deg, #ffffff, #f9fbff)",
// borderBottom: `1px solid ${colors["blue-button"]}20`,
// padding: '0 16px',
// }}
// px={{ base: 'sm', sm: 'md' }}
// py={{ base: 'xs', sm: 'sm' }}
// >
// <Group w="100%" h="100%" justify="space-between" wrap="nowrap">
// <Flex align="center" gap="sm">
// <Image
// src="/assets/images/darmasaba-icon.png"
// alt="Logo Darmasaba"
// w={{ base: 32, sm: 40 }}
// h={{ base: 32, sm: 40 }}
// radius="md"
// loading="lazy"
// style={{
// minWidth: '32px',
// height: 'auto',
// }}
// />
// <Text
// fw={700}
// c={colors["blue-button"]}
// fz={{ base: 'md', sm: 'xl' }}
// >
// Admin Darmasaba
// </Text>
// </Flex>
// <Group gap="xs">
// {!desktopOpened && (
// <Tooltip label="Buka Navigasi" position="bottom" withArrow>
// <ActionIcon
// variant="light"
// radius="xl"
// size="lg"
// onClick={toggleDesktop}
// color={colors["blue-button"]}
// >
// <IconChevronRight />
// </ActionIcon>
// </Tooltip>
// )}
// <Burger
// opened={opened}
// onClick={toggle}
// hiddenFrom="sm"
// size="md"
// color={colors["blue-button"]}
// mr="xs"
// />
// <Tooltip label="Kembali ke Website Desa" position="bottom" withArrow>
// <ActionIcon
// onClick={() => {
// router.push("/darmasaba");
// }}
// color={colors["blue-button"]}
// radius="xl"
// size="lg"
// variant="gradient"
// gradient={{ from: colors["blue-button"], to: "#228be6" }}
// >
// <Image
// src="/assets/images/darmasaba-icon.png"
// alt="Logo Darmasaba"
// w={20}
// h={20}
// radius="md"
// loading="lazy"
// style={{
// minWidth: '20px',
// height: 'auto',
// }}
// />
// </ActionIcon>
// </Tooltip>
// <Tooltip label="Keluar" position="bottom" withArrow>
// <ActionIcon
// onClick={handleLogout}
// color={colors["blue-button"]}
// radius="xl"
// size="lg"
// variant="gradient"
// gradient={{ from: colors["blue-button"], to: "#228be6" }}
// loading={isLoggingOut}
// disabled={isLoggingOut}
// >
// <IconLogout2 size={22} />
// </ActionIcon>
// </Tooltip>
// </Group>
// </Group>
// </AppShellHeader>
// <AppShellNavbar
// component={ScrollArea}
// style={{
// background: "#ffffff",
// borderRight: `1px solid ${colors["blue-button"]}20`,
// }}
// p={{ base: 'xs', sm: 'sm' }}
// >
// <AppShell.Section p="sm">
// {currentNav.map((v, k) => {
// const isParentActive = segments.includes(_.lowerCase(v.name));
// return (
// <NavLink
// key={k}
// defaultOpened={isParentActive}
// c={isParentActive ? colors["blue-button"] : "gray"}
// label={
// <Text fw={isParentActive ? 600 : 400} fz="sm">
// {v.name}
// </Text>
// }
// style={{
// borderRadius: rem(10),
// marginBottom: rem(4),
// transition: "background 150ms ease",
// }}
// styles={{
// root: {
// '&:hover': {
// backgroundColor: 'rgba(25, 113, 194, 0.05)',
// },
// },
// }}
// variant="light"
// active={isParentActive}
// >
// {v.children.map((child, key) => {
// const isChildActive = segments.includes(
// _.lowerCase(child.name)
// );
// return (
// <NavLink
// key={key}
// href={child.path}
// c={isChildActive ? colors["blue-button"] : "gray"}
// label={
// <Text fw={isChildActive ? 600 : 400} fz="sm">
// {child.name}
// </Text>
// }
// styles={{
// root: {
// borderRadius: rem(8),
// marginBottom: rem(2),
// transition: 'background 150ms ease',
// padding: '6px 12px',
// '&:hover': {
// backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)',
// },
// ...(isChildActive && {
// backgroundColor: 'rgba(25, 113, 194, 0.1)',
// }),
// },
// }}
// active={isChildActive}
// component={Link}
// />
// );
// })}
// </NavLink>
// );
// })}
// </AppShell.Section>
// <AppShell.Section py="md">
// <Group justify="end" pr="sm">
// <Tooltip
// label={desktopOpened ? "Tutup Navigasi" : "Buka Navigasi"}
// position="top"
// withArrow
// >
// <ActionIcon
// variant="light"
// radius="xl"
// size="lg"
// onClick={toggleDesktop}
// color={colors["blue-button"]}
// >
// <IconChevronLeft />
// </ActionIcon>
// </Tooltip>
// </Group>
// </AppShell.Section>
// </AppShellNavbar>
// <AppShellMain
// style={{
// background: "linear-gradient(180deg, #fdfdfd, #f6f9fc)",
// minHeight: "100vh",
// }}
// >
// {children}
// </AppShellMain>
// </AppShell>
// );
// }
// app/admin/layout.tsx
'use client' 'use client'
import colors from "@/con/colors"; import colors from "@/con/colors";

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Text, Paper, Skeleton } from '@mantine/core'; import { Stack, Box, Text, Paper, Skeleton, Center } from '@mantine/core';
import React from 'react'; import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { BarChart } from '@mantine/charts'; import { BarChart } from '@mantine/charts';
@@ -80,6 +80,7 @@ function Page() {
<Paper p="xl"> <Paper p="xl">
<Text pb={10} fw="bold" fz="h4">Statistik Sektor Unggulan Darmasaba</Text> <Text pb={10} fw="bold" fz="h4">Statistik Sektor Unggulan Darmasaba</Text>
<Box style={{ width: '100%', overflowX: 'auto', maxWidth: `${chartWidth}px` }}> <Box style={{ width: '100%', overflowX: 'auto', maxWidth: `${chartWidth}px` }}>
<Center>
<BarChart <BarChart
p={10} p={10}
h={300} h={300}
@@ -100,6 +101,7 @@ function Page() {
fontSize: '12px', // ukuran font lebih kecil di mobile fontSize: '12px', // ukuran font lebih kecil di mobile
}} }}
/> />
</Center>
</Box> </Box>
</Paper> </Paper>
</Box> </Box>

View File

@@ -54,7 +54,7 @@ function Page() {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }} w={"100%"}
/> />
</GridCol> </GridCol>
</Grid> </Grid>

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core' import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'
import { useShallowEffect } from '@mantine/hooks' import { useShallowEffect } from '@mantine/hooks'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { useProxy } from 'valtio/utils' import { useProxy } from 'valtio/utils'
@@ -46,14 +46,14 @@ function DetailKeamananLingkunganUser() {
> >
<Stack gap="lg"> <Stack gap="lg">
{/* Judul */} {/* Judul */}
<Text <Title
order={1}
ta="center" ta="center"
fz={{ base: 'xl', md: '2xl' }}
fw={700}
c={colors['blue-button']} c={colors['blue-button']}
style={{ lineHeight: 1.15 }}
> >
{data?.name || 'Tanpa Judul'} {data?.name || 'Tanpa Judul'}
</Text> </Title>
{/* Gambar */} {/* Gambar */}
<Center> <Center>
@@ -69,16 +69,19 @@ function DetailKeamananLingkunganUser() {
{/* Deskripsi */} {/* Deskripsi */}
<Box> <Box>
<Text fz="lg" fw="bold" mb={5}> <Title order={3} mb={5} style={{ lineHeight: 1.2 }}>
Deskripsi Deskripsi
</Text> </Title>
<Box pl={20}>
<Text <Text
fz="md" fz={{ base: 'sm', md: 'md' }}
c="dimmed" lh={{ base: 1.5, md: 1.55 }}
c={data?.deskripsi ? 'text' : 'dimmed'}
dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
/> />
</Box> </Box>
</Box>
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan'; import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react'; import { IconSearch } from '@tabler/icons-react';
import { useState } from 'react'; import { useState } from 'react';
@@ -14,7 +14,7 @@ function Page() {
const state = useProxy(keamananLingkunganState) const state = useProxy(keamananLingkunganState)
const router = useRouter() const router = useRouter()
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const { const {
data, data,
page, page,
@@ -43,9 +43,9 @@ function Page() {
<Box> <Box>
<Grid align='center' px={{ base: 'md', md: 100 }}> <Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title order={1} c={colors["blue-button"]} lh={1.15}>
Keamanan Lingkungan (Pecalang / Patwal) Keamanan Lingkungan (Pecalang / Patwal)
</Text> </Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 3 }}> <GridCol span={{ base: 12, md: 3 }}>
<TextInput <TextInput
@@ -54,18 +54,29 @@ function Page() {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }} w={"100%"}
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Text px={{ base: 'md', md: 100 }} pt={20} ta={"justify"} fz="md" mt={4} > <Text
px={{ base: 'md', md: 100 }}
pt={20}
ta={"justify"}
fz={{ base: 'sm', md: 'md' }}
lh={1.55}
mt={4}
c={'black'}
>
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga. Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
</Text> </Text>
</Box> </Box>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}> <Stack gap={'lg'}>
<SimpleGrid <SimpleGrid
cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" mt="lg"> cols={{ base: 1, sm: 2, md: 3 }}
spacing="xl"
mt="lg"
>
{data.map((v, k) => ( {data.map((v, k) => (
<Paper <Paper
key={k} key={k}
@@ -107,17 +118,18 @@ function Page() {
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')} onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
/> />
</Box> </Box>
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}> <Title order={2} ta="center" c={colors['blue-button']} lh={1.2}>
{v.name} {v.name}
</Text> </Title>
<Text <Text
fz="sm" fz={{ base: 'xs', md: 'sm' }}
ta="justify" ta="justify"
lineClamp={3} lineClamp={3}
lh={1.6} lh={1.55}
style={{ style={{
minHeight: '4.8em', minHeight: '4.8em',
}} }}
c={'black'}
> >
<span <span
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }} style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import kontakDarurat from '@/app/admin/(dashboard)/_state/keamanan/kontak-darurat-keamanan'; import kontakDarurat from '@/app/admin/(dashboard)/_state/keamanan/kontak-darurat-keamanan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Avatar, Box, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Avatar, Box, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
import { IconPhoneCall, IconSearch } from '@tabler/icons-react'; import { IconPhoneCall, IconSearch } from '@tabler/icons-react';
import { useState } from 'react'; import { useState } from 'react';
@@ -42,10 +42,10 @@ function Page() {
</Box> </Box>
<Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'> <Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'>
<Box> <Box>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title order={1} c={colors["blue-button"]} style={{ lineHeight: 1.15 }}>
Kontak Darurat Kontak Darurat
</Text> </Title>
<Text fz="md" > <Text fz={{ base: 'sm', md: 'md' }} lh={1.5} c={colors['blue-button-2']} style={{ color: colors['blue-button-2'] }}>
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung. Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung.
</Text> </Text>
</Box> </Box>
@@ -66,17 +66,21 @@ function Page() {
<IconPhoneCall size={30} color={colors["blue-button"]} /> <IconPhoneCall size={30} color={colors["blue-button"]} />
</Avatar> </Avatar>
<Box> <Box>
<Text ta={'center'} c={colors['blue-button']} py={10} fz={{ base: "md", md: "h4" }} fw={"bold"} > <Text ta={'center'} c={colors['blue-button']} py={10} fz={{ base: "sm", md: "md" }} fw={"bold"} lh={1.3} >
Nomor Darurat Utama Nomor Darurat Utama
</Text> </Text>
<Text ta={'center'} fw={"bold"} fz={'h2'} c={colors["blue-button"]}>112</Text> <Title order={2} ta={'center'} c={colors["blue-button"]} style={{ lineHeight: 1.15 }}>
112
</Title>
</Box> </Box>
</Flex> </Flex>
</Paper> </Paper>
</Stack> </Stack>
</Box> </Box>
<Center> <Center>
<Text fz={"h1"} c={colors["blue-button"]} fw={"bold"}>Tidak ada kontak darurat yang ditemukan</Text> <Title order={2} c={colors["blue-button"]} style={{ lineHeight: 1.15 }}>
Tidak ada kontak darurat yang ditemukan
</Title>
</Center> </Center>
</Stack> </Stack>
); );
@@ -89,10 +93,10 @@ function Page() {
</Box> </Box>
<Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'> <Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'>
<Box> <Box>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title order={1} c={colors["blue-button"]} style={{ lineHeight: 1.15 }}>
Kontak Darurat Kontak Darurat
</Text> </Title>
<Text fz={{ base: "h4", md: "h3" }} > <Text fz={{ base: 'sm', md: 'md' }} lh={1.5} c={colors['blue-button-2']}>
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung. Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung.
</Text> </Text>
</Box> </Box>
@@ -113,10 +117,12 @@ function Page() {
<IconPhoneCall size={30} color={colors["blue-button"]} /> <IconPhoneCall size={30} color={colors["blue-button"]} />
</Avatar> </Avatar>
<Box> <Box>
<Text ta={'center'} c={colors['blue-button']} py={10} fz={{ base: "md", md: "h4" }} fw={"bold"} > <Text ta={'center'} c={colors['blue-button']} py={10} fz={{ base: "sm", md: "md" }} fw={"bold"} lh={1.3} >
Nomor Darurat Utama Nomor Darurat Utama
</Text> </Text>
<Text ta={'center'} fw={"bold"} fz={'h2'} c={colors["blue-button"]}>112</Text> <Title order={2} ta={'center'} c={colors["blue-button"]} style={{ lineHeight: 1.15 }}>
112
</Title>
</Box> </Box>
</Flex> </Flex>
</Paper> </Paper>
@@ -124,19 +130,13 @@ function Page() {
</Box> </Box>
<Box px={{ base: "md", md: 100 }}> <Box px={{ base: "md", md: 100 }}>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl"> <SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
{/* Layanan Darurat */}
{data.map((item) => ( {data.map((item) => (
<a <a
key={item.id} key={item.id}
href={`tel:${item.kontakItems[0]?.kontakItem?.nomorTelepon || '112'}`} href={`tel:${item.kontakItems[0]?.kontakItem?.nomorTelepon || '112'}`}
style={{ textDecoration: 'none' }} style={{ textDecoration: 'none' }}
> >
<Paper <Paper p="lg" radius="md" bg={colors['white-trans-1']}>
p="lg"
radius="md"
bg={colors['white-trans-1']}
>
<Group pb="md" align="center"> <Group pb="md" align="center">
<Avatar radius="xl" size="lg" bg={colors['BG-trans']}> <Avatar radius="xl" size="lg" bg={colors['BG-trans']}>
{item.icon && ( {item.icon && (
@@ -147,12 +147,11 @@ function Page() {
/> />
)} )}
</Avatar> </Avatar>
<Text fw="bold" fz={{ base: "lg", md: "xl" }} c={colors["blue-button"]}> <Title order={3} c={colors["blue-button"]} style={{ lineHeight: 1.2 }}>
{item.nama} {item.nama}
</Text> </Title>
</Group> </Group>
{/* Kontak Items */}
{item.kontakItems?.map((kontak) => ( {item.kontakItems?.map((kontak) => (
<Paper <Paper
key={kontak.id} key={kontak.id}
@@ -171,11 +170,11 @@ function Page() {
color={colors['blue-button']} color={colors['blue-button']}
/> />
)} )}
<Text fw="bold" fz={{ base: "sm", md: "md" }} c={colors["blue-button"]}> <Text fw="bold" fz={{ base: "xs", md: "sm" }} c={colors["blue-button"]} lh={1.45}>
{kontak.kontakItem?.nama} {kontak.kontakItem?.nama}
</Text> </Text>
</Group> </Group>
<Text fw="bold" fz={{ base: "sm", md: "md" }} c={colors["blue-button"]}> <Text fw="bold" fz={{ base: "xs", md: "sm" }} c={colors["blue-button"]} lh={1.45}>
{kontak.kontakItem?.nomorTelepon} {kontak.kontakItem?.nomorTelepon}
</Text> </Text>
</Group> </Group>

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas'; import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconArrowRight } from '@tabler/icons-react'; import { IconArrowRight } from '@tabler/icons-react';
import { useTransitionRouter } from 'next-view-transitions'; import { useTransitionRouter } from 'next-view-transitions';
@@ -26,7 +26,7 @@ function Page() {
if (!findFirst.data && !findFirst.loading) { if (!findFirst.data && !findFirst.loading) {
kriminalitasState.findFirst.load(); kriminalitasState.findFirst.load();
} }
}, [findFirst.data, findFirst.loading]); }, []);
useShallowEffect(() => { useShallowEffect(() => {
const LIMIT = 3; const LIMIT = 3;
@@ -45,10 +45,10 @@ function Page() {
return ( return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}> <Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold"> <Title order={1} c={colors['blue-button']} fw="bold" lh={1.2}>
Pencegahan Kriminalitas Pencegahan Kriminalitas
</Text> </Title>
<Text fz='md'> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Keamanan Komunitas & Pencegahan Kriminal Keamanan Komunitas & Pencegahan Kriminal
</Text> </Text>
</Box> </Box>
@@ -58,11 +58,11 @@ function Page() {
spacing="xl" spacing="xl"
> >
<Paper p="xl" radius="xl" shadow="lg" > <Paper p="xl" radius="xl" shadow="lg" >
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold"> <Title order={2} c={colors['blue-button']} fw="bold" lh={1.2}>
Program Keamanan Berjalan Program Keamanan Berjalan
</Text> </Title>
<Stack pt={30} gap="lg"> <Stack pt={30} gap="lg">
<Text c="dimmed"> <Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Tidak ada data pencegahan kriminalitas yang cocok Tidak ada data pencegahan kriminalitas yang cocok
</Text> </Text>
</Stack> </Stack>
@@ -75,10 +75,10 @@ function Page() {
return ( return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}> <Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold"> <Title order={1} c={colors['blue-button']} fw="bold" lh={1.2}>
Pencegahan Kriminalitas Pencegahan Kriminalitas
</Text> </Title>
<Text fz='md'> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Keamanan Komunitas & Pencegahan Kriminal Keamanan Komunitas & Pencegahan Kriminal
</Text> </Text>
</Box> </Box>
@@ -88,13 +88,13 @@ function Page() {
spacing="xl" spacing="xl"
> >
<Paper p="xl" radius="xl" shadow="lg" > <Paper p="xl" radius="xl" shadow="lg" >
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold"> <Title order={2} c={colors['blue-button']} fw="bold" lh={1.2}>
Program Keamanan Berjalan Program Keamanan Berjalan
</Text> </Title>
<Stack pt={30} gap="lg"> <Stack pt={30} gap="lg">
<Box <Box
style={{ style={{
minHeight: 300, // sesuaikan: tinggi area yg muat 3 item minHeight: 300,
}} }}
> >
{data.length > 0 ? ( {data.length > 0 ? (
@@ -120,14 +120,16 @@ function Page() {
} }
> >
<Stack gap="xs"> <Stack gap="xs">
<Text fz="h3" c={colors['white-1']}> <Title order={3} c={colors['white-1']} lh={1.2}>
{item.judul} {item.judul}
</Text> </Title>
</Stack> </Stack>
</Paper> </Paper>
)) ))
) : ( ) : (
<Text c="dimmed">Tidak ada data pencegahan kriminalitas yang cocok</Text> <Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Tidak ada data pencegahan kriminalitas yang cocok
</Text>
)} )}
</Box> </Box>
<Button <Button
@@ -169,12 +171,18 @@ function Page() {
style={{ borderRadius: 8 }} style={{ borderRadius: 8 }}
/> />
) : ( ) : (
<Text fz="sm" c="dimmed">Tidak ada video</Text> <Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.4}>
)} Tidak ada video
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
{findFirst.data?.judul}
</Text> </Text>
<Text fz="h4" dangerouslySetInnerHTML={{ __html: findFirst.data?.deskripsiSingkat }} /> )}
<Title order={2} py={10} fw="bold" c={colors['blue-button']} lh={1.2}>
{findFirst.data?.judul}
</Title>
<Text
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
dangerouslySetInnerHTML={{ __html: findFirst.data?.deskripsiSingkat }}
/>
</Paper> </Paper>
) : null} ) : null}
</Box> </Box>

View File

@@ -2,7 +2,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat'; import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Badge, Box, Button, Center, Flex, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { Badge, Box, Button, Center, Flex, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconArrowDown, IconClock, IconNavigation, IconPhone, IconPin } from '@tabler/icons-react'; import { IconArrowDown, IconClock, IconNavigation, IconPhone, IconPin } from '@tabler/icons-react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -35,15 +35,15 @@ function Page() {
<BackButton /> <BackButton />
</Box> </Box>
<Box pb={10} px={{ base: 20, md: 100 }}> <Box pb={10} px={{ base: 20, md: 100 }}>
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold"> <Title order={1} c={colors['blue-button']}>
Kantor Polisi Terdekat Kantor Polisi Terdekat
</Text> </Title>
<Text pb={15} fz="md"> <Text pb={15} fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
</Text> </Text>
</Box> </Box>
<Center py="xl"> <Center py="xl">
<Text fz="lg" fw="bold" c="red"> <Text fz={{ base: 'md', md: 'lg' }} fw="bold" c="red" lh={1.4}>
Data Polsek tidak ada Data Polsek tidak ada
</Text> </Text>
</Center> </Center>
@@ -58,10 +58,10 @@ function Page() {
</Box> </Box>
<Box pb={10} px={{ base: 20, md: 100 }}> <Box pb={10} px={{ base: 20, md: 100 }}>
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold"> <Title order={1} c={colors['blue-button']}>
Kantor Polisi Terdekat Kantor Polisi Terdekat
</Text> </Title>
<Text pb={15} fz="h4"> <Text pb={15} fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
</Text> </Text>
</Box> </Box>
@@ -79,10 +79,10 @@ function Page() {
<> <>
{/* === KIRI === */} {/* === KIRI === */}
<Box> <Box>
<Text c={colors['blue-button']} fw="bold" fz="h2"> <Title order={2} c={colors['blue-button']} lh={1.2}>
{data.nama} {data.nama}
</Text> </Title>
<Text c={colors['blue-button']} fz="sm"> <Text c={colors['blue-button']} fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
{data.jarakKeDesa} {data.jarakKeDesa}
</Text> </Text>
@@ -98,11 +98,11 @@ function Page() {
<IconPin size={22} /> <IconPin size={22} />
</Box> </Box>
<Text <Text
fz="lg" fz={{ base: 'sm', md: 'md' }}
style={{ style={{
flex: 1, flex: 1,
wordBreak: 'break-word', wordBreak: 'break-word',
lineHeight: 1.4, lineHeight: 1.5,
}} }}
> >
{data.alamat} {data.alamat}
@@ -119,7 +119,7 @@ function Page() {
<Box w={25} mt={3}> <Box w={25} mt={3}>
<IconPhone size={22} /> <IconPhone size={22} />
</Box> </Box>
<Text fz="lg"> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
{data.nomorTelepon} {data.nomorTelepon}
</Text> </Text>
</Flex> </Flex>
@@ -135,24 +135,24 @@ function Page() {
<Box w={25} mt={3}> <Box w={25} mt={3}>
<IconClock size={22} /> <IconClock size={22} />
</Box> </Box>
<Text fz="lg"> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
{data.jamOperasional} {data.jamOperasional}
</Text> </Text>
</Flex> </Flex>
{/* Layanan */} {/* Layanan */}
<Box> <Box pt={15}>
<Text c={colors['blue-button']} fw="bold" fz="h2"> <Title order={2} c={colors['blue-button']} lh={1.2}>
Layanan Yang Tersedia: Layanan Yang Tersedia:
</Text> </Title>
<SimpleGrid py={10} cols={{ base: 1, md: 2 }}> <SimpleGrid py={10} cols={{ base: 1, md: 2 }}>
<Text fz="lg"> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
{data.layananPolsek.nama} {data.layananPolsek.nama}
</Text> </Text>
</SimpleGrid> </SimpleGrid>
</Box> </Box>
<Box> <Box pt={15}>
<Button <Button
bg={colors['blue-button']} bg={colors['blue-button']}
onClick={() => onClick={() =>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat'; import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Button, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconNavigation, IconSearch } from '@tabler/icons-react'; import { IconNavigation, IconSearch } from '@tabler/icons-react';
import React, { useState } from 'react'; import React, { useState } from 'react';
@@ -13,8 +13,8 @@ import { useDebouncedValue } from '@mantine/hooks';
function Page() { function Page() {
const state = useProxy(polsekTerdekatState); const state = useProxy(polsekTerdekatState);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay const [debouncedSearch] = useDebouncedValue(search, 1000);
const router = useRouter() const router = useRouter();
const { const {
data, data,
@@ -25,71 +25,98 @@ function Page() {
} = state.findMany; } = state.findMany;
useShallowEffect(() => { useShallowEffect(() => {
load(page, 3, debouncedSearch) load(page, 3, debouncedSearch);
}, [page, debouncedSearch]) }, [page, debouncedSearch]);
if (loading || !data) { if (loading || !data) {
return ( return (
<Box py={10}> <Box py={10}>
<Skeleton h={500} /> <Skeleton h={500} />
</Box> </Box>
) );
} }
return ( return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}> <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<Grid align="center" px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}> <GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}> <Title
order={1}
c={colors['blue-button']}
lh={1.2}
>
Semua Polsek Terdekat Semua Polsek Terdekat
</Text> </Title>
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 3 }}> <GridCol span={{ base: 12, md: 3 }}>
<TextInput <TextInput
radius={"lg"} radius="lg"
placeholder='Cari Polsek' placeholder="Cari Polsek"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />} leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }} w={{ base: '50%', md: '100%' }}
/> />
</GridCol> </GridCol>
</Grid> </Grid>
<Box px={{ base: "md", md: 100 }}>
<SimpleGrid <Box px={{ base: 'md', md: 100 }}>
cols={{ <SimpleGrid cols={{ base: 1, md: 3 }}>
base: 1, {data.map((v, k) => (
md: 3, <Paper p="xl" bg={colors['white-trans-1']} key={k}>
}} <Stack gap="xs">
<Title
order={3}
fw="bold"
lh={1.2}
> >
{data.map((v, k) => { {v.nama}
return ( </Title>
<Paper p={"xl"} bg={colors['white-trans-1']} key={k}> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
<Stack gap={"xs"}> Alamat: {v.alamat}
<Text fw={"bold"} fz={"h3"}>{v.nama}</Text> </Text>
<Text>Alamat: {v.alamat}</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
<Text>Jarak: {v.jarakKeDesa}</Text> Jarak: {v.jarakKeDesa}
<Text>Telepon: {v.nomorTelepon}</Text> </Text>
<Text>Jam Operasional: {v.jamOperasional}</Text> <Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Telepon: {v.nomorTelepon}
</Text>
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
Jam Operasional: {v.jamOperasional}
</Text>
<Box pt={20}> <Box pt={20}>
<iframe style={{ border: 2, width: "100%" }} src={v.embedMapUrl} width="550" height="300" ></iframe> <iframe
style={{ border: 2, width: '100%' }}
src={v.embedMapUrl}
width="550"
height="300"
></iframe>
</Box> </Box>
<Box pt={20}> <Box pt={20}>
<Button onClick={() => router.push(v.linkPetunjukArah)} fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20} />}>Petunjuk Arah</Button> <Button
onClick={() => router.push(v.linkPetunjukArah)}
fullWidth
bg={colors['blue-button']}
radius={10}
leftSection={<IconNavigation size={20} />}
>
Petunjuk Arah
</Button>
</Box> </Box>
</Stack> </Stack>
</Paper> </Paper>
) ))}
})}
</SimpleGrid> </SimpleGrid>
</Box> </Box>
<Center> <Center>
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => load(newPage)} // ini penting! onChange={(newPage) => load(newPage)}
total={totalPages} total={totalPages}
mt="md" mt="md"
mb="md" mb="md"

View File

@@ -1,7 +1,7 @@
// Create a new component: components/EdukasiCard.tsx // components/EdukasiCard.tsx
'use client'; 'use client';
import { Box, Paper, Stack, Text } from '@mantine/core'; import { Box, Paper, Stack, Text, Title } from '@mantine/core';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
interface EdukasiCardProps { interface EdukasiCardProps {
@@ -31,9 +31,9 @@ export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: Edu
<Box> <Box>
<Stack align="center" gap="xs" mb="md"> <Stack align="center" gap="xs" mb="md">
<Box style={{ color }}>{icon}</Box> <Box style={{ color }}>{icon}</Box>
<Text <Title
fz={{ base: 'h5', md: 'h4' }} order={3}
fw={700} fz={{ base: 'sm', md: 'md' }}
c={color} c={color}
ta="center" ta="center"
lineClamp={2} lineClamp={2}
@@ -42,22 +42,25 @@ export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: Edu
minHeight: '3.5rem', minHeight: '3.5rem',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
lineHeight: 1.2
}} }}
dangerouslySetInnerHTML={{ __html: title }} dangerouslySetInnerHTML={{ __html: title }}
/> />
</Stack> </Stack>
<Box pl={20}>
<Text <Text
size="sm" fz={{ base: 'sm', md: 'md' }}
pl={20} lh={1.5}
c="gray.7"
ta="justify"
style={{ style={{
wordBreak: 'break-word', wordBreak: 'break-word'
lineHeight: 1.6,
color: 'var(--mantine-color-gray-7)'
}} }}
dangerouslySetInnerHTML={{ __html: description }} dangerouslySetInnerHTML={{ __html: description }}
/> />
</Box> </Box>
</Box>
</Stack> </Stack>
</Paper> </Paper>
); );

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { Box, Container, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Container, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconLeaf, IconPlant2, IconRecycle } from '@tabler/icons-react'; import { IconLeaf, IconPlant2, IconRecycle } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -51,19 +51,21 @@ export default function EdukasiLingkunganPage() {
</Box> </Box>
<Container size="lg" ta="center"> <Container size="lg" ta="center">
<Text <Title
component="h1" order={1}
fz={{ base: 'h2', md: '2.5rem' }}
c={colors['blue-button']} c={colors['blue-button']}
fw={700} fw={700}
mb="md" mb="md"
lh={1.15}
> >
Edukasi Lingkungan Edukasi Lingkungan
</Text> </Title>
<Text <Text
fz={{ base: 'md', md: 'lg' }} fz={{ base: 'sm', md: 'md' }}
lh={1.5}
maw={800} maw={800}
mx="auto" mx="auto"
c="dark.9"
> >
Program edukasi ini membimbing masyarakat untuk peduli dan bertanggung jawab terhadap alam, Program edukasi ini membimbing masyarakat untuk peduli dan bertanggung jawab terhadap alam,
meningkatkan kesehatan, kenyamanan, dan keberlanjutan hidup bersama. meningkatkan kesehatan, kenyamanan, dan keberlanjutan hidup bersama.

View File

@@ -43,6 +43,10 @@ function DetailKegiatanDesaUser() {
mx="auto" mx="auto"
> >
<Stack gap="md"> <Stack gap="md">
{/* Judul */}
<Title order={1} ta={"center"} c={colors['blue-button']}>
{data.judul || 'Kegiatan Desa'}
</Title>
{data.image?.link && ( {data.image?.link && (
<Image <Image
src={data.image.link} src={data.image.link}
@@ -54,10 +58,6 @@ function DetailKegiatanDesaUser() {
style={{ objectPosition: 'center', width: '100%' }} style={{ objectPosition: 'center', width: '100%' }}
/> />
)} )}
{/* Judul */}
<Title order={2} c={colors['blue-button']}>
{data.judul || 'Kegiatan Desa'}
</Title>
{/* Meta Info */} {/* Meta Info */}
<Group gap="xl" c="dimmed"> <Group gap="xl" c="dimmed">

View File

@@ -1,6 +1,10 @@
// app/desa/berita/BeritaLayoutClient.tsx // app/desa/berita/BeritaLayoutClient.tsx
'use client' 'use client'
import colors from '@/con/colors';
import { Box } from '@mantine/core';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { usePathname } from 'next/navigation';
import BackButton from '../../desa/layanan/_com/BackButto';
const LayoutTabsGotongRoyong = dynamic( const LayoutTabsGotongRoyong = dynamic(
() => import('./_lib/layoutTabs'), () => import('./_lib/layoutTabs'),
@@ -8,5 +12,21 @@ const LayoutTabsGotongRoyong = dynamic(
); );
export default function GotongRoyongLayoutClient({ children }: { children: React.ReactNode }) { export default function GotongRoyongLayoutClient({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const segments = pathname.split('/').filter(Boolean)
const isDetailPage = segments.length === 5;
if (isDetailPage) {
// Tampilkan tanpa tab menu
return (
<Box bg={colors.Bg}>
<Box pt={33} px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
{children}
</Box>
);
}
return <LayoutTabsGotongRoyong>{children}</LayoutTabsGotongRoyong>; return <LayoutTabsGotongRoyong>{children}</LayoutTabsGotongRoyong>;
} }

View File

@@ -11,7 +11,6 @@ import {
Center, Center,
Container, Container,
Divider, Divider,
Flex,
Grid, Grid,
GridCol, GridCol,
Group, Group,
@@ -23,7 +22,7 @@ import {
Stack, Stack,
Text, Text,
Title, Title,
Transition, Transition
} from '@mantine/core'; } from '@mantine/core';
import { IconArrowRight, IconCalendar } from '@tabler/icons-react'; import { IconArrowRight, IconCalendar } from '@tabler/icons-react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
@@ -139,7 +138,6 @@ export default function Page() {
height={400} height={400}
fit="cover" fit="cover"
radius="md" radius="md"
style={{ borderBottomRightRadius: 0, borderTopRightRadius: 0 }}
loading="lazy" loading="lazy"
/> />
</GridCol> </GridCol>
@@ -222,6 +220,7 @@ export default function Page() {
alt={item.judul} alt={item.judul}
fit="cover" fit="cover"
loading="lazy" loading="lazy"
radius={"md"}
/> />
</Card.Section> </Card.Section>
@@ -241,7 +240,9 @@ export default function Page() {
dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }}
/> />
<Flex align="center" justify="apart" mt="md" gap="xs"> <Group justify="apart" mt="auto">
<Group gap="xs">
<IconCalendar size={18} />
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed">
{new Date(item.createdAt).toLocaleDateString('id-ID', { {new Date(item.createdAt).toLocaleDateString('id-ID', {
day: 'numeric', day: 'numeric',
@@ -249,6 +250,7 @@ export default function Page() {
year: 'numeric', year: 'numeric',
})} })}
</Text> </Text>
</Group>
<Button <Button
p="xs" p="xs"
@@ -262,7 +264,7 @@ export default function Page() {
> >
Baca Selengkapnya Baca Selengkapnya
</Button> </Button>
</Flex> </Group>
</Card> </Card>
))} ))}
</SimpleGrid> </SimpleGrid>

View File

@@ -1,11 +1,12 @@
'use client' 'use client'
import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali'; import stateKonservasiAdatBali from '@/app/admin/(dashboard)/_state/lingkungan/konservasi-adat-bali';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core'; import { Box, Center, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
function Page() { function Page() {
const filosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita.findById) const filosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita.findById)
const nilai = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat.findById) const nilai = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat.findById)
@@ -30,11 +31,24 @@ function Page() {
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<BackButton /> <BackButton />
</Box> </Box>
<Box px={{ base: 'md', md: 100 }} pb={30}> <Box px={{ base: 'md', md: 100 }} >
<Text ta="center" fz={{ base: '2xl', md: '3rem' }} c={colors['blue-button']} fw="bold"> <Title
order={1}
ta="center"
c={colors['blue-button']}
fw="bold"
style={{ lineHeight: 1.15 }}
>
Konservasi Adat Bali Konservasi Adat Bali
</Text> </Title>
<Text px={20} ta="center" fz="lg" c="black"> <Text
px={20}
ta="center"
fz={{ base: 'sm', md: 'lg' }}
c="black"
style={{ lineHeight: 1.55 }}
>
Pelestarian lingkungan di Bali yang berpijak pada kearifan lokal, menjaga harmoni antara alam, budaya, dan manusia. Pelestarian lingkungan di Bali yang berpijak pada kearifan lokal, menjaga harmoni antara alam, budaya, dan manusia.
</Text> </Text>
</Box> </Box>
@@ -54,15 +68,23 @@ function Page() {
> >
<Stack gap="md" px={20} style={{ height: '100%' }}> <Stack gap="md" px={20} style={{ height: '100%' }}>
<Center> <Center>
<Text fz="xl" fw="bold" c="black"> <Title
order={3}
c="black"
fw="bold"
style={{ lineHeight: 1.15 }}
>
{filosofi.data?.judul} {filosofi.data?.judul}
</Text> </Title>
</Center> </Center>
<div <div
style={{ style={{
wordBreak: "break-word", wordBreak: "break-word",
whiteSpace: "normal", whiteSpace: "normal",
flexGrow: 1 flexGrow: 1,
fontSize: '14px',
lineHeight: 1.55,
color: 'black'
}} }}
dangerouslySetInnerHTML={{ __html: filosofi.data?.deskripsi || '' }} dangerouslySetInnerHTML={{ __html: filosofi.data?.deskripsi || '' }}
/> />
@@ -83,16 +105,24 @@ function Page() {
> >
<Stack gap="md" px={20} style={{ height: '100%' }}> <Stack gap="md" px={20} style={{ height: '100%' }}>
<Center> <Center>
<Text fz="xl" fw="bold" c="black"> <Title
order={3}
c="black"
fw="bold"
style={{ lineHeight: 1.15 }}
>
{nilai.data?.judul} {nilai.data?.judul}
</Text> </Title>
</Center> </Center>
<div <div
style={{ style={{
wordBreak: "break-word", wordBreak: "break-word",
whiteSpace: "normal", whiteSpace: "normal",
flexGrow: 1, flexGrow: 1,
minHeight: 0 minHeight: 0,
fontSize: '14px',
lineHeight: 1.55,
color: 'black'
}} }}
dangerouslySetInnerHTML={{ __html: nilai.data?.deskripsi || '' }} dangerouslySetInnerHTML={{ __html: nilai.data?.deskripsi || '' }}
/> />
@@ -113,16 +143,24 @@ function Page() {
> >
<Stack gap="md" px={20} style={{ height: '100%' }}> <Stack gap="md" px={20} style={{ height: '100%' }}>
<Center> <Center>
<Text fz="xl" fw="bold" c="black"> <Title
order={3}
c="black"
fw="bold"
style={{ lineHeight: 1.15 }}
>
{bentuk.data?.judul} {bentuk.data?.judul}
</Text> </Title>
</Center> </Center>
<div <div
style={{ style={{
wordBreak: "break-word", wordBreak: "break-word",
whiteSpace: "normal", whiteSpace: "normal",
flexGrow: 1, flexGrow: 1,
minHeight: 0 minHeight: 0,
fontSize: '14px',
lineHeight: 1.55,
color: 'black'
}} }}
dangerouslySetInnerHTML={{ __html: bentuk.data?.deskripsi || '' }} dangerouslySetInnerHTML={{ __html: bentuk.data?.deskripsi || '' }}
/> />

View File

@@ -5,6 +5,7 @@ import {
Button, Button,
Container, Container,
Divider, Divider,
Flex,
Group, Group,
Modal, Modal,
Paper, Paper,
@@ -23,11 +24,11 @@ import { useProxy } from 'valtio/utils';
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa'; import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
import colors from '@/con/colors'; import colors from '@/con/colors';
export default function BeasiswaPage() { export default function BeasiswaPage() {
const router = useRouter(); const router = useRouter();
const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar) const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar);
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
const resetForm = () => { const resetForm = () => {
beasiswaDesa.create.form = { beasiswaDesa.create.form = {
namaLengkap: "", namaLengkap: "",
@@ -61,6 +62,7 @@ export default function BeasiswaPage() {
leftSection={<IconArrowLeft size={18} />} leftSection={<IconArrowLeft size={18} />}
onClick={() => router.back()} onClick={() => router.back()}
mb="lg" mb="lg"
style={{ fontSize: '1rem', fontWeight: 500 }}
> >
Kembali Kembali
</Button> </Button>
@@ -69,11 +71,18 @@ export default function BeasiswaPage() {
{/* Hero Section */} {/* Hero Section */}
<Container size="lg" py="xl"> <Container size="lg" py="xl">
<Stack gap="md" maw={600}> <Stack gap="md" maw={600}>
<Group> <Flex gap={"md"} justify={"flex-start"} align={"center"}>
<IconSchool size={30} color={colors["blue-button"]} /> <IconSchool size={30} color={colors["blue-button"]} />
<Title order={2} c={colors["blue-button"]}>Program Beasiswa Pendidikan Desa Darmasaba</Title> <Title
</Group> order={1}
<Text> fz={{ base: '1.125rem', md: '1.375rem' }}
lh={1.15}
c={colors["blue-button"]}
>
Program Beasiswa Pendidikan Desa Darmasaba
</Title>
</Flex>
<Text fz={{ base: '0.875rem', md: '1rem' }} lh={1.55}>
Program ini bertujuan untuk mendukung pendidikan generasi muda di Desa Darmasaba Program ini bertujuan untuk mendukung pendidikan generasi muda di Desa Darmasaba
agar dapat melanjutkan studi ke jenjang lebih tinggi dengan dukungan finansial dan pendampingan. agar dapat melanjutkan studi ke jenjang lebih tinggi dengan dukungan finansial dan pendampingan.
</Text> </Text>
@@ -84,20 +93,22 @@ export default function BeasiswaPage() {
<Container size="lg" py="xl"> <Container size="lg" py="xl">
<Group mb="sm"> <Group mb="sm">
<IconInfoCircle size={24} color={colors["blue-button"]} /> <IconInfoCircle size={24} color={colors["blue-button"]} />
<Title order={3} c={colors["blue-button"]}>Tentang Program</Title> <Title order={3} fz={{ base: '1.125rem', md: '1.375rem' }} lh={1.15} c={colors["blue-button"]}>
Tentang Program
</Title>
</Group> </Group>
<Text> <Text fz={{ base: '0.875rem', md: '1rem' }} lh={1.55}>
Program Beasiswa Desa Darmasaba adalah inisiatif pemerintah desa untuk meningkatkan akses Program Beasiswa Desa Darmasaba adalah inisiatif pemerintah desa untuk meningkatkan akses
pendidikan bagi siswa berprestasi dan kurang mampu. Melalui program ini, desa memberikan bantuan pendidikan bagi siswa berprestasi dan kurang mampu. Melalui program ini, desa memberikan bantuan
biaya sekolah, bimbingan akademik, serta pelatihan soft skill bagi peserta terpilih. biaya sekolah, bimbingan akademik, serta pelatihan soft skill bagi peserta terpilih.
</Text> </Text>
{/* Tambahkan info tahun berjalan di sini */} {/* Periode Beasiswa */}
<Paper mt="md" p="md" radius="lg" shadow="xs" bg="#f8fbff" withBorder> <Paper mt="md" p="md" radius="lg" shadow="xs" bg="#f8fbff" withBorder>
<Text fw={500}> <Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
Periode Beasiswa Tahun 2025 Periode Beasiswa Tahun 2025
</Text> </Text>
<Text fz="sm" c="dimmed"> <Text fz={{ base: '0.8125rem', md: '0.875rem' }} c="dimmed" lh={1.5}>
Pendaftaran beasiswa dibuka mulai <strong>1 Januari 2025</strong> dan ditutup pada <strong>31 Mei 2025</strong>. Pendaftaran beasiswa dibuka mulai <strong>1 Januari 2025</strong> dan ditutup pada <strong>31 Mei 2025</strong>.
Pengumuman hasil seleksi akan diumumkan pada pertengahan Juni 2025 melalui website resmi Desa Darmasaba. Pengumuman hasil seleksi akan diumumkan pada pertengahan Juni 2025 melalui website resmi Desa Darmasaba.
</Text> </Text>
@@ -108,27 +119,35 @@ export default function BeasiswaPage() {
<Container size="lg" py="xl"> <Container size="lg" py="xl">
<Group mb="sm"> <Group mb="sm">
<IconChecklist size={24} color={colors["blue-button"]} /> <IconChecklist size={24} color={colors["blue-button"]} />
<Title order={3} c={colors["blue-button"]}>Syarat Pendaftaran</Title> <Title order={3} fz={{ base: '1.125rem', md: '1.375rem' }} lh={1.15} c={colors["blue-button"]}>
Syarat Pendaftaran
</Title>
</Group> </Group>
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg"> <SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
<Paper shadow="sm" p="md" radius="lg" withBorder> <Paper shadow="sm" p="md" radius="lg" withBorder>
<Text fw={500}>Domisili Desa Darmasaba</Text> <Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
<Text c="dimmed" fz="sm"> Domisili Desa Darmasaba
</Text>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Peserta harus merupakan warga desa yang berdomisili minimal 2 tahun. Peserta harus merupakan warga desa yang berdomisili minimal 2 tahun.
</Text> </Text>
</Paper> </Paper>
<Paper shadow="sm" p="md" radius="lg" withBorder> <Paper shadow="sm" p="md" radius="lg" withBorder>
<Text fw={500}>Nilai Akademik</Text> <Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
<Text c="dimmed" fz="sm"> Nilai Akademik
</Text>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Rata-rata nilai raport minimal 80 atau setara. Rata-rata nilai raport minimal 80 atau setara.
</Text> </Text>
</Paper> </Paper>
<Paper shadow="sm" p="md" radius="lg" withBorder> <Paper shadow="sm" p="md" radius="lg" withBorder>
<Text fw={500}>Surat Rekomendasi</Text> <Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
<Text c="dimmed" fz="sm"> Surat Rekomendasi
</Text>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Diperlukan surat rekomendasi dari sekolah atau guru wali kelas. Diperlukan surat rekomendasi dari sekolah atau guru wali kelas.
</Text> </Text>
</Paper> </Paper>
@@ -139,75 +158,102 @@ export default function BeasiswaPage() {
<Container size="lg" py="xl"> <Container size="lg" py="xl">
<Group mb="sm"> <Group mb="sm">
<IconTimeline size={24} color={colors["blue-button"]} /> <IconTimeline size={24} color={colors["blue-button"]} />
<Title order={3} c={colors["blue-button"]}>Proses Seleksi</Title> <Title order={3} fz={{ base: '1.125rem', md: '1.375rem' }} lh={1.15} c={colors["blue-button"]}>
Proses Seleksi
</Title>
</Group> </Group>
<Timeline active={4} bulletSize={24} lineWidth={2}> <Timeline active={4} bulletSize={24} lineWidth={2}>
<Timeline.Item title="Pendaftaran Online"> <Timeline.Item
<Text c="dimmed" size="sm"> title={
<Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
Pendaftaran Online
</Text>
}
>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Calon peserta mengisi formulir pendaftaran dan mengunggah dokumen pendukung. Calon peserta mengisi formulir pendaftaran dan mengunggah dokumen pendukung.
</Text> </Text>
<Text size="sm" fw={500} mt={4}> <Text fz={{ base: '0.8125rem', md: '0.875rem' }} fw={600} mt={4} lh={1.4}>
Estimasi waktu: 1 Februari 31 Mei 2025 Estimasi waktu: 1 Februari 31 Mei 2025
</Text> </Text>
</Timeline.Item> </Timeline.Item>
<Timeline.Item title="Seleksi Administrasi"> <Timeline.Item
<Text c="dimmed" size="sm"> title={
<Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
Seleksi Administrasi
</Text>
}
>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Panitia memverifikasi kelengkapan dan validitas berkas. Panitia memverifikasi kelengkapan dan validitas berkas.
</Text> </Text>
<Text size="sm" fw={500} mt={4}> <Text fz={{ base: '0.8125rem', md: '0.875rem' }} fw={600} mt={4} lh={1.4}>
Estimasi waktu: 57 hari kerja setelah penutupan pendaftaran Estimasi waktu: 57 hari kerja setelah penutupan pendaftaran
</Text> </Text>
</Timeline.Item> </Timeline.Item>
<Timeline.Item title="Wawancara dan Penilaian"> <Timeline.Item
<Text c="dimmed" size="sm"> title={
<Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
Wawancara dan Penilaian
</Text>
}
>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Peserta yang lolos administrasi akan diundang untuk wawancara langsung dengan tim seleksi. Peserta yang lolos administrasi akan diundang untuk wawancara langsung dengan tim seleksi.
</Text> </Text>
<Text size="sm" fw={500} mt={4}> <Text fz={{ base: '0.8125rem', md: '0.875rem' }} fw={600} mt={4} lh={1.4}>
Estimasi waktu: 710 hari kerja setelah pengumuman seleksi administrasi Estimasi waktu: 710 hari kerja setelah pengumuman seleksi administrasi
</Text> </Text>
</Timeline.Item> </Timeline.Item>
<Timeline.Item title="Pengumuman Penerima"> <Timeline.Item
<Text c="dimmed" size="sm"> title={
<Text fz={{ base: '1rem', md: '1.125rem' }} fw={600} lh={1.4}>
Pengumuman Penerima
</Text>
}
>
<Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5}>
Daftar penerima beasiswa diumumkan melalui website resmi Desa Darmasaba. Daftar penerima beasiswa diumumkan melalui website resmi Desa Darmasaba.
</Text> </Text>
<Text size="sm" fw={500} mt={4}> <Text fz={{ base: '0.8125rem', md: '0.875rem' }} fw={600} mt={4} lh={1.4}>
Estimasi waktu: 5 hari kerja setelah tahap wawancara selesai Estimasi waktu: 5 hari kerja setelah tahap wawancara selesai
</Text> </Text>
</Timeline.Item> </Timeline.Item>
</Timeline> </Timeline>
<Text c="dimmed" size="sm" mt="lg" ta="center"> <Text c="dimmed" fz={{ base: '0.8125rem', md: '0.875rem' }} lh={1.5} mt="lg" ta="center">
Total estimasi keseluruhan proses: sekitar 34 minggu setelah penutupan pendaftaran Total estimasi keseluruhan proses: sekitar 34 minggu setelah penutupan pendaftaran
</Text> </Text>
</Container> </Container>
{/* Testimoni */} {/* Testimoni */}
<Container size="lg" py="xl"> <Container size="lg" py="xl">
<Group mb="sm"> <Group mb="sm">
<IconQuote size={24} color={colors["blue-button"]} /> <IconQuote size={24} color={colors["blue-button"]} />
<Title order={3} c={colors["blue-button"]}>Cerita Sukses Penerima Beasiswa</Title> <Title order={3} fz={{ base: '1.125rem', md: '1.375rem' }} lh={1.15} c={colors["blue-button"]}>
Cerita Sukses Penerima Beasiswa
</Title>
</Group> </Group>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg"> <SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
<Paper shadow="md" p="lg" radius="lg"> <Paper shadow="md" p="lg" radius="lg">
<Text fs={'italic'}> <Text fs="italic" fz={{ base: '0.9375rem', md: '1rem' }} lh={1.5}>
Program ini sangat membantu saya melanjutkan kuliah di Universitas Udayana. Terima kasih Desa Darmasaba! Program ini sangat membantu saya melanjutkan kuliah di Universitas Udayana. Terima kasih Desa Darmasaba!
</Text> </Text>
<Text mt="sm" fw={600}> <Text mt="sm" fw={600} fz={{ base: '0.9375rem', md: '1rem' }} lh={1.4}>
Ni Kadek Ayu S., Penerima Beasiswa 2024 Ni Kadek Ayu S., Penerima Beasiswa 2024
</Text> </Text>
</Paper> </Paper>
<Paper shadow="md" p="lg" radius="lg"> <Paper shadow="md" p="lg" radius="lg">
<Text fs={'italic'}> <Text fs="italic" fz={{ base: '0.9375rem', md: '1rem' }} lh={1.5}>
Selain bantuan dana, kami juga mendapatkan pelatihan komputer dan bahasa Inggris. Selain bantuan dana, kami juga mendapatkan pelatihan komputer dan bahasa Inggris.
</Text> </Text>
<Text mt="sm" fw={600}> <Text mt="sm" fw={600} fz={{ base: '0.9375rem', md: '1rem' }} lh={1.4}>
I Made Gede A., Penerima Beasiswa 2023 I Made Gede A., Penerima Beasiswa 2023
</Text> </Text>
</Paper> </Paper>
@@ -218,16 +264,25 @@ export default function BeasiswaPage() {
<Container size="lg" py="xl" ta="center"> <Container size="lg" py="xl" ta="center">
<Group justify="center" mb="sm"> <Group justify="center" mb="sm">
<IconUserPlus size={28} color={colors["blue-button"]} /> <IconUserPlus size={28} color={colors["blue-button"]} />
<Title order={3} c={colors["blue-button"]}>Siap Bergabung dengan Program Ini?</Title> <Title order={3} fz={{ base: '1.375rem', md: '1.5rem' }} lh={1.15} c={colors["blue-button"]}>
Siap Bergabung dengan Program Ini?
</Title>
</Group> </Group>
<Text c="dimmed" mb="md"> <Text c="dimmed" fz={{ base: '0.875rem', md: '1rem' }} lh={1.5} mb="md">
Segera daftar dan wujudkan mimpimu bersama Desa Darmasaba. Segera daftar dan wujudkan mimpimu bersama Desa Darmasaba.
</Text> </Text>
<Button onClick={open} size="lg" radius="xl" bg={colors["blue-button"]}> <Button
onClick={open}
size="lg"
radius="xl"
bg={colors["blue-button"]}
style={{ fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.4 }}
>
Daftar Sekarang Daftar Sekarang
</Button> </Button>
</Container> </Container>
{/* Modal Formulir */}
<Modal <Modal
opened={opened} opened={opened}
onClose={close} onClose={close}
@@ -235,7 +290,7 @@ export default function BeasiswaPage() {
size="lg" size="lg"
transitionProps={{ transition: 'fade', duration: 200 }} transitionProps={{ transition: 'fade', duration: 200 }}
title={ title={
<Text fz="xl" fw={800} c={colors['blue-button']}> <Text fz={{ base: '1.375rem', md: '1.5rem' }} fw={800} c={colors['blue-button']} lh={1.2}>
Formulir Beasiswa Formulir Beasiswa
</Text> </Text>
} }
@@ -245,60 +300,101 @@ export default function BeasiswaPage() {
<TextInput <TextInput
label="Nama Lengkap" label="Nama Lengkap"
placeholder="Masukkan nama lengkap" placeholder="Masukkan nama lengkap"
onChange={(val) => { beasiswaDesa.create.form.namaLengkap = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.namaLengkap = val.target.value }}
/>
<TextInput <TextInput
type="number" type="number"
label="NIS" label="NIS"
placeholder="Masukkan NIS" placeholder="Masukkan NIS"
onChange={(val) => { beasiswaDesa.create.form.nis = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.nis = val.target.value }}
/>
<TextInput <TextInput
label="Kelas" label="Kelas"
placeholder="Masukkan kelas" placeholder="Masukkan kelas"
onChange={(val) => { beasiswaDesa.create.form.kelas = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.kelas = val.target.value }}
/>
<Select <Select
label="Jenis Kelamin" label="Jenis Kelamin"
placeholder="Pilih jenis kelamin" placeholder="Pilih jenis kelamin"
data={[{ value: "LAKI_LAKI", label: "Laki-laki" }, { value: "PEREMPUAN", label: "Perempuan" }]} data={[
onChange={(val) => { if (val) beasiswaDesa.create.form.jenisKelamin = val }} /> { value: "LAKI_LAKI", label: "Laki-laki" },
{ value: "PEREMPUAN", label: "Perempuan" }
]}
labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { if (val) beasiswaDesa.create.form.jenisKelamin = val }}
/>
<TextInput <TextInput
label="Alamat Domisili" label="Alamat Domisili"
placeholder="Masukkan alamat domisili" placeholder="Masukkan alamat domisili"
onChange={(val) => { beasiswaDesa.create.form.alamatDomisili = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.alamatDomisili = val.target.value }}
/>
<TextInput <TextInput
label="Tempat Lahir" label="Tempat Lahir"
placeholder="Masukkan tempat lahir" placeholder="Masukkan tempat lahir"
onChange={(val) => { beasiswaDesa.create.form.tempatLahir = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.tempatLahir = val.target.value }}
/>
<TextInput <TextInput
type="date" type="date"
label="Tanggal Lahir" label="Tanggal Lahir"
placeholder="Pilih tanggal lahir" placeholder="Pilih tanggal lahir"
onChange={(val) => { beasiswaDesa.create.form.tanggalLahir = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.tanggalLahir = val.target.value }}
/>
<Box pt={15} pb={10}> <Box pt={15} pb={10}>
<Divider /> <Divider />
</Box> </Box>
<TextInput <TextInput
label="Nama Orang Tua / Wali" label="Nama Orang Tua / Wali"
placeholder="Masukkan nama orang tua / wali" placeholder="Masukkan nama orang tua / wali"
onChange={(val) => { beasiswaDesa.create.form.namaOrtu = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.namaOrtu = val.target.value }}
/>
<TextInput <TextInput
label="NIK Orang Tua / Wali" label="NIK Orang Tua / Wali"
placeholder="Masukkan NIK orang tua / wali" placeholder="Masukkan NIK orang tua / wali"
onChange={(val) => { beasiswaDesa.create.form.nik = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.nik = val.target.value }}
/>
<TextInput <TextInput
label="Pekerjaan Orang Tua / Wali" label="Pekerjaan Orang Tua / Wali"
placeholder="Masukkan pekerjaan orang tua / wali" placeholder="Masukkan pekerjaan orang tua / wali"
onChange={(val) => { beasiswaDesa.create.form.pekerjaanOrtu = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.pekerjaanOrtu = val.target.value }}
/>
<TextInput <TextInput
label="Penghasilan Orang Tua / Wali" label="Penghasilan Orang Tua / Wali"
placeholder="Masukkan penghasilan orang tua / wali" placeholder="Masukkan penghasilan orang tua / wali"
onChange={(val) => { beasiswaDesa.create.form.penghasilan = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.penghasilan = val.target.value }}
/>
<TextInput <TextInput
label="No HP" label="No HP"
placeholder="Masukkan no hp" placeholder="Masukkan no hp"
onChange={(val) => { beasiswaDesa.create.form.noHp = val.target.value }} /> labelProps={{ style: { fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 } }}
onChange={(val) => { beasiswaDesa.create.form.noHp = val.target.value }}
/>
<Group justify="flex-end" mt="md"> <Group justify="flex-end" mt="md">
<Button variant="default" radius="xl" onClick={close}>Batal</Button> <Button
<Button radius="xl" bg={colors['blue-button']} onClick={handleSubmit}>Kirim</Button> variant="default"
radius="xl"
onClick={close}
style={{ fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 }}
>
Batal
</Button>
<Button
radius="xl"
bg={colors['blue-button']}
onClick={handleSubmit}
style={{ fontSize: '0.9375rem', fontWeight: 600, lineHeight: 1.4 }}
>
Kirim
</Button>
</Group> </Group>
</Stack> </Stack>
</Paper> </Paper>

View File

@@ -38,11 +38,17 @@ function Page() {
</Box> </Box>
<Box px={{ base: 'md', md: 120 }} pb={80}> <Box px={{ base: 'md', md: 120 }} pb={80}>
<Box mb="lg"> <Box mb="lg">
<Title ta="center" order={1} fw="bold" c={colors['blue-button']} fz={{ base: 28, md: 38 }}> <Title ta="center" order={1} fw="bold" c={colors['blue-button']} fz={{ base: 'xl', md: '2em' }} lh={1.15}>
Program Bimbingan Belajar Desa Program Bimbingan Belajar Desa
</Title> </Title>
<Divider size="sm" my="md" mx="auto" w="60%" color={colors['blue-button']} /> <Divider size="sm" my="md" mx="auto" w="60%" color={colors['blue-button']} />
<Text ta="center" fz="lg" c="black" px={{ base: 'sm', md: 120 }}> <Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
c="black"
lh={{ base: 1.6, md: 1.5 }}
px={{ base: 'sm', md: 120 }}
>
Program unggulan untuk mendukung siswa Desa Darmasaba memahami pelajaran sekolah, meningkatkan prestasi akademik, dan menumbuhkan semangat belajar sejak dini. Program unggulan untuk mendukung siswa Desa Darmasaba memahami pelajaran sekolah, meningkatkan prestasi akademik, dan menumbuhkan semangat belajar sejak dini.
</Text> </Text>
</Box> </Box>
@@ -55,11 +61,16 @@ function Page() {
<IconBook2 size={36} stroke={1.5} color={colors['blue-button']} /> <IconBook2 size={36} stroke={1.5} color={colors['blue-button']} />
</Box> </Box>
</Tooltip> </Tooltip>
<Title order={3} fw={700} c={colors['blue-button']} mb="xs"> <Title order={3} fw={700} c={colors['blue-button']} lh={1.2} fz={{ base: 'sm', md: 'md' }}>
{stateTujuanProgram.findById.data?.judul} {stateTujuanProgram.findById.data?.judul}
</Title> </Title>
</Group> </Group>
<Text fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} lh={1.6} dangerouslySetInnerHTML={{ __html: stateTujuanProgram.findById.data?.deskripsi }} /> <Text
fz={{ base: 'xs', md: 'md' }}
lh={{ base: 1.6, md: 1.5 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: stateTujuanProgram.findById.data?.deskripsi }}
/>
</Stack> </Stack>
</Paper> </Paper>
<Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}> <Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}>
@@ -70,11 +81,16 @@ function Page() {
<IconMapPin size={36} stroke={1.5} color={colors['blue-button']} /> <IconMapPin size={36} stroke={1.5} color={colors['blue-button']} />
</Box> </Box>
</Tooltip> </Tooltip>
<Title order={3} fw={700} c={colors['blue-button']} mb="xs"> <Title order={3} fw={700} c={colors['blue-button']} lh={1.2} fz={{ base: 'sm', md: 'md' }}>
{stateLokasiDanJadwal.findById.data?.judul} {stateLokasiDanJadwal.findById.data?.judul}
</Title> </Title>
</Group> </Group>
<Text fz="md" lh={1.6} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: stateLokasiDanJadwal.findById.data?.deskripsi }} /> <Text
fz={{ base: 'xs', md: 'md' }}
lh={{ base: 1.6, md: 1.5 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: stateLokasiDanJadwal.findById.data?.deskripsi }}
/>
</Stack> </Stack>
</Paper> </Paper>
<Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}> <Paper p="xl" radius="lg" shadow="md" withBorder bg={colors['white-trans-1']}>
@@ -85,11 +101,16 @@ function Page() {
<IconCalendarTime size={36} stroke={1.5} color={colors['blue-button']} /> <IconCalendarTime size={36} stroke={1.5} color={colors['blue-button']} />
</Box> </Box>
</Tooltip> </Tooltip>
<Title order={3} fw={700} c={colors['blue-button']} mb="xs"> <Title order={3} fw={700} c={colors['blue-button']} lh={1.2} fz={{ base: 'sm', md: 'md' }}>
{stateFasilitas.findById.data?.judul} {stateFasilitas.findById.data?.judul}
</Title> </Title>
</Group> </Group>
<Text fz="md" lh={1.6} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: stateFasilitas.findById.data?.deskripsi }} /> <Text
fz={{ base: 'xs', md: 'md' }}
lh={{ base: 1.6, md: 1.5 }}
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
dangerouslySetInnerHTML={{ __html: stateFasilitas.findById.data?.deskripsi }}
/>
</Stack> </Stack>
</Paper> </Paper>
</SimpleGrid> </SimpleGrid>