Compare commits
4 Commits
nico/10-de
...
nico/16-de
| Author | SHA1 | Date | |
|---|---|---|---|
| c8484357cb | |||
| 342e9bbc65 | |||
| f6f77d9e35 | |||
| a00481152c |
@@ -1,14 +1,15 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
},
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
/* Mobile first */
|
||||
'mantine-breakpoint-xs': '30em', // 480px → mobile kecil–normal
|
||||
'mantine-breakpoint-sm': '48em', // 768px → tablet / mobile landscape
|
||||
'mantine-breakpoint-md': '64em', // 1024px → laptop & desktop kecil
|
||||
'mantine-breakpoint-lg': '80em', // 1280px → desktop standar
|
||||
'mantine-breakpoint-xl': '90em', // 1440px+ → desktop besar
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -44,18 +44,56 @@ 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 () => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
await polsekState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/keamanan/polsek-terdekat");
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast.error("Gagal menambah polsek terdekat");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
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 {
|
||||
setIsSubmitting(true);
|
||||
await polsekState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/keamanan/polsek-terdekat");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Gagal menambah polsek terdekat");
|
||||
} finally {
|
||||
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 () => {
|
||||
@@ -190,9 +228,14 @@ function CreatePolsekTerdekat() {
|
||||
/>
|
||||
<TextInput
|
||||
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>}
|
||||
placeholder="Masukkan embed map url"
|
||||
placeholder="Paste iframe dari Google Maps atau URL embed langsung"
|
||||
/>
|
||||
<TextInput
|
||||
value={polsekState.create.form.namaTempatMaps}
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function EditKolaborasiInovasi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: "sm", md: "lg" }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors["blue-button"]} size={24} />
|
||||
|
||||
@@ -42,7 +42,7 @@ function DetailSDGSDesa() {
|
||||
const data = sdgsState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -54,7 +54,7 @@ function DetailSDGSDesa() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -65,7 +65,7 @@ function CreateSDGsDesa() {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
|
||||
|
||||
|
||||
function SdgsDesa() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
@@ -27,7 +26,7 @@ function SdgsDesa() {
|
||||
}
|
||||
|
||||
function ListSdgsDesa({ search }: { search: string }) {
|
||||
const listState = useProxy(sdgsDesa)
|
||||
const listState = useProxy(sdgsDesa);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
@@ -39,10 +38,10 @@ function ListSdgsDesa({ search }: { search: string }) {
|
||||
} = listState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
@@ -53,79 +52,71 @@ function ListSdgsDesa({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
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={colors['blue-button']}
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/SDGs/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped verticalSpacing="sm">
|
||||
<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>
|
||||
<TableTr>
|
||||
<TableTd colSpan={3} style={{ textAlign: 'center', padding: '2rem' }}>
|
||||
<Text c="dimmed">Tidak ada data Sdgs Desa</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const isEmpty = data.length === 0;
|
||||
|
||||
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>
|
||||
<Box py={{ base: 'sm', md: 'lg' }}>
|
||||
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||
<Title order={2} lh={1.2}>
|
||||
Daftar Sdgs Desa
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color={colors['blue-button']}
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/SDGs/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover striped verticalSpacing="sm">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '60%' }}>Nama Sdgs Desa</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Jumlah</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
<TableTh style={{ width: '60%' }}>
|
||||
<Text fz="sm" fw={600} c="dark.7" ta="left">
|
||||
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>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '60%' }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.name}
|
||||
{isEmpty ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={3} ta="center" py="xl">
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
Tidak ada data Sdgs Desa
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{item.jumlah || '0'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
||||
<Button
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '60%' }}>
|
||||
<Text fz="md" fw={500} truncate="end" lineClamp={1} lh={1.5}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text fz="sm" c="dark.6" lh={1.5}>
|
||||
{item.jumlah || '0'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }} ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
@@ -135,27 +126,69 @@ function ListSdgsDesa({ search }: { search: string }) {
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</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>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center mt="lg">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={Math.max(1, totalPages)}
|
||||
withEdges
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{!isEmpty && (
|
||||
<Center mt={{ base: 'md', md: 'lg' }}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={Math.max(1, totalPages)}
|
||||
withEdges
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default SdgsDesa;
|
||||
export default SdgsDesa;
|
||||
@@ -204,7 +204,7 @@ function EditAPBDes() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
@@ -215,7 +215,7 @@ function EditAPBDes() {
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '100%' }}
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -65,7 +65,7 @@ function DetailAPBDes() {
|
||||
});
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -77,7 +77,7 @@ function DetailAPBDes() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '100%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -155,7 +155,7 @@ function CreateAPBDes() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -56,71 +56,85 @@ function ListAPBDes({ search }: { search: string }) {
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py={{ base: 'md', md: 'lg' }}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
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 APBDes</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/apbdes/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={2} size="lg" lh={1.2}>
|
||||
Daftar APBDes
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/apbdes/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>APBDes</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Tahun</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Dokumen</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text fw={500} lineClamp={1}>
|
||||
APBDes {item.tahun}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text fw={500}>{item.tahun || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
{item.file?.link ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={item.file.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="light"
|
||||
leftSection={<IconFile size={16} />}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
>
|
||||
Lihat Dokumen
|
||||
</Button>
|
||||
) : (
|
||||
<Text c="dimmed" fz="sm">
|
||||
Tidak ada dokumen
|
||||
<Box>
|
||||
<Table highlightOnHover miw={0}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
APBDes
|
||||
</TableTh>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
Tahun
|
||||
</TableTh>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
Dokumen
|
||||
</TableTh>
|
||||
<TableTh fz="md" fw={600} ta="left" w="25%">
|
||||
Aksi
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fz="md" fw={500} lh={1.5} lineClamp={1}>
|
||||
APBDes {item.tahun}
|
||||
</Text>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box w={100}>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="md" fw={500} lh={1.5}>
|
||||
{item.tahun || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{item.file?.link ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={item.file.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="light"
|
||||
leftSection={<IconFile size={16} />}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
fz="sm"
|
||||
>
|
||||
Lihat Dokumen
|
||||
</Button>
|
||||
) : (
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
Tidak ada dokumen
|
||||
</Text>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
@@ -128,29 +142,126 @@ function ListAPBDes({ search }: { search: string }) {
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={14} />}
|
||||
onClick={() => router.push(`/admin/landing-page/apbdes/${item.id}`)}
|
||||
fullWidth
|
||||
fz="sm"
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Box>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py="lg">
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
Tidak ada data APBDes yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data APBDes yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Center mt="md">
|
||||
{/* Mobile Cards */}
|
||||
<Box hiddenFrom="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={2} size="lg" lh={1.2}>
|
||||
Daftar APBDes
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/apbdes/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Stack gap="md">
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<Paper
|
||||
key={item.id}
|
||||
withBorder
|
||||
bg={colors['white-1']}
|
||||
p="md"
|
||||
shadow="sm"
|
||||
radius="md"
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
APBDes {item.tahun}
|
||||
</Text>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||
Tahun
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.tahun || '-'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
||||
Dokumen
|
||||
</Text>
|
||||
{item.file?.link ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={item.file.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="light"
|
||||
leftSection={<IconFile size={14} />}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
fz="xs"
|
||||
lh={1.4}
|
||||
>
|
||||
Lihat
|
||||
</Button>
|
||||
) : (
|
||||
<Text fz="xs" c="dimmed" lh={1.4}>
|
||||
Tidak ada
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={14} />}
|
||||
onClick={() => router.push(`/admin/landing-page/apbdes/${item.id}`)}
|
||||
mt="sm"
|
||||
fz="xs"
|
||||
lh={1.4}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
) : (
|
||||
<Paper withBorder bg={colors['white-1']} p="md" radius="md">
|
||||
<Center py="lg">
|
||||
<Text c="dimmed" fz="xs" lh={1.4}>
|
||||
Tidak ada data APBDes yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import colors from "@/con/colors";
|
||||
import {
|
||||
Box,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Tabs,
|
||||
@@ -68,37 +69,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ mencegah tab mengecil
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
<Box visibleFrom='md' pb={10}>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
|
||||
<Box hiddenFrom='md' pb={10}>
|
||||
<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) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function EditKategoriDesaAntiKorupsi() {
|
||||
|
||||
// 🧩 UI
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function CreateKategoriDesaAntiKorupsi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
'use client'
|
||||
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 { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -10,9 +27,8 @@ import HeaderSearch from '../../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
||||
|
||||
|
||||
function KategoriDesaAntiKorupsi() {
|
||||
const [search, setSearch] = useState("")
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
@@ -28,126 +44,188 @@ function KategoriDesaAntiKorupsi() {
|
||||
}
|
||||
|
||||
function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateKategori.findMany;
|
||||
const { data, page, totalPages, loading, load } = stateKategori.findMany;
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateKategori.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
stateKategori.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py="xl">
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>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 style={{ overflowX: "auto" }}>
|
||||
<Table highlightOnHover striped verticalSpacing="sm">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
// Mobile cards
|
||||
const renderMobileCards = () => (
|
||||
<Stack gap="md">
|
||||
{filteredData.length > 0 ? (
|
||||
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
|
||||
variant="light"
|
||||
color="green"
|
||||
size="xs"
|
||||
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</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>
|
||||
<TableTr>
|
||||
<TableTh>
|
||||
<Text fw={600} fz="sm" c="dimmed">
|
||||
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>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500} fz="md" lh={1.45} lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd w={60}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd w={60}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text fw={500} lineClamp={1}>{item.name}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={2}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">Tidak ada data kategori yang ditemukan</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={3} ta="center" py="xl">
|
||||
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||
Tidak ada data kategori yang ditemukan
|
||||
</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</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>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
@@ -158,4 +236,4 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default KategoriDesaAntiKorupsi
|
||||
export default KategoriDesaAntiKorupsi;
|
||||
@@ -150,7 +150,7 @@ export default function EditDesaAntiKorupsi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function DetailKegiatanDesa() {
|
||||
const data = detailState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -53,7 +53,7 @@ export default function DetailKegiatanDesa() {
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
w={{ base: "100%", md: "50%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function CreateDesaAntiKorupsi() {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -38,7 +38,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py="md">
|
||||
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||
<Skeleton height={650} radius="lg" />
|
||||
</Stack>
|
||||
);
|
||||
@@ -46,11 +46,13 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py="md">
|
||||
<Paper p="lg" radius="lg" shadow="md" withBorder>
|
||||
<Box py={{ base: 'sm', md: 'md' }}>
|
||||
<Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
|
||||
<Stack align="center" gap="sm">
|
||||
<Title order={4}>Data Program Desa Anti Korupsi</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
<Title order={2} lh={1.2}>
|
||||
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
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -61,48 +63,56 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="md">
|
||||
<Paper p="lg" radius="lg" shadow="md" withBorder>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Program Desa Anti Korupsi</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
|
||||
</Button>
|
||||
<Stack gap={'md'}>
|
||||
<Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
|
||||
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||
<Title order={2} lh={1.2}>
|
||||
Daftar Program Desa Anti Korupsi
|
||||
</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
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
|
||||
withRowBorders
|
||||
verticalSpacing="sm"
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '50%' }}>Nama Program</TableTh>
|
||||
<TableTh style={{ width: '30%' }}>Kategori</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
<TableTh w="50%">Nama Program</TableTh>
|
||||
<TableTh w="30%">Kategori</TableTh>
|
||||
<TableTh w="20%" ta="center">
|
||||
Aksi
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '50%' }}>
|
||||
<Text fw={500} lineClamp={1}>
|
||||
<TableTd w="50%">
|
||||
<Text fw={500} lineClamp={1} fz="md" lh={1.5}>
|
||||
{item.name || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '30%' }}>
|
||||
<Box w={200}>
|
||||
<Text fz="sm" c="dimmed" lineClamp={1}>
|
||||
<TableTd w="30%">
|
||||
<Text fz="sm" c="dimmed" lineClamp={1} lh={1.5}>
|
||||
{item.kategori?.name || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
||||
<TableTd w="20%" ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
@@ -123,7 +133,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
) : (
|
||||
<TableTr>
|
||||
<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
|
||||
</Text>
|
||||
</TableTd>
|
||||
@@ -132,6 +142,48 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</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>
|
||||
|
||||
<Center>
|
||||
@@ -144,7 +196,6 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
}}
|
||||
size="md"
|
||||
radius="md"
|
||||
mt="md"
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
@@ -152,4 +203,4 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default DesaAntiKorupsi;
|
||||
export default DesaAntiKorupsi;
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
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 { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -53,36 +53,41 @@ function LayoutTabsKepuasan({ children }: { children: React.ReactNode }) {
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={e.value}
|
||||
leftSection={e.icon}
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{e.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
<Box>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
<></>
|
||||
|
||||
@@ -149,7 +149,7 @@ function EditResponden() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function DetailResponden() {
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -50,7 +50,7 @@ export default function DetailResponden() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -60,7 +60,7 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py="md">
|
||||
<Stack py={{ base: 'md', md: 'lg' }}>
|
||||
<Skeleton height={650} radius="lg" />
|
||||
</Stack>
|
||||
);
|
||||
@@ -68,11 +68,13 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py="md">
|
||||
<Paper p="lg" radius="lg" shadow="md" withBorder>
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
<Paper p={{ base: 'md', md: 'lg' }} radius="lg" shadow="md" withBorder>
|
||||
<Stack align="center" gap="sm">
|
||||
<Title order={4}>Data Responden</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
<Title order={2} lh={1.2}>
|
||||
Data Responden
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Belum ada data responden yang tersedia
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -83,12 +85,13 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="md">
|
||||
<Paper p="lg" radius="lg" shadow="md" withBorder>
|
||||
<Title order={4} mb="sm">
|
||||
Daftar Responden
|
||||
</Title>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Stack gap={'lg'}>
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Paper p="lg" radius="lg" shadow="md" withBorder>
|
||||
<Title order={2} size="lg" mb="md" lh={1.2}>
|
||||
Daftar Responden
|
||||
</Title>
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
@@ -97,18 +100,18 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%' }}>No</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Nama</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Tanggal</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Jenis Kelamin</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
<TableTh fz="sm" fw={600} w={60}>No</TableTh>
|
||||
<TableTh fz="sm" fw={600}>Nama</TableTh>
|
||||
<TableTh fz="sm" fw={600}>Tanggal</TableTh>
|
||||
<TableTh fz="sm" fw={600}>Jenis Kelamin</TableTh>
|
||||
<TableTh fz="sm" fw={600} w={120}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<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
|
||||
</Text>
|
||||
</TableTd>
|
||||
@@ -116,24 +119,18 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{index + 1}</TableTd>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<TableTd fz="md" lh={1.5}>{index + 1}</TableTd>
|
||||
<TableTd fz="md" lh={1.5}>{item.name}</TableTd>
|
||||
<TableTd fz="md" lh={1.5}>
|
||||
{item.tanggal
|
||||
? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '-'}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
{item.jenisKelamin.name}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd fz="md" lh={1.5}>{item.jenisKelamin.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
@@ -155,8 +152,64 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
)}
|
||||
</TableTbody>
|
||||
</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>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -167,7 +220,7 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
}}
|
||||
size="md"
|
||||
radius="md"
|
||||
mt="md"
|
||||
mt={{ base: 'md', md: 'lg' }}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
@@ -175,4 +228,4 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default Responden;
|
||||
export default Responden;
|
||||
@@ -56,6 +56,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
@@ -63,6 +64,10 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
@@ -74,6 +79,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
|
||||
@@ -78,7 +78,7 @@ function EditKategoriPrestasi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -40,7 +40,7 @@ function CreateKategoriPrestasi() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -57,7 +57,7 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py="md">
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
@@ -65,28 +65,33 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* DESKTOP: Table */}
|
||||
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm" withBorder>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4} c="dark">List Kategori Prestasi</Title>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/landing-page/prestasi-desa/kategori-prestasi-desa/create')}>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
<Group justify="space-between" mb="xl">
|
||||
<Title order={2} size="lg" lh={1.2}>List Kategori Prestasi</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/prestasi-desa/kategori-prestasi-desa/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Box visibleFrom="md">
|
||||
<Table verticalSpacing="sm" highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh style={{ width: '120px' }} ta={'center'}>Edit</TableTh>
|
||||
<TableTh ta={'center'} style={{ width: '120px' }}>Delete</TableTh>
|
||||
<TableTh><Text fz="sm" fw={600} c="dark">Nama Kategori</Text></TableTh>
|
||||
<TableTh w={120} ta="center"><Text fz="sm" fw={600} c="dark">Edit</Text></TableTh>
|
||||
<TableTh w={120} ta="center"><Text fz="sm" fw={600} c="dark">Delete</Text></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={2} style={{ textAlign: 'center' }}>
|
||||
<Text py="md" c="dimmed">
|
||||
<TableTd colSpan={3} ta="center">
|
||||
<Text py="md" c="dimmed" fz="sm" lh={1.5}>
|
||||
{search ? 'Tidak ada hasil yang cocok' : 'Belum ada data kategori'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
@@ -95,68 +100,130 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||
</Box>
|
||||
<Text truncate="end" fz="md" lh={1.5} c="dark">
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
<TableTd ta="center" w={120}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
<TableTd ta="center" w={120}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="sm"
|
||||
styles={{
|
||||
control: {
|
||||
'&[data-active]': {
|
||||
background: `${colors['blue-button']} !important`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="lg">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="sm"
|
||||
styles={{
|
||||
control: {
|
||||
'&[data-active]': {
|
||||
background: `${colors['blue-button']} !important`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
{/* MOBILE: Card */}
|
||||
<Box hiddenFrom="md">
|
||||
<Stack gap="md">
|
||||
{filteredData.length === 0 ? (
|
||||
<Paper p="lg" ta="center">
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
{search ? 'Tidak ada hasil yang cocok' : 'Belum ada data kategori'}
|
||||
</Text>
|
||||
</Paper>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<Paper key={item.id} p="md" withBorder bg={colors['white-1']}>
|
||||
<Stack gap="xs">
|
||||
<Text fz="sm" lh={1.5} fw={600} c="dark">{item.name}</Text>
|
||||
<Group justify="flex-end" gap="xs">
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="xs"
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
)}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center py="lg">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="xs"
|
||||
styles={{
|
||||
control: {
|
||||
'&[data-active]': {
|
||||
background: `${colors['blue-button']} !important`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus kategori prestasi ini?'
|
||||
/>
|
||||
</Paper>
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus kategori prestasi ini?'
|
||||
/>
|
||||
</Box >
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default KategoriPrestasiDesa
|
||||
export default KategoriPrestasiDesa
|
||||
@@ -128,7 +128,7 @@ export default function EditPrestasiDesa() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -41,7 +41,7 @@ function DetailPrestasiDesa() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -53,7 +53,7 @@ function DetailPrestasiDesa() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -69,7 +69,7 @@ function CreatePrestasiDesa() {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
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 { useShallowEffect } from '@mantine/hooks';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -28,6 +28,7 @@ function ListPrestasiDesa() {
|
||||
function ListPrestasi({ search }: { search: string }) {
|
||||
const listState = useProxy(prestasiState.prestasiDesa)
|
||||
const router = useRouter();
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -39,60 +40,65 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
}, []);
|
||||
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
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 Prestasi Desa</Title>
|
||||
<Box py={{ base: 'sm', md: 'md' }}>
|
||||
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||
<Title order={2} size={isMobile ? 'md' : 'lg'} lh={1.2}>
|
||||
Daftar Prestasi Desa
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/landing-page/prestasi-desa/list-prestasi-desa/create')}
|
||||
size={isMobile ? 'xs' : 'sm'}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover miw={0}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>Nama Prestasi</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Kategori</TableTh>
|
||||
<TableTh style={{ width: '25%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
<TableTh w="25%">Nama Prestasi</TableTh>
|
||||
<TableTh w="25%">Deskripsi</TableTh>
|
||||
<TableTh w="25%">Kategori</TableTh>
|
||||
<TableTh w="25%" ta="center">Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||
</Box>
|
||||
<TableTd w="25%">
|
||||
<Text truncate="end" fz="md" lh={1.5}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<TableTd w="25%">
|
||||
<Text lineClamp={1} fz="md" c="dimmed" lh={1.5} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box w={150}>
|
||||
<Text truncate="end" fz={"sm"}>{item.kategori?.name || 'Tidak ada kategori'}</Text>
|
||||
</Box>
|
||||
<TableTd w="25%">
|
||||
<Text truncate="end" fz="md" lh={1.5}>
|
||||
{item.kategori?.name || 'Tidak ada kategori'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
||||
<TableTd w="25%" ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
@@ -108,23 +114,63 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4} style={{ textAlign: 'center' }}>
|
||||
<Text c="dimmed" py="md">Tidak ada data prestasi</Text>
|
||||
<TableTd colSpan={4} ta="center">
|
||||
<Text c="dimmed" py="md" fz="sm" lh={1.4}>
|
||||
Tidak ada data prestasi
|
||||
</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Cards */}
|
||||
<Stack hiddenFrom="md" gap="xs">
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||
<Stack gap={4}>
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Text fz="xs" c="dimmed" lh={1.4}>
|
||||
Kategori: {item.kategori?.name || 'Tidak ada kategori'}
|
||||
</Text>
|
||||
<Group justify="flex-end" mt="xs">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={14} />}
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
) : (
|
||||
<Center py="md">
|
||||
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||
Tidak ada data prestasi
|
||||
</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="lg">
|
||||
<Center mt={{ base: 'md', md: 'lg' }}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={load}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
size="sm"
|
||||
size={isMobile ? 'xs' : 'sm'}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
@@ -132,4 +178,4 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
export default ListPrestasiDesa;
|
||||
export default ListPrestasiDesa;
|
||||
@@ -2,6 +2,7 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Tabs,
|
||||
@@ -74,36 +75,76 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
<Box visibleFrom='md' pb={10}>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
|
||||
<Box hiddenFrom='md' pb={10}>
|
||||
<ScrollArea
|
||||
type="auto"
|
||||
offsetScrollbars={false}
|
||||
w="100%"
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
<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) => (
|
||||
<TabsPanel
|
||||
|
||||
@@ -177,7 +177,7 @@ function EditMediaSosial() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -50,7 +50,7 @@ function DetailMediaSosial() {
|
||||
const data = stateMediaSosial.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -62,7 +62,7 @@ function DetailMediaSosial() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "50%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -25,7 +25,6 @@ import { useProxy } from 'valtio/utils';
|
||||
import profileLandingPageState from '../../../../_state/landing-page/profile';
|
||||
import SelectSosialMedia from '@/app/admin/(dashboard)/_com/selectSocialMedia';
|
||||
|
||||
|
||||
// ⭐ Tambah type SosmedKey
|
||||
type SosmedKey =
|
||||
| 'facebook'
|
||||
@@ -88,7 +87,6 @@ export default function CreateMediaSosial() {
|
||||
stateMediaSosial.create.form.imageId = null;
|
||||
stateMediaSosial.create.form.icon = sosmedMap[selectedSosmed].src!;
|
||||
|
||||
|
||||
await stateMediaSosial.create.create();
|
||||
resetForm();
|
||||
router.push('/admin/landing-page/profil/media-sosial');
|
||||
@@ -129,13 +127,13 @@ export default function CreateMediaSosial() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</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
|
||||
</Title>
|
||||
</Group>
|
||||
@@ -155,7 +153,7 @@ export default function CreateMediaSosial() {
|
||||
{/* Custom icon uploader */}
|
||||
{selectedSosmed === 'custom' && (
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
<Text fw="bold" fz={{ base: 'sm', md: 'md' }} lh={1.45} mb={6}>
|
||||
Upload Custom Icon
|
||||
</Text>
|
||||
|
||||
@@ -185,8 +183,10 @@ export default function CreateMediaSosial() {
|
||||
</Dropzone.Idle>
|
||||
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fw={500}>Seret gambar atau klik untuk pilih</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
<Text fw={500} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
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
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -229,7 +229,11 @@ export default function CreateMediaSosial() {
|
||||
|
||||
{/* Input name */}
|
||||
<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"
|
||||
value={stateMediaSosial.create.form.name ?? ''}
|
||||
onChange={(e) => (stateMediaSosial.create.form.name = e.target.value)}
|
||||
@@ -238,7 +242,11 @@ export default function CreateMediaSosial() {
|
||||
|
||||
{/* Input link */}
|
||||
<TextInput
|
||||
label="Link / Kontak"
|
||||
label={
|
||||
<Text fw={500} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Link / Kontak
|
||||
</Text>
|
||||
}
|
||||
placeholder="Masukkan link atau nomor"
|
||||
value={stateMediaSosial.create.form.iconUrl ?? ''}
|
||||
onChange={(e) => (stateMediaSosial.create.form.iconUrl = e.target.value)}
|
||||
@@ -266,4 +274,4 @@ export default function CreateMediaSosial() {
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
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 { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -28,11 +46,11 @@ function MediaSosial() {
|
||||
}
|
||||
|
||||
function ListMediaSosial({ search }: { search: string }) {
|
||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||
const router = useRouter();
|
||||
|
||||
const getIconSource = (item: any) => {
|
||||
if (item.image?.link) return item.image.link;
|
||||
if (item.image?.link) return item.image.link;
|
||||
if (item.icon && sosmedMap[item.icon as keyof typeof sosmedMap]?.src) {
|
||||
return sosmedMap[item.icon as keyof typeof sosmedMap].src;
|
||||
}
|
||||
@@ -48,101 +66,201 @@ function ListMediaSosial({ search }: { search: string }) {
|
||||
} = stateMediaSosial.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py={{ base: 'sm', sm: 'md' }}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
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 Media Sosial</Title>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/landing-page/profil/media-sosial/create')}>
|
||||
<Box py={{ base: 'sm', sm: 'md' }}>
|
||||
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', sm: 'lg' }} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb={{ base: 'sm', sm: 'md' }}>
|
||||
<Title order={4} lh={1.15}>
|
||||
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
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>Nama Media Sosial / Kontak</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Gambar</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Link / No. Telepon</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%', }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Box w={50} h={50} style={{ borderRadius: 8, 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>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%', }}>
|
||||
<Box w={250}>
|
||||
<Text truncate fz="sm" c="dimmed" lineClamp={1}>
|
||||
{item.iconUrl || item.noTelp || '-'}
|
||||
<Box>
|
||||
{/* Desktop: Table | Mobile: Card-based vertical layout */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>
|
||||
<Text fw={600} fz="md" lh={1.45}>
|
||||
Nama Media Sosial / Kontak
|
||||
</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>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text fw={500} fz="md" lh={1.5} truncate="end" lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<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>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Box w={50} h={50} style={{ borderRadius: 8, 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>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Box w={250}>
|
||||
<Text truncate fz="sm" lh={1.5} c="dimmed" lineClamp={1}>
|
||||
{item.iconUrl || item.noTelp || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<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>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed" fz="md" lh={1.5}>
|
||||
Tidak ada data media sosial yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data media sosial yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</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>
|
||||
))
|
||||
) : (
|
||||
<Center py={24}>
|
||||
<Text c="dimmed" fz="sm" lh={1.5}>
|
||||
Tidak ada data media sosial yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -161,4 +279,4 @@ function ListMediaSosial({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default MediaSosial;
|
||||
export default MediaSosial;
|
||||
@@ -178,7 +178,7 @@ function EditPejabatDesa() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Stack gap="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
|
||||
@@ -3,7 +3,6 @@ import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
@@ -35,15 +34,15 @@ function Page() {
|
||||
<Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button
|
||||
c="green"
|
||||
variant="light"
|
||||
leftSection={<IconEdit size={18} stroke={2} />}
|
||||
radius="md"
|
||||
onClick={() => router.push(`/admin/landing-page/profil/pejabat-desa/${allList.findUnique.data?.id}`)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
style={{fontSize: 15, fontWeight: "bold"}}
|
||||
c="green"
|
||||
variant="light"
|
||||
radius="md"
|
||||
onClick={() => router.push(`/admin/landing-page/profil/pejabat-desa/${allList.findUnique.data?.id}`)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
{dataArray.map((item) => (
|
||||
@@ -52,7 +51,7 @@ function Page() {
|
||||
<Grid>
|
||||
<GridCol span={12}>
|
||||
<Center>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo Desa" loading="lazy"/>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo Desa" loading="lazy" />
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={12}>
|
||||
@@ -93,7 +92,7 @@ function Page() {
|
||||
</Paper>
|
||||
<Box mt="lg">
|
||||
<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}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -130,7 +130,7 @@ function EditProgramInovasi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -40,13 +40,15 @@ function DetailProgramInovasi() {
|
||||
const data = stateProgramInovasi.findUnique.data
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'md', md: 'xl' }} py="lg">
|
||||
<Button variant="subtle" onClick={() => router.back()} leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}>
|
||||
Kembali
|
||||
</Button>
|
||||
<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']} />}>
|
||||
Kembali
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
@@ -68,9 +70,9 @@ function DetailProgramInovasi() {
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Gambar</Text>
|
||||
{data.image?.link ? (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt="Gambar Program"
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt="Gambar Program"
|
||||
radius="md"
|
||||
style={{ maxWidth: '100%', maxHeight: 300, objectFit: 'contain' }}
|
||||
loading="lazy"
|
||||
@@ -106,28 +108,28 @@ function DetailProgramInovasi() {
|
||||
</Box>
|
||||
|
||||
<Group gap="sm">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() => router.push(`/admin/landing-page/profil/program-inovasi/${data.id}/edit`)}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() => router.push(`/admin/landing-page/profil/program-inovasi/${data.id}/edit`)}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -76,7 +76,7 @@ function CreateProgramInovasi() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
|
||||
@@ -13,7 +13,7 @@ function ProgramInovasi() {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
return (
|
||||
<Box px="md" py="lg">
|
||||
<Box px={{base: 0, md: "md"}} py="lg">
|
||||
<HeaderSearch
|
||||
title="Program Inovasi"
|
||||
placeholder="Cari program inovasi..."
|
||||
@@ -52,75 +52,137 @@ function ListProgramInovasi({ search }: { search: string }) {
|
||||
<Group justify='space-between'>
|
||||
<Title order={4}>Daftar Program Inovasi</Title>
|
||||
<Button
|
||||
color="blue"
|
||||
leftSection={<IconPlus size={18} />}
|
||||
variant="light"
|
||||
radius="md"
|
||||
onClick={() => router.push('/admin/landing-page/profil/program-inovasi/create')}
|
||||
>
|
||||
Tambah Program
|
||||
</Button>
|
||||
color="blue"
|
||||
leftSection={<IconPlus size={18} />}
|
||||
variant="light"
|
||||
radius="md"
|
||||
onClick={() => router.push('/admin/landing-page/profil/program-inovasi/create')}
|
||||
>
|
||||
Tambah Program
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped verticalSpacing="sm">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Program</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Link</TableTh>
|
||||
<TableTh>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<Box visibleFrom='md'>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped verticalSpacing="sm">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Belum ada data program inovasi</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
<TableTh>Nama Program</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Link</TableTh>
|
||||
<TableTh>Aksi</TableTh>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500}>{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ maxWidth: 250 }}>
|
||||
<Text fz="sm" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '-' }}></Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ maxWidth: 250 }}>
|
||||
<Tooltip label="Buka tautan program" position="top" withArrow>
|
||||
<a
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: colors['blue-button'], textDecoration: 'underline' }}
|
||||
>
|
||||
<Text truncate fz="sm">{item.link}</Text>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<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>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Belum ada data program inovasi</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500}>{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ maxWidth: 250 }}>
|
||||
<Text fz="sm" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '-' }}></Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ maxWidth: 250 }}>
|
||||
<Tooltip label="Buka tautan program" position="top" withArrow>
|
||||
<a
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: colors['blue-button'], textDecoration: 'underline' }}
|
||||
>
|
||||
<Text truncate fz="sm">{item.link}</Text>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<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>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</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 && (
|
||||
<Center mt="md">
|
||||
<Pagination
|
||||
|
||||
@@ -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'
|
||||
|
||||
import colors from "@/con/colors";
|
||||
@@ -429,7 +33,7 @@ import { useEffect, useState } from "react";
|
||||
import { getNavbar } from "./(dashboard)/user&role/_com/dynamicNavbar";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [opened, { toggle }] = useDisclosure();
|
||||
const [opened, { toggle, close }] = useDisclosure(); // ✅ Tambahkan 'close'
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
|
||||
@@ -441,21 +45,19 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me', {
|
||||
credentials: 'include' // ✅ ADD credentials
|
||||
credentials: 'include'
|
||||
});
|
||||
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}`, {
|
||||
credentials: 'include' // ✅ ADD credentials
|
||||
credentials: 'include'
|
||||
});
|
||||
const menuData = await menuRes.json();
|
||||
|
||||
@@ -463,7 +65,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
? [...menuData.menuIds]
|
||||
: null;
|
||||
|
||||
// ✅ Set user dengan menuIds yang fresh
|
||||
authStore.setUser({
|
||||
id: data.user.id,
|
||||
name: data.user.name,
|
||||
@@ -472,7 +73,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
isActive: data.user.isActive
|
||||
});
|
||||
|
||||
// ✅ IMPROVED: Redirect ONLY if di root /admin
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
if (currentPath === '/admin') {
|
||||
@@ -480,7 +80,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
console.log('🔄 Redirecting from /admin to:', expectedPath);
|
||||
router.replace(expectedPath);
|
||||
}
|
||||
// ✅ Jangan redirect jika user sudah di path yang valid
|
||||
|
||||
} else {
|
||||
authStore.setUser(null);
|
||||
@@ -496,17 +95,17 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
|
||||
fetchUser();
|
||||
}, [router]); // ✅ Only depend on router
|
||||
}, [router]);
|
||||
|
||||
const getRedirectPath = (roleId: number): string => {
|
||||
switch (roleId) {
|
||||
case 0: // DEVELOPER
|
||||
case 1: // SUPERADMIN
|
||||
case 2: // ADMIN_DESA
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
return '/admin/landing-page/profil/program-inovasi';
|
||||
case 3: // ADMIN_KESEHATAN
|
||||
case 3:
|
||||
return '/admin/kesehatan/posyandu';
|
||||
case 4: // ADMIN_PENDIDIKAN
|
||||
case 4:
|
||||
return '/admin/pendidikan/info-sekolah/jenjang-pendidikan';
|
||||
default:
|
||||
return '/admin';
|
||||
@@ -535,7 +134,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const response = await fetch('/api/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include' // ✅ ADD credentials
|
||||
credentials: 'include'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
@@ -559,6 +158,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Handler untuk menutup mobile menu saat navigasi
|
||||
const handleNavClick = (path: string) => {
|
||||
router.push(path);
|
||||
close(); // Tutup mobile menu
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
suppressHydrationWarning
|
||||
@@ -573,7 +178,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
}}
|
||||
padding="md"
|
||||
>
|
||||
{/* ... rest of your JSX (Header, Navbar, Main) sama seperti sebelumnya ... */}
|
||||
<AppShellHeader
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #ffffff, #f9fbff)",
|
||||
@@ -626,16 +230,48 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
</AppShellHeader>
|
||||
|
||||
<AppShellNavbar component={ScrollArea} style={{ background: "#ffffff", borderRight: `1px solid ${colors["blue-button"]}20` }} p={{ base: 'xs', sm: 'sm' }}>
|
||||
{/* ... Navbar content sama seperti sebelumnya ... */}
|
||||
<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}>
|
||||
<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
|
||||
key={key}
|
||||
// ✅ PERBAIKAN: Gunakan onClick untuk handle navigasi dan close menu
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNavClick(child.path);
|
||||
}}
|
||||
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>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default async function grafikJumlahPendudukMiskinFindMany(
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
orderBy: { year: "asc" },
|
||||
}),
|
||||
prisma.grafikJumlahPendudukMiskin.count({
|
||||
where,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
'use client';
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import {
|
||||
Badge,
|
||||
@@ -51,10 +51,14 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
|
||||
{/* === Berita Utama === */}
|
||||
{featuredState.loading ? (
|
||||
<Center><Skeleton h={400} /></Center>
|
||||
<Center>
|
||||
<Skeleton h={400} />
|
||||
</Center>
|
||||
) : featured ? (
|
||||
<Box mb={50}>
|
||||
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
|
||||
<Title order={2} mb="md">
|
||||
Berita Utama
|
||||
</Title>
|
||||
<Paper shadow="md" radius="md" withBorder>
|
||||
<Grid gutter={0}>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
@@ -74,13 +78,29 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
<Badge color="blue" variant="light" mb="md">
|
||||
{featured.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Title order={2} mb="md">{featured.judul}</Title>
|
||||
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featured.deskripsi }} />
|
||||
<Title order={3} mb="md">
|
||||
{featured.judul}
|
||||
</Title>
|
||||
<Text
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
mb="md"
|
||||
style={{ lineHeight: 1.6 }}
|
||||
dangerouslySetInnerHTML={{ __html: featured.deskripsi }}
|
||||
/>
|
||||
</div>
|
||||
<Group justify="apart" mt="auto">
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dimmed"
|
||||
lh={1.5}
|
||||
style={{
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.5rem',
|
||||
}}
|
||||
>
|
||||
{new Date(featured.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
@@ -91,7 +111,9 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
<Button
|
||||
variant="light"
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)}
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)
|
||||
}
|
||||
>
|
||||
Baca Selengkapnya
|
||||
</Button>
|
||||
@@ -105,19 +127,29 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
|
||||
{/* === Daftar Berita === */}
|
||||
<Box mt={50}>
|
||||
<Title order={2} mb="md">Daftar Berita</Title>
|
||||
<Title order={2} mb="md">
|
||||
Daftar Berita
|
||||
</Title>
|
||||
<Divider mb="xl" />
|
||||
|
||||
{state.findMany.loading ? (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
|
||||
{Array(3).fill(0).map((_, i) => (
|
||||
<Skeleton key={i} h={300} radius="md" />
|
||||
))}
|
||||
{Array(3)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<Skeleton key={i} h={300} radius="md" />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : paginatedNews.length === 0 ? (
|
||||
<Text c="dimmed" ta="center">Belum ada berita di kategori "{kategori}".</Text>
|
||||
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Belum ada berita di kategori "{kategori}".
|
||||
</Text>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
||||
<SimpleGrid
|
||||
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||
spacing="xl"
|
||||
verticalSpacing="xl"
|
||||
>
|
||||
{paginatedNews.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
@@ -125,19 +157,51 @@ export default function Content({ kategori }: { kategori: string }) {
|
||||
p="lg"
|
||||
radius="md"
|
||||
withBorder
|
||||
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)}
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/desa/berita/${kategori}/${item.id}`)
|
||||
}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Card.Section>
|
||||
<Image src={item.image?.link} height={200} alt={item.judul} fit="cover" loading="lazy"/>
|
||||
<Image
|
||||
src={item.image?.link}
|
||||
height={200}
|
||||
alt={item.judul}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Card.Section>
|
||||
<Badge color="blue" variant="light" mt="md">
|
||||
{item.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
<Text size="sm" c="dimmed" lineClamp={3} style={{wordBreak: "break-word", whiteSpace: "normal"}} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Title
|
||||
order={4}
|
||||
mt="sm"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
style={{ lineHeight: 1.4 }}
|
||||
lineClamp={2}
|
||||
>
|
||||
{item.judul}
|
||||
</Title>
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
mt="xs"
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||
/>
|
||||
<Group justify="apart" mt="md" gap="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'xs' }}
|
||||
c="dimmed"
|
||||
lh={1.4}
|
||||
style={{ fontSize: '0.75rem', lineHeight: '1.125rem' }}
|
||||
>
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
|
||||
@@ -3,18 +3,16 @@
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import NewsReader from '@/app/darmasaba/_com/NewsReader';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Center, Container, Group, Image, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function Page() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||
const state = useProxy(stateDashboardBerita.berita)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const state = useProxy(stateDashboardBerita.berita);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
@@ -27,9 +25,9 @@ function Page() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}, [id])
|
||||
};
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -47,41 +45,49 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} pb={"xl"} gap={"xs"} px={{ base: "md", md: 0 }}>
|
||||
<Group px={{ base: "md", md: 100 }}>
|
||||
<Stack pos="relative" bg={colors.Bg} pb="xl" gap="xs" px={{ base: 'md', md: 0 }}>
|
||||
<Group px={{ base: 'md', md: 100 }}>
|
||||
<NewsReader />
|
||||
</Group>
|
||||
<Container w={{ base: "100%", md: "50%" }} >
|
||||
<Container w={{ base: '100%', md: '50%' }}>
|
||||
<Box pb={20}>
|
||||
<Text id='news-title' ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||
{state.findUnique.data?.judul}
|
||||
</Text>
|
||||
<Text
|
||||
ta={"center"}
|
||||
fw={"bold"}
|
||||
fz={"1.5rem"}
|
||||
<Title
|
||||
id="news-title"
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fw="bold"
|
||||
lh={{ base: 1.2, md: 1.25 }}
|
||||
>
|
||||
{state.findUnique.data.judul}
|
||||
</Title>
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
fw="bold"
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={{ base: 1.3, md: 1.35 }}
|
||||
>
|
||||
Informasi dan Pelayanan Administrasi Digital
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy" />
|
||||
<Image src={state.findUnique.data.image?.link || ''} alt="" w="100%" loading="lazy" />
|
||||
</Container>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="xs">
|
||||
<Text
|
||||
id='news-content'
|
||||
id="news-content"
|
||||
py={20}
|
||||
fz={{ base: "sm", md: "lg" }}
|
||||
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.6, md: 1.8 }}
|
||||
ta="justify"
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: state.findUnique.data?.content || "",
|
||||
__html: state.findUnique.data.content || '',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -90,4 +96,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -16,35 +16,30 @@ function Semua() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useTransitionRouter();
|
||||
|
||||
// Ambil parameter langsung dari URL
|
||||
const search = searchParams.get('search') || '';
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
|
||||
// Gunakan proxy untuk state global
|
||||
const state = useProxy(stateDashboardBerita.berita);
|
||||
const featured = useProxy(stateDashboardBerita.berita.findFirst);
|
||||
const loadingGrid = state.findMany.loading;
|
||||
const loadingFeatured = featured.loading;
|
||||
|
||||
// Load berita utama sekali saja
|
||||
useEffect(() => {
|
||||
if (!featured.data && !loadingFeatured) {
|
||||
stateDashboardBerita.berita.findFirst.load();
|
||||
}
|
||||
}, [featured.data, loadingFeatured]);
|
||||
|
||||
// Load berita terbaru tiap page / search berubah
|
||||
useEffect(() => {
|
||||
const limit = 3;
|
||||
state.findMany.load(page, limit, search);
|
||||
}, [page, search]);
|
||||
|
||||
// Handler pagination → langsung update URL
|
||||
const handlePageChange = (newPage: number) => {
|
||||
const url = new URLSearchParams(searchParams.toString());
|
||||
if (search) url.set('search', search);
|
||||
if (newPage > 1) url.set('page', newPage.toString());
|
||||
else url.delete('page'); // biar page=1 ga muncul di URL
|
||||
else url.delete('page');
|
||||
|
||||
router.replace(`?${url.toString()}`);
|
||||
};
|
||||
@@ -61,7 +56,7 @@ function Semua() {
|
||||
<Center><Skeleton h={400} /></Center>
|
||||
) : featuredData ? (
|
||||
<Box mb={50}>
|
||||
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
|
||||
<Title order={2} mb="md">Berita Utama</Title>
|
||||
<Paper shadow="md" radius="md" withBorder>
|
||||
<Grid gutter={0}>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
@@ -81,13 +76,24 @@ function Semua() {
|
||||
<Badge color="blue" variant="light" mb="md">
|
||||
{featuredData.kategoriBerita?.name || 'Berita'}
|
||||
</Badge>
|
||||
<Title order={2} mb="md">{featuredData.judul}</Title>
|
||||
<Text c="dimmed" lineClamp={3} mb="md" dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }} />
|
||||
<Title order={3} mb="md">{featuredData.judul}</Title>
|
||||
<Text
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
mb="md"
|
||||
dangerouslySetInnerHTML={{ __html: featuredData.deskripsi }}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
/>
|
||||
</div>
|
||||
<Group justify="apart" mt="auto">
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">
|
||||
<Text
|
||||
c="dimmed"
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.4, md: 1.5 }}
|
||||
>
|
||||
{new Date(featuredData.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
@@ -124,7 +130,9 @@ function Semua() {
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : paginatedNews.length === 0 ? (
|
||||
<Text c="dimmed" ta="center">Tidak ada berita ditemukan.</Text>
|
||||
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh={{ base: 1.5, md: 1.6 }}>
|
||||
Tidak ada berita ditemukan.
|
||||
</Text>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
||||
{paginatedNews.map((item) => (
|
||||
@@ -143,11 +151,24 @@ function Semua() {
|
||||
{item.kategoriBerita?.name || 'Berita'}
|
||||
</Badge>
|
||||
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
<Text size="sm" c="dimmed" lineClamp={3} mt="xs" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Title order={4} mt="sm" lineClamp={2}>
|
||||
{item.judul}
|
||||
</Title>
|
||||
<Text
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
mt="xs"
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
/>
|
||||
|
||||
<Flex align="center" justify="apart" mt="md" gap="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
<Text
|
||||
c="dimmed"
|
||||
fz={{ base: 'xs', md: 'xs' }}
|
||||
lh={{ base: 1.4, md: 1.4 }}
|
||||
>
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
@@ -187,4 +208,4 @@ function Semua() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Semua;
|
||||
export default Semua;
|
||||
@@ -17,17 +17,11 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconPhoto } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
// Komponen kartu foto
|
||||
function FotoCard({ item }: { item: any }) {
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/darmasaba/galeri/foto/${item.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid.Col span={{ base: 12, xs: 6, md: 4 }}>
|
||||
@@ -35,19 +29,19 @@ function FotoCard({ item }: { item: any }) {
|
||||
shadow="sm"
|
||||
radius="md"
|
||||
p={0}
|
||||
onClick={handleClick}
|
||||
style={{ cursor: 'pointer', transition: 'transform 0.2s' }}
|
||||
style={{ transition: 'transform 0.2s' }}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||
|
||||
>
|
||||
{item.imageGalleryFoto?.link ? (
|
||||
<Box
|
||||
pos="relative"
|
||||
style={{
|
||||
paddingBottom: '100%', // ✅ Ubah ke 1:1 (square) — atau sesuaikan
|
||||
paddingBottom: '100%',
|
||||
overflow: 'hidden',
|
||||
borderRadius: '4px 4px 0 0',
|
||||
backgroundColor: '#f9f9f9', // ✅ background netral
|
||||
backgroundColor: '#f9f9f9',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
@@ -61,8 +55,8 @@ function FotoCard({ item }: { item: any }) {
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain', // ✅ Tampilkan utuh, jangan crop
|
||||
objectPosition: 'center', // rata tengah
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'center',
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
@@ -74,13 +68,23 @@ function FotoCard({ item }: { item: any }) {
|
||||
)}
|
||||
|
||||
<Stack p="md" gap={4}>
|
||||
<Text fw={600} lineClamp={1}>
|
||||
<Text fw={600} lineClamp={1} fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
|
||||
{item.name || 'Tanpa Judul'}
|
||||
</Text>
|
||||
{item.deskripsi && (
|
||||
<Text fz="sm" c="dimmed" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dimmed"
|
||||
lineClamp={2}
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||
lh={{ base: '1.4', md: '1.5' }}
|
||||
/>
|
||||
)}
|
||||
<Text fz="xs" c="dimmed">
|
||||
<Text
|
||||
fz={{ base: 11, md: 'xs' }}
|
||||
c="dimmed"
|
||||
lh={{ base: '1.3', md: '1.4' }}
|
||||
>
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
@@ -99,7 +103,7 @@ export default function GaleriFotoUser() {
|
||||
return (
|
||||
<Box py="xl" px={{ base: 'md', md: 'lg' }}>
|
||||
{/* Header */}
|
||||
<Title order={2} c={colors['blue-button']} mb="lg">
|
||||
<Title order={2} c={colors['blue-button']} mb="lg" ta="center">
|
||||
Galeri Foto Desa Darmasaba
|
||||
</Title>
|
||||
|
||||
@@ -115,7 +119,7 @@ function FotoList({ search }: { search: string }) {
|
||||
const { data, page, totalPages, loading, load } = FotoState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, search); // ✅ 9 item per halaman
|
||||
load(page, 3, search);
|
||||
}, [page, search]);
|
||||
|
||||
if (loading) {
|
||||
@@ -135,7 +139,9 @@ function FotoList({ search }: { search: string }) {
|
||||
<Center py="xl">
|
||||
<Stack align="center" c="dimmed">
|
||||
<IconPhoto size={48} />
|
||||
<Text>Tidak ada foto ditemukan</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={{ base: '1.4', md: '1.5' }}>
|
||||
Tidak ada foto ditemukan
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
@@ -150,19 +156,18 @@ function FotoList({ search }: { search: string }) {
|
||||
</Grid>
|
||||
|
||||
{/* Pagination */}
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 3, search);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 3, search);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text
|
||||
Text,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
@@ -19,15 +20,13 @@ import { useSnapshot } from 'valtio';
|
||||
|
||||
export default function VideoContent() {
|
||||
const videoState = useSnapshot(stateGallery.video);
|
||||
const router = useTransitionRouter()
|
||||
const router = useTransitionRouter();
|
||||
const { data, page, totalPages, loading } = videoState.findMany;
|
||||
|
||||
// Handle search and pagination changes
|
||||
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
||||
stateGallery.video.findMany.load(pageNum, 3, searchTerm.trim());
|
||||
}, []);
|
||||
|
||||
// Initial load and URL change handler
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -57,13 +56,14 @@ export default function VideoContent() {
|
||||
loadData(newPage, search);
|
||||
};
|
||||
|
||||
|
||||
const dataVideo = data || [];
|
||||
|
||||
if (loading && !data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Text>Memuat Video...</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed" ta="center">
|
||||
Memuat Video...
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -78,55 +78,71 @@ export default function VideoContent() {
|
||||
p="md"
|
||||
radius={26}
|
||||
bg={colors['white-trans-1']}
|
||||
w={{ base: '100%', md: '100%' }}
|
||||
w="100%"
|
||||
>
|
||||
<Box>
|
||||
<Center>
|
||||
<Box
|
||||
component="iframe"
|
||||
src={convertToEmbedUrl(v.linkVideo)}
|
||||
width="100%"
|
||||
height={300}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap="sm" py={10}>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
<Text fw="bold" fz="sm" lineClamp={1}>
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text
|
||||
ta="justify"
|
||||
fz="sm"
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
lineClamp={3}
|
||||
truncate="end"
|
||||
/>
|
||||
<Group justify={"right"}>
|
||||
<Button
|
||||
<Center>
|
||||
<Box
|
||||
component="iframe"
|
||||
src={convertToEmbedUrl(v.linkVideo)}
|
||||
width="100%"
|
||||
height={300}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Center>
|
||||
|
||||
<Stack gap="sm" py={10}>
|
||||
{/* Tanggal: Caption */}
|
||||
<Text
|
||||
fz={{ base: 12, md: 14 }}
|
||||
c="dimmed"
|
||||
ta="left"
|
||||
>
|
||||
{new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
|
||||
{/* Judul Video: Subsection (H3) */}
|
||||
<Title
|
||||
order={3}
|
||||
c="dark"
|
||||
ta="left"
|
||||
lh={1.3}
|
||||
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{v.name}
|
||||
</Title>
|
||||
|
||||
{/* Deskripsi: Body kecil */}
|
||||
<Text
|
||||
ta="justify"
|
||||
fz={{ base: 13, md: 14 }}
|
||||
c="dimmed"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
lineClamp={3}
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Text>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={() => router.push(`/darmasaba/desa/galery/video/${v.id}`)}
|
||||
bg={colors['blue-button']}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -140,7 +156,6 @@ export default function VideoContent() {
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ Fix: convert YouTube URL ke embed
|
||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||
try {
|
||||
const url = new URL(youtubeUrl);
|
||||
@@ -151,4 +166,4 @@ function convertToEmbedUrl(youtubeUrl: string): string {
|
||||
console.error('Error converting YouTube URL to embed:', err);
|
||||
return youtubeUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,17 @@ import {
|
||||
Stack,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconInfoCircle, IconVideo } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery'; // pastikan state bisa dipakai di publik
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import BackButton from '../../../layanan/_com/BackButto';
|
||||
|
||||
// Fungsi helper: aman dan tanpa spasi
|
||||
|
||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||
try {
|
||||
const url = new URL(youtubeUrl);
|
||||
@@ -72,7 +73,9 @@ export default function DetailVideoUser() {
|
||||
color="red"
|
||||
radius="md"
|
||||
>
|
||||
Video yang Anda cari tidak tersedia.
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="red.9">
|
||||
Video yang Anda cari tidak tersedia.
|
||||
</Text>
|
||||
</Alert>
|
||||
<Button
|
||||
leftSection={<IconArrowBack size={16} />}
|
||||
@@ -91,20 +94,20 @@ export default function DetailVideoUser() {
|
||||
return (
|
||||
<Box py="xl" px={{ base: 'md', md: 100 }}>
|
||||
{/* Tombol Kembali */}
|
||||
<Box >
|
||||
<Box>
|
||||
<BackButton />
|
||||
</Box>
|
||||
|
||||
{/* Header */}
|
||||
<Text
|
||||
{/* Header - Dijadikan Title */}
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={700}
|
||||
c={colors['blue-button']}
|
||||
mb="lg"
|
||||
lh={{ base: 1.2, md: 1.25 }}
|
||||
>
|
||||
{data.name || 'Video Galeri Desa'}
|
||||
</Text>
|
||||
</Title>
|
||||
|
||||
{/* Konten Utama */}
|
||||
<Card
|
||||
@@ -118,7 +121,7 @@ export default function DetailVideoUser() {
|
||||
{embedUrl ? (
|
||||
<Box
|
||||
pos="relative"
|
||||
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }} // 16:9 aspect ratio
|
||||
style={{ paddingBottom: '56.25%', height: 0, overflow: 'hidden' }}
|
||||
>
|
||||
<iframe
|
||||
src={embedUrl}
|
||||
@@ -144,7 +147,9 @@ export default function DetailVideoUser() {
|
||||
title="Gagal memuat video"
|
||||
radius="md"
|
||||
>
|
||||
Mohon maaf, video tidak dapat diputar.
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c="orange.9">
|
||||
Mohon maaf, video tidak dapat diputar.
|
||||
</Text>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert
|
||||
@@ -153,7 +158,9 @@ export default function DetailVideoUser() {
|
||||
title="Tidak ada video"
|
||||
radius="md"
|
||||
>
|
||||
Konten video belum tersedia.
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed">
|
||||
Konten video belum tersedia.
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -163,7 +170,11 @@ export default function DetailVideoUser() {
|
||||
<ThemeIcon variant="light" size="sm" radius="xl">
|
||||
<IconInfoCircle size={14} />
|
||||
</ThemeIcon>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dimmed"
|
||||
lh={{ base: 1.4, md: 1.5 }}
|
||||
>
|
||||
Diunggah pada{' '}
|
||||
{new Date(data.createdAt).toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
@@ -179,8 +190,9 @@ export default function DetailVideoUser() {
|
||||
{data.deskripsi && (
|
||||
<Paper p="md" bg="gray.0" radius="md">
|
||||
<Text
|
||||
fz="md"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
c="dark"
|
||||
ta={"justify"}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { ActionIcon, Box, Divider, Flex, Group, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -39,30 +39,38 @@ function PelayananPendudukNonPermanent() {
|
||||
) : (
|
||||
<Stack gap="xl">
|
||||
<Box>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw={700} lh={1.3} c="dark">
|
||||
<Title
|
||||
order={1}
|
||||
fz={{ base: 'lg', md: 'xl' }}
|
||||
fw={700}
|
||||
lh={{ base: 1.3, md: 1.3 }}
|
||||
c="dark"
|
||||
>
|
||||
{data?.name || "Judul belum tersedia"}
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
{data?.deskripsi ? (
|
||||
<Text
|
||||
fz={{ base: "sm", md: "md" }}
|
||||
lh={1.7}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.6, md: 1.7 }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
c="black"
|
||||
dangerouslySetInnerHTML={{ __html: data?.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="gray">Deskripsi belum tersedia.</Text>
|
||||
<Text fz="xs" c="gray">
|
||||
Deskripsi belum tersedia.
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider color={colors["blue-button"]} size="sm" />
|
||||
|
||||
<Flex justify="space-between" align="center" wrap="wrap" gap="md">
|
||||
<Text fz={{ base: "xs", md: "sm" }} c="dimmed">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.5 }} c="black">
|
||||
25 Mei 2021 • Darmasaba
|
||||
</Text>
|
||||
<Group gap="md">
|
||||
@@ -96,4 +104,4 @@ function PelayananPendudukNonPermanent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default PelayananPendudukNonPermanent;
|
||||
export default PelayananPendudukNonPermanent;
|
||||
@@ -47,7 +47,7 @@ function PelayananPerizinanBerusaha() {
|
||||
return (
|
||||
<Center mih={300}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Text fz="lg" fw={500} c="dimmed">
|
||||
<Text fz={{ base: 'md', md: 'lg' }} fw={500} c="dimmed" lh="sm">
|
||||
Belum ada informasi layanan yang tersedia
|
||||
</Text>
|
||||
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
|
||||
@@ -67,10 +67,10 @@ function PelayananPerizinanBerusaha() {
|
||||
) : (
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
|
||||
<Title order={2} fw={700} mb="sm">
|
||||
Perizinan Berusaha Berbasis Risiko melalui OSS
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="black" lh="sm">
|
||||
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -83,13 +83,13 @@ function PelayananPerizinanBerusaha() {
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>
|
||||
<Title order={3} fw={600} mb="sm">
|
||||
Alur pendaftaran NIB:
|
||||
</Text>
|
||||
</Title>
|
||||
<Stepper
|
||||
active={active}
|
||||
onStepClick={(step) => {
|
||||
if (step <= active) { // Only allow clicking on previous or current steps
|
||||
if (step <= active) {
|
||||
setActive(step);
|
||||
}
|
||||
}}
|
||||
@@ -102,28 +102,42 @@ function PelayananPerizinanBerusaha() {
|
||||
}}
|
||||
>
|
||||
<StepperStep label="Langkah 1" description="Daftar Akun">
|
||||
<Text fz="sm">Membuat akun di portal OSS</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||
Membuat akun di portal OSS
|
||||
</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
|
||||
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||
Lengkapi informasi perusahaan, data pemegang saham, dan alamat
|
||||
</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 3" description="Pilih KBLI">
|
||||
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||
Menentukan kode KBLI sesuai jenis usaha
|
||||
</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 4" description="Unggah Dokumen">
|
||||
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||
Unggah akta pendirian, surat izin, dan dokumen wajib lainnya
|
||||
</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
|
||||
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||
Menunggu verifikasi dan persetujuan dari pihak berwenang
|
||||
</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 6" description="Terbit NIB">
|
||||
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh="sm">
|
||||
Menerima NIB sebagai identitas resmi usaha
|
||||
</Text>
|
||||
</StepperStep>
|
||||
<StepperCompleted>
|
||||
<Center>
|
||||
<Stack align="center" gap="xs">
|
||||
<IconCheck size={40} color="green" />
|
||||
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} fw={500} lh="sm">
|
||||
Proses pendaftaran selesai
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</StepperCompleted>
|
||||
@@ -159,7 +173,7 @@ function PelayananPerizinanBerusaha() {
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Text fz="sm" ta="justify" c="dimmed" mt="md">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} ta="justify" c="black" lh="sm" mt="md">
|
||||
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
|
||||
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
|
||||
oss.go.id
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||
import colors from '@/con/colors';
|
||||
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { BackgroundImage, Box, Button, Center, Group, Pagination, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconFileDescription, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -35,7 +35,7 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
||||
<Center py="xl">
|
||||
<Stack align="center" gap="xs">
|
||||
<IconFileDescription size={40} stroke={1.5} color={colors["blue-button"]} />
|
||||
<Text c="dimmed" ta="center">
|
||||
<Text c="dimmed" ta="center" fz={{ base: 'sm', md: 'md' }} lh="sm">
|
||||
Tidak ada layanan surat keterangan yang ditemukan
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -48,9 +48,9 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
||||
<Group justify="space-between" align="center" mb="md">
|
||||
<Group gap="xs">
|
||||
<IconFileDescription size={28} stroke={1.8} />
|
||||
<Text fz={{ base: "h4", md: "h2" }} fw={700}>
|
||||
<Title order={2} c="black">
|
||||
Layanan Surat Keterangan
|
||||
</Text>
|
||||
</Title>
|
||||
</Group>
|
||||
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
|
||||
<IconInfoCircle size={22} stroke={1.8} />
|
||||
@@ -82,15 +82,15 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
||||
style={{ borderRadius: 16 }}
|
||||
/>
|
||||
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
||||
<Text
|
||||
<Title
|
||||
order={3}
|
||||
c="white"
|
||||
fw={600}
|
||||
fz="lg"
|
||||
ta="center"
|
||||
lineClamp={2}
|
||||
lh="sm"
|
||||
>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Title>
|
||||
<Group justify="center">
|
||||
<Button
|
||||
size="md"
|
||||
@@ -128,4 +128,4 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default PelayananSuratKeterangan;
|
||||
export default PelayananSuratKeterangan;
|
||||
@@ -42,9 +42,10 @@ function PelayananTelunjukSaktiDesa() {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Title order={2} mb="lg" fz={{ base: 22, md: 28 }} fw={700} style={{ lineHeight: 1.4 }}>
|
||||
Layanan Telunjuk Sakti Desa <br />
|
||||
<Text span c="dimmed" fz="lg" fw={400}>
|
||||
<Title order={2} mb="lg" fw={700} style={{ lineHeight: 1.3 }} ta="left">
|
||||
Layanan Telunjuk Sakti Desa
|
||||
<Text span c="black" fz={{ base: 'sm', md: 'md' }} fw={400} style={{ lineHeight: 1.5 }}>
|
||||
{' '}
|
||||
Terwujudnya sistem administrasi kependudukan terintegrasi berbasis elektronik, cerdas, dan aman
|
||||
</Text>
|
||||
</Title>
|
||||
@@ -53,7 +54,7 @@ function PelayananTelunjukSaktiDesa() {
|
||||
<Skeleton h={400} radius="lg" />
|
||||
) : data.length === 0 ? (
|
||||
<Card shadow="sm" radius="lg" withBorder>
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
<Text c="black" ta="center" py="xl" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Belum ada layanan tersedia untuk saat ini
|
||||
</Text>
|
||||
</Card>
|
||||
@@ -72,9 +73,9 @@ function PelayananTelunjukSaktiDesa() {
|
||||
}}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Text fw={700} fz="lg" lh={1.4}>
|
||||
<Title order={3} fw={700} lh={1.3}>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Title>
|
||||
<Flex gap="xs" align="center">
|
||||
<IconExternalLink size={18} stroke={1.5} />
|
||||
<Text
|
||||
@@ -82,7 +83,7 @@ function PelayananTelunjukSaktiDesa() {
|
||||
href={v.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
fz="sm"
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="blue"
|
||||
td="underline"
|
||||
style={{ cursor: 'pointer' }}
|
||||
@@ -100,4 +101,4 @@ function PelayananTelunjukSaktiDesa() {
|
||||
);
|
||||
}
|
||||
|
||||
export default PelayananTelunjukSaktiDesa;
|
||||
export default PelayananTelunjukSaktiDesa;
|
||||
@@ -1,58 +1,94 @@
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Container, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../../layanan/_com/BackButto';
|
||||
|
||||
import NewsReader from '@/app/darmasaba/_com/NewsReader';
|
||||
import BackButton from '../../../layanan/_com/BackButto';
|
||||
|
||||
function Page() {
|
||||
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique)
|
||||
|
||||
const params = useParams()
|
||||
|
||||
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique);
|
||||
const params = useParams();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
if (!detail.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={400} />
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
||||
{/* Header */}
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
|
||||
<Container size="lg" px="md">
|
||||
<Group>
|
||||
<NewsReader />
|
||||
</Group>
|
||||
<Stack gap="xs" >
|
||||
<Group justify={"space-between"} align={"center"}>
|
||||
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
||||
{detail.data?.judul}
|
||||
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between" align="flex-start" wrap="wrap">
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 28, md: 36 }}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
flex: '1 1 auto',
|
||||
minWidth: 0
|
||||
}}
|
||||
>
|
||||
{detail.data?.judul}
|
||||
</Title>
|
||||
<Paper bg={colors['blue-button']} p={8} style={{ flexShrink: 0 }}>
|
||||
<Text c={colors['white-1']} fz={{ base: 'xs', md: 'sm' }} lh={1.2}>
|
||||
{detail.data?.CategoryPengumuman?.name}
|
||||
</Text>
|
||||
<Group justify='end'>
|
||||
<Paper bg={colors['blue-button']} p={5}>
|
||||
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
</Group>
|
||||
<Paper bg={colors["white-1"]} p="md">
|
||||
<Text px="lg" id='news-content' fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
||||
<Text px="lg" fz={"md"} c={colors["blue-button"]} fw="bold" >
|
||||
</Paper>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="md"
|
||||
w="100%"
|
||||
mih={{ base: 200, md: 300 }}
|
||||
>
|
||||
<Text
|
||||
px="lg"
|
||||
id="news-content"
|
||||
fz={{ base: 14, md: 16 }}
|
||||
lh={{ base: 1.6, md: 1.6 }}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
width: '100%'
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: detail.data?.content }}
|
||||
/>
|
||||
<Text
|
||||
px="lg"
|
||||
fz={{ base: 12, md: 14 }}
|
||||
c={colors['blue-button']}
|
||||
fw="bold"
|
||||
lh={{ base: 1.4, md: 1.4 }}
|
||||
mt="md"
|
||||
>
|
||||
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
</Paper>
|
||||
@@ -62,4 +98,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,14 +2,13 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Group, Paper, Stack, Text } from '@mantine/core';
|
||||
import { Box, Container, Group, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconCalendar } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../layanan/_com/BackButto';
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
|
||||
function Page() {
|
||||
const unwrappedParams = useParams();
|
||||
const kategoriState = useProxy(stateDesaPengumuman);
|
||||
@@ -26,48 +25,85 @@ function Page() {
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md" >
|
||||
<Stack align="center" gap="0" >
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
{categoryName.split('-').map(word =>
|
||||
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="xs">
|
||||
<Title
|
||||
order={1}
|
||||
c={colors["blue-button"]}
|
||||
ta="center"
|
||||
style={{ fontWeight: 'bold' }}
|
||||
>
|
||||
{categoryName.split('-').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')}
|
||||
</Text>
|
||||
<Text ta="center" px="md" pb={10}>
|
||||
</Title>
|
||||
<Text
|
||||
ta="center"
|
||||
px="md"
|
||||
pb="sm"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
c="dimmed"
|
||||
>
|
||||
Informasi dan pengumuman resmi terkait {categoryName.split('-').join(' ')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
{!kategoriState.pengumuman.findMany.data?.length ? (
|
||||
<Paper p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
|
||||
Tidak ada pengumuman yang ditemukan
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="center"
|
||||
c="dimmed"
|
||||
>
|
||||
Tidak ada pengumuman yang ditemukan
|
||||
</Text>
|
||||
</Paper>
|
||||
) : kategoriState.pengumuman.findMany.data?.map((v, k) => {
|
||||
return (
|
||||
<Paper mb={10} key={k} withBorder p="lg" radius="md" shadow="md" bg={colors["white-1"]}>
|
||||
<Text fz={'h3'}>{v.judul}</Text>
|
||||
<Group style={{ color: 'black' }} pb={20}>
|
||||
) : (
|
||||
kategoriState.pengumuman.findMany.data?.map((v, k) => (
|
||||
<Paper
|
||||
mb="md"
|
||||
key={k}
|
||||
withBorder
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="md"
|
||||
bg={colors["white-1"]}
|
||||
>
|
||||
<Title order={3}>{v.judul}</Title>
|
||||
<Group style={{ color: 'black' }} pb="sm">
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">
|
||||
{v.createdAt ? new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}) : 'No date available'}
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.4, md: 1.5 }}
|
||||
>
|
||||
{v.createdAt
|
||||
? new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: 'No date available'}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Text ta={'justify'}>
|
||||
<Text
|
||||
ta="justify"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.6, md: 1.7 }}
|
||||
>
|
||||
{v.deskripsi}
|
||||
</Text>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
Center,
|
||||
Container,
|
||||
Divider,
|
||||
Flex,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
@@ -22,7 +21,7 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
UnstyledButton,
|
||||
UnstyledButton
|
||||
} from '@mantine/core';
|
||||
import { IconCalendar, IconClock, IconSearch } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
@@ -98,10 +97,14 @@ function Page() {
|
||||
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="0">
|
||||
<Text fz={{ base: '2rem', md: '3.4rem' }} c={colors['blue-button']} fw="bold" ta="center">
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
>
|
||||
Pengumuman Desa Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md" pb={10}>
|
||||
</Title>
|
||||
<Text ta="center" px="md" pb={10} fz={{ base: 'sm', md: 'md' }} lh="sm">
|
||||
Informasi dan pengumuman resmi terkait kegiatan dan kebijakan Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -126,17 +129,17 @@ function Page() {
|
||||
withCloseButton={false}
|
||||
title={item.CategoryPengumuman?.name || 'Pengumuman'}
|
||||
>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fz="sm" fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
|
||||
<Stack gap="xs">
|
||||
<Text fz={{ base: 'sm', md: 'sm' }} fw="bold" c="black" style={{ textTransform: 'uppercase' }}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Text ta="justify" fz="sm" c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Text ta="justify" fz={{ base: 'xs', md: 'sm' }} c="black" lineClamp={3} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Stack>
|
||||
<Flex pt={20} gap="md" justify="space-between">
|
||||
<Group pt={20} gap="md" justify="space-between">
|
||||
<Group style={{ color: 'black' }}>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }}>
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
@@ -147,7 +150,7 @@ function Page() {
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconClock size={18} />
|
||||
<Text size="sm">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }}>
|
||||
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
@@ -157,11 +160,11 @@ function Page() {
|
||||
</Group>
|
||||
</Group>
|
||||
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
|
||||
<Text fs="unset" c={colors['blue-button']} fz="sm">
|
||||
<Text fs="unset" c={colors['blue-button']} fz={{ base: 'xs', md: 'sm' }}>
|
||||
Baca Selengkapnya
|
||||
</Text>
|
||||
</Anchor>
|
||||
</Flex>
|
||||
</Group>
|
||||
</Notification>
|
||||
))
|
||||
)}
|
||||
@@ -169,19 +172,19 @@ function Page() {
|
||||
|
||||
<Paper p="md">
|
||||
<Stack gap="xs">
|
||||
<Text fw="bold" fz="lg" c={colors['blue-button']}>
|
||||
<Title order={3} c={colors['blue-button']}>
|
||||
Kategori
|
||||
</Text>
|
||||
</Title>
|
||||
{stateDesaPengumuman.category.findMany.data?.map((v: any, k) => {
|
||||
const count = v._count?.pengumumans || 0;
|
||||
return (
|
||||
<UnstyledButton component={Link} href={`/darmasaba/desa/pengumuman/${v.name}`} key={k}>
|
||||
<Paper bg={colors['BG-trans']} p={5}>
|
||||
<Group px={3} justify="space-between">
|
||||
<Text fz="md" c="black">
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="black">
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text fz="md" c="black">
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="black">
|
||||
{count}
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -200,7 +203,7 @@ function Page() {
|
||||
<Divider mb={10} color={colors['blue-button']} />
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Title order={3}>Daftar Pengumuman</Title>
|
||||
<Title order={2}>Daftar Pengumuman</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<TextInput
|
||||
@@ -210,6 +213,7 @@ function Page() {
|
||||
w="100%"
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
@@ -223,7 +227,9 @@ function Page() {
|
||||
</SimpleGrid>
|
||||
) : !state.findMany.data?.length ? (
|
||||
<Notification withCloseButton={false} h={100}>
|
||||
Tidak ada pengumuman yang ditemukan
|
||||
<Text fz={{ base: 'sm', md: 'md' }} ta="center">
|
||||
Tidak ada pengumuman yang ditemukan
|
||||
</Text>
|
||||
</Notification>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" verticalSpacing="lg">
|
||||
@@ -231,26 +237,26 @@ function Page() {
|
||||
<Paper key={item.id} p="md" withBorder radius="md" h="100%">
|
||||
<Stack h="100%" justify="space-between">
|
||||
<div>
|
||||
<Text fw={600} c={colors['blue-button']} mb={5}>
|
||||
<Text fw={600} c={colors['blue-button']} mb={5} fz={{ base: 'sm', md: 'md' }}>
|
||||
{item.CategoryPengumuman?.name || 'Pengumuman'}
|
||||
</Text>
|
||||
<Text fz="lg" fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }}>
|
||||
<Text fw={700} mb="sm" lineClamp={2} style={{ textTransform: 'uppercase' }} fz={{ base: 'sm', md: 'lg' }}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
lineClamp={4}
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||
mb="md"
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Group mb="sm" c="dimmed">
|
||||
<Group gap={5}>
|
||||
<IconCalendar size={16} />
|
||||
<Text size="xs">
|
||||
<Text fz={{ base: 'xs', md: 'xs' }}>
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
@@ -260,19 +266,19 @@ function Page() {
|
||||
</Group>
|
||||
<Group gap={5}>
|
||||
<IconClock size={16} />
|
||||
<Text size="xs">
|
||||
<Text fz={{ base: 'xs', md: 'xs' }}>
|
||||
{new Date(item.createdAt).toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
|
||||
<Text fw={600} c={colors['blue-button']} fz={{ base: 'sm', md: 'sm' }}>
|
||||
Baca Selengkapnya →
|
||||
</Text>
|
||||
</Anchor>
|
||||
</Group>
|
||||
<Anchor variant="transparent" href={`/darmasaba/desa/pengumuman/${item.CategoryPengumuman?.name}/${item.id}`}>
|
||||
<Text fw={600} c={colors['blue-button']} size="sm">
|
||||
Baca Selengkapnya →
|
||||
</Text>
|
||||
</Anchor>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -289,6 +295,7 @@ function Page() {
|
||||
siblings={1}
|
||||
boundaries={1}
|
||||
withEdges
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../layanan/_com/BackButto';
|
||||
|
||||
|
||||
function Page() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||
@@ -35,7 +36,9 @@ function Page() {
|
||||
<Center h="80vh">
|
||||
<Stack align="center" gap="md">
|
||||
<Loader size="lg" color="blue" />
|
||||
<Text c="dimmed" fz="sm">Sedang memuat informasi...</Text>
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
|
||||
Sedang memuat informasi...
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
@@ -46,28 +49,31 @@ function Page() {
|
||||
<Center h="80vh">
|
||||
<Stack align="center" gap="sm">
|
||||
<IconMoodSad size={64} stroke={1.5} color="var(--mantine-color-blue-6)" />
|
||||
<Title order={3}>Data Tidak Ditemukan</Title>
|
||||
<Text c="dimmed" fz="sm">Mohon periksa kembali atau coba beberapa saat lagi</Text>
|
||||
<Title order={3} ta="center">
|
||||
Data Tidak Ditemukan
|
||||
</Title>
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} ta="center">
|
||||
Mohon periksa kembali atau coba beberapa saat lagi
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: "md", md: 0 }}>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl" px={{ base: 'md', md: 0 }}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container w={{ base: "100%", md: "60%" }}>
|
||||
<Container w={{ base: '100%', md: '60%' }}>
|
||||
<Paper radius="2xl" shadow="lg" p="xl" withBorder>
|
||||
<Stack gap="lg" align="center">
|
||||
<Title ta="center" fz={{ base: "2rem", md: "3rem" }} c={colors["blue-button"]} fw={800}>
|
||||
<Title order={1} ta="center" c={colors['blue-button']} fw={800}>
|
||||
{state.findUnique.data?.name}
|
||||
</Title>
|
||||
<Text ta="center" fw={600} fz={{ base: "md", md: "lg" }} c="dimmed">
|
||||
<Text ta="center" fw={600} fz={{ base: 'md', md: 'lg' }} c="dimmed">
|
||||
Informasi & Pelayanan Potensi Desa Digital
|
||||
</Text>
|
||||
{/* ✅ Bagian gambar dibuat konsisten tanpa CSS manual */}
|
||||
<Box
|
||||
w="100%"
|
||||
h={{ base: 220, md: 400 }}
|
||||
@@ -87,7 +93,15 @@ function Page() {
|
||||
radius="lg"
|
||||
/>
|
||||
</Box>
|
||||
<Text py="md" fz={{ base: "sm", md: "md" }} ta="justify" lh={1.8} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.deskripsi || 'Belum ada deskripsi untuk potensi desa ini.' }} />
|
||||
<Text
|
||||
py="md"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="justify"
|
||||
lh={{ base: 1.6, md: 1.8 }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: state.findUnique.data?.content || 'Belum ada deskripsi untuk potensi desa ini.',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Container>
|
||||
@@ -95,4 +109,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
||||
import colors from '@/con/colors';
|
||||
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { BackgroundImage, Box, Button, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconEye } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -41,10 +41,10 @@ function Page() {
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Flex justify="space-between" align="center" direction={{ base: "column", md: "row" }} gap="lg">
|
||||
<Stack gap="sm" maw={600}>
|
||||
<Text fz={{ base: "2rem", md: "3rem" }} fw={900} c={colors["blue-button"]} lh={1.2}>
|
||||
<Title order={1} fz={{ base: 28, md: 36 }} lh={1.2} c={colors["blue-button"]}>
|
||||
Potensi Desa Darmasaba
|
||||
</Text>
|
||||
<Text fz="lg" ta="justify">
|
||||
</Title>
|
||||
<Text fz={{ base: 14, md: 16 }} lh={1.6} ta="justify">
|
||||
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -58,18 +58,18 @@ function Page() {
|
||||
>
|
||||
<Flex justify="center" align="center" gap="xl">
|
||||
<Box>
|
||||
<Text ta="center" fz="2rem" fw={800} c="white">
|
||||
<Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
|
||||
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata').length || 0}
|
||||
</Text>
|
||||
<Text ta="center" fz="sm" c="white" fw={500}>
|
||||
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
|
||||
Potensi
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text ta="center" fz="2rem" fw={800} c="white">
|
||||
<Text ta="center" fz={{ base: 20, md: 32 }} fw={800} c="white" lh={1.2}>
|
||||
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata').length || 0}
|
||||
</Text>
|
||||
<Text ta="center" fz="sm" c="white" fw={500}>
|
||||
<Text ta="center" fz={{ base: 12, md: 14 }} c="white" fw={500}>
|
||||
Wisata
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -91,45 +91,40 @@ function Page() {
|
||||
radius="xl"
|
||||
onMouseEnter={() => setHoveredId(v.id)}
|
||||
onMouseLeave={() => setHoveredId(null)}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.3s ease'
|
||||
}}
|
||||
>
|
||||
{/* Overlay with smooth transition */}
|
||||
<Box
|
||||
pos="absolute"
|
||||
inset={0}
|
||||
bg={hoveredId === v.id
|
||||
? "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.75) 100%)"
|
||||
bg={hoveredId === v.id
|
||||
? "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.75) 100%)"
|
||||
: "linear-gradient(180deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.15) 100%)"
|
||||
}
|
||||
style={{
|
||||
transition: 'background 0.3s ease'
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<Stack justify="space-between" h="100%" gap="md" p="lg" pos="relative">
|
||||
{/* Kategori badge - always visible */}
|
||||
<Group>
|
||||
<Paper
|
||||
radius="lg"
|
||||
py={6}
|
||||
px={12}
|
||||
shadow="md"
|
||||
withBorder
|
||||
<Paper
|
||||
radius="lg"
|
||||
py={6}
|
||||
px={12}
|
||||
shadow="md"
|
||||
withBorder
|
||||
bg="rgba(255,255,255,0.9)"
|
||||
style={{
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
style={{ transition: 'all 0.3s ease' }}
|
||||
>
|
||||
<Text fz="sm" fw={600}>{v.kategori?.nama}</Text>
|
||||
<Text fz={{ base: 11, md: 14 }} fw={600}>{v.kategori?.nama}</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
|
||||
{/* Nama potensi - visible on hover */}
|
||||
<Box
|
||||
style={{
|
||||
opacity: hoveredId === v.id ? 1 : 0,
|
||||
@@ -138,20 +133,20 @@ function Page() {
|
||||
pointerEvents: hoveredId === v.id ? 'auto' : 'none'
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
<Title
|
||||
order={3}
|
||||
fw={800}
|
||||
c="white"
|
||||
fz="xl"
|
||||
fz={{ base: 18, md: 20 }}
|
||||
ta="center"
|
||||
lineClamp={2}
|
||||
lh={1.3}
|
||||
>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
{/* Button - visible on hover */}
|
||||
<Group
|
||||
<Group
|
||||
justify="center"
|
||||
style={{
|
||||
opacity: hoveredId === v.id ? 1 : 0,
|
||||
@@ -169,23 +164,21 @@ function Page() {
|
||||
gradient={{ from: colors["blue-button"], to: "#4dabf7", deg: 45 }}
|
||||
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
|
||||
>
|
||||
Lihat Detail
|
||||
<Text c={'white'} fz={{ base: 12, md: 14 }} fw={500}>Lihat Detail</Text>
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
))
|
||||
) : (
|
||||
<Center h={240}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz="lg" fw={600} c="dimmed">
|
||||
Belum ada potensi desa
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
Data potensi akan tampil di sini setelah tersedia.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz={{ base: 14, md: 16 }} fw={600} c="dimmed">
|
||||
Belum ada potensi desa
|
||||
</Text>
|
||||
<Text fz={{ base: 12, md: 14 }} c="dimmed">
|
||||
Data potensi akan tampil di sini setelah tersedia.
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
@@ -193,4 +186,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
|
||||
@@ -26,7 +26,6 @@ function DetailPegawaiUser() {
|
||||
statePegawai.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
|
||||
if (!statePegawai.findUnique.data) {
|
||||
return (
|
||||
<Stack py="lg">
|
||||
@@ -41,7 +40,7 @@ function DetailPegawaiUser() {
|
||||
<Box px={{ base: 'md', md: 100 }} py="xl">
|
||||
{/* Back button */}
|
||||
<Group mb="lg" px={{ base: 'md', md: 100 }}>
|
||||
<BackButton/>
|
||||
<BackButton />
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
@@ -69,11 +68,17 @@ function DetailPegawaiUser() {
|
||||
/>
|
||||
|
||||
{/* Nama & Jabatan */}
|
||||
<Stack align="center" gap={2}>
|
||||
<Title order={3} fw={700} c={colors['blue-button']}>
|
||||
<Stack align="center" gap={4}>
|
||||
{/* Title utama → H2 karena ini judul profil */}
|
||||
<Title order={2} c={colors['blue-button']} lh={1.2}>
|
||||
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
|
||||
</Title>
|
||||
<Text fz="sm" c="dimmed">
|
||||
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.4}
|
||||
c="dimmed"
|
||||
>
|
||||
{data.posisi?.nama || 'Posisi tidak tersedia'}
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -82,7 +87,11 @@ function DetailPegawaiUser() {
|
||||
<Divider my="lg" />
|
||||
|
||||
{/* Informasi Detail */}
|
||||
<Stack gap="md">
|
||||
<Stack gap="lg">
|
||||
<Title order={3} lh={1.3}>
|
||||
Informasi Pegawai
|
||||
</Title>
|
||||
|
||||
<InfoRow label="Email" value={data.email} />
|
||||
<InfoRow label="Telepon" value={data.telepon} />
|
||||
<InfoRow label="Alamat" value={data.alamat} multiline />
|
||||
@@ -91,10 +100,10 @@ function DetailPegawaiUser() {
|
||||
value={
|
||||
data.tanggalMasuk
|
||||
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '-'
|
||||
}
|
||||
/>
|
||||
@@ -123,11 +132,18 @@ function InfoRow({
|
||||
}) {
|
||||
return (
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} c="dark">
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
fw={600}
|
||||
lh={1.3}
|
||||
c="dark"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
fz="sm"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
c={valueColor || 'dimmed'}
|
||||
style={{
|
||||
whiteSpace: multiline ? 'normal' : 'nowrap',
|
||||
|
||||
@@ -36,11 +36,12 @@ import { useTransitionRouter } from 'next-view-transitions'
|
||||
import { OrganizationChart } from 'primereact/organizationchart'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import './struktur.css'
|
||||
import BackButton from '../_com/BackButto'
|
||||
import { useMediaQuery } from '@mantine/hooks'
|
||||
|
||||
export default function StrukturPerangkatDesa() {
|
||||
import './struktur.css'
|
||||
import { useMediaQuery } from '@mantine/hooks'
|
||||
import BackButton from '../_com/BackButto'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
@@ -59,10 +60,11 @@ export default function StrukturPerangkatDesa() {
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 28, md: 36, lg: 44 }}
|
||||
lh={{ base: 1.05, md: 1.03 }}
|
||||
>
|
||||
Struktur Perangkat Desa
|
||||
</Title>
|
||||
<Text ta="center" c="black" maw={800}>
|
||||
<Text ta="center" c="black" maw={800} fz={{ base: 13, md: 15 }} lh={1.45}>
|
||||
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
|
||||
untuk melihat detail atau klik node untuk fokus tampilan.
|
||||
</Text>
|
||||
@@ -105,8 +107,8 @@ function StrukturPerangkatDesaNode() {
|
||||
<Center py={48}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader size="lg" />
|
||||
<Text fw={600}>Memuat struktur organisasi…</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.2}>Memuat struktur organisasi…</Text>
|
||||
<Text c="dimmed" fz={{ base: 12, md: 13 }} lh={1.4}>
|
||||
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -132,10 +134,10 @@ function StrukturPerangkatDesaNode() {
|
||||
<Center>
|
||||
<IconUsers size={56} />
|
||||
</Center>
|
||||
<Title order={3} mt="md">
|
||||
<Title order={3} mt="md" fz={{ base: 16, md: 18 }} lh={1.15}>
|
||||
Data pegawai belum tersedia
|
||||
</Title>
|
||||
<Text c="dimmed" mt="xs">
|
||||
<Text c="dimmed" mt="xs" fz={{ base: 13, md: 14 }} lh={1.4}>
|
||||
Belum ada data pegawai yang tercatat untuk PPID.
|
||||
</Text>
|
||||
<Group justify="center" mt="lg">
|
||||
@@ -232,11 +234,18 @@ function StrukturPerangkatDesaNode() {
|
||||
{/* 🔍 Controls */}
|
||||
<Paper
|
||||
shadow="xs"
|
||||
w={{
|
||||
base: '100%', // Mobile: 100%
|
||||
sm: '40%', // Tablet: 95%
|
||||
md: '39%', // Desktop: 70%
|
||||
lg: '38%', // Desktop L: 60%
|
||||
xl: '37%', // 4K: 50%
|
||||
'2xl': '36%', // Ultra-wide: 45%
|
||||
}}
|
||||
p="md"
|
||||
radius="md"
|
||||
style={{
|
||||
background: colors['blue-button'],
|
||||
width: '100%', // ⬅️ penting
|
||||
background: colors['blue-button'], // ⬅️ penting
|
||||
maxWidth: '100%', // ⬅️ penting
|
||||
overflowX: 'auto' // ⬅️ untuk mencegah overflow
|
||||
}}
|
||||
@@ -269,30 +278,33 @@ function StrukturPerangkatDesaNode() {
|
||||
fontSize: '0.875rem',
|
||||
padding: '6px 12px',
|
||||
minHeight: 'auto',
|
||||
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
|
||||
flexShrink: 0,
|
||||
},
|
||||
}}
|
||||
style={{ width: '100%' }} // 👈 penting
|
||||
>
|
||||
<TabsList
|
||||
style={{
|
||||
display: 'flex',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden', // 👈 tambahkan ini
|
||||
overflowY: 'hidden',
|
||||
gap: '4px',
|
||||
paddingBottom: '4px',
|
||||
flexWrap: 'nowrap',
|
||||
WebkitOverflowScrolling: 'touch', // 👈 smooth scroll di iOS
|
||||
scrollbarWidth: 'thin', // 👈 scrollbar tipis di Firefox
|
||||
msOverflowStyle: '-ms-autohiding-scrollbar', // 👈 untuk IE/Edge
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: 'thin',
|
||||
msOverflowStyle: '-ms-autohiding-scrollbar',
|
||||
maxWidth: '100%',
|
||||
scrollBehavior: 'smooth', // 👈 smooth scroll
|
||||
}}
|
||||
>
|
||||
<TabsTab
|
||||
value="zoom-out"
|
||||
onClick={handleZoomOut}
|
||||
leftSection={<IconZoomOut size={16} />}
|
||||
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Zoom Out
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
|
||||
</TabsTab>
|
||||
|
||||
<Box
|
||||
@@ -301,7 +313,6 @@ function StrukturPerangkatDesaNode() {
|
||||
px={12}
|
||||
py={6}
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
borderRadius: '6px',
|
||||
minWidth: 60,
|
||||
@@ -310,10 +321,12 @@ function StrukturPerangkatDesaNode() {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
whiteSpace: 'nowrap', // 👈 mencegah text wrap
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{Math.round(scale * 100)}%
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
|
||||
{Math.round(scale * 100)}%
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<TabsTab
|
||||
@@ -322,7 +335,7 @@ function StrukturPerangkatDesaNode() {
|
||||
leftSection={<IconZoomIn size={16} />}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Zoom In
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
|
||||
</TabsTab>
|
||||
|
||||
<TabsTab
|
||||
@@ -330,7 +343,7 @@ function StrukturPerangkatDesaNode() {
|
||||
onClick={resetZoom}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Reset
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
|
||||
</TabsTab>
|
||||
|
||||
<TabsTab
|
||||
@@ -345,7 +358,9 @@ function StrukturPerangkatDesaNode() {
|
||||
}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
|
||||
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
||||
</Text>
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
@@ -451,17 +466,17 @@ function NodeCard({ node, router }: any) {
|
||||
{/* Name */}
|
||||
<Text
|
||||
fw={700}
|
||||
size="sm"
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
lineClamp={2}
|
||||
fz={{ base: 13, md: 15 }}
|
||||
lh={1.2}
|
||||
style={{
|
||||
minHeight: 40,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.3,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
@@ -469,18 +484,18 @@ function NodeCard({ node, router }: any) {
|
||||
|
||||
{/* Title/Position */}
|
||||
<Text
|
||||
size="xs"
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
fw={500}
|
||||
lineClamp={2}
|
||||
fz={{ base: 12, md: 13 }}
|
||||
lh={1.3}
|
||||
style={{
|
||||
minHeight: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@@ -496,14 +511,14 @@ function NodeCard({ node, router }: any) {
|
||||
mt={8}
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/desa/profile/struktur-perangkat-desa/${node.data.id}`)
|
||||
router.push(`/darmasaba/desa/profil/struktur-perangkat-desa/${node.data.id}`)
|
||||
}
|
||||
style={{
|
||||
height: 32,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Lihat Detail
|
||||
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
|
||||
import colors from '@/con/colors'
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'
|
||||
import { useEffect } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
|
||||
@@ -26,6 +26,8 @@ function LambangDesa() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="lg">
|
||||
|
||||
{/* HEADER */}
|
||||
<Box pb="lg">
|
||||
<Center>
|
||||
<Image
|
||||
@@ -36,17 +38,20 @@ function LambangDesa() {
|
||||
loading="lazy"
|
||||
/>
|
||||
</Center>
|
||||
<Text
|
||||
|
||||
{/* TITLE - H1 */}
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={800}
|
||||
fz={{ base: 28, md: 40 }}
|
||||
mt="sm"
|
||||
style={{ letterSpacing: '-0.5px' }}
|
||||
>
|
||||
Lambang Desa
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
{/* DESKRIPSI */}
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="xl"
|
||||
@@ -58,15 +63,20 @@ function LambangDesa() {
|
||||
borderColor: '#e0e9ff',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
||||
lh={1.8}
|
||||
c="dark"
|
||||
ta="justify"
|
||||
style={{ fontWeight: 400, wordBreak: "break-word", whiteSpace: "normal", }}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }} // Body text mobile & desktop
|
||||
lh={1.7}
|
||||
c="dark"
|
||||
ta="justify"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
|
||||
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconPhoto } from '@tabler/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -21,7 +21,9 @@ function MaskotDesa() {
|
||||
<Center mih={500}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader size="lg" color="blue" />
|
||||
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text>
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
|
||||
Sedang memuat data maskot desa...
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
@@ -31,8 +33,21 @@ function MaskotDesa() {
|
||||
<Box>
|
||||
<Stack align="center" gap="xl">
|
||||
<Stack align="center" gap={10}>
|
||||
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} loading="lazy"/>
|
||||
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
|
||||
<Image
|
||||
src="/pudak-icon.png"
|
||||
alt="Ikon Desa"
|
||||
w={{ base: 160, md: 240 }}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* Page Title */}
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
Maskot Desa
|
||||
</Title>
|
||||
</Stack>
|
||||
|
||||
<Paper
|
||||
@@ -42,48 +57,60 @@ function MaskotDesa() {
|
||||
withBorder
|
||||
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
|
||||
>
|
||||
{/* Body Description */}
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'lg' }}
|
||||
lh={1.7}
|
||||
ta="justify"
|
||||
c="dark"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
|
||||
<Group justify="center" gap="lg" mt="lg">
|
||||
{data.images.length > 0 ? (
|
||||
data.images.map((img, index) => (
|
||||
<Card
|
||||
<Card
|
||||
key={index}
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w={220}
|
||||
p="sm"
|
||||
style={{
|
||||
transition: 'transform 200ms ease, box-shadow 200ms ease',
|
||||
}}
|
||||
className="hover:scale-105 hover:shadow-lg"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w={220}
|
||||
p="sm"
|
||||
style={{
|
||||
transition: 'transform 200ms ease, box-shadow 200ms ease',
|
||||
}}
|
||||
className="hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
alt={img.label}
|
||||
w="100%"
|
||||
h={200}
|
||||
fit="cover"
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* Image Label */}
|
||||
<Text
|
||||
ta="center"
|
||||
mt="sm"
|
||||
fw={600}
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dark"
|
||||
>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
alt={img.label}
|
||||
w="100%"
|
||||
h={200}
|
||||
fit="cover"
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text ta="center" mt="sm" fw={600} fz="sm" c="dark">
|
||||
{img.label}
|
||||
</Text>
|
||||
</Card>
|
||||
{img.label}
|
||||
</Text>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<Stack align="center" gap="xs" mt="lg">
|
||||
<IconPhoto size={48} stroke={1.5} color="gray" />
|
||||
<Text c="dimmed" fz="sm">Belum ada gambar maskot yang ditambahkan</Text>
|
||||
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>
|
||||
Belum ada gambar maskot yang ditambahkan
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
@@ -1,35 +1,15 @@
|
||||
'use client'
|
||||
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||
import { motion } from 'framer-motion';
|
||||
import { IconSparkles } from '@tabler/icons-react';
|
||||
import colors from '@/con/colors';
|
||||
|
||||
const dataText = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Santun",
|
||||
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Adaptif",
|
||||
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Inovatif",
|
||||
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Profesional",
|
||||
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Gesit",
|
||||
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
|
||||
},
|
||||
{ id: 1, title: "Santun", description: "Pelayanan ramah, penuh empati, sopan, dan beretika." },
|
||||
{ id: 2, title: "Adaptif", description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif." },
|
||||
{ id: 3, title: "Inovatif", description: "Berani menciptakan pembaruan dan ide-ide kreatif." },
|
||||
{ id: 4, title: "Profesional", description: "Berpengetahuan luas, terampil, dan bertanggung jawab." },
|
||||
{ id: 5, title: "Gesit", description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja." },
|
||||
];
|
||||
|
||||
const letters = ["S", "I", "G", "A", "P"];
|
||||
@@ -38,11 +18,14 @@ function MotoDesa() {
|
||||
return (
|
||||
<Box px={{ base: "md", md: "xl" }}>
|
||||
<Stack align="center" gap="lg">
|
||||
{/* Page Title */}
|
||||
<Box>
|
||||
<Text
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
fw={800}
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
fz={{ base: 28, md: 36 }}
|
||||
lh={{ base: 1.2, md: 1.3 }}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
|
||||
WebkitBackgroundClip: "text",
|
||||
@@ -50,9 +33,10 @@ function MotoDesa() {
|
||||
}}
|
||||
>
|
||||
Moto Desa Darmasaba
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
{/* Letter Icons */}
|
||||
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
|
||||
{letters.map((letter, i) => (
|
||||
<motion.div
|
||||
@@ -71,7 +55,7 @@ function MotoDesa() {
|
||||
backdropFilter: "blur(6px)",
|
||||
}}
|
||||
>
|
||||
<Text c="white" fw={800} fz="xl">
|
||||
<Text c="white" fw={800} fz={{ base: 20, md: 24 }}>
|
||||
{letter}
|
||||
</Text>
|
||||
</ActionIcon>
|
||||
@@ -79,6 +63,7 @@ function MotoDesa() {
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
{/* Values Card */}
|
||||
<Paper
|
||||
radius="lg"
|
||||
p="xl"
|
||||
@@ -90,19 +75,22 @@ function MotoDesa() {
|
||||
>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
||||
{dataText.map((v) => (
|
||||
<motion.div
|
||||
key={v.id}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<motion.div key={v.id} whileHover={{ scale: 1.02 }} transition={{ duration: 0.2 }}>
|
||||
<Stack gap={4}>
|
||||
{/* Section Title */}
|
||||
<Flex align="center" gap="sm">
|
||||
<IconSparkles size={20} color={colors['blue-button']} />
|
||||
<Text fw={700} fz={{ base: "lg", md: "xl" }} c={colors['blue-button']}>
|
||||
<Title
|
||||
order={3}
|
||||
fw={700}
|
||||
fz={{ base: 20, md: 24 }}
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
{v.title}
|
||||
</Text>
|
||||
</Title>
|
||||
</Flex>
|
||||
<Text fz={{ base: "sm", md: "md" }} c="gray.7">
|
||||
{/* Body Text */}
|
||||
<Text fz={{ base: 14, md: 16 }} lh={{ base: 1.5, md: 1.6 }} c="gray.7">
|
||||
{v.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -111,16 +99,15 @@ function MotoDesa() {
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
{/* Motto Description */}
|
||||
<Text
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: "md", md: "xl" }}
|
||||
fz={{ base: 15, md: 20 }}
|
||||
lh={{ base: 1.6, md: 1.8 }}
|
||||
c="blue.8"
|
||||
mt="md"
|
||||
style={{
|
||||
maxWidth: 720,
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
style={{ maxWidth: 720 }}
|
||||
>
|
||||
"Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
|
||||
<Text span fw={800} c="cyan.6">
|
||||
|
||||
@@ -2,44 +2,45 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function ProfilPerbekel() {
|
||||
const state = useProxy(stateProfileDesa.profilPerbekel)
|
||||
const state = useProxy(stateProfileDesa.profilPerbekel);
|
||||
|
||||
useEffect(() => {
|
||||
state.findUnique.load("edit")
|
||||
}, [])
|
||||
state.findUnique.load("edit");
|
||||
}, []);
|
||||
|
||||
const { data, loading } = state.findUnique
|
||||
const { data, loading } = state.findUnique;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py={20} px="md">
|
||||
<Skeleton h={500} radius="lg" />
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px="md">
|
||||
{/* ===== PAGE TITLE ===== */}
|
||||
<Stack align="center" gap={0} mb={40}>
|
||||
<Text
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw="bold"
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
style={{ letterSpacing: "0.5px" }}
|
||||
>
|
||||
Profil Perbekel
|
||||
</Text>
|
||||
</Title>
|
||||
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
|
||||
</Stack>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
|
||||
{/* ========== FOTO PERBEKEL ========== */}
|
||||
<Box>
|
||||
<Paper
|
||||
bg={colors['white-trans-1']}
|
||||
@@ -60,6 +61,8 @@ function ProfilPerbekel() {
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* ===== NAMA DAN JABATAN ===== */}
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
px="lg"
|
||||
@@ -67,22 +70,23 @@ function ProfilPerbekel() {
|
||||
className="glass3"
|
||||
py={{ base: 20, md: 50 }}
|
||||
>
|
||||
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
|
||||
<Title order={3} c={colors['white-1']}>
|
||||
Perbekel Desa Darmasaba
|
||||
</Text>
|
||||
<Text
|
||||
</Title>
|
||||
|
||||
<Title
|
||||
order={2}
|
||||
c={colors['white-1']}
|
||||
fw="bolder"
|
||||
fz={{ base: "xl", md: "h2" }}
|
||||
mt={8}
|
||||
>
|
||||
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
|
||||
</Text>
|
||||
</Title>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{/* ========== BIODATA & PENGALAMAN ========== */}
|
||||
<Paper
|
||||
p="xl"
|
||||
bg={colors['white-trans-1']}
|
||||
@@ -92,34 +96,39 @@ function ProfilPerbekel() {
|
||||
withBorder
|
||||
>
|
||||
<Stack gap="xl">
|
||||
|
||||
{/* ===== BIODATA ===== */}
|
||||
<Box>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconUser size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text>
|
||||
<Title order={3}>Biodata</Title>
|
||||
</Stack>
|
||||
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
fz={{ base: "sm", md: "md" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
lh={1.7}
|
||||
dangerouslySetInnerHTML={{ __html: data.biodata }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
style={{ wordBreak: "break-word" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* ===== PENGALAMAN ===== */}
|
||||
<Box>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconBriefcase size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
|
||||
<Title order={3}>Pengalaman</Title>
|
||||
</Stack>
|
||||
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
fz={{ base: "sm", md: "md" }}
|
||||
ta="left"
|
||||
lh={1.6}
|
||||
lh={1.7}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
style={{ wordBreak: "break-word" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
@@ -127,6 +136,7 @@ function ProfilPerbekel() {
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
|
||||
{/* ========== ORGANISASI & PROGRAM UNGGULAN ========== */}
|
||||
<Paper
|
||||
p="xl"
|
||||
bg={colors['white-trans-1']}
|
||||
@@ -136,35 +146,41 @@ function ProfilPerbekel() {
|
||||
withBorder
|
||||
>
|
||||
<Stack gap="xl">
|
||||
|
||||
{/* ===== PENGALAMAN ORGANISASI ===== */}
|
||||
<Box>
|
||||
<Stack align="center" gap={6} >
|
||||
<Stack align="center" gap={6}>
|
||||
<IconUsers size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text>
|
||||
<Title order={3}>Pengalaman Organisasi</Title>
|
||||
</Stack>
|
||||
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
fz={{ base: "sm", md: "md" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
lh={1.7}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
style={{ wordBreak: "break-word" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* ===== PROGRAM UNGGULAN ===== */}
|
||||
<Box>
|
||||
<Stack align="center" gap={6} mb={6}>
|
||||
<IconTargetArrow size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Program Kerja Unggulan</Text>
|
||||
<Title order={3}>Program Kerja Unggulan</Title>
|
||||
</Stack>
|
||||
|
||||
<Box px={10}>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
fz={{ base: "sm", md: "md" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
lh={1.7}
|
||||
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
style={{ wordBreak: "break-word" }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
@@ -26,29 +26,32 @@ function SejarahDesa() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="xl">
|
||||
{/* HEADER ICON + TITLE */}
|
||||
<Stack align="center" gap="sm">
|
||||
<Center>
|
||||
<Image
|
||||
src="/darmasaba-icon.png"
|
||||
alt="Ikon Desa Darmasaba"
|
||||
w={{ base: 180, md: 260 }}
|
||||
w={{ base: 160, md: 240 }}
|
||||
radius="md"
|
||||
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
<Center>
|
||||
<Text
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: '2rem', md: '2.8rem' }}
|
||||
style={{ letterSpacing: '-0.5px' }}
|
||||
>
|
||||
Sejarah Desa
|
||||
</Text>
|
||||
</Title>
|
||||
</Center>
|
||||
</Stack>
|
||||
|
||||
{/* CONTENT */}
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="lg"
|
||||
@@ -61,10 +64,14 @@ function SejarahDesa() {
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={1.8}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.75}
|
||||
ta="justify"
|
||||
style={{ color: '#2a2a2a', wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
c="dark.7"
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -28,8 +28,10 @@ function SemuaPerbekel() {
|
||||
<Center py="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<IconUser size={48} stroke={1.5} />
|
||||
<Title fw="bold" order={2}>Belum ada data Perbekel</Title>
|
||||
<Text c="dimmed" fz="sm" ta="center">Data mantan Perbekel akan muncul di sini ketika sudah tersedia</Text>
|
||||
<Title order={2} ta="center">Belum ada data Perbekel</Title>
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }} lh={{ base: 1.4, md: 1.6 }} ta="center">
|
||||
Data mantan Perbekel akan muncul di sini ketika sudah tersedia
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
@@ -38,17 +40,20 @@ function SemuaPerbekel() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="lg">
|
||||
<Box>
|
||||
<Text
|
||||
ta="center"
|
||||
fw={900}
|
||||
fz={{ base: "2rem", md: "2.5rem" }}
|
||||
variant="gradient"
|
||||
gradient={{ from: "blue", to: "cyan", deg: 45 }}
|
||||
>
|
||||
Perbekel Dari Masa ke Masa
|
||||
</Text>
|
||||
</Box>
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
style={{
|
||||
background: 'linear-gradient(45deg, blue, cyan)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
}}
|
||||
fz={{ base: 28, md: 36 }}
|
||||
lh={{ base: 1.2, md: 1.3 }}
|
||||
fw={900}
|
||||
>
|
||||
Perbekel Dari Masa ke Masa
|
||||
</Title>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
|
||||
{data.map((v: any, k: number) => (
|
||||
@@ -59,9 +64,7 @@ function SemuaPerbekel() {
|
||||
withBorder
|
||||
p="lg"
|
||||
bg="white"
|
||||
style={{
|
||||
transition: "all 250ms ease",
|
||||
}}
|
||||
style={{ transition: "all 250ms ease" }}
|
||||
className="hover:shadow-xl hover:scale-[1.02]"
|
||||
>
|
||||
<Stack gap="md" align="center">
|
||||
@@ -77,17 +80,17 @@ function SemuaPerbekel() {
|
||||
</Box>
|
||||
|
||||
<Stack gap={4} align="center">
|
||||
<Text fw={700} fz="lg" ta="center">
|
||||
{v.nama}
|
||||
</Text>
|
||||
<Title order={3} fz={{ base: 18, md: 20 }} ta="center" fw={700}>
|
||||
{v.nama}
|
||||
</Title>
|
||||
|
||||
<Text c="dimmed" fz="sm" ta="center">
|
||||
{v.daerah}
|
||||
</Text>
|
||||
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
|
||||
{v.daerah}
|
||||
</Text>
|
||||
|
||||
<Text c="blue" fw={600} fz="sm" ta="center">
|
||||
{v.periode}
|
||||
</Text>
|
||||
<Text c="blue" fw={600} fz={{ base: 12, md: 14 }} lh={{ base: 1.4, md: 1.6 }} ta="center">
|
||||
{v.periode}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
@@ -34,60 +34,57 @@ function VisiMisiDesa() {
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* VISI */}
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w="100%"
|
||||
style={{
|
||||
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
|
||||
}}
|
||||
style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
|
||||
>
|
||||
<Text
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: '2rem', md: '2.5rem' }}
|
||||
mb="md"
|
||||
>
|
||||
Visi Desa
|
||||
</Text>
|
||||
</Title>
|
||||
|
||||
<Text
|
||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
||||
fz={{ base: 'sm', md: 'md' }} // body text responsive
|
||||
lh={1.7}
|
||||
ta="center"
|
||||
fw={500}
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.visi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
{/* MISI */}
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w="100%"
|
||||
style={{
|
||||
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
|
||||
}}
|
||||
style={{ background: 'linear-gradient(145deg, #ffffff, #f5f7fa)' }}
|
||||
>
|
||||
<Text
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: '2rem', md: '2.5rem' }}
|
||||
mb="md"
|
||||
>
|
||||
Misi Desa
|
||||
</Text>
|
||||
</Title>
|
||||
|
||||
<Text
|
||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
||||
fw={500}
|
||||
lh={1.6}
|
||||
fz={{ base: 'sm', md: 'md' }} // body text responsive
|
||||
lh={1.7}
|
||||
ta="left"
|
||||
dangerouslySetInnerHTML={{ __html: data.misi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</Paper>
|
||||
</Stack>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||
import { Box, Flex, Group, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
@@ -30,196 +30,265 @@ function Page() {
|
||||
// Hasil akhir
|
||||
const sisaAnggaran = totalPendapatan - totalBelanja - totalPembiayaan;
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||
|
||||
{/* Page Title */}
|
||||
<Title
|
||||
ta="center"
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
order={1}
|
||||
fz={{ base: 28, md: 36 }}
|
||||
>
|
||||
Pendapatan Asli Desa
|
||||
</Text>
|
||||
</Title>
|
||||
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap="lg" justify="center">
|
||||
<Paper bg={colors['white-1']} p="xl">
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }}>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||
{/* Pendapatan Card */}
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Pendapatan</Title>
|
||||
{latestApb?.pendapatan?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="md" fw={500}>{item.name}</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Box
|
||||
p="md"
|
||||
style={{
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: '8px',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Title order={3} fz={{ base: 18, md: 20 }} c={colors['blue-button']}>
|
||||
Pendapatan
|
||||
</Title>
|
||||
|
||||
<Stack gap="sm">
|
||||
{latestApb?.pendapatan?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Flex gap={1}>
|
||||
<Text
|
||||
fz="md"
|
||||
fz={{ base: 13, md: 14 }}
|
||||
fw={500}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
lh={1.4}
|
||||
c="black"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(item.value)}
|
||||
{item.name} {formatCurrency(item.value)}
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}} fz="xl" fw={700} c={colors['blue-button']}>
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(totalPendapatan)}
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
pt="sm"
|
||||
mt="auto"
|
||||
style={{
|
||||
borderTop: `2px solid ${colors['blue-button']}`
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap={4}>
|
||||
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
|
||||
Total Pendapatan
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text
|
||||
fz={{ base: 18, md: 22 }}
|
||||
fw={700}
|
||||
c={colors['blue-button']}
|
||||
lh={1.4}
|
||||
>
|
||||
{formatCurrency(totalPendapatan)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Belanja Card */}
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Belanja</Title>
|
||||
{latestApb?.belanja?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="md" fw={500}>{item.name}</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text
|
||||
fz="md"
|
||||
<Box
|
||||
p="md"
|
||||
style={{
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: '8px',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Title order={3} fz={{ base: 18, md: 20 }} c="orange">
|
||||
Belanja
|
||||
</Title>
|
||||
|
||||
<Stack gap="sm">
|
||||
{latestApb?.belanja?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Group gap={1}>
|
||||
<Text
|
||||
fz={{ base: 13, md: 14 }}
|
||||
fw={500}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
lh={1.4}
|
||||
c="black"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}
|
||||
{item.name} {formatCurrency(item.value)}
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="xl" fw={700} c="orange">
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(totalBelanja)}
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
pt="sm"
|
||||
mt="auto"
|
||||
style={{
|
||||
borderTop: '2px solid orange'
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap={4}>
|
||||
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
|
||||
Total Belanja
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text
|
||||
fz={{ base: 18, md: 22 }}
|
||||
fw={700}
|
||||
c="orange"
|
||||
lh={1.4}
|
||||
>
|
||||
{formatCurrency(totalBelanja)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Pembiayaan Card */}
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Pembiayaan</Title>
|
||||
{latestApb?.pembiayaan?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="md" fw={500}>{item.name}</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text
|
||||
fz="md"
|
||||
<Box
|
||||
p="md"
|
||||
style={{
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: '8px',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Title order={3} fz={{ base: 18, md: 20 }} c="green">
|
||||
Pembiayaan
|
||||
</Title>
|
||||
|
||||
<Stack gap="sm">
|
||||
{latestApb?.pembiayaan?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Group gap={1}>
|
||||
<Text
|
||||
fz={{ base: 13, md: 14 }}
|
||||
fw={500}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
lh={1.4}
|
||||
c="black"
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}
|
||||
{item.name} {formatCurrency(item.value)}
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="xl" fw={700} c="green">
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(totalPembiayaan)}
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
pt="sm"
|
||||
mt="auto"
|
||||
style={{
|
||||
borderTop: '2px solid green'
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap={4}>
|
||||
<Text fz={{ base: 14, md: 16 }} fw={600} lh={1.4}>
|
||||
Total Pembiayaan
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text
|
||||
fz={{ base: 18, md: 22 }}
|
||||
fw={700}
|
||||
c="green"
|
||||
lh={1.4}
|
||||
>
|
||||
{formatCurrency(totalPembiayaan)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
{/* 🔽 Tambahan Ringkasan Anggaran */}
|
||||
<Paper bg={colors['white-1']} p="xl" shadow="sm" withBorder>
|
||||
<Title order={3} mb="md">Ringkasan Anggaran</Title>
|
||||
{/* Ringkasan Anggaran */}
|
||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="sm" withBorder>
|
||||
<Title order={3} mb="md" fz={{ base: 18, md: 20 }}>
|
||||
Ringkasan Anggaran
|
||||
</Title>
|
||||
<Table striped highlightOnHover withTableBorder>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Keterangan</Table.Th>
|
||||
<Table.Th ta={"right"}>Jumlah</Table.Th>
|
||||
<Table.Th>
|
||||
<Text fz={{ base: 13, md: 14 }} fw={600}>Keterangan</Text>
|
||||
</Table.Th>
|
||||
<Table.Th ta="right">
|
||||
<Text fz={{ base: 13, md: 14 }} fw={600}>Jumlah</Text>
|
||||
</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Table.Td>Total Pendapatan</Table.Td>
|
||||
<Table.Td align="right">
|
||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPendapatan)}
|
||||
<Table.Td>
|
||||
<Text fz={{ base: 13, md: 14 }} lh={1.4}>Total Pendapatan</Text>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4}>
|
||||
{formatCurrency(totalPendapatan)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>Total Belanja</Table.Td>
|
||||
<Table.Td align="right" c="orange">
|
||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalBelanja)}
|
||||
<Table.Td>
|
||||
<Text fz={{ base: 13, md: 14 }} lh={1.4} c="orange">Total Belanja</Text>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="orange">
|
||||
{formatCurrency(totalBelanja)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>Total Pembiayaan</Table.Td>
|
||||
<Table.Td align="right" c="green">
|
||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(totalPembiayaan)}
|
||||
<Table.Td>
|
||||
<Text fz={{ base: 13, md: 14 }} lh={1.4} c="green">Total Pembiayaan</Text>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
<Text fz={{ base: 13, md: 14 }} fw={600} lh={1.4} c="green">
|
||||
{formatCurrency(totalPembiayaan)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td><b>Sisa Anggaran</b></Table.Td>
|
||||
<Table.Td align="right" c={sisaAnggaran >= 0 ? "blue" : "red"}>
|
||||
<b>
|
||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(sisaAnggaran)}
|
||||
</b>
|
||||
<Table.Tr style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<Table.Td>
|
||||
<Text fz={{ base: 14, md: 15 }} fw={700} lh={1.4}>Sisa Anggaran</Text>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
<Text
|
||||
fz={{ base: 14, md: 15 }}
|
||||
fw={700}
|
||||
c={sisaAnggaran >= 0 ? colors['blue-button'] : "red"}
|
||||
lh={1.4}
|
||||
>
|
||||
{formatCurrency(sisaAnggaran)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
</Table.Tbody>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Paper, Text, ColorSwatch, Flex, Skeleton } from '@mantine/core';
|
||||
import { Stack, Box, Paper, Text, ColorSwatch, Flex, Skeleton, Title } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { BarChart } from '@mantine/charts';
|
||||
@@ -32,23 +32,47 @@ function Page() {
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
lh={1.2}
|
||||
style={{ lineHeight: 1.2 }}
|
||||
>
|
||||
Demografi Pekerjaan
|
||||
</Title>
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
c="black"
|
||||
style={{ lineHeight: 1.5 }}
|
||||
>
|
||||
Desa Darmasaba memiliki komposisi penduduk yang beragam dalam sektor pekerjaan
|
||||
</Text>
|
||||
<Text ta={'center'} fz={'h4'}>Desa Darmasaba memiliki komposisi penduduk yang beragam dalam sektor pekerjaan</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Paper p={'xl'}>
|
||||
<Box style={{overflowX: 'scroll'}}>
|
||||
<Text pb={5} fw={'bold'} fz={'h4'}>Statistik Demografi Pekerjaan Di Desa Darmasaba</Text>
|
||||
<Box style={{ overflowX: 'auto' }} w={"100%"}>
|
||||
<Text
|
||||
pb={5}
|
||||
fw={'bold'}
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={1.2}
|
||||
c="black"
|
||||
style={{ lineHeight: 1.2 }}
|
||||
>
|
||||
Statistik Demografi Pekerjaan Di Desa Darmasaba
|
||||
</Text>
|
||||
<BarChart
|
||||
type='stacked'
|
||||
p={10}
|
||||
mb={50}
|
||||
h={400}
|
||||
w={Math.max(data.length * 120, 800)} // auto lebar sesuai jumlah data
|
||||
w={Math.max(data.length * 120, 800)}
|
||||
data={data.map((item) => ({
|
||||
id: item.id,
|
||||
Pekerjaan: item.pekerjaan,
|
||||
@@ -62,28 +86,45 @@ function Page() {
|
||||
]}
|
||||
tickLine="y"
|
||||
xAxisProps={{
|
||||
angle: -45, // Rotate labels by -45 degrees
|
||||
textAnchor: 'end', // Anchor text to the end for better alignment
|
||||
height: 100, // Increase height for rotated labels
|
||||
interval: 0, // Show all labels
|
||||
angle: -45,
|
||||
textAnchor: 'end',
|
||||
height: 100,
|
||||
interval: 0,
|
||||
style: {
|
||||
fontSize: '12px', // Adjust font size if needed
|
||||
fontSize: '12px',
|
||||
overflow: 'visible',
|
||||
whiteSpace: 'nowrap'
|
||||
whiteSpace: 'nowrap',
|
||||
lineHeight: 1.4,
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Laki-Laki</Text>
|
||||
<Flex gap={{ base: 7, md: 5 }} align={'center'}>
|
||||
<Text
|
||||
fw={'bold'}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.2}
|
||||
c="black"
|
||||
style={{ lineHeight: 1.2 }}
|
||||
>
|
||||
Laki-Laki
|
||||
</Text>
|
||||
<ColorSwatch color="#5082EE" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Perempuan</Text>
|
||||
<Flex gap={{ base: 7, md: 5 }} align={'center'}>
|
||||
<Text
|
||||
fw={'bold'}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.2}
|
||||
c="black"
|
||||
style={{ lineHeight: 1.2 }}
|
||||
>
|
||||
Perempuan
|
||||
</Text>
|
||||
<ColorSwatch color="#6EDF9C" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
@@ -95,4 +136,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,7 +2,7 @@
|
||||
import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin';
|
||||
import colors from '@/con/colors';
|
||||
import { BarChart } from '@mantine/charts';
|
||||
import { Box, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Paper, Skeleton, Stack, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -17,13 +17,10 @@ function Page() {
|
||||
const state = useProxy(jumlahPendudukMiskin)
|
||||
const [chartData, setChartData] = useState<JPMGrafik[]>([])
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findMany.load()
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (state.findMany.data) {
|
||||
setChartData(state.findMany.data.map((item) => ({
|
||||
@@ -48,20 +45,30 @@ function Page() {
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title
|
||||
order={1}
|
||||
ta={"center"}
|
||||
c={colors["blue-button"]}
|
||||
fw={"bold"}
|
||||
lh={1.1}
|
||||
>
|
||||
Jumlah Penduduk Miskin
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Paper p={'xl'}>
|
||||
<Text fz={'h3'}>Jumlah Data Penduduk Miskin</Text>
|
||||
<Text fw={"bold"} fz={'h1'}>
|
||||
<Title order={3} fw={'normal'} lh={1.1}>
|
||||
Jumlah Data Penduduk Miskin
|
||||
</Title>
|
||||
<Title order={2} fw={"bold"} lh={1.1}>
|
||||
{state.findMany.data?.reduce((sum, item) => sum + (Number(item.totalPoorPopulation) || 0), 0).toLocaleString()} Orang
|
||||
</Text>
|
||||
</Title>
|
||||
</Paper>
|
||||
<Paper p={'xl'}>
|
||||
<Text pb={10} fw={'bold'} fz={'h4'}>Jumlah Penduduk Miskin Per Tahun</Text>
|
||||
<Title order={3} pb={10} fw={'bold'} lh={1.1}>
|
||||
Jumlah Penduduk Miskin Per Tahun
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={chartData}
|
||||
@@ -79,4 +86,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -3,7 +3,7 @@
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
import colors from '@/con/colors';
|
||||
import { PieChart } from '@mantine/charts';
|
||||
import { Box, Center, ColorSwatch, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Center, ColorSwatch, Flex, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -56,7 +56,7 @@ function Page() {
|
||||
}
|
||||
}, [stateGrafikNganggurPendidikan.findMany.data])
|
||||
|
||||
if (!stateGrafikNganggur.findMany.data) {
|
||||
if (!stateGrafikNganggur.findMany.data || !stateGrafikNganggurPendidikan.findMany.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
@@ -64,114 +64,151 @@ function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
if (!stateGrafikNganggur.findMany.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22" style={{ overflow: 'auto' }}>
|
||||
<Box px={{ base: 'md', md: 50, lg: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 50, lg: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Box px={{ base: 'md', md: 50, lg: 100 }}>
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
style={{ lineHeight: 1.15 }}
|
||||
>
|
||||
Jumlah Penduduk Usia Kerja Yang Menganggur
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 50, lg: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Paper p={'lg'}>
|
||||
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Usia</Text>
|
||||
{mounted && donutGrafikNganggurData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<Box w="100%" maw={{ base: '100%', md: 400 }} mx="auto">
|
||||
<PieChart
|
||||
w="100%"
|
||||
h={250} // lebih kecil biar aman di mobile
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurData}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
<Title
|
||||
order={2}
|
||||
fw="bold"
|
||||
style={{ lineHeight: 1.2 }}
|
||||
>
|
||||
Pengangguran Berdasarkan Usia
|
||||
</Title>
|
||||
{mounted && donutGrafikNganggurData.length > 0 ? (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<Box w="100%" maw={{ base: '100%', md: 400 }} mx="auto">
|
||||
<PieChart
|
||||
w="100%"
|
||||
h={250}
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurData}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>) : <Skeleton h={500} />}
|
||||
) : (
|
||||
<Skeleton h={500} />
|
||||
)}
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'} wrap="wrap">
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>18-25</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
18-25
|
||||
</Text>
|
||||
<ColorSwatch color="#4b6Ef5" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>26-35</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
26-35
|
||||
</Text>
|
||||
<ColorSwatch color="#14b885" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>36-45</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
36-45
|
||||
</Text>
|
||||
<ColorSwatch color="#E6A03B" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>46+</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
46+
|
||||
</Text>
|
||||
<ColorSwatch color="#DB524D" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Paper>
|
||||
<Paper p={'lg'}>
|
||||
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Pendidikan</Text>
|
||||
{mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (<Center>
|
||||
<Box w="100%" style={{ maxWidth: 400, margin: "0 auto" }}>
|
||||
<PieChart
|
||||
w="100%"
|
||||
h="min(250px, 50vh)" // lebih kecil biar aman di mobile
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurDataPendidikan}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
</Box>
|
||||
</Center>) : <Skeleton h={500} />}
|
||||
<Title
|
||||
order={2}
|
||||
fw="bold"
|
||||
style={{ lineHeight: 1.2 }}
|
||||
>
|
||||
Pengangguran Berdasarkan Pendidikan
|
||||
</Title>
|
||||
{mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (
|
||||
<Center>
|
||||
<Box w="100%" style={{ maxWidth: 400, margin: "0 auto" }}>
|
||||
<PieChart
|
||||
w="100%"
|
||||
h="min(250px, 50vh)"
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurDataPendidikan}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
</Box>
|
||||
</Center>
|
||||
) : (
|
||||
<Skeleton h={500} />
|
||||
)}
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'} wrap="wrap">
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SD</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
SD
|
||||
</Text>
|
||||
<ColorSwatch color="#4b6Ef5" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMP</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
SMP
|
||||
</Text>
|
||||
<ColorSwatch color="#14b885" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMA/SMK</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
SMA/SMK
|
||||
</Text>
|
||||
<ColorSwatch color="#E6A03B" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>D3</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
D3
|
||||
</Text>
|
||||
<ColorSwatch color="#DB524D" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>S1</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} style={{ lineHeight: 1.45 }}>
|
||||
S1
|
||||
</Text>
|
||||
<ColorSwatch color="#1018A8FF" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
@@ -183,4 +220,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -36,7 +36,6 @@ function Page() {
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
if (state.findMany.data) {
|
||||
// Set chart data
|
||||
setChartData(state.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
bulan: item.month,
|
||||
@@ -44,7 +43,6 @@ function Page() {
|
||||
takberpendidikan: Number(item.uneducatedUnemployment),
|
||||
})));
|
||||
|
||||
// Calculate yearly totals
|
||||
const currentYearData = state.findMany.data.filter(item => item.year === currentYear);
|
||||
if (currentYearData.length > 0) {
|
||||
const yearlyTotal = {
|
||||
@@ -72,30 +70,37 @@ function Page() {
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Title
|
||||
order={1}
|
||||
ta={"center"}
|
||||
c={colors["blue-button"]}
|
||||
fw={"bold"}
|
||||
lh={1.2}
|
||||
>
|
||||
Jumlah Pengangguran
|
||||
</Text>
|
||||
</Title>
|
||||
<Group py={20} align='center' justify='space-between'>
|
||||
<Text fz={'h4'} fw={"bold"}>DATA PENGANGGURAN DESA</Text>
|
||||
<Title order={2} fw={"bold"} lh={1.2}>
|
||||
DATA PENGANGGURAN DESA
|
||||
</Title>
|
||||
</Group>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<SimpleGrid
|
||||
cols={1}
|
||||
pb={20}
|
||||
>
|
||||
<SimpleGrid cols={1} pb={20}>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||
{/* Total Unemployment Card */}
|
||||
<Paper px={25} py={'lg'} bg={colors['white-1']} shadow="md">
|
||||
<Flex direction="column" gap="md">
|
||||
<IconUserOff size={35} color={colors['blue-button']} />
|
||||
<Text fz="h4" fw={600}>Total Pengangguran</Text>
|
||||
<Text fz="h2" fw={700} c={colors['blue-button']}>
|
||||
<Title order={3} fw={600} lh={1.2}>
|
||||
Total Pengangguran
|
||||
</Title>
|
||||
<Text fz={{ base: 'lg', md: 'xl' }} fw={700} c={colors['blue-button']} lh={1.2}>
|
||||
{yearlyData?.total.toLocaleString() || 0} Orang
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.4}>
|
||||
Total data tahun {currentYear}
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -105,11 +110,13 @@ function Page() {
|
||||
<Paper px={25} py={'lg'} bg={colors['white-1']} shadow="md">
|
||||
<Flex direction="column" gap="md">
|
||||
<IconSchool size={35} color="#5082EE" />
|
||||
<Text fz="h4" fw={600}>Pengangguran Terdidik</Text>
|
||||
<Text fz="h2" fw={700} c="#5082EE">
|
||||
<Title order={3} fw={600} lh={1.2}>
|
||||
Pengangguran Terdidik
|
||||
</Title>
|
||||
<Text fz={{ base: 'lg', md: 'xl' }} fw={700} c="#5082EE" lh={1.2}>
|
||||
{yearlyData?.educated.toLocaleString() || 0} Orang
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.4}>
|
||||
{yearlyData ?
|
||||
<>
|
||||
{((yearlyData.educated / yearlyData.total) * 100).toFixed(1)}%
|
||||
@@ -123,11 +130,13 @@ function Page() {
|
||||
<Paper px={25} py={'lg'} bg={colors['white-1']} shadow="md">
|
||||
<Flex direction="column" gap="md">
|
||||
<IconSchoolOff size={35} color="#DA524C" />
|
||||
<Text fz="h4" fw={600}>Pengangguran Tidak Terdidik</Text>
|
||||
<Text fz="h2" fw={700} c="#DA524C">
|
||||
<Title order={3} fw={600} lh={1.2}>
|
||||
Pengangguran Tidak Terdidik
|
||||
</Title>
|
||||
<Text fz={{ base: 'lg', md: 'xl' }} fw={700} c="#DA524C" lh={1.2}>
|
||||
{yearlyData?.uneducated.toLocaleString() || 0} Orang
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.4}>
|
||||
{yearlyData ?
|
||||
<>
|
||||
{((yearlyData.uneducated / yearlyData.total) * 100).toFixed(1)}%
|
||||
@@ -142,13 +151,17 @@ function Page() {
|
||||
<Flex pb={30} justify={'flex-end'} gap={'xl'} align={'center'}>
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Pengangguran Berpendidikan</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Pengangguran Berpendidikan
|
||||
</Text>
|
||||
<ColorSwatch color="#5082EE" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Pengangguran Tak Berpendidikan</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Pengangguran Tak Berpendidikan
|
||||
</Text>
|
||||
<ColorSwatch color="#DA524C" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
@@ -156,15 +169,24 @@ function Page() {
|
||||
{!mounted || chartData.length === 0 ? (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={3}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
<Title order={3} pb={10} lh={1.2}>
|
||||
Data Pengangguran Terdidik dan Tidak Terdidik
|
||||
</Title>
|
||||
<Text c='dimmed' fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
) : (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||
<Box w={{ base: '100%', md: '70%' }}>
|
||||
<Title order={3} pb={10} lh={1.2}>
|
||||
Data Pengangguran Terdidik dan Tidak Terdidik
|
||||
</Title>
|
||||
<Box
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
style={{ overflowX: "auto" }}
|
||||
>
|
||||
<BarChart
|
||||
h={450}
|
||||
data={chartData}
|
||||
@@ -178,32 +200,55 @@ function Page() {
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</Paper>
|
||||
<Paper p={'lg'}>
|
||||
<Text fw={'bold'} fz={'h4'}>Detail Data Pengangguran</Text>
|
||||
<Table striped highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh ta={'center'}>Bulan</TableTh>
|
||||
<TableTh ta={'center'}>Total</TableTh>
|
||||
<TableTh ta={'center'}>Terdidik</TableTh>
|
||||
<TableTh ta={'center'}>Tidak Terdidik</TableTh>
|
||||
<TableTh ta={'center'}>Perubahan</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{state.findMany.data?.map((item, index) => (
|
||||
<TableTr key={item?.id ? String(item.id) : `row-${index}`}>
|
||||
<TableTd ta={'center'}>{item.month}</TableTd>
|
||||
<TableTd ta={'center'}>{item.totalUnemployment}</TableTd>
|
||||
<TableTd ta={'center'}>{item.educatedUnemployment}</TableTd>
|
||||
<TableTd ta={'center'}>{item.uneducatedUnemployment}</TableTd>
|
||||
<TableTd ta={'center'}>{item.percentageChange}%</TableTd>
|
||||
<Title order={2} fw={'bold'} fz={{ base: 'md', md: 'lg' }} lh={1.2}>
|
||||
Detail Data Pengangguran
|
||||
</Title>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Bulan
|
||||
</TableTh>
|
||||
<TableTh ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Total
|
||||
</TableTh>
|
||||
<TableTh ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Terdidik
|
||||
</TableTh>
|
||||
<TableTh ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Tidak Terdidik
|
||||
</TableTh>
|
||||
<TableTh ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
Perubahan
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{state.findMany.data?.map((item, index) => (
|
||||
<TableTr key={item?.id ? String(item.id) : `row-${index}`}>
|
||||
<TableTd ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
{item.month}
|
||||
</TableTd>
|
||||
<TableTd ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
{item.totalUnemployment}
|
||||
</TableTd>
|
||||
<TableTd ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
{item.educatedUnemployment}
|
||||
</TableTd>
|
||||
<TableTd ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
{item.uneducatedUnemployment}
|
||||
</TableTd>
|
||||
<TableTd ta={'center'} fz={{ base: 'sm', md: 'md' }} lh={1.45}>
|
||||
{item.percentageChange}%
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
@@ -211,4 +256,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconBrandWhatsapp, IconBriefcase, IconCurrencyDollar, IconMapPin, IconPhone } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
@@ -33,18 +33,25 @@ function DetailLowonganKerjaUser() {
|
||||
);
|
||||
}
|
||||
|
||||
const formatRupiah = (value: number) =>
|
||||
new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
currency: "IDR",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
|
||||
return (
|
||||
<Stack bg={colors.Bg} py="xl" px={{ base: 'md', md: 100 }} align="center">
|
||||
<Box w={{ base: '100%', md: '70%' }}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
leftSection={<IconArrowBack size={20} />}
|
||||
mb="md"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
leftSection={<IconArrowBack size={20} />}
|
||||
mb="md"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
radius="lg"
|
||||
@@ -54,11 +61,17 @@ function DetailLowonganKerjaUser() {
|
||||
bg={colors['white-1']}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Judul */}
|
||||
<Text fz={{ base: '1.6rem', md: '2rem' }} fw={700} c={colors['blue-button']}>
|
||||
{/* Judul Posisi - H1 */}
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: 1.15 }}
|
||||
>
|
||||
{data.posisi}
|
||||
</Text>
|
||||
<Text c="dimmed" fz="sm">
|
||||
</Title>
|
||||
|
||||
{/* Tanggal Posting - Caption */}
|
||||
<Text c="dimmed" fz={{ base: 12, md: 'sm' }} lh={1.4}>
|
||||
Diposting: {new Date(data.createdAt).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
@@ -70,44 +83,72 @@ function DetailLowonganKerjaUser() {
|
||||
<Stack gap="sm" mt="md">
|
||||
<Group gap="xs">
|
||||
<IconBriefcase size={20} color={colors['blue-button']} />
|
||||
<Text fz="md" fw={600}>{data.namaPerusahaan}</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
fw={600}
|
||||
lh={1.5}
|
||||
>
|
||||
{data.namaPerusahaan}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.lokasi}</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
>
|
||||
{data.lokasi}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconPhone size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.notelp}</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
>
|
||||
{data.notelp}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconCurrencyDollar size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.gaji || '-'}</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
>
|
||||
{formatRupiah(Number(data.gaji)) || '-'}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconBriefcase size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.tipePekerjaan}</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
>
|
||||
{data.tipePekerjaan}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
{/* Deskripsi Pekerjaan - H2 */}
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" mb={4}>
|
||||
<Title order={2} mb={8} style={{ lineHeight: 1.2 }}>
|
||||
Deskripsi Pekerjaan
|
||||
</Text>
|
||||
</Title>
|
||||
<Text
|
||||
fz="sm"
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={1.6}
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Kualifikasi - H2 */}
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" mb={4}>
|
||||
<Title order={2} mb={8} style={{ lineHeight: 1.2 }}>
|
||||
Kualifikasi
|
||||
</Text>
|
||||
</Title>
|
||||
<Text
|
||||
fz="sm"
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={1.6}
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
dangerouslySetInnerHTML={{ __html: data.kualifikasi || '-' }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Button, Center, Flex, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { IconBriefcase, IconClock, IconMapPin, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -53,17 +53,19 @@ function Page() {
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} pb={80}>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title order={1} ta="center" c={colors["blue-button"]} fw="bold" lh={1.15}>
|
||||
Lowongan Kerja Lokal
|
||||
</Text>
|
||||
</Title>
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
radius={'xl'}
|
||||
w={{ base: 500, md: 700 }}
|
||||
w={{ base: '100%', md: 700 }}
|
||||
placeholder='Cari Pekerjaan'
|
||||
leftSection={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
@@ -80,30 +82,42 @@ function Page() {
|
||||
<Paper key={k} p={'xl'}>
|
||||
<Stack gap={'md'}>
|
||||
<Box>
|
||||
<Flex gap={'xl'} align={'center'}>
|
||||
<IconBriefcase color={colors['blue-button']} size={50} />
|
||||
<Flex gap={{ base: 'md', md: 'xl' }} align={'center'}>
|
||||
<IconBriefcase color={colors['blue-button']} size={40} />
|
||||
<Box>
|
||||
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.posisi}</Text>
|
||||
<Text fz={'h4'}>{v.namaPerusahaan}</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'lg', md: 'h4' }} c={colors['blue-button']} lh={1.3}>
|
||||
{v.posisi}
|
||||
</Text>
|
||||
<Text fz={{ base: 'md', md: 'h4' }} lh={1.5}>
|
||||
{v.namaPerusahaan}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={'xl'} align={'center'}>
|
||||
<IconMapPin color={colors['blue-button']} size={50} />
|
||||
<Text fz={'h4'}>{v.lokasi}</Text>
|
||||
<Flex gap={{ base: 'md', md: 'xl' }} align={'center'}>
|
||||
<IconMapPin color={colors['blue-button']} size={40} />
|
||||
<Text fz={{ base: 'md', md: 'h4' }} lh={1.5}>
|
||||
{v.lokasi}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={'xl'} align={'center'}>
|
||||
<IconClock color={colors['blue-button']} size={50} />
|
||||
<Flex gap={{ base: 'md', md: 'xl' }} align={'center'}>
|
||||
<IconClock color={colors['blue-button']} size={40} />
|
||||
<Box>
|
||||
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>Full Time</Text>
|
||||
<Text fz={'h4'}>{formatCurrency(v.gaji)}</Text>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }} c={colors['blue-button']} lh={1.3}>
|
||||
Full Time
|
||||
</Text>
|
||||
<Text fz={{ base: 'sm', md: 'h4' }} lh={1.5}>
|
||||
{formatCurrency(v.gaji)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Button onClick={() => router.push(`/darmasaba/ekonomi/lowongan-kerja-lokal/${v.id}`)}>Detail</Button>
|
||||
<Button onClick={() => router.push(`/darmasaba/ekonomi/lowongan-kerja-lokal/${v.id}`)} fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Detail
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
@@ -123,4 +137,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider } from '@mantine/core';
|
||||
import { IconArrowBack, IconMapPin, IconPhone, IconStar } from '@tabler/icons-react';
|
||||
import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider, Title } from '@mantine/core';
|
||||
import { IconArrowBack, IconBrandWhatsapp, IconMapPin, IconPhone, IconStar } from '@tabler/icons-react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -31,14 +31,16 @@ function DetailProdukPasarUser() {
|
||||
<Box py={20}>
|
||||
{/* Tombol kembali */}
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={20} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali ke daftar produk
|
||||
</Button>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={20} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
<Text fz={{ base: 'md', md: 'lg' }} lh={1.5}>
|
||||
Kembali ke daftar produk
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
@@ -65,26 +67,31 @@ function DetailProdukPasarUser() {
|
||||
<Box
|
||||
h={300}
|
||||
bg="gray.1"
|
||||
display="flex"
|
||||
style={{ alignItems: 'center', justifyContent: 'center', borderRadius: 'md' }}
|
||||
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 'var(--mantine-radius-md)' }}
|
||||
>
|
||||
<Text c="dimmed">Tidak ada gambar</Text>
|
||||
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Tidak ada gambar
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Detail Produk */}
|
||||
<Stack gap="xs">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
<Title order={2} lh={1.1} c={colors['blue-button']}>
|
||||
{data.nama || 'Produk Tanpa Nama'}
|
||||
</Text>
|
||||
</Title>
|
||||
<Group>
|
||||
<Badge color="green" size="lg" radius="md">
|
||||
Rp {data.harga?.toLocaleString('id-ID')}
|
||||
<Text c={"white"} fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.4}>
|
||||
Rp {data.harga?.toLocaleString('id-ID')}
|
||||
</Text>
|
||||
</Badge>
|
||||
{data.rating && (
|
||||
<Group gap={4}>
|
||||
<IconStar size={18} color="#FFD43B" />
|
||||
<Text fz="md" fw={500}>{data.rating}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={500} lh={1.5}>
|
||||
{data.rating}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
@@ -95,16 +102,20 @@ function DetailProdukPasarUser() {
|
||||
{/* Info Tambahan */}
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz="lg" fw={600}>Kategori</Text>
|
||||
<Title order={3} lh={1.15}>
|
||||
Kategori
|
||||
</Title>
|
||||
<Group gap="xs" mt={4}>
|
||||
{data.KategoriToPasar && data.KategoriToPasar.length > 0 ? (
|
||||
data.KategoriToPasar.map((kategori) => (
|
||||
<Badge key={kategori.id} color="blue" variant="light">
|
||||
<Badge key={kategori.id} color="blue" variant="light" fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||
{kategori.kategori.nama}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada kategori</Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.5}>
|
||||
Tidak ada kategori
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</Box>
|
||||
@@ -112,14 +123,18 @@ function DetailProdukPasarUser() {
|
||||
{data.alamatUsaha && (
|
||||
<Group gap={6}>
|
||||
<IconMapPin size={18} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.alamatUsaha}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
{data.alamatUsaha}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{data.kontak && (
|
||||
<Group gap={6}>
|
||||
<IconPhone size={18} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.kontak}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
{data.kontak}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -128,8 +143,10 @@ function DetailProdukPasarUser() {
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fz="lg" fw={600}>Deskripsi Produk</Text>
|
||||
<Text fz="md" c="dimmed" mt={4}>
|
||||
<Title order={3} lh={1.15}>
|
||||
Deskripsi Produk
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed" mt={4} lh={1.5}>
|
||||
Tidak ada deskripsi.
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -144,8 +161,11 @@ function DetailProdukPasarUser() {
|
||||
component="a"
|
||||
href={`https://wa.me/${data.kontak.replace(/[^0-9]/g, '')}`}
|
||||
target="_blank"
|
||||
leftSection={<IconBrandWhatsapp/>}
|
||||
>
|
||||
Hubungi Penjual via WhatsApp
|
||||
<Text c={"white"} fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Hubungi Penjual via WhatsApp
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -154,4 +174,4 @@ function DetailProdukPasarUser() {
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailProdukPasarUser;
|
||||
export default DetailProdukPasarUser;
|
||||
@@ -1,20 +1,20 @@
|
||||
'use client'
|
||||
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react';
|
||||
import { motion } from 'motion/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const state = useProxy(pasarDesaState.pasarDesa)
|
||||
const [search, setSearch] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const {
|
||||
data,
|
||||
@@ -28,18 +28,16 @@ function Page() {
|
||||
pasarDesaState.kategoriProduk.findManyAll.load()
|
||||
}, [])
|
||||
|
||||
// Filter data based on selected category
|
||||
const filteredData = selectedCategory
|
||||
? data?.filter(item =>
|
||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||
)
|
||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||
)
|
||||
: data;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 4, debouncedSearch, selectedCategory || undefined)
|
||||
}, [page, debouncedSearch, selectedCategory])
|
||||
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
@@ -49,44 +47,45 @@ function Page() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
||||
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title order={1} c={colors["blue-button"]} fw="bold" lh={1.15}>
|
||||
Pasar Desa
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
placeholder='Cari Produk'
|
||||
radius="lg"
|
||||
placeholder="Cari Produk"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={"100%"}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
||||
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan
|
||||
</Text>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
||||
dan memperkenalkan produk mereka.
|
||||
|
||||
<Text
|
||||
px={{ base: 'md', md: 100 }}
|
||||
pt={20}
|
||||
ta="justify"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.55 }}
|
||||
c="black"
|
||||
>
|
||||
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan dan memperkenalkan produk mereka.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
<SimpleGrid
|
||||
pb={30}
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 2
|
||||
}}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<SimpleGrid pb={30} cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Select
|
||||
placeholder="Pilih Kategori"
|
||||
@@ -100,53 +99,82 @@ function Page() {
|
||||
searchable
|
||||
nothingFoundMessage="Tidak ada kategori ditemukan"
|
||||
style={{ width: '100%' }}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.55 }}
|
||||
c="black"
|
||||
/>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 4 }}>
|
||||
{filteredData?.map((v, k) => {
|
||||
return (
|
||||
<Stack key={k}>
|
||||
<motion.div
|
||||
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.8 }}
|
||||
>
|
||||
<Paper p={'lg'}>
|
||||
<Image
|
||||
radius={'lg'}
|
||||
src={v.image?.link || '/placeholder-product.jpg'}
|
||||
alt={v.nama}
|
||||
h={200}
|
||||
w='100%'
|
||||
style={{ objectFit: 'cover' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text>
|
||||
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text>
|
||||
<Flex py={10} gap={'md'}>
|
||||
<IconStarFilled size={20} color='#EBCB09' />
|
||||
<Text fz={'sm'} ml={2}>{v.rating}</Text>
|
||||
</Flex>
|
||||
<Flex justify={'space-between'} align={'center'}>
|
||||
<Box>
|
||||
<Flex gap={'md'} align={'center'}>
|
||||
<IconMapPinFilled size={20} color='red' />
|
||||
<Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
|
||||
</Flex>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Stack>
|
||||
)
|
||||
})}
|
||||
{filteredData?.map((v, k) => (
|
||||
<Stack key={k}>
|
||||
<motion.div
|
||||
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.8 }}
|
||||
>
|
||||
<Paper p="lg">
|
||||
<Image
|
||||
radius="lg"
|
||||
src={v.image?.link || '/placeholder-product.jpg'}
|
||||
alt={v.nama}
|
||||
h={200}
|
||||
w="100%"
|
||||
style={{ objectFit: 'cover' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text
|
||||
py="sm"
|
||||
fw="bold"
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={{ base: 1.3, md: 1.25 }}
|
||||
c="black"
|
||||
>
|
||||
{v.nama}
|
||||
</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.55 }}
|
||||
c="black"
|
||||
>
|
||||
Rp {v.harga.toLocaleString('id-ID')}
|
||||
</Text>
|
||||
<Flex py="sm" gap="md" align="center">
|
||||
<IconStarFilled size={20} color="#EBCB09" />
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.4, md: 1.45 }}
|
||||
c="black"
|
||||
>
|
||||
{v.rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Box>
|
||||
<Flex gap="md" align="center">
|
||||
<IconMapPinFilled size={20} color="red" />
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.4, md: 1.45 }}
|
||||
c="black"
|
||||
>
|
||||
{v.alamatUsaha}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
|
||||
</Flex>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Stack>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
my="md"
|
||||
/>
|
||||
@@ -157,4 +185,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
@@ -32,10 +32,9 @@ interface ProgramKemiskinanData {
|
||||
|
||||
function Page() {
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const state = useProxy(programKemiskinanState)
|
||||
|
||||
// 🔧 Get valid statistics data with proper type checking
|
||||
const statistikData = state.findMany.data
|
||||
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
|
||||
return !!item?.statistik &&
|
||||
@@ -43,11 +42,11 @@ function Page() {
|
||||
item.statistik.jumlah !== undefined;
|
||||
})
|
||||
.map(item => ({
|
||||
tahun: Number(item.statistik.tahun) || 0, // Ensure tahun is a number
|
||||
jumlah: Number(item.statistik.jumlah) || 0, // Ensure jumlah is a number
|
||||
tahun: Number(item.statistik.tahun) || 0,
|
||||
jumlah: Number(item.statistik.jumlah) || 0,
|
||||
}))
|
||||
.sort((a, b) => a.tahun - b.tahun)
|
||||
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah)); // Remove any invalid entries
|
||||
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah));
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -70,34 +69,47 @@ function Page() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py={{ base: 'xl', md: 'xl' }} gap={'22'}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Grid align='center'>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Grid align="center">
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title
|
||||
order={1}
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
lh={{ base: 1.2, md: 1.2 }}
|
||||
>
|
||||
Program Kemiskinan
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
placeholder='Cari Program'
|
||||
radius="lg"
|
||||
placeholder="Cari Program"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w="100%"
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text fz={'h4'}>Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat</Text>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
c="black"
|
||||
ta="left"
|
||||
pt={{ base: 'sm', md: 20 }}
|
||||
>
|
||||
Berbagai program bantuan untuk mengurangi kemiskinan dan meningkatkan kesejahteraan masyarakat
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Stack gap={'lg'} justify="center">
|
||||
<SimpleGrid
|
||||
pb={10}
|
||||
pb={{ base: 'md', md: 10 }}
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 2
|
||||
@@ -105,14 +117,27 @@ function Page() {
|
||||
>
|
||||
{state.findMany.data.map(v => {
|
||||
return (
|
||||
<Paper p={'xl'} key={v.id}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
|
||||
<Text fz={'lg'} c={'black'} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
|
||||
<Paper p={{ base: 'lg', md: 'xl' }} key={v.id}>
|
||||
<Title
|
||||
order={3}
|
||||
fw="bold"
|
||||
c={colors['blue-button']}
|
||||
lh={{ base: 1.2, md: 1.2 }}
|
||||
>
|
||||
{v.nama}
|
||||
</Title>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
c="black"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Center my={10}>
|
||||
<Center my={{ base: 'md', md: 10 }}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
@@ -120,17 +145,25 @@ function Page() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}}
|
||||
total={totalPages}
|
||||
my={"md"}
|
||||
my="md"
|
||||
/>
|
||||
</Center>
|
||||
<Paper p={'xl'}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text>
|
||||
<Paper p={{ base: 'lg', md: 'xl' }}>
|
||||
<Title
|
||||
order={3}
|
||||
fw="bold"
|
||||
c={colors['blue-button']}
|
||||
lh={{ base: 1.2, md: 1.2 }}
|
||||
mb="md"
|
||||
>
|
||||
Statistik Kemiskinan Masyarakat
|
||||
</Title>
|
||||
<Box style={{ width: '100%', height: 'auto' }}>
|
||||
{statistikData.length > 0 ? (
|
||||
<Box w="100%" style={{ overflowX: 'auto' }}>
|
||||
<Center>
|
||||
<RechartsLineChart
|
||||
width={Math.min(800, window.innerWidth - 100)}
|
||||
width={Math.min(800, typeof window !== 'undefined' ? window.innerWidth - 100 : 800)}
|
||||
height={400}
|
||||
data={statistikData}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
@@ -139,10 +172,12 @@ function Page() {
|
||||
<XAxis
|
||||
dataKey="tahun"
|
||||
label={{ value: 'Tahun', position: 'insideBottomRight', offset: -5 }}
|
||||
tick={{ fontSize: 12 }}
|
||||
/>
|
||||
<YAxis
|
||||
label={{ value: 'Jumlah', angle: -90, position: 'insideLeft' }}
|
||||
domain={[0, 'auto']}
|
||||
tick={{ fontSize: 12 }}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value) => [`${value} orang`, 'Jumlah']}
|
||||
@@ -162,7 +197,11 @@ function Page() {
|
||||
</Box>
|
||||
) : (
|
||||
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
|
||||
<Text c="dimmed">
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dimmed"
|
||||
lh={{ base: 1.4, md: 1.4 }}
|
||||
>
|
||||
{state.findMany.loading
|
||||
? 'Memuat data statistik...'
|
||||
: 'Belum ada data statistik yang tersedia atau data tidak valid'}
|
||||
@@ -177,4 +216,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, Paper, Skeleton } from '@mantine/core';
|
||||
import { Stack, Box, Text, Paper, Skeleton, Center, Title } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { BarChart } from '@mantine/charts';
|
||||
@@ -28,16 +28,15 @@ function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
// Add this check before the return statement
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
|
||||
<Title order={1} c={colors['blue-button']} fw="bold">
|
||||
Sektor Unggulan Desa Darmasaba
|
||||
</Text>
|
||||
<Text c="dimmed" mt="md">
|
||||
</Title>
|
||||
<Text c="dimmed" mt="md" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Data sektor unggulan belum tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -53,49 +52,72 @@ function Page() {
|
||||
Ton: item.value,
|
||||
}));
|
||||
|
||||
const chartWidth = Math.max(600, chartData.length * 150);
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Title ta="center" order={1} c={colors['blue-button']} fw="bold">
|
||||
Sektor Unggulan Desa Darmasaba
|
||||
</Title>
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
mt="sm"
|
||||
>
|
||||
Desa Darmasaba dikenal sebagai desa dengan potensi unggulan di sektor pertanian dan peternakan
|
||||
</Text>
|
||||
<Text ta={'center'} fz={'h4'}> Desa Darmasaba dikenal sebagai desa dengan potensi unggulan di sektor pertanian dan peternakan</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="lg" justify="center">
|
||||
{data.map((v, k) => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Text fw={'bold'} fz={'h4'}>{v.name}</Text>
|
||||
<Text fz={'h4'} ta={'justify'} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.description || '' }} />
|
||||
<Paper p="xl" key={k}>
|
||||
<Title order={3} fw="bold">
|
||||
{v.name}
|
||||
</Title>
|
||||
<Text
|
||||
ta="justify"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: v.description || '' }}
|
||||
/>
|
||||
</Paper>
|
||||
)
|
||||
);
|
||||
})}
|
||||
<Box style={{ width: '100%', overflowX: 'auto' }}>
|
||||
<Paper p="xl">
|
||||
<Text pb={10} fw="bold" fz="h4">Statistik Sektor Unggulan Darmasaba</Text>
|
||||
<Box style={{ width: '100%', minWidth: '600px' }}>
|
||||
<BarChart
|
||||
p={10}
|
||||
h={300}
|
||||
data={chartData}
|
||||
dataKey="sektor"
|
||||
series={[
|
||||
{ name: 'Ton', color: colors['blue-button'] },
|
||||
]}
|
||||
tickLine="y"
|
||||
tooltipAnimationDuration={200}
|
||||
withTooltip
|
||||
style={{
|
||||
fontFamily: 'inherit',
|
||||
}}
|
||||
xAxisLabel="Sektor"
|
||||
yAxisLabel="Ton"
|
||||
/>
|
||||
<Title order={3} fw="bold" pb="md">
|
||||
Statistik Sektor Unggulan Darmasaba
|
||||
</Title>
|
||||
<Box style={{ width: '100%', overflowX: 'auto', maxWidth: `${chartWidth}px` }}>
|
||||
<Center>
|
||||
<BarChart
|
||||
p={10}
|
||||
h={300}
|
||||
data={chartData}
|
||||
dataKey="sektor"
|
||||
series={[
|
||||
{ name: 'Ton', color: colors['blue-button'] },
|
||||
]}
|
||||
tickLine="y"
|
||||
tooltipAnimationDuration={200}
|
||||
withTooltip
|
||||
withXAxis
|
||||
withYAxis
|
||||
xAxisLabel="Sektor"
|
||||
yAxisLabel="Ton"
|
||||
style={{
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
@@ -105,4 +127,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -0,0 +1,174 @@
|
||||
'use client';
|
||||
import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Group,
|
||||
Image,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailPegawaiBumdes() {
|
||||
const statePegawai = useProxy(stateStrukturBumDes.pegawai);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateStrukturBumDes.posisiOrganisasi.findMany.load();
|
||||
statePegawai.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
if (!statePegawai.findUnique.data) {
|
||||
return (
|
||||
<Stack py="lg">
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = statePegawai.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'md', md: 100 }} py="xl">
|
||||
{/* Back button */}
|
||||
<Group mb="lg" px={{ base: 'md', md: 100 }}>
|
||||
<Box
|
||||
onClick={() => router.back()}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<IconArrowBack size={22} color={colors['blue-button']} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh="1.4" fw={500} c={colors['blue-button']}>
|
||||
Kembali
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
mx="auto"
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
bg="white"
|
||||
style={{ border: '1px solid #eaeaea' }}
|
||||
>
|
||||
<Stack align="center" gap="md">
|
||||
{/* Foto Profil */}
|
||||
<Image
|
||||
src={data.image?.link || '/placeholder-profile.png'}
|
||||
alt={data.namaLengkap || 'Foto Profil'}
|
||||
w={160}
|
||||
h={160}
|
||||
radius={100}
|
||||
fit="cover"
|
||||
style={{ border: `2px solid ${colors['blue-button']}` }}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* Nama & Jabatan */}
|
||||
<Stack align="center" gap={2}>
|
||||
<Title
|
||||
order={2}
|
||||
c={colors['blue-button']}
|
||||
fw={700}
|
||||
fz={{ base: 'xl', md: '28px' }}
|
||||
lh="1.2"
|
||||
ta="center"
|
||||
>
|
||||
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
|
||||
</Title>
|
||||
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh="1.4"
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
>
|
||||
{data.posisi?.nama || 'Posisi tidak tersedia'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Divider my="lg" />
|
||||
|
||||
{/* Informasi Detail */}
|
||||
<Stack gap="md">
|
||||
<InfoRow label="Email" value={data.email} />
|
||||
<InfoRow label="Telepon" value={data.telepon} />
|
||||
<InfoRow label="Alamat" value={data.alamat} multiline />
|
||||
<InfoRow
|
||||
label="Tanggal Masuk"
|
||||
value={
|
||||
data.tanggalMasuk
|
||||
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '-'
|
||||
}
|
||||
/>
|
||||
<InfoRow
|
||||
label="Status"
|
||||
value={data.isActive ? 'Aktif' : 'Tidak Aktif'}
|
||||
valueColor={data.isActive ? 'green' : 'red'}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/* Komponen Baris Informasi */
|
||||
function InfoRow({
|
||||
label,
|
||||
value,
|
||||
valueColor,
|
||||
multiline = false,
|
||||
}: {
|
||||
label: string;
|
||||
value?: string | null;
|
||||
valueColor?: string;
|
||||
multiline?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Box>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
fw={600}
|
||||
lh="1.3"
|
||||
c="dark"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh="1.5"
|
||||
c={valueColor || 'dimmed'}
|
||||
style={{
|
||||
whiteSpace: multiline ? 'normal' : 'nowrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{value || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailPegawaiBumdes;
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
|
||||
import stateStrukturBumDes from '@/app/admin/(dashboard)/_state/ekonomi/struktur-organisasi/struktur-organisasi'
|
||||
import colors from '@/con/colors'
|
||||
import {
|
||||
@@ -14,6 +13,9 @@ import {
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTab,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
@@ -29,10 +31,13 @@ import {
|
||||
IconZoomOut,
|
||||
} from '@tabler/icons-react'
|
||||
import { debounce } from 'lodash'
|
||||
import { useTransitionRouter } from 'next-view-transitions'
|
||||
import { OrganizationChart } from 'primereact/organizationchart'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import BackButton from '../../desa/layanan/_com/BackButto'
|
||||
import '../../ppid/struktur-ppid/struktur.css'
|
||||
import { useMediaQuery } from '@mantine/hooks'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -44,18 +49,20 @@ export default function Page() {
|
||||
paddingBottom: 48,
|
||||
}}
|
||||
>
|
||||
<Box px={{ base: 'md', md: 100 }} py="xl">
|
||||
<Box px={{ base: 'md', md: 100 }} py={"xl"}>
|
||||
<BackButton />
|
||||
|
||||
<Stack align="center" gap="xl" mt="xl">
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 28, md: 36, lg: 44 }}
|
||||
lh={{ base: 1.05, md: 1.03 }}
|
||||
>
|
||||
Struktur Organisasi & SK Pengurus BumDes
|
||||
</Title>
|
||||
<Text ta="center" c="black" maw={800}>
|
||||
<Text ta="center" c="black" maw={800} fz={{ base: 14, md: 16 }} lh={1.6}>
|
||||
Gambaran visual peran dan pengurus yang ditugaskan. Gunakan kontrol
|
||||
di bawah untuk mencari, memperbesar, atau melihat lebih jelas.
|
||||
</Text>
|
||||
@@ -71,12 +78,17 @@ export default function Page() {
|
||||
|
||||
function StrukturOrganisasiBumDes() {
|
||||
const stateOrganisasi: any = useProxy(stateStrukturBumDes.pegawai)
|
||||
const router = useTransitionRouter()
|
||||
const chartContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [scale, setScale] = useState(1)
|
||||
const [isFullscreen, setFullscreen] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
// debounce pencarian
|
||||
const debouncedSearch = useRef(
|
||||
debounce((value: string) => setSearchQuery(value), 400)
|
||||
debounce((value: string) => {
|
||||
setSearchQuery(value)
|
||||
}, 1000)
|
||||
).current
|
||||
|
||||
useEffect(() => {
|
||||
@@ -84,16 +96,17 @@ function StrukturOrganisasiBumDes() {
|
||||
}, [])
|
||||
|
||||
const isLoading =
|
||||
!stateOrganisasi.findMany.data &&
|
||||
stateOrganisasi.findMany.loading !== false
|
||||
!stateOrganisasi.findMany.data && stateOrganisasi.findMany.loading !== false
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Center py={48}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader size="lg" />
|
||||
<Text fw={600}>Memuat struktur organisasi…</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
<Text fw={600} fz={{ base: 15, md: 16 }} lh={1.4}>
|
||||
Memuat struktur organisasi…
|
||||
</Text>
|
||||
<Text c="dimmed" fz={{ base: 12, md: 14 }} lh={1.4}>
|
||||
Mengambil data pengurus dan posisi. Mohon tunggu sebentar.
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -119,10 +132,10 @@ function StrukturOrganisasiBumDes() {
|
||||
<Center>
|
||||
<IconUsers size={56} />
|
||||
</Center>
|
||||
<Title order={3} mt="md">
|
||||
<Title order={3} mt="md" ta="center">
|
||||
Data pengurus belum tersedia
|
||||
</Title>
|
||||
<Text c="dimmed" mt="xs">
|
||||
<Text c="dimmed" mt="xs" fz={{ base: 12, md: 14 }} lh={1.4}>
|
||||
Belum ada data pengurus yang tercatat untuk BumDes.
|
||||
</Text>
|
||||
<Group justify="center" mt="lg">
|
||||
@@ -141,7 +154,7 @@ function StrukturOrganisasiBumDes() {
|
||||
)
|
||||
}
|
||||
|
||||
// 📊 susun struktur organisasi
|
||||
// 🧩 buat struktur organisasi
|
||||
const posisiMap = new Map<string, any>()
|
||||
const aktifPegawai = data.filter((p: any) => p.isActive)
|
||||
|
||||
@@ -175,7 +188,6 @@ function StrukturOrganisasiBumDes() {
|
||||
name: pegawai?.namaLengkap || 'Belum Ditugaskan',
|
||||
title: node.nama || 'Tanpa Jabatan',
|
||||
image: pegawai?.image?.link || '/img/default.png',
|
||||
description: node.deskripsi || '',
|
||||
},
|
||||
children: node.children?.map(toOrgChartFormat) || [],
|
||||
}
|
||||
@@ -200,7 +212,7 @@ function StrukturOrganisasiBumDes() {
|
||||
chartData = filterNodes(chartData)
|
||||
}
|
||||
|
||||
// 🔍 fullscreen dan zoom control
|
||||
// 🎬 fullscreen & zoom control
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
chartContainerRef.current?.requestFullscreen()
|
||||
@@ -217,85 +229,143 @@ function StrukturOrganisasiBumDes() {
|
||||
|
||||
return (
|
||||
<Stack align="center" mt="xl">
|
||||
{/* 🧭 Kontrol atas */}
|
||||
<Paper shadow="xs" p="md" radius="md" bg={colors['blue-button']}>
|
||||
<Group gap="sm" wrap="wrap" justify="center">
|
||||
<TextInput
|
||||
placeholder="Cari nama atau jabatan..."
|
||||
leftSection={<IconSearch size={16} />}
|
||||
onChange={(e) => debouncedSearch(e.target.value)}
|
||||
{/* 🔍 Controls */}
|
||||
<Paper
|
||||
shadow="xs"
|
||||
w={{
|
||||
base: '100%', // Mobile: 100%
|
||||
sm: '40%', // Tablet: 95%
|
||||
md: '39%', // Desktop: 70%
|
||||
lg: '38%', // Desktop L: 60%
|
||||
xl: '37%', // 4K: 50%
|
||||
'2xl': '36%', // Ultra-wide: 45%
|
||||
}}
|
||||
p="md"
|
||||
radius="md"
|
||||
style={{
|
||||
background: colors['blue-button'], // ⬅️ penting
|
||||
maxWidth: '100%', // ⬅️ penting
|
||||
overflowX: 'auto' // ⬅️ untuk mencegah overflow
|
||||
}}
|
||||
>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
placeholder="Cari nama atau jabatan..."
|
||||
leftSection={<IconSearch size={16} />}
|
||||
onChange={(e) => debouncedSearch(e.target.value)}
|
||||
styles={{
|
||||
input: {
|
||||
minWidth: 250,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Tabs
|
||||
defaultValue="zoom-out"
|
||||
variant="outline"
|
||||
radius="md"
|
||||
styles={{
|
||||
input: {
|
||||
minWidth: 250,
|
||||
panel: { display: 'none' },
|
||||
tab: {
|
||||
color: colors['blue-button'],
|
||||
backgroundColor: colors['blue-button-2'],
|
||||
border: 'none',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.875rem',
|
||||
padding: '6px 12px',
|
||||
minHeight: 'auto',
|
||||
flexShrink: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Button
|
||||
variant="light"
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
size="sm"
|
||||
onClick={handleZoomOut}
|
||||
leftSection={<IconZoomOut size={16} />}
|
||||
>
|
||||
Zoom Out
|
||||
</Button>
|
||||
<Box
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
px={16}
|
||||
py={8}
|
||||
style={{ width: '100%' }} // 👈 penting
|
||||
>
|
||||
<TabsList
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
borderRadius: '8px',
|
||||
minWidth: 70,
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
gap: '4px',
|
||||
paddingBottom: '4px',
|
||||
flexWrap: 'nowrap',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
scrollbarWidth: 'thin',
|
||||
msOverflowStyle: '-ms-autohiding-scrollbar',
|
||||
maxWidth: '100%',
|
||||
scrollBehavior: 'smooth', // 👈 smooth scroll
|
||||
}}
|
||||
>
|
||||
{Math.round(scale * 100)}%
|
||||
</Box>
|
||||
<Button
|
||||
variant="light"
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
size="sm"
|
||||
onClick={handleZoomIn}
|
||||
leftSection={<IconZoomIn size={16} />}
|
||||
>
|
||||
Zoom In
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
size="sm"
|
||||
onClick={resetZoom}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
size="sm"
|
||||
onClick={toggleFullscreen}
|
||||
leftSection={
|
||||
isFullscreen ? (
|
||||
<IconArrowsMinimize size={16} />
|
||||
) : (
|
||||
<IconArrowsMaximize size={16} />
|
||||
)
|
||||
}
|
||||
>
|
||||
Fullscreen
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
<TabsTab
|
||||
value="zoom-out"
|
||||
onClick={handleZoomOut}
|
||||
leftSection={<IconZoomOut size={16} />}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom Out</Text>
|
||||
</TabsTab>
|
||||
|
||||
<Box
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
px={12}
|
||||
py={6}
|
||||
style={{
|
||||
fontWeight: 700,
|
||||
borderRadius: '6px',
|
||||
minWidth: 60,
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} c={colors['blue-button']}>
|
||||
{Math.round(scale * 100)}%
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<TabsTab
|
||||
value="zoom-in"
|
||||
onClick={handleZoomIn}
|
||||
leftSection={<IconZoomIn size={16} />}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Zoom In</Text>
|
||||
</TabsTab>
|
||||
|
||||
<TabsTab
|
||||
value="reset"
|
||||
onClick={resetZoom}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">Reset</Text>
|
||||
</TabsTab>
|
||||
|
||||
<TabsTab
|
||||
value="fullscreen"
|
||||
onClick={toggleFullscreen}
|
||||
leftSection={
|
||||
isFullscreen ? (
|
||||
<IconArrowsMinimize size={16} />
|
||||
) : (
|
||||
<IconArrowsMaximize size={16} />
|
||||
)
|
||||
}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<Text fz={{ base: 12, sm: 13 }} lh={1} ta="center">
|
||||
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
||||
</Text>
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* 📊 Chart Container */}
|
||||
{/* 🧩 Chart Container */}
|
||||
<Center style={{ width: '100%' }}>
|
||||
<Box
|
||||
ref={chartContainerRef}
|
||||
@@ -303,28 +373,35 @@ function StrukturOrganisasiBumDes() {
|
||||
overflowX: 'auto',
|
||||
overflowY: 'auto',
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
padding: '32px 16px',
|
||||
transition: 'transform 0.2s ease',
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'center top',
|
||||
}}
|
||||
>
|
||||
<OrganizationChart
|
||||
value={chartData}
|
||||
nodeTemplate={(node) => <NodeCard node={node} />}
|
||||
className="p-organizationchart p-organizationchart-horizontal"
|
||||
/>
|
||||
<Box style={{
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'center top',
|
||||
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
|
||||
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
|
||||
}}>
|
||||
<OrganizationChart
|
||||
value={chartData}
|
||||
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
|
||||
className="p-organizationchart p-organizationchart-horizontal"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Center>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function NodeCard({ node }: any) {
|
||||
function NodeCard({ node, router }: any) {
|
||||
const imageSrc = node?.data?.image || '/img/default.png'
|
||||
const name = node?.data?.name || 'Tanpa Nama'
|
||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||
const description = node?.data?.description || ''
|
||||
const hasId = Boolean(node?.data?.id)
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
return (
|
||||
<Transition mounted transition="pop" duration={300}>
|
||||
@@ -335,35 +412,113 @@ function NodeCard({ node }: any) {
|
||||
withBorder
|
||||
style={{
|
||||
...styles,
|
||||
width: 240,
|
||||
padding: 20,
|
||||
background:
|
||||
'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
|
||||
width: '100%',
|
||||
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
|
||||
minHeight: isMobile ? 240 : 280,
|
||||
padding: isMobile ? 16 : 20,
|
||||
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
|
||||
borderColor: 'rgba(28, 110, 164, 0.3)',
|
||||
borderWidth: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: hasId ? 'pointer' : 'default',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(-4px)'
|
||||
e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap={10}>
|
||||
<Stack align="center" gap={12}>
|
||||
{/* Photo */}
|
||||
<Box
|
||||
style={{
|
||||
width: 90,
|
||||
height: 90,
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
border: '3px solid rgba(28, 110, 164, 0.4)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
background: 'white',
|
||||
}}
|
||||
>
|
||||
<Image src={imageSrc} alt={name} fit="cover" loading="lazy" />
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
width={96}
|
||||
height={96}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Text fw={700} size="sm" ta="center" c={colors['blue-button']}>
|
||||
|
||||
{/* Name */}
|
||||
<Text
|
||||
fw={700}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
lineClamp={2}
|
||||
fz={{ base: 13, md: 15 }}
|
||||
lh={1.2}
|
||||
style={{
|
||||
minHeight: 40,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" ta="center">
|
||||
|
||||
{/* Title/Position */}
|
||||
<Text
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
fw={500}
|
||||
lineClamp={2}
|
||||
fz={{ base: 12, md: 13 }}
|
||||
lh={1.3}
|
||||
style={{
|
||||
minHeight: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" ta="center" lineClamp={3}>
|
||||
{description || 'Belum ada deskripsi.'}
|
||||
</Text>
|
||||
|
||||
{/* Detail Button */}
|
||||
{hasId && (
|
||||
<Button
|
||||
variant="gradient"
|
||||
gradient={{ from: 'blue', to: 'cyan' }}
|
||||
size="xs"
|
||||
fullWidth
|
||||
mt={8}
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/${node.data.id}`)
|
||||
}
|
||||
style={{
|
||||
height: 32,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<Text c={"white"} fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react';
|
||||
import { IconBulbFilled } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
|
||||
function Page() {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const ideInovatif = useProxy(ajukanIdeInovatifState)
|
||||
|
||||
const ideInovatif = useProxy(ajukanIdeInovatifState);
|
||||
|
||||
const resetForm = () => {
|
||||
// Reset state di valtio
|
||||
ideInovatif.create.form = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
@@ -23,53 +21,66 @@ function Page() {
|
||||
masalah: "",
|
||||
benefit: "",
|
||||
};
|
||||
|
||||
// Reset state lokal
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Submit data berita
|
||||
await ideInovatif.create.create();
|
||||
|
||||
// Reset form setelah submit
|
||||
resetForm();
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
style={{ fontSize: 'clamp(1.75rem, 4vw, 2.25rem)' }}
|
||||
>
|
||||
Ajukan Ide Inovatif
|
||||
</Title>
|
||||
<Text ta="center" fz={{ base: 'sm', md: 'md' }} c="black" lh="1.6">
|
||||
Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform "Ajukan Ide Inovatif" hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.
|
||||
</Text>
|
||||
<Text ta={'center'} fz={'h4'}>Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform "Ajukan Ide Inovatif" hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} p={'lg'}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 2,
|
||||
}}
|
||||
>
|
||||
<Paper p={'xl'} >
|
||||
<Stack gap={"xs"}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Tujuan Ide Inovatif Ini</Text>
|
||||
<List>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mendorong partisipasi aktif masyarakat</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memfasilitasi inovasi berbasis lokal</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memecahkan tantangan komunal</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mengembangkan potensi kreativitas warga</ListItem>
|
||||
</List>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="lg" p="lg">
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Paper p="xl">
|
||||
<Stack gap="xs">
|
||||
<Title order={2} c={colors['blue-button']} fw="bold">
|
||||
Tujuan Ide Inovatif Ini
|
||||
</Title>
|
||||
<List>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Mendorong partisipasi aktif masyarakat
|
||||
</ListItem>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Memfasilitasi inovasi berbasis lokal
|
||||
</ListItem>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Memecahkan tantangan komunal
|
||||
</ListItem>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Mengembangkan potensi kreativitas warga
|
||||
</ListItem>
|
||||
</List>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Paper p={'xl'} >
|
||||
<Flex align={'center'} justify={'space-between'}>
|
||||
|
||||
<Paper p="xl">
|
||||
<Flex align="center" justify="space-between" direction={{ base: 'column', md: 'row' }} gap="md">
|
||||
<Box>
|
||||
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar Di Samping</Text>
|
||||
<IconArrowRight size={30} color={colors['blue-button']} />
|
||||
<Title order={3} c={colors['blue-button']} fw="bold" ta={{ base: 'center', md: 'start' }}>
|
||||
Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar
|
||||
</Title>
|
||||
</Box>
|
||||
<Box px={{ base: 5, md: 10 }} py={5}>
|
||||
<ActionIcon variant="transparent" size={150} onClick={open}>
|
||||
@@ -88,32 +99,46 @@ function Page() {
|
||||
radius={0}
|
||||
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||
>
|
||||
<Paper p={"md"} withBorder>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper p="md" withBorder>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Ajukan Ide Inovatif</Title>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Nama
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan nama"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.name = val.target.value
|
||||
ideInovatif.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Alamat
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan alamat"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.alamat = val.target.value
|
||||
ideInovatif.create.form.alamat = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Ide</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Nama Ide
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan nama ide"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.namaIde = val.target.value
|
||||
ideInovatif.create.form.namaIde = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fz="sm" fw="bold">
|
||||
Deskripsi
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={ideInovatif.create.form.deskripsi}
|
||||
onChange={(htmlContent) => {
|
||||
@@ -122,26 +147,35 @@ function Page() {
|
||||
/>
|
||||
</Box>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Masalah</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Masalah
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan masalah"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.masalah = val.target.value
|
||||
ideInovatif.create.form.masalah = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Benefit</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Benefit
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan benefit"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.benefit = val.target.value
|
||||
ideInovatif.create.form.benefit = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||
Simpan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Modal>
|
||||
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -54,11 +54,11 @@ function Page() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={"100%"}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text>
|
||||
<Text pt={20} fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text>
|
||||
<Text fz={'md'}>dan peningkatan ekonomi berbasis teknologi.</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
function Page() {
|
||||
const [search, setSearch] = useState("")
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const state = useProxy(infoTeknoState)
|
||||
const {
|
||||
data,
|
||||
@@ -34,17 +34,24 @@ function Page() {
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Grid align='center'>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title
|
||||
order={1}
|
||||
c={colors["blue-button"]}
|
||||
fw={"bold"}
|
||||
ta={{ base: 'center', md: 'left' }}
|
||||
>
|
||||
Info Teknologi Tepat Guna
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
@@ -53,13 +60,19 @@ function Page() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={{ base: "100%", md: "100%" }}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text fz={'md'}>Desa Darmasaba berkomitmen mengembangkan teknologi tepat guna yang sesuai dengan kebutuhan masyarakat,</Text>
|
||||
<Text fz={'md'}>mendukung pembangunan berkelanjutan, dan meningkatkan kualitas hidup warga.</Text>
|
||||
|
||||
<Text pt={20} fz={{ base: 'sm', md: 'md' }} ta={{ base: 'center', md: 'left' }} lh={1.5}>
|
||||
Desa Darmasaba berkomitmen mengembangkan teknologi tepat guna yang sesuai dengan kebutuhan masyarakat,
|
||||
</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} ta={{ base: 'center', md: 'left' }} lh={1.5}>
|
||||
mendukung pembangunan berkelanjutan, dan meningkatkan kualitas hidup warga.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} p={'lg'}>
|
||||
<SimpleGrid
|
||||
@@ -74,12 +87,14 @@ function Page() {
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Stack gap={"xs"}>
|
||||
<Image src={v.image.link || ''} pb={10} radius={10} alt='' loading="lazy" />
|
||||
<Text fz={'h3'} fw={'bold'}>{v.name}</Text>
|
||||
<Title order={3} fw={'bold'} ta="left">
|
||||
{v.name}
|
||||
</Title>
|
||||
<Box pr={'lg'} pb={10}>
|
||||
<Text
|
||||
size="md"
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
ta="justify"
|
||||
lh={1} // line height biar enak dibaca
|
||||
lh={1.5}
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
@@ -94,10 +109,11 @@ function Page() {
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
my="md"
|
||||
/>
|
||||
@@ -106,4 +122,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,6 +1,6 @@
|
||||
'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 { useParams } from 'next/navigation'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
@@ -31,59 +31,62 @@ function DetailKeamananLingkunganUser() {
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
|
||||
{/* Wrapper Detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '80%' }}
|
||||
mx="auto"
|
||||
bg={colors['white-1']}
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Judul */}
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={700}
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
{data?.name || 'Tanpa Judul'}
|
||||
</Text>
|
||||
{/* Wrapper Detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '80%' }}
|
||||
mx="auto"
|
||||
bg={colors['white-1']}
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Judul */}
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: 1.15 }}
|
||||
>
|
||||
{data?.name || 'Tanpa Judul'}
|
||||
</Title>
|
||||
|
||||
{/* Gambar */}
|
||||
<Center>
|
||||
<Image
|
||||
w={{ base: 250, sm: 400, md: 550 }}
|
||||
src={data?.image?.link}
|
||||
alt={data?.name || 'gambar keamanan lingkungan'}
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
fit="cover"
|
||||
/>
|
||||
</Center>
|
||||
{/* Gambar */}
|
||||
<Center>
|
||||
<Image
|
||||
w={{ base: 250, sm: 400, md: 550 }}
|
||||
src={data?.image?.link}
|
||||
alt={data?.name || 'gambar keamanan lingkungan'}
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
fit="cover"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold" mb={5}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<Text
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Title order={3} mb={5} style={{ lineHeight: 1.2 }}>
|
||||
Deskripsi
|
||||
</Title>
|
||||
<Box pl={20}>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.55 }}
|
||||
c={data?.deskripsi ? 'text' : 'dimmed'}
|
||||
dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default DetailKeamananLingkunganUser
|
||||
export default DetailKeamananLingkunganUser
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan';
|
||||
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 { IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
@@ -14,7 +14,7 @@ function Page() {
|
||||
const state = useProxy(keamananLingkunganState)
|
||||
const router = useRouter()
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
@@ -43,9 +43,9 @@ function Page() {
|
||||
<Box>
|
||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title order={1} c={colors["blue-button"]} lh={1.15}>
|
||||
Keamanan Lingkungan (Pecalang / Patwal)
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
@@ -54,18 +54,29 @@ function Page() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={"100%"}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" 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.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
<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) => (
|
||||
<Paper
|
||||
key={k}
|
||||
@@ -107,17 +118,18 @@ function Page() {
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||
/>
|
||||
</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}
|
||||
</Text>
|
||||
</Title>
|
||||
<Text
|
||||
fz="sm"
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
ta="justify"
|
||||
lineClamp={3}
|
||||
lh={1.6}
|
||||
lh={1.55}
|
||||
style={{
|
||||
minHeight: '4.8em',
|
||||
}}
|
||||
c={'black'}
|
||||
>
|
||||
<span
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
@@ -159,4 +171,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import kontakDarurat from '@/app/admin/(dashboard)/_state/keamanan/kontak-darurat-keamanan';
|
||||
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 { IconPhoneCall, IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
@@ -42,10 +42,10 @@ function Page() {
|
||||
</Box>
|
||||
<Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'>
|
||||
<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
|
||||
</Text>
|
||||
<Text fz="md" >
|
||||
</Title>
|
||||
<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.
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -66,17 +66,21 @@ function Page() {
|
||||
<IconPhoneCall size={30} color={colors["blue-button"]} />
|
||||
</Avatar>
|
||||
<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
|
||||
</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>
|
||||
</Flex>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
<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>
|
||||
</Stack>
|
||||
);
|
||||
@@ -89,10 +93,10 @@ function Page() {
|
||||
</Box>
|
||||
<Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'>
|
||||
<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
|
||||
</Text>
|
||||
<Text fz={{ base: "h4", md: "h3" }} >
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5} c={colors['blue-button-2']}>
|
||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung.
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -113,10 +117,12 @@ function Page() {
|
||||
<IconPhoneCall size={30} color={colors["blue-button"]} />
|
||||
</Avatar>
|
||||
<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
|
||||
</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>
|
||||
</Flex>
|
||||
</Paper>
|
||||
@@ -124,19 +130,13 @@ function Page() {
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
||||
{/* Layanan Darurat */}
|
||||
{data.map((item) => (
|
||||
<a
|
||||
key={item.id}
|
||||
href={`tel:${item.kontakItems[0]?.kontakItem?.nomorTelepon || '112'}`}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<Paper
|
||||
|
||||
p="lg"
|
||||
radius="md"
|
||||
bg={colors['white-trans-1']}
|
||||
>
|
||||
<Paper p="lg" radius="md" bg={colors['white-trans-1']}>
|
||||
<Group pb="md" align="center">
|
||||
<Avatar radius="xl" size="lg" bg={colors['BG-trans']}>
|
||||
{item.icon && (
|
||||
@@ -147,12 +147,11 @@ function Page() {
|
||||
/>
|
||||
)}
|
||||
</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}
|
||||
</Text>
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Kontak Items */}
|
||||
{item.kontakItems?.map((kontak) => (
|
||||
<Paper
|
||||
key={kontak.id}
|
||||
@@ -171,11 +170,11 @@ function Page() {
|
||||
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}
|
||||
</Text>
|
||||
</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}
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -204,4 +203,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -71,14 +71,14 @@ function Page() {
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" >Judul</Text>
|
||||
<Text fz="sm" c="dimmed">{data.judul || '-'}</Text>
|
||||
<Text fz="sm" c="black">{data.judul || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" >Tanggal</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text fz="sm" c="black">
|
||||
{data.tanggalWaktu
|
||||
? new Date(data.tanggalWaktu).toLocaleString('id-ID', { dateStyle: 'full', timeStyle: 'short' })
|
||||
: '-'}
|
||||
@@ -89,7 +89,7 @@ function Page() {
|
||||
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" >Lokasi</Text>
|
||||
<Text fz="sm" c="dimmed">{data.lokasi || '-'}</Text>
|
||||
<Text fz="sm" c="black">{data.lokasi || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
@@ -120,7 +120,7 @@ function Page() {
|
||||
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" >Kronologi</Text>
|
||||
<Text fz="sm" c="dimmed" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
|
||||
<Text fz="sm" c="black" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data.kronologi || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
@@ -136,11 +136,11 @@ function Page() {
|
||||
radius="md"
|
||||
shadow="xs"
|
||||
withBorder
|
||||
bg="dark.5"
|
||||
bg={colors['blue-button-1']}
|
||||
>
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
c="black"
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
|
||||
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 { IconArrowRight } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
@@ -26,7 +26,7 @@ function Page() {
|
||||
if (!findFirst.data && !findFirst.loading) {
|
||||
kriminalitasState.findFirst.load();
|
||||
}
|
||||
}, [findFirst.data, findFirst.loading]);
|
||||
}, []);
|
||||
|
||||
useShallowEffect(() => {
|
||||
const LIMIT = 3;
|
||||
@@ -45,10 +45,10 @@ function Page() {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<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
|
||||
</Text>
|
||||
<Text fz='md'>
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Keamanan Komunitas & Pencegahan Kriminal
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -58,11 +58,11 @@ function Page() {
|
||||
spacing="xl"
|
||||
>
|
||||
<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
|
||||
</Text>
|
||||
</Title>
|
||||
<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
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -75,10 +75,10 @@ function Page() {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<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
|
||||
</Text>
|
||||
<Text fz='md'>
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Keamanan Komunitas & Pencegahan Kriminal
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -88,13 +88,13 @@ function Page() {
|
||||
spacing="xl"
|
||||
>
|
||||
<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
|
||||
</Text>
|
||||
</Title>
|
||||
<Stack pt={30} gap="lg">
|
||||
<Box
|
||||
style={{
|
||||
minHeight: 300, // sesuaikan: tinggi area yg muat 3 item
|
||||
minHeight: 300,
|
||||
}}
|
||||
>
|
||||
{data.length > 0 ? (
|
||||
@@ -120,14 +120,16 @@ function Page() {
|
||||
}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fz="h3" c={colors['white-1']}>
|
||||
<Title order={3} c={colors['white-1']} lh={1.2}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</Title>
|
||||
</Stack>
|
||||
</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>
|
||||
<Button
|
||||
@@ -169,12 +171,18 @@ function Page() {
|
||||
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>
|
||||
)}
|
||||
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
|
||||
<Title order={2} py={10} fw="bold" c={colors['blue-button']} lh={1.2}>
|
||||
{findFirst.data?.judul}
|
||||
</Text>
|
||||
<Text fz="h4" dangerouslySetInnerHTML={{ __html: findFirst.data?.deskripsiSingkat }} />
|
||||
</Title>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.5}
|
||||
dangerouslySetInnerHTML={{ __html: findFirst.data?.deskripsiSingkat }}
|
||||
/>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Box>
|
||||
@@ -195,4 +203,4 @@ function Page() {
|
||||
}
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,7 +2,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
|
||||
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 { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -35,15 +35,15 @@ function Page() {
|
||||
<BackButton />
|
||||
</Box>
|
||||
<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
|
||||
</Text>
|
||||
<Text pb={15} fz="md">
|
||||
</Title>
|
||||
<Text pb={15} fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
|
||||
</Text>
|
||||
</Box>
|
||||
<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
|
||||
</Text>
|
||||
</Center>
|
||||
@@ -58,10 +58,10 @@ function Page() {
|
||||
</Box>
|
||||
|
||||
<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
|
||||
</Text>
|
||||
<Text pb={15} fz="h4">
|
||||
</Title>
|
||||
<Text pb={15} fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -79,10 +79,10 @@ function Page() {
|
||||
<>
|
||||
{/* === KIRI === */}
|
||||
<Box>
|
||||
<Text c={colors['blue-button']} fw="bold" fz="h2">
|
||||
<Title order={2} c={colors['blue-button']} lh={1.2}>
|
||||
{data.nama}
|
||||
</Text>
|
||||
<Text c={colors['blue-button']} fz="sm">
|
||||
</Title>
|
||||
<Text c={colors['blue-button']} fz={{ base: 'xs', md: 'sm' }} lh={1.4}>
|
||||
{data.jarakKeDesa}
|
||||
</Text>
|
||||
|
||||
@@ -98,11 +98,11 @@ function Page() {
|
||||
<IconPin size={22} />
|
||||
</Box>
|
||||
<Text
|
||||
fz="lg"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
style={{
|
||||
flex: 1,
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.4,
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
>
|
||||
{data.alamat}
|
||||
@@ -119,7 +119,7 @@ function Page() {
|
||||
<Box w={25} mt={3}>
|
||||
<IconPhone size={22} />
|
||||
</Box>
|
||||
<Text fz="lg">
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
{data.nomorTelepon}
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -133,26 +133,26 @@ function Page() {
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
<Box w={25} mt={3}>
|
||||
<IconClock size={22} />
|
||||
<IconClock size={22} />
|
||||
</Box>
|
||||
<Text fz="lg">
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
{data.jamOperasional}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{/* Layanan */}
|
||||
<Box>
|
||||
<Text c={colors['blue-button']} fw="bold" fz="h2">
|
||||
Layanan Yang Tersedia :
|
||||
</Text>
|
||||
<Box pt={15}>
|
||||
<Title order={2} c={colors['blue-button']} lh={1.2}>
|
||||
Layanan Yang Tersedia:
|
||||
</Title>
|
||||
<SimpleGrid py={10} cols={{ base: 1, md: 2 }}>
|
||||
<Text fz="lg">
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
{data.layananPolsek.nama}
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Box pt={15}>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={() =>
|
||||
@@ -205,4 +205,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
|
||||
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 { IconNavigation, IconSearch } from '@tabler/icons-react';
|
||||
import React, { useState } from 'react';
|
||||
@@ -13,8 +13,8 @@ import { useDebouncedValue } from '@mantine/hooks';
|
||||
function Page() {
|
||||
const state = useProxy(polsekTerdekatState);
|
||||
const [search, setSearch] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
const router = useRouter()
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -25,71 +25,98 @@ function Page() {
|
||||
} = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, debouncedSearch)
|
||||
}, [page, debouncedSearch])
|
||||
load(page, 3, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
||||
|
||||
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
lh={1.2}
|
||||
>
|
||||
Semua Polsek Terdekat
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
placeholder='Cari Polsek'
|
||||
radius="lg"
|
||||
placeholder="Cari Polsek"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={{ base: '50%', md: '100%' }}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 3,
|
||||
}}
|
||||
>
|
||||
{data.map((v, k) => {
|
||||
return (
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']} key={k}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"h3"}>{v.nama}</Text>
|
||||
<Text>Alamat: {v.alamat}</Text>
|
||||
<Text>Jarak: {v.jarakKeDesa}</Text>
|
||||
<Text>Telepon: {v.nomorTelepon}</Text>
|
||||
<Text>Jam Operasional: {v.jamOperasional}</Text>
|
||||
<Box pt={20}>
|
||||
<iframe style={{ border: 2, width: "100%" }} src={v.embedMapUrl} width="550" height="300" ></iframe>
|
||||
</Box>
|
||||
<Box pt={20}>
|
||||
<Button onClick={() => router.push(v.linkPetunjukArah)} fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20} />}>Petunjuk Arah</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{data.map((v, k) => (
|
||||
<Paper p="xl" bg={colors['white-trans-1']} key={k}>
|
||||
<Stack gap="xs">
|
||||
<Title
|
||||
order={3}
|
||||
fw="bold"
|
||||
lh={1.2}
|
||||
>
|
||||
{v.nama}
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Alamat: {v.alamat}
|
||||
</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Jarak: {v.jarakKeDesa}
|
||||
</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}>
|
||||
<iframe
|
||||
style={{ border: 2, width: '100%' }}
|
||||
src={v.embedMapUrl}
|
||||
width="550"
|
||||
height="300"
|
||||
></iframe>
|
||||
</Box>
|
||||
<Box pt={20}>
|
||||
<Button
|
||||
onClick={() => router.push(v.linkPetunjukArah)}
|
||||
fullWidth
|
||||
bg={colors['blue-button']}
|
||||
radius={10}
|
||||
leftSection={<IconNavigation size={20} />}
|
||||
>
|
||||
Petunjuk Arah
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
@@ -99,4 +126,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import tipsKeamananState from '@/app/admin/(dashboard)/_state/keamanan/tips-keamanan';
|
||||
@@ -8,11 +8,10 @@ import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(tipsKeamananState)
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
const state = useProxy(tipsKeamananState);
|
||||
const [search, setSearch] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
@@ -22,84 +21,114 @@ function Page() {
|
||||
} = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, debouncedSearch)
|
||||
}, [page, debouncedSearch])
|
||||
load(page, 3, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Grid align='center' px={{ base: 'md', md: 100 }}>
|
||||
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Title
|
||||
order={1}
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: '1.2' }}
|
||||
>
|
||||
Tips Keamanan
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
placeholder='Cari Tips'
|
||||
radius="lg"
|
||||
placeholder="Cari Tips"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={'100%'}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
||||
|
||||
<Text
|
||||
px={{ base: 'md', md: 100 }}
|
||||
ta="justify"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: '1.5', md: '1.6' }}
|
||||
mt="sm"
|
||||
>
|
||||
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal).
|
||||
</Text>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
||||
<Text
|
||||
px={{ base: 'md', md: 100 }}
|
||||
ta="justify"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: '1.5', md: '1.6' }}
|
||||
mt="xs"
|
||||
>
|
||||
Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="lg">
|
||||
<SimpleGrid
|
||||
pb={10}
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 3,
|
||||
}}>
|
||||
{data.map((v, k) => {
|
||||
return (
|
||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
||||
<Stack gap={'xs'}>
|
||||
<Center p={10}>
|
||||
<Image src={v.image?.link} radius={10} loading="lazy"
|
||||
alt='' />
|
||||
</Center>
|
||||
<Box px={'xl'}>
|
||||
<Box pb={20}>
|
||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Box>
|
||||
<Text pb={10} fz={"md"} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Box>
|
||||
pb="10"
|
||||
cols={{ base: 1, md: 3 }}
|
||||
>
|
||||
{data.map((v, k) => (
|
||||
<Paper radius={10} key={k} bg={colors['white-trans-1']}>
|
||||
<Stack gap="xs">
|
||||
<Center p="10">
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
radius={10}
|
||||
loading="lazy"
|
||||
alt=""
|
||||
/>
|
||||
</Center>
|
||||
<Box px="xl">
|
||||
<Box pb="20">
|
||||
<Title
|
||||
order={3}
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: '1.3' }}
|
||||
>
|
||||
{v.judul}
|
||||
</Title>
|
||||
<Box>
|
||||
<Text
|
||||
pb="10"
|
||||
fz={{ base: 'xs', md: 'md' }}
|
||||
lh={{ base: '1.5', md: '1.6' }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
my="md"
|
||||
/>
|
||||
@@ -108,4 +137,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,7 +2,7 @@
|
||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Divider, Flex, Group, Image, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Divider, Flex, Group, Image, List, ListItem, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconAlertCircle, IconCalendar, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { useParams } from 'next/navigation';
|
||||
@@ -37,9 +37,9 @@ function Page() {
|
||||
<Stack gap="lg">
|
||||
<Paper radius="xl" shadow="md" withBorder>
|
||||
<Box style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} bg={colors['blue-button']}>
|
||||
<Text p="md" fz={{ base: 'h3', md: 'h2' }} c={colors['white-1']} fw="bold">
|
||||
<Title order={1} p="md" c={colors['white-1']} fw="bold">
|
||||
{state.findUnique.data.title || 'Detail Artikel Kesehatan'}
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
<Box p="lg">
|
||||
@@ -64,7 +64,7 @@ function Page() {
|
||||
<Stack gap="lg">
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} color={colors['blue-button']} />
|
||||
<Text c="dimmed" fz="sm">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.5}>
|
||||
{new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
@@ -74,48 +74,47 @@ function Page() {
|
||||
</Group>
|
||||
|
||||
<Stack gap="lg">
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">Pendahuluan</Text>
|
||||
<Title order={2} fw="bold">Pendahuluan</Title>
|
||||
<Divider my="xs" />
|
||||
<Box pl={20}>
|
||||
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text>
|
||||
<Title order={2} fw="bold">{state.findUnique.data.symptom?.title}</Title>
|
||||
<Divider my="xs" />
|
||||
<Box pl={20}>
|
||||
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">{state.findUnique.data.prevention?.title}</Text>
|
||||
<Title order={2} fw="bold">{state.findUnique.data.prevention?.title}</Title>
|
||||
<Divider my="xs" />
|
||||
<Box pl={20}>
|
||||
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.prevention?.content }} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">{state.findUnique.data.firstaid?.title}</Text>
|
||||
<Title order={2} fw="bold">{state.findUnique.data.firstaid?.title}</Title>
|
||||
<Divider my="xs" />
|
||||
<Box pl={20}>
|
||||
<Text fz="md" lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} ta="justify" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.firstaid?.content }} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">{state.findUnique.data.mythvsfact?.title}</Text>
|
||||
<Title order={2} fw="bold">{state.findUnique.data.mythvsfact?.title}</Title>
|
||||
<Divider my="xs" />
|
||||
<Box pb="md">
|
||||
<Table highlightOnHover withTableBorder withColumnBorders striped>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh fz="sm" fw="bold">Mitos</TableTh>
|
||||
<TableTh fz="sm" fw="bold">Fakta</TableTh>
|
||||
<TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Mitos</TableTh>
|
||||
<TableTh fz={{ base: 'xs', md: 'sm' }} fw="bold">Fakta</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
@@ -123,12 +122,12 @@ function Page() {
|
||||
<TableTr>
|
||||
<TableTd>
|
||||
<Box pl={20}>
|
||||
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.mitos }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box pl={20}>
|
||||
<Text fz="sm" lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} lh={1.6} style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.mythvsfact.fakta }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -143,34 +142,35 @@ function Page() {
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">Kapan Harus ke Dokter?</Text>
|
||||
<Title order={2} fw="bold">Kapan Harus ke Dokter?</Title>
|
||||
<Divider my="xs" />
|
||||
<Flex justify={'flex-start'} gap={"xs"} align={"center"} mb="xs">
|
||||
<IconAlertCircle size={18} color="red" />
|
||||
<Text fz="md">Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>Segera bawa penderita ke fasilitas kesehatan jika mengalami:</Text>
|
||||
</Flex>
|
||||
<Box pl={20}>
|
||||
<Text fz="md" lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} /></Box>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.6} dangerouslySetInnerHTML={{ __html: state.findUnique.data.doctorsign.content }} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} withBorder>
|
||||
<Group gap="xs" mb="sm">
|
||||
<IconInfoCircle size={20} color={colors['white-1']} />
|
||||
<Text fz="h4" c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Text>
|
||||
<Title order={3} c={colors['white-1']} fw="bold">Informasi Lebih Lanjut</Title>
|
||||
</Group>
|
||||
<Stack gap={4}>
|
||||
<Text fz="sm" c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
|
||||
<Text fz="sm" c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
|
||||
<Text fz="sm" c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Hotline DBD: <b>(0361) 123456</b></Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>WhatsApp Center: <b>081234567890</b></Text>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} c={colors['white-1']}>Email: <b>p2p@dinkes.badungkab.go.id</b></Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">Referensi</Text>
|
||||
<Title order={2} fw="bold">Referensi</Title>
|
||||
<Divider my="xs" />
|
||||
<List spacing="xs" size="sm" type="ordered">
|
||||
<List spacing="xs" fz={{ base: 'xs', md: 'sm' }} type="ordered">
|
||||
<ListItem>Kementerian Kesehatan RI. (2024). Pedoman Pencegahan dan Pengendalian DBD.</ListItem>
|
||||
<ListItem>World Health Organization. (2024). Dengue Guidelines for Diagnosis, Treatment, Prevention and Control.</ListItem>
|
||||
<ListItem>Dinas Kesehatan Kabupaten Badung. (2025). Laporan Surveilans DBD Triwulan I 2025.</ListItem>
|
||||
@@ -186,4 +186,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Card, Divider, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Card, Divider, Group, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -17,9 +17,8 @@ function ArtikelKesehatanPage() {
|
||||
|
||||
if (!state.findMany.data) {
|
||||
return (
|
||||
<Box py="xl" ta="center">
|
||||
<Loader size="lg" color={colors['blue-button']} />
|
||||
<Text mt="md" c="dimmed" fz="md">Memuat artikel kesehatan...</Text>
|
||||
<Box py="lg">
|
||||
<Skeleton h={500} radius="lg" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -28,13 +27,13 @@ function ArtikelKesehatanPage() {
|
||||
<Box>
|
||||
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
|
||||
<Stack gap="lg">
|
||||
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
||||
<Title ta="center" order={1} c={colors['blue-button']}>
|
||||
Artikel Kesehatan
|
||||
</Text>
|
||||
</Title>
|
||||
<Divider size="sm" color={colors['blue-button']} />
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
<Text fz="lg" c="dimmed">
|
||||
<Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
|
||||
Belum ada artikel kesehatan yang tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -51,17 +50,26 @@ function ArtikelKesehatanPage() {
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
|
||||
>
|
||||
<Card.Section>
|
||||
<Image style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }} src={item.image?.link} alt={item.title} height={200} fit="cover" loading="lazy" />
|
||||
<Image
|
||||
style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }}
|
||||
src={item.image?.link}
|
||||
alt={item.title}
|
||||
height={200}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Card.Section>
|
||||
<Stack gap="xs" mt="md">
|
||||
<Text fw="bold" fz="xl" c={colors['blue-button']}>{item.title}</Text>
|
||||
<Title order={3} c={colors['blue-button']}>
|
||||
{item.title}
|
||||
</Title>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={16} color='gray' />
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text fz={{ base: 'xs', sm: 'sm' }} c="dimmed">
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} • Dinas Kesehatan
|
||||
</Text>
|
||||
</Group>
|
||||
<Text fz="md" lineClamp={3}>
|
||||
<Text fz={{ base: 'sm', sm: 'md' }} lh={{ base: 'sm', sm: 'md' }} lineClamp={3}>
|
||||
{item.content}
|
||||
</Text>
|
||||
<Group justify="flex-start">
|
||||
@@ -85,4 +93,4 @@ function ArtikelKesehatanPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export default ArtikelKesehatanPage;
|
||||
export default ArtikelKesehatanPage;
|
||||
@@ -16,7 +16,6 @@ interface Kontak {
|
||||
email: string;
|
||||
}
|
||||
|
||||
|
||||
interface Lokasi {
|
||||
mapsEmbed: string;
|
||||
}
|
||||
@@ -35,7 +34,7 @@ function Page() {
|
||||
state.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const data = state.findUnique.data as any; // Temporary any to fix type issues
|
||||
const data = state.findUnique.data as any;
|
||||
|
||||
const nama = data?.name || 'Fasilitas Kesehatan';
|
||||
const prosedur = data?.prosedurpendaftaran.content || '';
|
||||
@@ -111,11 +110,11 @@ function Page() {
|
||||
<Group gap="md" wrap="wrap">
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconMapPin size={18} /></ThemeIcon>
|
||||
<Text>{alamat}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{alamat}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconDeviceLandlinePhone size={18} /></ThemeIcon>
|
||||
<Text>{kontak.telepon}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.telepon}</Text>
|
||||
<CopyButton value={kontak.telepon}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label={copied ? 'Disalin' : 'Salin nomor'}>
|
||||
@@ -126,7 +125,7 @@ function Page() {
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconBrandWhatsapp size={18} /></ThemeIcon>
|
||||
<Text>{kontak.whatsapp}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.whatsapp}</Text>
|
||||
<CopyButton value={kontak.whatsapp}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label={copied ? 'Disalin' : 'Salin WhatsApp'}>
|
||||
@@ -137,7 +136,7 @@ function Page() {
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconMail size={18} /></ThemeIcon>
|
||||
<Text>{kontak.email}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} lh={1.5}>{kontak.email}</Text>
|
||||
<CopyButton value={kontak.email}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label={copied ? 'Disalin' : 'Salin email'}>
|
||||
@@ -163,33 +162,43 @@ function Page() {
|
||||
<Divider />
|
||||
<Group gap="xl" align="start">
|
||||
<Stack gap={2}>
|
||||
<Text c="dimmed" fz="sm">Nama Fasilitas</Text>
|
||||
<Text fw={600}>{nama}</Text>
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>Nama Fasilitas</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.5}>{nama}</Text>
|
||||
</Stack>
|
||||
<Stack gap={2}>
|
||||
<Text c="dimmed" fz="sm">Jam Operasional</Text>
|
||||
<Text fw={600}>{jam}</Text>
|
||||
<Text c="dimmed" fz={{ base: 'xs', md: 'sm' }}>Jam Operasional</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={600} lh={1.5}>{jam}</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Divider />
|
||||
<Title order={4}>Layanan Unggulan</Title>
|
||||
<Divider />
|
||||
{layananUnggulan ? (
|
||||
<Box pl={"lg"}>
|
||||
<Text fz="md" style={{ lineHeight: 1.7, wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: layananUnggulan }} />
|
||||
<Box pl="lg">
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.6, md: 1.7 }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: layananUnggulan }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group gap="sm">
|
||||
<IconMoodEmpty />
|
||||
<Text>Belum ada informasi fasilitas pendukung.</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada informasi layanan unggulan.</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
<Divider />
|
||||
<Title order={4}>Peta Lokasi</Title>
|
||||
<AspectRatio ratio={16 / 9}>
|
||||
<iframe src={lokasi.mapsEmbed} style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }} loading="lazy" aria-label="Peta Lokasi" />
|
||||
<iframe
|
||||
src={lokasi.mapsEmbed}
|
||||
style={{ border: 0, width: '100%', height: '100%', borderRadius: 16 }}
|
||||
loading="lazy"
|
||||
aria-label="Peta Lokasi"
|
||||
/>
|
||||
</AspectRatio>
|
||||
</Stack>
|
||||
</Card>
|
||||
@@ -201,9 +210,15 @@ function Page() {
|
||||
<Stack gap="md">
|
||||
<Title order={4}>Kontak Cepat</Title>
|
||||
<Group gap="sm" wrap="wrap">
|
||||
<Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">Telepon</Button>
|
||||
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">WhatsApp</Button>
|
||||
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">Email</Button>
|
||||
<Button variant="light" leftSection={<IconDeviceLandlinePhone size={18} />} component="a" href={`tel:${kontak.telepon}`} aria-label="Hubungi Telepon">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }}>Telepon</Text>
|
||||
</Button>
|
||||
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }}>WhatsApp</Text>
|
||||
</Button>
|
||||
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">
|
||||
<Text fz={{ base: 'xs', md: 'sm' }}>Email</Text>
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
@@ -214,9 +229,15 @@ function Page() {
|
||||
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Dokter">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Spesialisasi</TableTh>
|
||||
<TableTh>Jadwal</TableTh>
|
||||
<TableTh>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Nama</Text>
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Spesialisasi</Text>
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Jadwal</Text>
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
@@ -226,11 +247,15 @@ function Page() {
|
||||
<TableTd>
|
||||
<Group gap="xs">
|
||||
<IconUser size={16} />
|
||||
<Text>{dokter.name || '-'}</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.name || '-'}</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>{dokter.specialist || '-'}</TableTd>
|
||||
<TableTd>{dokter.jadwal || '-'}</TableTd>
|
||||
<TableTd>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.specialist || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>{dokter.jadwal || '-'}</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
@@ -238,7 +263,7 @@ function Page() {
|
||||
<TableTd colSpan={3}>
|
||||
<Group justify="center" gap="xs" c="dimmed">
|
||||
<IconSearch size={18} />
|
||||
<Text>Tidak ada data tenaga medis.</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>Tidak ada data tenaga medis.</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -254,13 +279,18 @@ function Page() {
|
||||
<Divider />
|
||||
{fasilitasPendukungHtml ? (
|
||||
<Box pl="lg">
|
||||
<Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }} />
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.6, md: 1.7 }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: fasilitasPendukungHtml }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group gap="sm">
|
||||
<IconMoodEmpty />
|
||||
<Text>Belum ada informasi fasilitas pendukung.</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada informasi fasilitas pendukung.</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
@@ -274,16 +304,24 @@ function Page() {
|
||||
<Table highlightOnHover withTableBorder withColumnBorders aria-label="Tabel Layanan dan Tarif">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Layanan</TableTh>
|
||||
<TableTh>Tarif</TableTh>
|
||||
<TableTh>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Layanan</Text>
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} fw={600}>Tarif</Text>
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{Array.isArray(data?.tarifdanlayanan) && data.tarifdanlayanan.length > 0 ? (
|
||||
data.tarifdanlayanan.map((item: any) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.layanan || '-'}</TableTd>
|
||||
<TableTd>{formatRupiah(item.tarif)}</TableTd>
|
||||
<TableTd>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>{item.layanan || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>{formatRupiah(item.tarif)}</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
@@ -291,7 +329,7 @@ function Page() {
|
||||
<TableTd colSpan={2}>
|
||||
<Group justify="center" gap="xs" c="dimmed">
|
||||
<IconSearch size={18} />
|
||||
<Text>Tidak ada data tarif.</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }}>Tidak ada data tarif.</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -301,7 +339,7 @@ function Page() {
|
||||
{gratisBpjs && (
|
||||
<Group gap="xs">
|
||||
<ThemeIcon variant="light" radius="xl"><IconCheck size={18} /></ThemeIcon>
|
||||
<Text fw={600}>Gratis dengan BPJS Kesehatan</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={600}>Gratis dengan BPJS Kesehatan</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -317,9 +355,16 @@ function Page() {
|
||||
<Title order={3}>Prosedur Pendaftaran</Title>
|
||||
<Divider />
|
||||
{prosedur ? (
|
||||
<Box pl="lg"><Text fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal", lineHeight: 1.7 }} dangerouslySetInnerHTML={{ __html: prosedur }} /></Box>
|
||||
<Box pl="lg">
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.6, md: 1.7 }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: prosedur }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Text fz="md" c="dimmed">Belum ada prosedur pendaftaran</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">Belum ada prosedur pendaftaran</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -328,4 +373,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -17,12 +17,8 @@ function FasilitasKesehatanPage() {
|
||||
|
||||
if (!state.findMany.data) {
|
||||
return (
|
||||
<Box py="xl" px="md">
|
||||
<Stack gap="md">
|
||||
<Skeleton height={80} radius="lg" />
|
||||
<Skeleton height={80} radius="lg" />
|
||||
<Skeleton height={80} radius="lg" />
|
||||
</Stack>
|
||||
<Box py="lg">
|
||||
<Skeleton h={500} radius="lg" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -31,14 +27,24 @@ function FasilitasKesehatanPage() {
|
||||
<Box>
|
||||
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="100%">
|
||||
<Stack gap="lg">
|
||||
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
fw={700}
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: '1.2' }}
|
||||
>
|
||||
Fasilitas Kesehatan
|
||||
</Text>
|
||||
</Title>
|
||||
<Divider size="sm" color={colors['blue-button']} />
|
||||
<Stack gap="lg">
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
<Text fz="lg" c="dimmed">
|
||||
<Text
|
||||
fz={{ base: 'sm', sm: 'md' }}
|
||||
c={colors['blue-button']}
|
||||
lh={{ base: '1.5', sm: '1.6' }}
|
||||
>
|
||||
Belum ada fasilitas kesehatan yang tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -65,22 +71,36 @@ function FasilitasKesehatanPage() {
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text fw={700} fz="lg" c={colors['blue-button']}>
|
||||
<Title
|
||||
order={3}
|
||||
fw={700}
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 'sm', sm: 'md' }}
|
||||
lh={{ base: '1.3', sm: '1.3' }}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Badge color="blue" radius="sm" variant="light" fz="xs">
|
||||
</Title>
|
||||
<Badge color="blue" radius="sm" variant="light" size="xs">
|
||||
Aktif
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={18} stroke={1.5} />
|
||||
<Text fz="sm">
|
||||
<Text
|
||||
fz={{ base: 'xs', sm: 'sm' }}
|
||||
lh={{ base: '1.5', sm: '1.5' }}
|
||||
c="text"
|
||||
>
|
||||
{item.informasiumum.alamat}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconClock size={18} stroke={1.5} />
|
||||
<Text fz="sm">
|
||||
<Text
|
||||
fz={{ base: 'xs', sm: 'sm' }}
|
||||
lh={{ base: '1.5', sm: '1.5' }}
|
||||
c="text"
|
||||
>
|
||||
{item.informasiumum.jamOperasional}
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -110,4 +130,4 @@ function FasilitasKesehatanPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export default FasilitasKesehatanPage;
|
||||
export default FasilitasKesehatanPage;
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text
|
||||
Text,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconMail, IconPhone, IconUser } from '@tabler/icons-react';
|
||||
@@ -51,55 +52,97 @@ function Page() {
|
||||
style={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
<Text p="md" fz={{ base: "h3", md: "h2" }} c={colors['white-1']} fw="bold">
|
||||
<Title
|
||||
p="md"
|
||||
order={1}
|
||||
c={colors['white-1']}
|
||||
fw="bold"
|
||||
ta={{ base: 'center', md: 'left' }}
|
||||
>
|
||||
Detail & Pendaftaran Kegiatan
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
<Box p="lg">
|
||||
<Stack gap="xl">
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Informasi Kegiatan</Text>
|
||||
<Title order={2} fw="bold">Informasi Kegiatan</Title>
|
||||
<Divider />
|
||||
<Text fz="md" fw="bold">Nama Kegiatan: <Text span>{state.findUnique.data.informasijadwalkegiatan.name}</Text></Text>
|
||||
<Text fz="md" fw="bold">Tanggal: <Text span>{state.findUnique.data.informasijadwalkegiatan.tanggal}</Text></Text>
|
||||
<Text fz="md" fw="bold">Waktu: <Text span>{state.findUnique.data.informasijadwalkegiatan.waktu}</Text></Text>
|
||||
<Text fz="md" fw="bold">Lokasi: <Text span>{state.findUnique.data.informasijadwalkegiatan.lokasi}</Text></Text>
|
||||
<Text fw="bold">
|
||||
Nama Kegiatan:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.name}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text fw="bold">
|
||||
Tanggal:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.tanggal}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text fw="bold">
|
||||
Waktu:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.waktu}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text fw="bold">
|
||||
Lokasi:
|
||||
<Text span fw="normal">
|
||||
{state.findUnique.data.informasijadwalkegiatan.lokasi}
|
||||
</Text>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
|
||||
<Title order={2} fw="bold">Deskripsi Kegiatan</Title>
|
||||
<Divider />
|
||||
<Box pl={20}>
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
||||
<Text
|
||||
ta="justify"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
|
||||
<Title order={2} fw="bold">Layanan yang Tersedia</Title>
|
||||
<Divider />
|
||||
<Box pl={20}>
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
|
||||
<Text
|
||||
ta="justify"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
|
||||
<Title order={2} fw="bold">Syarat & Ketentuan</Title>
|
||||
<Divider />
|
||||
<Box pl={20}>
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
|
||||
<Text
|
||||
ta="justify"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
|
||||
<Title order={2} fw="bold">Dokumen yang Perlu Dibawa</Title>
|
||||
<Divider />
|
||||
<Box pl={20}>
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
|
||||
<Text
|
||||
ta="justify"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Pendaftaran Kegiatan</Text>
|
||||
<Title order={2} fw="bold">Pendaftaran Kegiatan</Title>
|
||||
<Divider />
|
||||
<Group>
|
||||
<Button onClick={open}>Buat Pendaftaran</Button>
|
||||
@@ -112,18 +155,21 @@ function Page() {
|
||||
|
||||
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
|
||||
<Stack gap="xs">
|
||||
<Text fz="lg" c={colors['white-1']} fw="bold">Informasi Kontak</Text>
|
||||
<Group gap="xs">
|
||||
<Title order={3} c={colors['white-1']} fw="bold">Informasi Kontak</Title>
|
||||
<Group gap="xs" justify="flex-start">
|
||||
<IconUser size={18} color="white" />
|
||||
<Text fz="md" c={colors['white-1']}>Penanggung Jawab: <Text span fw="bold">Bidan Komang Ayu</Text></Text>
|
||||
<Text c={colors['white-1']}>
|
||||
Penanggung Jawab:
|
||||
<Text span fw="bold">Bidan Komang Ayu</Text>
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconPhone size={18} color="white" />
|
||||
<Text fz="md" c={colors['white-1']}>081234567890</Text>
|
||||
<Text c={colors['white-1']}>081234567890</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMail size={18} color="white" />
|
||||
<Text fz="md" c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text>
|
||||
<Text c={colors['white-1']}>puskesmasabiansemal3@gmail.com</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -136,4 +182,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -2,15 +2,14 @@
|
||||
'use client'
|
||||
import pendaftaranJadwalKegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/pendafataranJadwalKegiatan';
|
||||
import colors from '@/con/colors';
|
||||
import { Button, Divider, Stack, Text, Textarea, TextInput } from '@mantine/core';
|
||||
import { Button, Divider, Stack, Text, Textarea, TextInput, Title } from '@mantine/core';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreatePendaftaran() {
|
||||
const stateCreate = useProxy(pendaftaranJadwalKegiatanState);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
stateCreate.findMany.load();
|
||||
}, []);
|
||||
|
||||
@@ -32,15 +31,19 @@ useEffect(() => {
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Formulir Pendaftaran</Text>
|
||||
<Title order={2} ta="left">Formulir Pendaftaran</Title>
|
||||
<Divider />
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Balita"
|
||||
placeholder="Masukkan nama balita"
|
||||
size="md"
|
||||
value={stateCreate.create.form.name}
|
||||
onChange={(e) => stateCreate.create.form.name = e.target.value}
|
||||
label="Nama Balita"
|
||||
placeholder="Masukkan nama balita"
|
||||
size="md"
|
||||
value={stateCreate.create.form.name}
|
||||
onChange={(e) => stateCreate.create.form.name = e.target.value}
|
||||
styles={{
|
||||
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
|
||||
input: { fontSize: '16px', lineHeight: 1.5 },
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
type='date'
|
||||
@@ -50,41 +53,63 @@ useEffect(() => {
|
||||
w={{ base: '100%', md: '85%', lg: '75%', xl: '50%' }}
|
||||
value={stateCreate.create.form.tanggal}
|
||||
onChange={(e) => stateCreate.create.form.tanggal = e.target.value}
|
||||
styles={{
|
||||
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
|
||||
input: { fontSize: '16px', lineHeight: 1.5 },
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Nama Orang Tua / Wali"
|
||||
placeholder="Masukkan nama orang tua / wali"
|
||||
size="md"
|
||||
value={stateCreate.create.form.namaOrangtua}
|
||||
onChange={(e) => stateCreate.create.form.namaOrangtua = e.target.value}
|
||||
label="Nama Orang Tua / Wali"
|
||||
placeholder="Masukkan nama orang tua / wali"
|
||||
size="md"
|
||||
value={stateCreate.create.form.namaOrangtua}
|
||||
onChange={(e) => stateCreate.create.form.namaOrangtua = e.target.value}
|
||||
styles={{
|
||||
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
|
||||
input: { fontSize: '16px', lineHeight: 1.5 },
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Nomor Telepon"
|
||||
placeholder="Masukkan nomor telepon"
|
||||
size="md"
|
||||
value={stateCreate.create.form.nomor}
|
||||
onChange={(e) => stateCreate.create.form.nomor = e.target.value}
|
||||
label="Nomor Telepon"
|
||||
placeholder="Masukkan nomor telepon"
|
||||
size="md"
|
||||
value={stateCreate.create.form.nomor}
|
||||
onChange={(e) => stateCreate.create.form.nomor = e.target.value}
|
||||
styles={{
|
||||
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
|
||||
input: { fontSize: '16px', lineHeight: 1.5 },
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat lengkap"
|
||||
size="md"
|
||||
value={stateCreate.create.form.alamat}
|
||||
onChange={(e) => stateCreate.create.form.alamat = e.target.value}
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat lengkap"
|
||||
size="md"
|
||||
value={stateCreate.create.form.alamat}
|
||||
onChange={(e) => stateCreate.create.form.alamat = e.target.value}
|
||||
styles={{
|
||||
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
|
||||
input: { fontSize: '16px', lineHeight: 1.5 },
|
||||
}}
|
||||
/>
|
||||
<Textarea
|
||||
label="Catatan Khusus (Opsional)"
|
||||
placeholder="Masukkan catatan jika ada"
|
||||
size="md"
|
||||
value={stateCreate.create.form.catatan}
|
||||
onChange={(e) => stateCreate.create.form.catatan = e.target.value}
|
||||
label="Catatan Khusus (Opsional)"
|
||||
placeholder="Masukkan catatan jika ada"
|
||||
size="md"
|
||||
value={stateCreate.create.form.catatan}
|
||||
onChange={(e) => stateCreate.create.form.catatan = e.target.value}
|
||||
styles={{
|
||||
label: { fontSize: '14px', lineHeight: 1.4, fontWeight: 500 },
|
||||
input: { fontSize: '16px', lineHeight: 1.5 },
|
||||
}}
|
||||
/>
|
||||
<Button size="md" radius="lg" bg={colors['blue-button']} onClick={handleSubmit}>
|
||||
Daftar Sekarang
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={600} c="white">
|
||||
Daftar Sekarang
|
||||
</Text>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePendaftaran;
|
||||
export default CreatePendaftaran;
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import jadwalkegiatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconChevronRight, IconClockHour4, IconMapPin } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -27,13 +27,13 @@ function JadwalKegiatanPage() {
|
||||
<Box>
|
||||
<Paper bg={colors['white-trans-1']} p="xl" radius="xl" shadow="md" h="auto" mih="100vh">
|
||||
<Stack gap="lg">
|
||||
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
||||
<Title ta="center" order={1} c={colors['blue-button']} fw={700}>
|
||||
Jadwal Kegiatan Warga
|
||||
</Text>
|
||||
</Title>
|
||||
<Divider size="sm" color={colors['blue-button']} />
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
<Text fz="lg" c="dimmed">
|
||||
<Text fz={{ base: 'sm', sm: 'md' }} c="dimmed">
|
||||
Belum ada jadwal kegiatan yang tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -48,11 +48,11 @@ function JadwalKegiatanPage() {
|
||||
style={{ backdropFilter: 'blur(8px)' }}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between">
|
||||
<Text fw={700} fz="xl" c={colors['blue-button']}>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Title order={2} c={colors['blue-button']} fw={700} fz={{ base: 'md', sm: 'xl' }}>
|
||||
{item.informasijadwalkegiatan.name}
|
||||
</Text>
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']}>
|
||||
</Title>
|
||||
<Text fw={600} fz={{ base: 'xs', sm: 'sm' }} c={colors['blue-button']} lh="1.4">
|
||||
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
@@ -63,12 +63,16 @@ function JadwalKegiatanPage() {
|
||||
|
||||
<Group gap="xs">
|
||||
<IconClockHour4 size={18} />
|
||||
<Text fz="sm">{item.informasijadwalkegiatan.waktu}</Text>
|
||||
<Text fz={{ base: 'xs', sm: 'sm' }} lh="1.5">
|
||||
{item.informasijadwalkegiatan.waktu}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={18} />
|
||||
<Text fz="sm">{item.informasijadwalkegiatan.lokasi}</Text>
|
||||
<Text fz={{ base: 'xs', sm: 'sm' }} lh="1.5">
|
||||
{item.informasijadwalkegiatan.lokasi}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Divider my="sm" />
|
||||
@@ -98,4 +102,4 @@ function JadwalKegiatanPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export default JadwalKegiatanPage;
|
||||
export default JadwalKegiatanPage;
|
||||
@@ -6,9 +6,7 @@ import { Box, Center, ColorSwatch, Flex, Paper, SimpleGrid, Skeleton, Stack, Tex
|
||||
import { useEffect, useState } from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
// import { useRouter } from 'next/navigation';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
@@ -17,7 +15,6 @@ import GrafikPenyakit from './grafik-penyakit/page';
|
||||
import JadwalKegiatan from './jadwal-kegiatan-page/page';
|
||||
import ArtikelKesehatanPage from './artikel-kesehatan-page/page';
|
||||
|
||||
|
||||
function Page() {
|
||||
type DataTahunan = {
|
||||
tahun: string;
|
||||
@@ -31,7 +28,6 @@ function Page() {
|
||||
}>;
|
||||
};
|
||||
|
||||
// Count occurrences per year
|
||||
const countByYear = (data: any[], dateField: string) => {
|
||||
const counts: Record<string, number> = {};
|
||||
data?.forEach(item => {
|
||||
@@ -43,28 +39,23 @@ function Page() {
|
||||
|
||||
const statePersentase = useProxy(persentasekelahiran);
|
||||
const [chartData, setChartData] = useState<DataTahunan[]>([]);
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)');
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
statePersentase.kelahiran.findMany.load(1, 1000); // Load all kelahiran data
|
||||
statePersentase.kematian.findMany.load(1, 1000); // Load all kematian data
|
||||
statePersentase.kelahiran.findMany.load(1, 1000);
|
||||
statePersentase.kematian.findMany.load(1, 1000);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (statePersentase.kelahiran.findMany.data && statePersentase.kematian.findMany.data) {
|
||||
// Count kelahiran and kematian by year
|
||||
const kelahiranByYear = countByYear(statePersentase.kelahiran.findMany.data, 'tanggal');
|
||||
const kematianByYear = countByYear(statePersentase.kematian.findMany.data, 'tanggal');
|
||||
|
||||
// Get all unique years
|
||||
const allYears = new Set([
|
||||
...Object.keys(kelahiranByYear),
|
||||
...Object.keys(kematianByYear)
|
||||
]);
|
||||
|
||||
// Create data structure for the chart
|
||||
const dataByYear = Array.from(allYears).reduce<Record<string, DataTahunan>>((acc, year) => {
|
||||
acc[year] = {
|
||||
tahun: year,
|
||||
@@ -93,32 +84,44 @@ function Page() {
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
|
||||
{/* Page Title */}
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fw="bold"
|
||||
lh={1.2}
|
||||
>
|
||||
Data Kesehatan Masyarakat Puskesmas Darmasaba
|
||||
</Text>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
</Title>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="lg">
|
||||
{/* Bar Chart Kematian Kelahiran */}
|
||||
<Box>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<Paper p="xl" bg={colors['white-trans-1']}>
|
||||
<Box pb={30}>
|
||||
<Title order={2} mb="md">Data Kematian dan Kelahiran</Title>
|
||||
<Title order={2} mb="md" ta="center">
|
||||
Data Kematian dan Kelahiran
|
||||
</Title>
|
||||
|
||||
{chartData.length === 0 ? (
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
<Text c="dimmed" ta="center" py="xl" size="md">
|
||||
Belum ada data yang tersedia untuk ditampilkan
|
||||
</Text>
|
||||
) : (
|
||||
<>
|
||||
{/* Main Chart */}
|
||||
<Center>
|
||||
<Box h={400}>
|
||||
<Box style={{
|
||||
width: isMobile ? '90vw' : isTablet ? '700px' : '800px',
|
||||
width: isMobile ? '90vw' : '800px',
|
||||
maxWidth: '100%',
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
@@ -137,16 +140,21 @@ function Page() {
|
||||
</Center>
|
||||
</>
|
||||
)}
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
||||
|
||||
<Flex pb={30} justify="center" gap="xl" align="center" wrap="wrap">
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text>
|
||||
<ColorSwatch color="#EF3E3E" size={30} />
|
||||
<Flex gap={{ base: 'xs', md: 'sm' }} align="center">
|
||||
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
|
||||
Angka Kematian
|
||||
</Text>
|
||||
<ColorSwatch color="#EF3E3E" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kelahiran</Text>
|
||||
<Flex gap={{ base: 'xs', md: 'sm' }} align="center">
|
||||
<Text fw="bold" fz={{ base: 'sm', md: 'md' }}>
|
||||
Angka Kelahiran
|
||||
</Text>
|
||||
<ColorSwatch color="#3290CA" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
@@ -154,20 +162,13 @@ function Page() {
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<GrafikPenyakit />
|
||||
{/* Artikel Kesehatan */}
|
||||
|
||||
<Box>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 3,
|
||||
}}
|
||||
>
|
||||
{/* Fasilitas Kesehatan */}
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
<FasilitasKesehatan />
|
||||
{/* Jadwal Kegiatan */}
|
||||
<JadwalKegiatan />
|
||||
{/* Artikel Kesehatan */}
|
||||
<ArtikelKesehatanPage />
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
@@ -177,4 +178,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user