Compare commits
4 Commits
nico/11-de
...
nico/17-de
| Author | SHA1 | Date | |
|---|---|---|---|
| dc8793e3ae | |||
| c8484357cb | |||
| 342e9bbc65 | |||
| f6f77d9e35 |
@@ -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
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -136,12 +137,43 @@ const statepermohonanInformasiPublik = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.permohonaninformasipublik[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
statepermohonanInformasiPublik.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
statepermohonanInformasiPublik.findMany.loading = true; // Use the full path to access the property
|
||||
statepermohonanInformasiPublik.findMany.page = page;
|
||||
statepermohonanInformasiPublik.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ppid.permohonaninformasipublik[
|
||||
"find-many"
|
||||
].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
statepermohonanInformasiPublik.findMany.data = res.data.data || [];
|
||||
statepermohonanInformasiPublik.findMany.total = res.data.total || 0;
|
||||
statepermohonanInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load permohonan keberatan informasi:", res.data?.message);
|
||||
statepermohonanInformasiPublik.findMany.data = [];
|
||||
statepermohonanInformasiPublik.findMany.total = 0;
|
||||
statepermohonanInformasiPublik.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading permohonan keberatan informasi:", error);
|
||||
statepermohonanInformasiPublik.findMany.data = [];
|
||||
statepermohonanInformasiPublik.findMany.total = 0;
|
||||
statepermohonanInformasiPublik.findMany.totalPages = 1;
|
||||
} finally {
|
||||
statepermohonanInformasiPublik.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -57,17 +58,48 @@ const permohonanKeberatanInformasi = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
data: null as
|
||||
| null
|
||||
| Prisma.FormulirPermohonanKeberatanGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
permohonanKeberatanInformasi.findMany.data = res.data?.data ?? [];
|
||||
}>[],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
permohonanKeberatanInformasi.findMany.loading = true; // Use the full path to access the property
|
||||
permohonanKeberatanInformasi.findMany.page = page;
|
||||
permohonanKeberatanInformasi.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik[
|
||||
"find-many"
|
||||
].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
permohonanKeberatanInformasi.findMany.data = res.data.data || [];
|
||||
permohonanKeberatanInformasi.findMany.total = res.data.total || 0;
|
||||
permohonanKeberatanInformasi.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load permohonan keberatan informasi:", res.data?.message);
|
||||
permohonanKeberatanInformasi.findMany.data = [];
|
||||
permohonanKeberatanInformasi.findMany.total = 0;
|
||||
permohonanKeberatanInformasi.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading permohonan keberatan informasi:", error);
|
||||
permohonanKeberatanInformasi.findMany.data = [];
|
||||
permohonanKeberatanInformasi.findMany.total = 0;
|
||||
permohonanKeberatanInformasi.findMany.totalPages = 1;
|
||||
} finally {
|
||||
permohonanKeberatanInformasi.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -108,7 +108,7 @@ function DetailPerbekelDariMasa() {
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconX size={20} />
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -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"
|
||||
@@ -106,7 +106,7 @@ function DetailSDGSDesa() {
|
||||
size="md"
|
||||
disabled={sdgsState.delete.loading}
|
||||
>
|
||||
<IconX size={20} />
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -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,8 +26,10 @@ function SdgsDesa() {
|
||||
}
|
||||
|
||||
function ListSdgsDesa({ search }: { search: string }) {
|
||||
const listState = useProxy(sdgsDesa)
|
||||
const listState = useProxy(sdgsDesa);
|
||||
const router = useRouter();
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -39,10 +40,10 @@ function ListSdgsDesa({ search }: { search: string }) {
|
||||
} = listState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
@@ -53,79 +54,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='blue'
|
||||
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 +128,75 @@ 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}>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Nama SDGs Desa</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Jumlah</Text>
|
||||
<Text fz="xs" c="dark.6" lh={1.4}>
|
||||
{item.jumlah || '0'}
|
||||
</Text>
|
||||
</Box>
|
||||
<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"
|
||||
@@ -368,6 +368,13 @@ function EditAPBDes() {
|
||||
{ value: '2', label: 'Level 2 (Sub-kelompok)' },
|
||||
{ value: '3', label: 'Level 3 (Detail)' },
|
||||
]}
|
||||
styles={{
|
||||
option: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}}
|
||||
value={String(newItem.level)}
|
||||
onChange={(val) => setNewItem({ ...newItem, level: Number(val) || 1 })}
|
||||
/>
|
||||
@@ -378,6 +385,13 @@ function EditAPBDes() {
|
||||
{ value: 'belanja', label: 'Belanja' },
|
||||
{ value: 'pembiayaan', label: 'Pembiayaan' },
|
||||
]}
|
||||
styles={{
|
||||
option: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}}
|
||||
value={newItem.tipe}
|
||||
onChange={(val) => setNewItem({ ...newItem, tipe: (val as any) || 'pendapatan' })}
|
||||
/>
|
||||
|
||||
@@ -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} />
|
||||
@@ -353,6 +353,13 @@ function CreateAPBDes() {
|
||||
{ value: '2', label: 'Level 2 (Sub-kelompok)' },
|
||||
{ value: '3', label: 'Level 3 (Detail)' },
|
||||
]}
|
||||
styles={{
|
||||
option: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}}
|
||||
value={String(newItem.level)}
|
||||
onChange={(val) => setNewItem({ ...newItem, level: Number(val) || 1 })}
|
||||
/>
|
||||
@@ -363,6 +370,13 @@ function CreateAPBDes() {
|
||||
{ value: 'belanja', label: 'Belanja' },
|
||||
{ value: 'pembiayaan', label: 'Pembiayaan' },
|
||||
]}
|
||||
styles={{
|
||||
option: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}}
|
||||
value={newItem.level === 1 ? null : newItem.tipe}
|
||||
onChange={(val) => setNewItem({ ...newItem, tipe: val as any })}
|
||||
disabled={newItem.level === 1}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconFile, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -45,82 +45,97 @@ function APBDes() {
|
||||
function ListAPBDes({ search }: { search: string }) {
|
||||
const listState = useProxy(apbdes);
|
||||
const router = useRouter();
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
|
||||
const { data, page, totalPages, loading, load } = listState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
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 +143,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>
|
||||
<Box>
|
||||
<Text fz="sm"fw={600} lh={1.4}>
|
||||
Tahun
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.tahun || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm"fw={600} 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>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<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={120}>
|
||||
<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={120}>
|
||||
<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: 20, md: 20 }}>
|
||||
<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,54 @@ 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">
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Nama Program</Text>
|
||||
<Text fw={500} fz="sm" lh={1.5} lineClamp={1}>
|
||||
{item.name || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Kategori</Text>
|
||||
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1}>
|
||||
{item.kategori?.name || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
<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 +202,6 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
}}
|
||||
size="md"
|
||||
radius="md"
|
||||
mt="md"
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
@@ -152,4 +209,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} />
|
||||
|
||||
@@ -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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -32,6 +32,7 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
@@ -50,14 +51,14 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
} = stateKategori.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, debouncedSearch)
|
||||
}, [page, debouncedSearch])
|
||||
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py="md">
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
@@ -65,28 +66,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 +101,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 { useDebouncedValue, 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,8 @@ function ListPrestasiDesa() {
|
||||
function ListPrestasi({ search }: { search: string }) {
|
||||
const listState = useProxy(prestasiState.prestasiDesa)
|
||||
const router = useRouter();
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -38,93 +40,149 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
} = listState.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
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" style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped verticalSpacing="sm" miw={800}>
|
||||
<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>Nama Prestasi</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Kategori</TableTh>
|
||||
<TableTh 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 style={{ maxWidth: 250 }}>
|
||||
<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 style={{ maxWidth: 250 }}>
|
||||
<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>
|
||||
<Text truncate="end" fz="md" lh={1.5}>
|
||||
{item.kategori?.name || 'Tidak ada kategori'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<TableTd ta="center">
|
||||
<Center>
|
||||
<Button
|
||||
size="sm"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<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={"xs"}>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Nama Prestasi</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4} lineClamp={2}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
|
||||
<Text fz="sm" fw={500} lh={1.5} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Kategori</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.kategori?.name || 'Tidak ada kategori'}
|
||||
</Text>
|
||||
</Box>
|
||||
<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 +190,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,8 +1,26 @@
|
||||
/* 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 { useShallowEffect } from '@mantine/hooks';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -28,11 +46,12 @@ function MediaSosial() {
|
||||
}
|
||||
|
||||
function ListMediaSosial({ search }: { search: string }) {
|
||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||
const router = useRouter();
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
|
||||
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 +67,204 @@ function ListMediaSosial({ search }: { search: string }) {
|
||||
} = stateMediaSosial.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
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">
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Nama Media Sosial / Kontak</Text>
|
||||
<Text fw={500} fz="sm" lh={1.45}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Gambar</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>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Link / No. Telepon</Text>
|
||||
<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 +283,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} />
|
||||
|
||||
@@ -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, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -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..."
|
||||
@@ -29,12 +29,13 @@ function ProgramInovasi() {
|
||||
function ListProgramInovasi({ search }: { search: string }) {
|
||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
||||
const router = useRouter();
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||
|
||||
const { data, page, totalPages, loading, load } = stateProgramInovasi.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
@@ -52,75 +53,144 @@ 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 */}
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Nama Program</Text>
|
||||
<Text fw={500} lh={1.4}>{item.name}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Description */}
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
|
||||
<Text fz="sm" c="gray.7" lineClamp={2}>
|
||||
{item.description || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Link */}
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Link</Text>
|
||||
<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
|
||||
|
||||
@@ -79,7 +79,7 @@ function EditDaftarInformasiPublik() {
|
||||
};
|
||||
|
||||
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} />
|
||||
|
||||
@@ -52,7 +52,7 @@ function DetailDaftarInformasiPublik() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
@@ -83,39 +83,39 @@ function DetailDaftarInformasiPublik() {
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold" mb={4}>Deskripsi</Text>
|
||||
<Box
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
<Text
|
||||
px={"xs"}
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||
className="prose max-w-none"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group gap="sm" mt="md">
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
leftSection={<IconEdit size={18} />}
|
||||
onClick={() => router.push(`/admin/ppid/daftar-informasi-publik/${data.id}/edit`)}
|
||||
disabled={!data}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
leftSection={<IconEdit size={18} />}
|
||||
onClick={() => router.push(`/admin/ppid/daftar-informasi-publik/${data.id}/edit`)}
|
||||
disabled={!data}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
leftSection={<IconTrash size={18} />}
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={stateDaftarInformasi.delete.loading || !data}
|
||||
loading={stateDaftarInformasi.delete.loading}
|
||||
>
|
||||
Hapus
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
leftSection={<IconTrash size={18} />}
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={stateDaftarInformasi.delete.loading || !data}
|
||||
loading={stateDaftarInformasi.delete.loading}
|
||||
>
|
||||
Hapus
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function CreateDaftarInformasi() {
|
||||
};
|
||||
|
||||
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,24 @@
|
||||
'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, useViewportSize } from '@mantine/hooks';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect, useViewportSize } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -10,7 +27,7 @@ import HeaderSearch from '../../_com/header';
|
||||
import daftarInformasiPublik from '../../_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||
|
||||
function DaftarInformasiPublik() {
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
@@ -26,102 +43,158 @@ function DaftarInformasiPublik() {
|
||||
}
|
||||
|
||||
function ListDaftarInformasi({ search }: { search: string }) {
|
||||
const listData = useProxy(daftarInformasiPublik)
|
||||
const router = useRouter()
|
||||
const listData = useProxy(daftarInformasiPublik);
|
||||
const router = useRouter();
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
|
||||
const { data, page, totalPages, loading, load } = listData.findMany
|
||||
const { width } = useViewportSize()
|
||||
const isMobile = width < 768
|
||||
const { data, page, totalPages, loading, load } = listData.findMany;
|
||||
const { width } = useViewportSize();
|
||||
const isMobile = width < 768;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Stack py="md">
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>List Daftar Informasi Publik</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/daftar-informasi-publik/create')}
|
||||
>
|
||||
{isMobile ? 'Tambah' : 'Tambah Baru'}
|
||||
</Button>
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||
<Title order={2} lh={1.2}>
|
||||
List Daftar Informasi Publik
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/daftar-informasi-publik/create')}
|
||||
>
|
||||
{isMobile ? 'Tambah' : 'Tambah Baru'}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{filteredData.length === 0 ? (
|
||||
<Stack align="center" py="xl">
|
||||
<IconDeviceImacCog size={40} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fw={500} c="dimmed">Belum ada informasi publik yang tersedia</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={500} c="dimmed" lh={1.5}>
|
||||
Belum ada informasi publik yang tersedia
|
||||
</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table
|
||||
highlightOnHover
|
||||
striped
|
||||
stickyHeader
|
||||
style={{ minWidth: '700px' }}
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Jenis Informasi</TableTh>
|
||||
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Text fz="sm">{(page - 1) * 10 + index + 1}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text fw={500} lineClamp={1}>{item.jenisInformasi}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text lineClamp={1} fz="sm" c="dimmed">
|
||||
{item.deskripsi?.replace(/<[^>]*>?/gm, '').substring(0, 80) + '...'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<>
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md" style={{ overflowX: 'auto' }}>
|
||||
<Table
|
||||
highlightOnHover
|
||||
striped
|
||||
stickyHeader
|
||||
style={{ minWidth: '700px' }}
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh w="25%">
|
||||
<Text fw={600} lh={1.4}>
|
||||
Jenis Informasi
|
||||
</Text>
|
||||
</TableTh>
|
||||
<TableTh w="40%">
|
||||
<Text fw={600} lh={1.4}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
</TableTh>
|
||||
<TableTh ta="center" w="20%">
|
||||
<Text fw={600} lh={1.4}>
|
||||
Aksi
|
||||
</Text>
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fz="sm" fw={600} lh={1.5} lineClamp={1}>
|
||||
{item.jenisInformasi}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1}>
|
||||
{item.deskripsi?.replace(/<[^>]*>?/gm, '').substring(0, 80)}...
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/ppid/daftar-informasi-publik/${item.id}`)}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ppid/daftar-informasi-publik/${item.id}`)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Card List */}
|
||||
<Stack hiddenFrom="md" gap="sm">
|
||||
{filteredData.map((item) => (
|
||||
<Paper key={item.id} withBorder p="sm" radius="md">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Text fw={600} lh={1.4}>
|
||||
Jenis Informasi
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.jenisInformasi}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={600} lh={1.4}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.5} c="dimmed">
|
||||
{item.deskripsi?.replace(/<[^>]*>?/gm, '').substring(0, 80)}...
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ppid/daftar-informasi-publik/${item.id}`)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
<Center mt="lg">
|
||||
|
||||
<Center mt={{ base: 'lg', md: 'xl' }}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
@@ -129,14 +202,12 @@ function ListDaftarInformasi({ search }: { search: string }) {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default DaftarInformasiPublik;
|
||||
export default DaftarInformasiPublik;
|
||||
@@ -8,11 +8,11 @@ import { useProxy } from 'valtio/utils';
|
||||
import stateDasarHukumPPID from '../../_state/ppid/dasar_hukum/dasarHukum';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listDasarHukum = useProxy(stateDasarHukumPPID)
|
||||
const router = useRouter();
|
||||
const listDasarHukum = useProxy(stateDasarHukumPPID);
|
||||
useShallowEffect(() => {
|
||||
listDasarHukum.findById.load('1')
|
||||
}, [])
|
||||
listDasarHukum.findById.load('1');
|
||||
}, []);
|
||||
|
||||
if (listDasarHukum.findById.loading) {
|
||||
return (
|
||||
@@ -40,15 +40,16 @@ function Page() {
|
||||
<Title order={3} c={colors['blue-button']}>Preview Dasar Hukum PPID</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/ppid/dasar-hukum/edit')}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
w={{ base: '100%', md: "110%" }}
|
||||
c="green"
|
||||
variant="light"
|
||||
leftSection={<IconEdit size={18} stroke={2} />}
|
||||
radius="md"
|
||||
onClick={() => router.push('/admin/ppid/dasar-hukum/edit')}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
@@ -57,33 +58,39 @@ function Page() {
|
||||
<Grid>
|
||||
<GridCol span={12}>
|
||||
<Center>
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo PPID" />
|
||||
<Image loading="lazy" src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo PPID" />
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={12}>
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: '1.5rem', md: '2rem' }}
|
||||
fw="bold"
|
||||
<Title
|
||||
order={3}
|
||||
ta="center"
|
||||
lh={{ base: 1.15, md: 1.1 }}
|
||||
fw="bold"
|
||||
c={colors['blue-button']}
|
||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.judul }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.judul }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
<Divider my="xl" color={colors['blue-button']} />
|
||||
|
||||
<Box
|
||||
className="prose max-w-none"
|
||||
<Text
|
||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.content }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal", fontSize: '1.1rem', lineHeight: 1.7, textAlign: 'justify' }}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1.55,
|
||||
textAlign: 'justify',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -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, Tooltip } 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';
|
||||
@@ -56,42 +56,77 @@ function LayoutTabsIKM({ children }: { children: React.ReactNode }) {
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<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) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={e.description}
|
||||
withArrow
|
||||
position="bottom"
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<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
|
||||
value={e.value}
|
||||
leftSection={e.icon}
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||
}}
|
||||
>
|
||||
{e.label}
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
))}
|
||||
</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((e, i) => (
|
||||
<TabsPanel key={i} value={e.value} mt="md">
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
|
||||
@@ -85,11 +85,11 @@ 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} />
|
||||
</Button>
|
||||
<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">
|
||||
Edit Responden
|
||||
</Title>
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -51,7 +51,7 @@ function RespondenCreate() {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
@@ -96,24 +96,24 @@ function RespondenCreate() {
|
||||
}
|
||||
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
<Select
|
||||
key={"rating_responden"}
|
||||
label={"Rating"}
|
||||
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
|
||||
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.ratingId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||
/>
|
||||
label={"Rating"}
|
||||
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
|
||||
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
|
||||
onChange={(val) => {
|
||||
stategrafikBerdasarkanResponden.create.form.ratingId = val ?? "";
|
||||
}}
|
||||
data={
|
||||
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||
.filter(Boolean) // Hapus null, undefined, dll
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name || 'Tanpa Nama',
|
||||
}))
|
||||
}
|
||||
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||
/>
|
||||
<Select
|
||||
key={"kelompokUmur"}
|
||||
label={"Kelompok Umur"}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
'use client';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -9,11 +25,11 @@ import HeaderSearch from '../../../_com/header';
|
||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
||||
|
||||
function Responden() {
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title="Data Responden"
|
||||
title="Responden"
|
||||
placeholder="Cari nama responden..."
|
||||
searchIcon={<IconSearch size={18} />}
|
||||
value={search}
|
||||
@@ -33,17 +49,13 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
const router = useRouter();
|
||||
const { data, page, totalPages, loading, load } = state.findMany;
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10)
|
||||
load(page, 10);
|
||||
}, [page]);
|
||||
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const filteredData = (data || []).filter((item) => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
return item.name.toLowerCase().includes(keyword);
|
||||
});
|
||||
|
||||
if (loading || !data) {
|
||||
@@ -56,21 +68,25 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Paper withBorder bg="white" p="lg" radius="md" shadow="sm">
|
||||
<Paper withBorder bg="white" p={{ base: 'md', sm: 'lg' }} radius="md" shadow="sm">
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Data Responden</Title>
|
||||
<Table striped withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Tanggal</TableTh>
|
||||
<TableTh>Jenis Kelamin</TableTh>
|
||||
<TableTh style={{ textAlign: 'center' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text c="dimmed" ta="center" py="md">
|
||||
<Title order={2} lh={1.2}>
|
||||
Data Responden
|
||||
</Title>
|
||||
<Box visibleFrom="md">
|
||||
<Table striped withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh ta="center">No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Tanggal</TableTh>
|
||||
<TableTh>Jenis Kelamin</TableTh>
|
||||
<TableTh ta="center">Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
</Box>
|
||||
<Text c="dimmed" ta="center" py="md" fz={{ base: 'sm', md: 'md' }} lh={1.4}>
|
||||
Belum ada data responden yang tersedia
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -79,54 +95,133 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper withBorder bg="white" p="lg" radius="md" shadow="sm">
|
||||
<Paper withBorder bg="white" p={{ base: 'md', sm: 'lg' }} radius="md" shadow="sm">
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Data Responden</Title>
|
||||
<Table striped withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Nama</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Tanggal</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Jenis Kelamin</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<Title order={2} lh={1.2}>
|
||||
Data Responden
|
||||
</Title>
|
||||
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table striped withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Text c="dimmed" ta="center" py="md">
|
||||
Tidak ada data yang cocok dengan pencarian
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTh w="5%" ta="center">
|
||||
No
|
||||
</TableTh>
|
||||
<TableTh w="25%">Nama</TableTh>
|
||||
<TableTh w="25%">Tanggal</TableTh>
|
||||
<TableTh w="20%">Jenis Kelamin</TableTh>
|
||||
<TableTh w="15%" ta="center">
|
||||
Aksi
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>{item.name}</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID') : '-'}
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>{item.jenisKelamin.name}</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Text c="dimmed" ta="center" py="md" fz="sm" lh={1.4}>
|
||||
Tidak ada data yang cocok dengan pencarian
|
||||
</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center">{index + 1}</TableTd>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID') : '-'}
|
||||
</TableTd>
|
||||
<TableTd>{item.jenisKelamin.name}</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Card View */}
|
||||
<Box hiddenFrom="md">
|
||||
{filteredData.length === 0 ? (
|
||||
<Text c="dimmed" ta="center" py="md" fz="sm" lh={1.4}>
|
||||
Tidak ada data yang cocok dengan pencarian
|
||||
</Text>
|
||||
) : (
|
||||
<Stack gap="sm">
|
||||
{filteredData.map((item, index) => (
|
||||
<Paper key={item.id} withBorder p="sm" radius="md">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
No
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{index + 1}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
Nama
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
Tanggal
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.tanggal
|
||||
? new Date(item.tanggal).toLocaleDateString('id-ID')
|
||||
: '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>
|
||||
Jenis Kelamin
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.4}>
|
||||
{item.jenisKelamin.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{filteredData.length > 0 && (
|
||||
<Center>
|
||||
<Pagination
|
||||
@@ -138,7 +233,6 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
}}
|
||||
size="md"
|
||||
radius="md"
|
||||
mt="md"
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
@@ -147,6 +241,4 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default Responden;
|
||||
|
||||
|
||||
export default Responden;
|
||||
@@ -27,7 +27,7 @@ function DetailPermohonanInformasiPublik() {
|
||||
const data = state.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py="md">
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -39,7 +39,7 @@ function DetailPermohonanInformasiPublik() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -1,105 +1,277 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors'
|
||||
import { Box, Button, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'
|
||||
import { useShallowEffect } from '@mantine/hooks'
|
||||
import { IconDeviceImacCog, IconId, IconInfoCircle, IconPhone, IconUser } from '@tabler/icons-react'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core'
|
||||
import {
|
||||
IconDeviceImacCog,
|
||||
IconId,
|
||||
IconInfoCircle,
|
||||
IconPhone,
|
||||
IconSearch,
|
||||
IconUser,
|
||||
} from '@tabler/icons-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import statepermohonanInformasiPublikForm from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik'
|
||||
import { useDebouncedValue } from '@mantine/hooks'
|
||||
|
||||
function Page() {
|
||||
const permohonanInformasiPublikState = useProxy(statepermohonanInformasiPublikForm)
|
||||
const router = useRouter()
|
||||
const { data, page, totalPages, loading, load } = permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
|
||||
useShallowEffect(() => {
|
||||
permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany.load()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (!permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany.data) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} p="lg" align="center">
|
||||
<Skeleton radius="md" h={40} w="60%" />
|
||||
<Stack pos="relative" p="lg" align="center">
|
||||
<Skeleton radius="md" h={200} w="100%" />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap={'sm'}>
|
||||
<Grid mb={10}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Title order={2} lh={1.2} c="dark">
|
||||
Daftar Permohonan Informasi Publik
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<Paper radius="lg" bg={colors['white-1']}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={"Cari nama..."}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
w="100%"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Stack align="center" py="xl" ta="center">
|
||||
<IconInfoCircle size={40} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={500} c="dimmed" lh={1.5}>
|
||||
{search
|
||||
? 'Tidak ditemukan data yang sesuai dengan pencarian'
|
||||
: 'Belum ada permohonan yang tercatat'
|
||||
}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const data = permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany.data
|
||||
|
||||
return (
|
||||
<Box py="md">
|
||||
<Paper bg={colors['white-1']} p="lg" radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between">
|
||||
<Title order={2} c="dark">Daftar Permohonan Informasi Publik</Title>
|
||||
<IconInfoCircle size={20} stroke={1.5} />
|
||||
</Group>
|
||||
<Box py={{ base: 'sm', md: 'md' }}>
|
||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap={'sm'}>
|
||||
<Grid mb={10}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Title order={2} lh={1.2} c="dark">
|
||||
Daftar Permohonan Informasi Publik
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<Paper radius="lg" bg={colors['white-1']}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={"Cari nama..."}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
w="100%"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{data.length === 0 ? (
|
||||
<Stack align="center" py="xl">
|
||||
<Stack align="center" py={{ base: 'xl', md: 'xl' }}>
|
||||
<IconInfoCircle size={40} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fw={500} c="dimmed">Belum ada permohonan informasi yang tercatat</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={500} c="dimmed" lh={1.5}>
|
||||
Belum ada permohonan informasi yang tercatat
|
||||
</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table
|
||||
highlightOnHover
|
||||
withRowBorders
|
||||
withColumnBorders
|
||||
withTableBorder
|
||||
striped
|
||||
stickyHeader
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh><Group gap={5}><IconUser size={16} /> Nama</Group></TableTh>
|
||||
<TableTh><Group gap={5}><IconId size={16} /> NIK</Group></TableTh>
|
||||
<TableTh><Group gap={5}><IconPhone size={16} /> Telepon</Group></TableTh>
|
||||
<TableTh><Group gap={5}><IconInfoCircle size={16} /> Detail</Group></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text lineClamp={1} fw={500}>{item.name}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
{item.nik}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
{item.notelp}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<>
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh fz="sm" fw={600} ta="center" w={60}>
|
||||
No
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600}>
|
||||
<Group gap={5}>
|
||||
<IconUser size={16} />
|
||||
Nama
|
||||
</Group>
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600}>
|
||||
<Group gap={5}>
|
||||
<IconId size={16} />
|
||||
NIK
|
||||
</Group>
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600}>
|
||||
<Group gap={5}>
|
||||
<IconPhone size={16} />
|
||||
Telepon
|
||||
</Group>
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} w={140}>
|
||||
<Group gap={5}>
|
||||
<IconInfoCircle size={16} />
|
||||
Detail
|
||||
</Group>
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center" fz="sm" lh={1.5}>
|
||||
{index + 1}
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" fw={500} lh={1.5} lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" lh={1.5}>{item.nik}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" lh={1.5}>{item.notelp}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ppid/permohonan-informasi-publik/${item.id}`)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Cards */}
|
||||
<Stack hiddenFrom="md" gap="xs">
|
||||
{data.map((item, index) => (
|
||||
<Paper key={item.id} p="sm" radius="md" withBorder bg="white">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dark">
|
||||
No
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.5} c="dark">
|
||||
{index + 1}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dark">
|
||||
Nama
|
||||
</Text>
|
||||
<Text fz="sm" fw={500} lh={1.5} c="dark" lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dark">
|
||||
NIK
|
||||
</Text>
|
||||
<Text fz="sm" lh={1.5} c="dark">{item.nik}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dark">
|
||||
Telepon
|
||||
</Text>
|
||||
<Text fz="sm" lh={1.5} c="dark">{item.notelp}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dark">
|
||||
Detail
|
||||
</Text>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/ppid/permohonan-informasi-publik/${item.id}`)}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ppid/permohonan-informasi-publik/${item.id}`)
|
||||
}
|
||||
mt={2}
|
||||
>
|
||||
Detail
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
withControls
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
export default Page
|
||||
@@ -28,7 +28,7 @@ function DetailPermohonanKeberatanInformasiPublik() {
|
||||
const data = state.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py="md">
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
@@ -40,7 +40,7 @@ function DetailPermohonanKeberatanInformasiPublik() {
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -1,97 +1,285 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors'
|
||||
import { Box, Button, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'
|
||||
import { useShallowEffect } from '@mantine/hooks'
|
||||
import { IconDeviceImacCog, IconInfoCircle, IconMail, IconPhone, IconUser } from '@tabler/icons-react'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core'
|
||||
import {
|
||||
IconDeviceImacCog,
|
||||
IconInfoCircle,
|
||||
IconMail,
|
||||
IconPhone,
|
||||
IconSearch,
|
||||
IconUser,
|
||||
} from '@tabler/icons-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import statePermohonanKeberatan from '../../_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useDebouncedValue } from '@mantine/hooks'
|
||||
|
||||
function Page() {
|
||||
const listState = useProxy(statePermohonanKeberatan)
|
||||
const router = useRouter()
|
||||
useShallowEffect(() => {
|
||||
listState.findMany.load()
|
||||
}, [])
|
||||
const listState = useProxy(statePermohonanKeberatan)
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
const { data, page, totalPages, loading, load } = listState.findMany
|
||||
|
||||
if (!listState.findMany.data) {
|
||||
useEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} p="lg" align="center">
|
||||
<Skeleton radius="md" h={40} w="60%" />
|
||||
<Stack pos="relative" p="lg" align="center">
|
||||
<Skeleton radius="md" h={200} w="100%" />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const data = listState.findMany.data
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap={'sm'}>
|
||||
<Grid mb={10}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Title order={2} lh={1.2} c="dark">
|
||||
Daftar Permohonan Keberatan Informasi Publik
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<Paper radius="lg" bg={colors['white-1']}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={"Cari nama..."}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
w="100%"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Stack align="center" py="xl" ta="center">
|
||||
<IconInfoCircle size={40} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={500} c="dimmed" lh={1.5}>
|
||||
{search
|
||||
? 'Tidak ditemukan data yang sesuai dengan pencarian'
|
||||
: 'Belum ada permohonan keberatan yang tercatat'
|
||||
}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py="md">
|
||||
<Paper bg={colors['white-1']} p="lg" radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between">
|
||||
<Title order={2} c="dark">Daftar Permohonan Keberatan Informasi Publik</Title>
|
||||
<IconInfoCircle size={20} stroke={1.5} />
|
||||
</Group>
|
||||
<Box py={{ base: 'md', md: 'lg' }}>
|
||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap={'sm'}>
|
||||
<Grid mb={10}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Title order={2} lh={1.2} c="dark">
|
||||
Daftar Permohonan Keberatan Informasi Publik
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<Paper radius="lg" bg={colors['white-1']}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={"Cari nama..."}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
w="100%"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{data.length === 0 ? (
|
||||
<Stack align="center" py="xl">
|
||||
<Stack align="center" py="xl" ta="center">
|
||||
<IconInfoCircle size={40} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fw={500} c="dimmed">Belum ada permohonan keberatan yang tercatat</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} fw={500} c="dimmed" lh={1.5}>
|
||||
Belum ada permohonan keberatan yang tercatat
|
||||
</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table
|
||||
highlightOnHover
|
||||
withRowBorders
|
||||
withColumnBorders
|
||||
withTableBorder
|
||||
striped
|
||||
stickyHeader
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh><Group gap={5}><IconUser size={16} /> Nama</Group></TableTh>
|
||||
<TableTh><Group gap={5}><IconMail size={16} /> Email</Group></TableTh>
|
||||
<TableTh><Group gap={5}><IconPhone size={16} /> Telepon</Group></TableTh>
|
||||
<TableTh><Group gap={5}><IconInfoCircle size={16} /> Detail</Group></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Text lineClamp={1} fw={500}>{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text size="sm">{item.email || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>{item.notelp || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<>
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh fz="sm" fw={600} lh={1.4} ta="center">
|
||||
No
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
<Group gap={5}>
|
||||
<IconUser size={16} />
|
||||
Nama
|
||||
</Group>
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
<Group gap={5}>
|
||||
<IconMail size={16} />
|
||||
Email
|
||||
</Group>
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
<Group gap={5}>
|
||||
<IconPhone size={16} />
|
||||
Telepon
|
||||
</Group>
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4} ta="center">
|
||||
<Group gap={5}>
|
||||
<IconInfoCircle size={16} />
|
||||
Detail
|
||||
</Group>
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center" fz="sm" lh={1.5}>
|
||||
{index + 1}
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" fw={500} lh={1.5} lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" lh={1.5}>
|
||||
{item.email || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" lh={1.5}>
|
||||
{item.notelp || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/ppid/permohonan-keberatan-informasi-publik/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Cards */}
|
||||
<Stack hiddenFrom="md" gap="xs">
|
||||
{data.map((item, index) => (
|
||||
<Paper key={item.id} p="sm" radius="md" withBorder bg="white">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dimmed">
|
||||
No
|
||||
</Text>
|
||||
<Text fz="sm" fw={600} lh={1.5}>
|
||||
{index + 1}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dimmed">
|
||||
Nama
|
||||
</Text>
|
||||
<Text fz="sm" fw={600} lh={1.5}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dimmed">
|
||||
Email
|
||||
</Text>
|
||||
<Text fz="sm" lh={1.5}>
|
||||
{item.email || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4} c="dimmed">
|
||||
Telepon
|
||||
</Text>
|
||||
<Text fz="sm" lh={1.5}>
|
||||
{item.notelp || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/ppid/permohonan-keberatan-informasi-publik/${item.id}`)}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/ppid/permohonan-keberatan-informasi-publik/${item.id}`
|
||||
)
|
||||
}
|
||||
mt={4}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
withEdges
|
||||
withControls
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
)
|
||||
}
|
||||
export default Page;
|
||||
|
||||
export default Page
|
||||
@@ -138,7 +138,7 @@ function EditProfilePPID() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box p="md">
|
||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||
<Stack gap="md">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="md">
|
||||
|
||||
@@ -7,13 +7,13 @@ import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
|
||||
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const allList = useProxy(stateProfilePPID)
|
||||
const router = useRouter();
|
||||
const allList = useProxy(stateProfilePPID);
|
||||
|
||||
useShallowEffect(() => {
|
||||
allList.profile.load("edit") // Assuming "1" is your default ID, adjust as needed
|
||||
}, [])
|
||||
allList.profile.load("edit");
|
||||
}, []);
|
||||
|
||||
if (!allList.profile.data) {
|
||||
return (
|
||||
@@ -32,19 +32,19 @@ function Page() {
|
||||
<Stack gap="md">
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={3} c={colors['blue-button']}>Preview Profil PPID</Title>
|
||||
<Title order={3} c={colors['blue-button']} lh={1.2}>Preview Profil PPID</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button
|
||||
w={{base: '100%', md: "110%"}}
|
||||
c="green"
|
||||
variant="light"
|
||||
leftSection={<IconEdit size={18} stroke={2} />}
|
||||
radius="md"
|
||||
onClick={() => router.push(`/admin/ppid/profil-ppid/${allList.profile.data?.id}`)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
w={{ base: '100%', md: "110%" }}
|
||||
c="green"
|
||||
variant="light"
|
||||
leftSection={<IconEdit size={18} stroke={2} />}
|
||||
radius="md"
|
||||
onClick={() => router.push(`/admin/ppid/profil-ppid/${allList.profile.data?.id}`)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
{dataArray.map((item) => (
|
||||
@@ -57,9 +57,14 @@ function Page() {
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={12}>
|
||||
<Text ta="center" fz={{ base: "1.2rem", md: "1.8rem" }} fw="bold" c={colors['blue-button']}>
|
||||
<Title
|
||||
order={2}
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
lh={1.15}
|
||||
>
|
||||
PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA
|
||||
</Text>
|
||||
</Title>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
@@ -87,34 +92,77 @@ function Page() {
|
||||
className="glass3"
|
||||
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
||||
>
|
||||
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.2rem", md: "1.6rem" }}>
|
||||
<Title
|
||||
order={3}
|
||||
c={colors['white-1']}
|
||||
ta="center"
|
||||
lh={1.2}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Title>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
|
||||
<Box mt="lg">
|
||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Biodata</Text>
|
||||
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.biodata }} />
|
||||
<Title order={3} lh={1.2} mb={4}>
|
||||
Biodata
|
||||
</Title>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="justify"
|
||||
c={colors['blue-button']}
|
||||
lh={1.5}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: item.biodata }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box mt="xl">
|
||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Riwayat Karir</Text>
|
||||
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.riwayat }} />
|
||||
</Box>
|
||||
|
||||
<Box mt="xl">
|
||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Pengalaman Organisasi</Text>
|
||||
<Title order={3} lh={1.2} mb={4}>
|
||||
Riwayat Karir
|
||||
</Title>
|
||||
<Box px={20}>
|
||||
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.pengalaman }} />
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="justify"
|
||||
c={colors['blue-button']}
|
||||
lh={1.5}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: item.riwayat }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box mt="xl" mb="lg">
|
||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Program Kerja Unggulan</Text>
|
||||
|
||||
<Box mt="xl">
|
||||
<Title order={3} lh={1.2} mb={4}>
|
||||
Pengalaman Organisasi
|
||||
</Title>
|
||||
<Box px={20}>
|
||||
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.unggulan }} />
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="justify"
|
||||
c={colors['blue-button']}
|
||||
lh={1.5}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: item.pengalaman }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box mt="xl" mb="lg">
|
||||
<Title order={3} lh={1.2} mb={4}>
|
||||
Program Kerja Unggulan
|
||||
</Title>
|
||||
<Box px={20}>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="justify"
|
||||
c={colors['blue-button']}
|
||||
lh={1.5}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: item.unggulan }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -122,9 +170,7 @@ function Page() {
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Page;
|
||||
@@ -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 { IconBuildingCommunity, IconHierarchy2, IconUsers } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -63,51 +63,92 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
<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}
|
||||
value={tab.value}
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
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
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsTab
|
||||
key={i}
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Stack >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function EditPegawaiPPID() {
|
||||
};
|
||||
|
||||
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} />
|
||||
|
||||
@@ -51,7 +51,7 @@ function DetailPegawai() {
|
||||
const data = statePegawai.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
@@ -59,7 +59,7 @@ function DetailPegawai() {
|
||||
</Box>
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
w={{ base: "100%", md: "70%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
|
||||
@@ -78,7 +78,7 @@ function CreatePegawaiPPID() {
|
||||
};
|
||||
|
||||
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,13 +1,32 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title } from '@mantine/core';
|
||||
import { IconCheck, IconDeviceImacCog, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
|
||||
function PegawaiPPID() {
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -28,6 +47,7 @@ function PegawaiPPID() {
|
||||
function ListPegawaiPPID({ search }: { search: string }) {
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai);
|
||||
const router = useRouter();
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -38,47 +58,28 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
||||
} = stateOrganisasi.findMany;
|
||||
|
||||
useEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
<Stack py="xl">
|
||||
<Skeleton height={300} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box py="xl">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Pegawai PPID</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/struktur-ppid/pegawai/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Center py="xl">
|
||||
<Text c="dimmed">Tidak ada data pegawai yang ditemukan</Text>
|
||||
</Center>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Pegawai PPID</Title>
|
||||
<Title order={2} lh={1.2}>
|
||||
Daftar Pegawai PPID
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
@@ -87,53 +88,70 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Center py="xl">
|
||||
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||
Tidak ada data pegawai yang ditemukan
|
||||
</Text>
|
||||
</Center>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py="xl">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={2} lh={1.2}>
|
||||
Daftar Pegawai PPID
|
||||
</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/struktur-ppid/pegawai/create')}
|
||||
>
|
||||
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 Lengkap</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Posisi</TableTh>
|
||||
<TableTh style={{ width: '10%' }}>Status</TableTh>
|
||||
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
Nama Lengkap
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
Posisi
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
Status
|
||||
</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||
Aksi
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.namaLengkap}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text fz="md" fw={500} lh={1.5} truncate="end">
|
||||
{item.namaLengkap}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Badge variant="light" color="blue">
|
||||
{item.posisi?.nama || 'Belum diatur'}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Badge variant="light" color="blue" fz="sm" lh={1.4}>
|
||||
{item.posisi?.nama || 'Belum diatur'}
|
||||
</Badge>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Box visibleFrom="sm">
|
||||
<Badge color={item.isActive ? "green" : "red"}>
|
||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box hiddenFrom="sm">
|
||||
{item.isActive ? (
|
||||
<ThemeIcon color="green" variant="light" size="sm">
|
||||
<IconCheck size={16} />
|
||||
</ThemeIcon>
|
||||
) : (
|
||||
<ThemeIcon color="red" variant="light" size="sm">
|
||||
<IconX size={16} />
|
||||
</ThemeIcon>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
<Badge color={item.isActive ? "green" : "red"} fz="sm" lh={1.4}>
|
||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||
</Badge>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
@@ -143,6 +161,7 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}
|
||||
fz="sm"
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
@@ -152,7 +171,47 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
<Center mt="lg">
|
||||
|
||||
{/* Mobile: Card List */}
|
||||
<Stack hiddenFrom="md" gap="sm" mt="md">
|
||||
{filteredData.map((item) => (
|
||||
<Paper key={item.id} withBorder p="md" radius="md">
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Nama Lengkap</Text>
|
||||
<Text fz="md" fw={500} lh={1.4}>
|
||||
{item.namaLengkap}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Posisi</Text>
|
||||
<Badge variant="light" color="blue" fz="xs" lh={1.4}>
|
||||
{item.posisi?.nama || 'Belum diatur'}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} lh={1.4}>Status</Text>
|
||||
<Badge color={item.isActive ? "green" : "red"} fz="xs" lh={1.4}>
|
||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}
|
||||
fz="xs"
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
@@ -170,4 +229,4 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default PegawaiPPID;
|
||||
export default PegawaiPPID;
|
||||
@@ -107,7 +107,7 @@ function EditPosisiOrganisasiPPID() {
|
||||
};
|
||||
|
||||
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} />
|
||||
|
||||
@@ -46,7 +46,7 @@ function CreatePosisiOrganisasiPPID() {
|
||||
};
|
||||
|
||||
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 { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -31,6 +31,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -41,8 +42,8 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
} = stateOrganisasi.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
const handleHapus = async () => {
|
||||
if (selectedId) {
|
||||
@@ -56,63 +57,63 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
|
||||
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 Posisi Organisasi PPID</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
<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}>Daftar Posisi Organisasi PPID</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
|
||||
{/* Desktop Table */}
|
||||
<Box visibleFrom="md">
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '20%' }}>Nama Posisi</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Hierarki</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>Nama Posisi</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>Deskripsi</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>Hierarki</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>Edit</TableTh>
|
||||
<TableTh fz="sm" fw={600} lh={1.4}>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||
<TableTd>
|
||||
<Text fz="md" fw={600} lh={1.5} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Box w={200}>
|
||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||
</Box>
|
||||
<TableTd w={200}>
|
||||
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text>{item.hierarki || '-'}</Text>
|
||||
<TableTd>
|
||||
<Text fz="md" lh={1.5}>{item.hierarki || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
@@ -129,9 +130,11 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data posisi organisasi yang cocok</Text>
|
||||
<TableTd colSpan={5}>
|
||||
<Center py={{ base: 'sm', md: 'md' }}>
|
||||
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
|
||||
Tidak ada data posisi organisasi yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -139,7 +142,59 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
{/* Mobile Card View */}
|
||||
<Stack gap="xs" hiddenFrom="md">
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||
<Stack gap={4}>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4}>Nama Posisi</Text>
|
||||
<Text fz="sm" fw={600} lh={1.5}>{item.nama}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4}>Deskripsi</Text>
|
||||
<Text fz="sm" lh={1.5} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz="xs" fw={600} lh={1.4}>Hierarki</Text>
|
||||
<Text fz="sm" lh={1.5}>{item.hierarki || '-'}</Text>
|
||||
</Box>
|
||||
<Group justify="flex-end" gap="xs">
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="xs"
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))
|
||||
) : (
|
||||
<Center py="sm">
|
||||
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
|
||||
Tidak ada data posisi organisasi yang cocok
|
||||
</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -154,6 +209,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
@@ -165,4 +221,4 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default PosisiOrganisasiPPID;
|
||||
export default PosisiOrganisasiPPID;
|
||||
@@ -71,7 +71,7 @@ function VisiMisiPPIDEdit() {
|
||||
};
|
||||
|
||||
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} />
|
||||
|
||||
@@ -7,9 +7,8 @@ import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateVisiMisiPPID from '../../_state/ppid/visi_misi_ppid/visimisiPPID';
|
||||
|
||||
|
||||
function VisiMisiPPIDList() {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const listVisiMisi = useProxy(stateVisiMisiPPID);
|
||||
useShallowEffect(() => {
|
||||
listVisiMisi.findById.load('1');
|
||||
@@ -41,15 +40,16 @@ function VisiMisiPPIDList() {
|
||||
<Title order={3} c={colors['blue-button']}>Preview Visi Misi PPID</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/ppid/visi-misi-ppid/edit')}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
w={{ base: '100%', md: "110%" }}
|
||||
c="green"
|
||||
variant="light"
|
||||
leftSection={<IconEdit size={18} stroke={2} />}
|
||||
radius="md"
|
||||
onClick={() => router.push('/admin/ppid/visi-misi-ppid/edit')}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
@@ -58,14 +58,25 @@ function VisiMisiPPIDList() {
|
||||
<Grid>
|
||||
<GridCol span={12}>
|
||||
<Center>
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo PPID" />
|
||||
<Image loading="lazy" src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo PPID" />
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={12}>
|
||||
<Text ta="center" fz={{ base: '1.2rem', md: '1.8rem' }} fw="bold" c={colors['blue-button']}>
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: 1.15 }}
|
||||
>
|
||||
MOTO PPID DESA DARMASABA
|
||||
</Text>
|
||||
<Text ta="center" fz={{ base: '1rem', md: '1.2rem' }} mt="sm">
|
||||
</Title>
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.5 }}
|
||||
mt="sm"
|
||||
c="black"
|
||||
>
|
||||
MEMBERIKAN INFORMASI YANG CEPAT, MUDAH, TEPAT DAN TRANSPARAN
|
||||
</Text>
|
||||
</GridCol>
|
||||
@@ -74,26 +85,50 @@ function VisiMisiPPIDList() {
|
||||
<Divider my="xl" color={colors['blue-button']} />
|
||||
|
||||
<Box>
|
||||
<Text fz={{ base: '1.5rem', md: '1.75rem' }} fw="bold" ta="center" mb="lg" c={colors['blue-button']}>
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
mb="lg"
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: 1.15 }}
|
||||
>
|
||||
VISI PPID
|
||||
</Text>
|
||||
<Box
|
||||
className="prose max-w-none"
|
||||
</Title>
|
||||
<Text
|
||||
ta={{ base: "center", md: "justify" }}
|
||||
dangerouslySetInnerHTML={{ __html: listVisiMisi.findById.data.visi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal", textAlign: "justify"}}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1.55,
|
||||
color: 'black',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Divider my="xl" color={colors['blue-button']} />
|
||||
|
||||
<Box mt="xl">
|
||||
<Text fz={{ base: '1.5rem', md: '1.75rem' }} fw="bold" ta="center" mb="lg" c={colors['blue-button']}>
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
mb="lg"
|
||||
c={colors['blue-button']}
|
||||
style={{ lineHeight: 1.15 }}
|
||||
>
|
||||
MISI PPID
|
||||
</Text>
|
||||
<Box
|
||||
className="prose max-w-none"
|
||||
</Title>
|
||||
<Text
|
||||
ta={"justify"}
|
||||
dangerouslySetInnerHTML={{ __html: listVisiMisi.findById.data.misi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal", textAlign: "justify"}}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1.55,
|
||||
color: 'black',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -103,4 +138,4 @@ function VisiMisiPPIDList() {
|
||||
);
|
||||
}
|
||||
|
||||
export default VisiMisiPPIDList;
|
||||
export default VisiMisiPPIDList;
|
||||
@@ -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,14 +1,56 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function permohonanInformasiPublikFindMany() {
|
||||
const res = await prisma.permohonanInformasiPublik.findMany({
|
||||
include: {
|
||||
jenisInformasiDiminta: true,
|
||||
caraMemperolehInformasi: true,
|
||||
caraMemperolehSalinanInformasi: true,
|
||||
}
|
||||
});
|
||||
return {
|
||||
data: res,
|
||||
};
|
||||
export default async function permohonanInformasiPublikFindMany(
|
||||
context: Context
|
||||
) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || "";
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: "insensitive" } },
|
||||
{ email: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.permohonanInformasiPublik.findMany({
|
||||
where,
|
||||
skip,
|
||||
include: {
|
||||
jenisInformasiDiminta: true,
|
||||
caraMemperolehInformasi: true,
|
||||
caraMemperolehSalinanInformasi: true,
|
||||
},
|
||||
take: limit,
|
||||
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.permohonanInformasiPublik.count({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch formulir permohonan keberatan with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch formulir permohonan keberatan with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function permohonanKeberatanInformasiPublikFindMany() {
|
||||
const res = await prisma.formulirPermohonanKeberatan.findMany();
|
||||
return {
|
||||
data: res,
|
||||
};
|
||||
export default async function permohonanKeberatanInformasiPublikFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{email: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.formulirPermohonanKeberatan.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.formulirPermohonanKeberatan.count({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch formulir permohonan keberatan with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch formulir permohonan keberatan with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -7,7 +7,7 @@ import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from
|
||||
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() {
|
||||
@@ -30,8 +30,8 @@ function Page() {
|
||||
|
||||
const filteredData = selectedCategory
|
||||
? data?.filter(item =>
|
||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||
)
|
||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||
)
|
||||
: data;
|
||||
|
||||
useShallowEffect(() => {
|
||||
@@ -55,7 +55,7 @@ function Page() {
|
||||
<Box>
|
||||
<Grid align="center" px={{ base: 'md', md: 100 }}>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Title order={1} c={colors["blue-button"]} fw="bold">
|
||||
<Title order={1} c={colors["blue-button"]} fw="bold" lh={1.15}>
|
||||
Pasar Desa
|
||||
</Title>
|
||||
</GridCol>
|
||||
@@ -66,12 +66,19 @@ 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 }} pt={20} ta="justify" fz={{ base: 'sm', md: 'md' }}>
|
||||
<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>
|
||||
@@ -92,6 +99,9 @@ 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>
|
||||
@@ -114,15 +124,29 @@ function Page() {
|
||||
style={{ objectFit: 'cover' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text py="sm" fw="bold" fz={{ base: 'md', md: 'lg' }}>
|
||||
<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' }}>
|
||||
<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">
|
||||
<Flex py="sm" gap="md" align="center">
|
||||
<IconStarFilled size={20} color="#EBCB09" />
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.4, md: 1.45 }}
|
||||
c="black"
|
||||
>
|
||||
{v.rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -130,7 +154,11 @@ function Page() {
|
||||
<Box>
|
||||
<Flex gap="md" align="center">
|
||||
<IconMapPinFilled size={20} color="red" />
|
||||
<Text fz={{ base: 'xs', md: 'sm' }} ml={2}>
|
||||
<Text
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
lh={{ base: 1.4, md: 1.45 }}
|
||||
c="black"
|
||||
>
|
||||
{v.alamatUsaha}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -69,48 +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'>
|
||||
<Grid align="center">
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Title
|
||||
order={1}
|
||||
c={colors["blue-button"]}
|
||||
fw={"bold"}
|
||||
fz={{ base: '28px', md: '32px' }}
|
||||
lh={{ base: '1.2', md: '1.25' }}
|
||||
fw="bold"
|
||||
lh={{ base: 1.2, md: 1.2 }}
|
||||
>
|
||||
Program Kemiskinan
|
||||
</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={{ base: '14px', md: '16px' }}
|
||||
lh={{ base: '1.5', md: '1.6' }}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
c="black"
|
||||
ta={{ base: 'left', md: 'left' }}
|
||||
pt={20}
|
||||
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
|
||||
@@ -118,20 +117,19 @@ function Page() {
|
||||
>
|
||||
{state.findMany.data.map(v => {
|
||||
return (
|
||||
<Paper p={'xl'} key={v.id}>
|
||||
<Paper p={{ base: 'lg', md: 'xl' }} key={v.id}>
|
||||
<Title
|
||||
order={3}
|
||||
fw={'bold'}
|
||||
fw="bold"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: '18px', md: '20px' }}
|
||||
lh={{ base: '1.3', md: '1.35' }}
|
||||
lh={{ base: 1.2, md: 1.2 }}
|
||||
>
|
||||
{v.nama}
|
||||
</Title>
|
||||
<Text
|
||||
fz={{ base: '14px', md: '16px' }}
|
||||
lh={{ base: '1.5', md: '1.6' }}
|
||||
c={'black'}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={{ base: 1.5, md: 1.6 }}
|
||||
c="black"
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
@@ -139,7 +137,7 @@ function Page() {
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Center my={10}>
|
||||
<Center my={{ base: 'md', md: 10 }}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
@@ -147,16 +145,15 @@ function Page() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}}
|
||||
total={totalPages}
|
||||
my={"md"}
|
||||
my="md"
|
||||
/>
|
||||
</Center>
|
||||
<Paper p={'xl'}>
|
||||
<Paper p={{ base: 'lg', md: 'xl' }}>
|
||||
<Title
|
||||
order={3}
|
||||
fw={'bold'}
|
||||
fw="bold"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: '18px', md: '20px' }}
|
||||
lh={{ base: '1.3', md: '1.35' }}
|
||||
lh={{ base: 1.2, md: 1.2 }}
|
||||
mb="md"
|
||||
>
|
||||
Statistik Kemiskinan Masyarakat
|
||||
@@ -166,7 +163,7 @@ function Page() {
|
||||
<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 }}
|
||||
@@ -175,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']}
|
||||
@@ -199,9 +198,9 @@ function Page() {
|
||||
) : (
|
||||
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
|
||||
<Text
|
||||
fz={{ base: '12px', md: '14px' }}
|
||||
fz={{ base: 'xs', md: 'sm' }}
|
||||
c="dimmed"
|
||||
lh={{ base: '1.4', md: '1.5' }}
|
||||
lh={{ base: 1.4, md: 1.4 }}
|
||||
>
|
||||
{state.findMany.loading
|
||||
? 'Memuat data statistik...'
|
||||
|
||||
@@ -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 {
|
||||
@@ -32,12 +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'
|
||||
import { useTransitionRouter } from 'next-view-transitions'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -49,14 +49,16 @@ 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 }}
|
||||
fz={{ base: 28, md: 36, lg: 44 }}
|
||||
lh={{ base: 1.05, md: 1.03 }}
|
||||
>
|
||||
Struktur Organisasi & SK Pengurus BumDes
|
||||
</Title>
|
||||
@@ -75,14 +77,18 @@ export default function Page() {
|
||||
}
|
||||
|
||||
function StrukturOrganisasiBumDes() {
|
||||
const router = useTransitionRouter()
|
||||
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), 1000)
|
||||
debounce((value: string) => {
|
||||
setSearchQuery(value)
|
||||
}, 1000)
|
||||
).current
|
||||
|
||||
useEffect(() => {
|
||||
@@ -90,8 +96,7 @@ function StrukturOrganisasiBumDes() {
|
||||
}, [])
|
||||
|
||||
const isLoading =
|
||||
!stateOrganisasi.findMany.data &&
|
||||
stateOrganisasi.findMany.loading !== false
|
||||
!stateOrganisasi.findMany.data && stateOrganisasi.findMany.loading !== false
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -149,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)
|
||||
|
||||
@@ -183,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) || [],
|
||||
}
|
||||
@@ -208,7 +212,7 @@ function StrukturOrganisasiBumDes() {
|
||||
chartData = filterNodes(chartData)
|
||||
}
|
||||
|
||||
// 🔍 fullscreen dan zoom control
|
||||
// 🎬 fullscreen & zoom control
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
chartContainerRef.current?.requestFullscreen()
|
||||
@@ -225,7 +229,7 @@ function StrukturOrganisasiBumDes() {
|
||||
|
||||
return (
|
||||
<Stack align="center" mt="xl">
|
||||
{/* 🧭 Kontrol atas */}
|
||||
{/* 🔍 Controls */}
|
||||
<Paper
|
||||
shadow="xs"
|
||||
w={{
|
||||
@@ -244,6 +248,7 @@ function StrukturOrganisasiBumDes() {
|
||||
overflowX: 'auto' // ⬅️ untuk mencegah overflow
|
||||
}}
|
||||
>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
@@ -360,166 +365,163 @@ function StrukturOrganisasiBumDes() {
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* 🧩 Chart Container */}
|
||||
<Center style={{ width: '100%' }}>
|
||||
<Box
|
||||
ref={chartContainerRef}
|
||||
{/* 🧩 Chart Container */}
|
||||
<Center style={{ width: '100%' }}>
|
||||
<Box
|
||||
ref={chartContainerRef}
|
||||
style={{
|
||||
overflowX: 'auto',
|
||||
overflowY: 'auto',
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
padding: '32px 16px',
|
||||
transition: 'transform 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<Box style={{
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'center top',
|
||||
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
|
||||
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
|
||||
}}>
|
||||
<OrganizationChart
|
||||
value={chartData}
|
||||
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
|
||||
className="p-organizationchart p-organizationchart-horizontal"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Center>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function NodeCard({ node, router }: any) {
|
||||
const imageSrc = node?.data?.image || '/img/default.png'
|
||||
const name = node?.data?.name || 'Tanpa Nama'
|
||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||
const hasId = Boolean(node?.data?.id)
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
return (
|
||||
<Transition mounted transition="pop" duration={300}>
|
||||
{(styles) => (
|
||||
<Card
|
||||
shadow="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
style={{
|
||||
...styles,
|
||||
width: '100%',
|
||||
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
|
||||
minHeight: isMobile ? 240 : 280,
|
||||
padding: isMobile ? 16 : 20,
|
||||
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
|
||||
borderColor: 'rgba(28, 110, 164, 0.3)',
|
||||
borderWidth: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: hasId ? 'pointer' : 'default',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(-4px)'
|
||||
e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap={12}>
|
||||
{/* Photo */}
|
||||
<Box
|
||||
style={{
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
border: '3px solid rgba(28, 110, 164, 0.4)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
background: 'white',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
width={96}
|
||||
height={96}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
borderRadius: '12px',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'auto',
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
padding: '32px 16px',
|
||||
transition: 'transform 0.2s ease',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Name */}
|
||||
<Text
|
||||
fw={700}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
lineClamp={2}
|
||||
fz={{ base: 13, md: 15 }}
|
||||
lh={1.2}
|
||||
style={{
|
||||
minHeight: 40,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
|
||||
{/* Title/Position */}
|
||||
<Text
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
fw={500}
|
||||
lineClamp={2}
|
||||
fz={{ base: 12, md: 13 }}
|
||||
lh={1.3}
|
||||
style={{
|
||||
minHeight: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{/* Detail Button */}
|
||||
{hasId && (
|
||||
<Button
|
||||
variant="gradient"
|
||||
gradient={{ from: 'blue', to: 'cyan' }}
|
||||
size="xs"
|
||||
fullWidth
|
||||
mt={8}
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/${node.data.id}`)
|
||||
}
|
||||
style={{
|
||||
height: 32,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<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, router }: any) {
|
||||
const imageSrc = node?.data?.image || '/img/default.png'
|
||||
const name = node?.data?.name || 'Tanpa Nama'
|
||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||
const hasId = Boolean(node?.data?.id)
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
return (
|
||||
<Transition mounted transition="pop" duration={300}>
|
||||
{(styles) => (
|
||||
<Card
|
||||
shadow="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
|
||||
style={{
|
||||
...styles,
|
||||
width: '100%',
|
||||
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
|
||||
minHeight: isMobile ? 240 : 280,
|
||||
padding: isMobile ? 16 : 20,
|
||||
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
|
||||
borderColor: 'rgba(28, 110, 164, 0.3)',
|
||||
borderWidth: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: hasId ? 'pointer' : 'default',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(-4px)'
|
||||
e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap={12}>
|
||||
{/* Photo */}
|
||||
<Box
|
||||
style={{
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
border: '3px solid rgba(28, 110, 164, 0.4)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
background: 'white',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
width={96}
|
||||
height={96}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Name */}
|
||||
<Text
|
||||
fw={700}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
lineClamp={2}
|
||||
fz={{ base: 13, md: 15 }}
|
||||
lh={1.2}
|
||||
style={{
|
||||
minHeight: 40,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
|
||||
{/* Title/Position */}
|
||||
<Text
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
fw={500}
|
||||
lineClamp={2}
|
||||
fz={{ base: 12, md: 13 }}
|
||||
lh={1.3}
|
||||
style={{
|
||||
minHeight: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{/* Detail Button */}
|
||||
{hasId && (
|
||||
<Button
|
||||
variant="gradient"
|
||||
gradient={{ from: 'blue', to: 'cyan' }}
|
||||
size="xs"
|
||||
fullWidth
|
||||
mt={8}
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/ppid/struktur-ppid/${node.data.id}`)
|
||||
}
|
||||
style={{
|
||||
height: 32,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<Text fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
<Text c={"white"} fz={{ base: 12, md: 13 }} lh={1} ta="center">Lihat Detail</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import ajukanIdeInovatifState from '@/app/admin/(dashboard)/_state/inovasi/ajukan-ide-inovatif';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, Box, Button, Flex, List, ListItem, Modal, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconArrowRight, IconBulbFilled } from '@tabler/icons-react';
|
||||
import { IconBulbFilled } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
|
||||
function Page() {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const ideInovatif = useProxy(ajukanIdeInovatifState)
|
||||
|
||||
const ideInovatif = useProxy(ajukanIdeInovatifState);
|
||||
|
||||
const resetForm = () => {
|
||||
// Reset state di valtio
|
||||
ideInovatif.create.form = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
@@ -23,53 +21,66 @@ function Page() {
|
||||
masalah: "",
|
||||
benefit: "",
|
||||
};
|
||||
|
||||
// Reset state lokal
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Submit data berita
|
||||
await ideInovatif.create.create();
|
||||
|
||||
// Reset form setelah submit
|
||||
resetForm();
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors["blue-button"]}
|
||||
fw="bold"
|
||||
style={{ fontSize: 'clamp(1.75rem, 4vw, 2.25rem)' }}
|
||||
>
|
||||
Ajukan Ide Inovatif
|
||||
</Title>
|
||||
<Text ta="center" fz={{ base: 'sm', md: 'md' }} c="black" lh="1.6">
|
||||
Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform "Ajukan Ide Inovatif" hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.
|
||||
</Text>
|
||||
<Text ta={'center'} fz={'h4'}>Desa Darmasaba percaya bahwa setiap warga memiliki potensi luar biasa untuk menciptakan perubahan positif. Platform "Ajukan Ide Inovatif" hadir sebagai ruang inklusif bagi seluruh masyarakat untuk mengembangkan dan mengusulkan gagasan transformatif.</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} p={'lg'}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 2,
|
||||
}}
|
||||
>
|
||||
<Paper p={'xl'} >
|
||||
<Stack gap={"xs"}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Tujuan Ide Inovatif Ini</Text>
|
||||
<List>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mendorong partisipasi aktif masyarakat</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memfasilitasi inovasi berbasis lokal</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memecahkan tantangan komunal</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mengembangkan potensi kreativitas warga</ListItem>
|
||||
</List>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="lg" p="lg">
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Paper p="xl">
|
||||
<Stack gap="xs">
|
||||
<Title order={2} c={colors['blue-button']} fw="bold">
|
||||
Tujuan Ide Inovatif Ini
|
||||
</Title>
|
||||
<List>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Mendorong partisipasi aktif masyarakat
|
||||
</ListItem>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Memfasilitasi inovasi berbasis lokal
|
||||
</ListItem>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Memecahkan tantangan komunal
|
||||
</ListItem>
|
||||
<ListItem ta="justify" fz={{ base: 'sm', md: 'md' }} lh="1.5">
|
||||
Mengembangkan potensi kreativitas warga
|
||||
</ListItem>
|
||||
</List>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Paper p={'xl'} >
|
||||
<Flex align={'center'} justify={'space-between'}>
|
||||
|
||||
<Paper p="xl">
|
||||
<Flex align="center" justify="space-between" direction={{ base: 'column', md: 'row' }} gap="md">
|
||||
<Box>
|
||||
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar Di Samping</Text>
|
||||
<IconArrowRight size={30} color={colors['blue-button']} />
|
||||
<Title order={3} c={colors['blue-button']} fw="bold" ta={{ base: 'center', md: 'start' }}>
|
||||
Apabila Anda Ingin Mengajukan Ide Inovatif Bisa Klik Pada Gambar
|
||||
</Title>
|
||||
</Box>
|
||||
<Box px={{ base: 5, md: 10 }} py={5}>
|
||||
<ActionIcon variant="transparent" size={150} onClick={open}>
|
||||
@@ -88,32 +99,46 @@ function Page() {
|
||||
radius={0}
|
||||
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||
>
|
||||
<Paper p={"md"} withBorder>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper p="md" withBorder>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Ajukan Ide Inovatif</Title>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Nama
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan nama"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.name = val.target.value
|
||||
ideInovatif.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Alamat
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan alamat"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.alamat = val.target.value
|
||||
ideInovatif.create.form.alamat = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Ide</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Nama Ide
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan nama ide"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.namaIde = val.target.value
|
||||
ideInovatif.create.form.namaIde = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fz="sm" fw="bold">
|
||||
Deskripsi
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={ideInovatif.create.form.deskripsi}
|
||||
onChange={(htmlContent) => {
|
||||
@@ -122,26 +147,35 @@ function Page() {
|
||||
/>
|
||||
</Box>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Masalah</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Masalah
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan masalah"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.masalah = val.target.value
|
||||
ideInovatif.create.form.masalah = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Benefit</Text>}
|
||||
label={
|
||||
<Text fz="sm" fw="bold">
|
||||
Benefit
|
||||
</Text>
|
||||
}
|
||||
placeholder="masukkan benefit"
|
||||
onChange={(val) => {
|
||||
ideInovatif.create.form.benefit = val.target.value
|
||||
ideInovatif.create.form.benefit = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||
Simpan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Modal>
|
||||
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
export default Page;
|
||||
@@ -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 }} pt={20} ta={"justify"} fz="md" mt={4} >
|
||||
<Text
|
||||
px={{ base: 'md', md: 100 }}
|
||||
pt={20}
|
||||
ta={"justify"}
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
lh={1.55}
|
||||
mt={4}
|
||||
c={'black'}
|
||||
>
|
||||
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||
</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;
|
||||
@@ -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;
|
||||
@@ -56,7 +56,7 @@ function Page() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: '50%', md: '100%' }}
|
||||
w={'100%'}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
@@ -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, Paper, Skeleton, 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';
|
||||
@@ -27,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>
|
||||
@@ -50,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">
|
||||
@@ -84,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;
|
||||
@@ -40,7 +40,11 @@ function FasilitasKesehatanPage() {
|
||||
<Stack gap="lg">
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
<Text size="lg" c="dimmed" lh="1.5">
|
||||
<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>
|
||||
@@ -67,20 +71,36 @@ function FasilitasKesehatanPage() {
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={3} fw={700} c={colors['blue-button']} lh="1.3" />
|
||||
<Title
|
||||
order={3}
|
||||
fw={700}
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 'sm', sm: 'md' }}
|
||||
lh={{ base: '1.3', sm: '1.3' }}
|
||||
>
|
||||
{item.name}
|
||||
</Title>
|
||||
<Badge color="blue" radius="sm" variant="light" size="xs">
|
||||
Aktif
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={18} stroke={1.5} />
|
||||
<Text size="sm" lh="1.5">
|
||||
<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 size="sm" lh="1.5">
|
||||
<Text
|
||||
fz={{ base: 'xs', sm: 'sm' }}
|
||||
lh={{ base: '1.5', sm: '1.5' }}
|
||||
c="text"
|
||||
>
|
||||
{item.informasiumum.jamOperasional}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user