Merge pull request 'nico/17-des-25' (#43) from nico/17-des-25 into staggingweb
Reviewed-on: http://wibugit.wibudev.com/wibu/desa-darmasaba/pulls/43
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -136,12 +137,43 @@ const statepermohonanInformasiPublik = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
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[
|
const res = await ApiFetch.api.ppid.permohonaninformasipublik[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({
|
||||||
if (res.status === 200) {
|
query,
|
||||||
statepermohonanInformasiPublik.findMany.data = res.data?.data ?? [];
|
});
|
||||||
|
|
||||||
|
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 ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -58,16 +59,47 @@ const permohonanKeberatanInformasi = proxy({
|
|||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
|
| null
|
||||||
| Prisma.FormulirPermohonanKeberatanGetPayload<{
|
| Prisma.FormulirPermohonanKeberatanGetPayload<{
|
||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[],
|
||||||
| null,
|
page: 1,
|
||||||
async load() {
|
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[
|
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({
|
||||||
if (res.status === 200) {
|
query,
|
||||||
permohonanKeberatanInformasi.findMany.data = res.data?.data ?? [];
|
});
|
||||||
|
|
||||||
|
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 colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
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 { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -108,7 +108,7 @@ function DetailPerbekelDariMasa() {
|
|||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
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 { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -106,7 +106,7 @@ function DetailSDGSDesa() {
|
|||||||
size="md"
|
size="md"
|
||||||
disabled={sdgsState.delete.loading}
|
disabled={sdgsState.delete.loading}
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -28,6 +28,8 @@ function SdgsDesa() {
|
|||||||
function ListSdgsDesa({ search }: { search: string }) {
|
function ListSdgsDesa({ search }: { search: string }) {
|
||||||
const listState = useProxy(sdgsDesa);
|
const listState = useProxy(sdgsDesa);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -38,8 +40,8 @@ function ListSdgsDesa({ search }: { search: string }) {
|
|||||||
} = listState.findMany;
|
} = listState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ function ListSdgsDesa({ search }: { search: string }) {
|
|||||||
</Title>
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color={colors['blue-button']}
|
color='blue'
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/landing-page/SDGs/create')}
|
onClick={() => router.push('/admin/landing-page/SDGs/create')}
|
||||||
>
|
>
|
||||||
@@ -147,12 +149,18 @@ function ListSdgsDesa({ search }: { search: string }) {
|
|||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<Paper key={item.id} withBorder p="md" radius="md">
|
<Paper key={item.id} withBorder p="md" radius="md">
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
<Text fz="sm" fw={600} lh={1.4}>
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama SDGs Desa</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Jumlah</Text>
|
||||||
<Text fz="xs" c="dark.6" lh={1.4}>
|
<Text fz="xs" c="dark.6" lh={1.4}>
|
||||||
Jumlah: {item.jumlah || '0'}
|
{item.jumlah || '0'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
<Group justify="flex-end" mt="xs">
|
<Group justify="flex-end" mt="xs">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|||||||
@@ -368,6 +368,13 @@ function EditAPBDes() {
|
|||||||
{ value: '2', label: 'Level 2 (Sub-kelompok)' },
|
{ value: '2', label: 'Level 2 (Sub-kelompok)' },
|
||||||
{ value: '3', label: 'Level 3 (Detail)' },
|
{ value: '3', label: 'Level 3 (Detail)' },
|
||||||
]}
|
]}
|
||||||
|
styles={{
|
||||||
|
option: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
}}
|
||||||
value={String(newItem.level)}
|
value={String(newItem.level)}
|
||||||
onChange={(val) => setNewItem({ ...newItem, level: Number(val) || 1 })}
|
onChange={(val) => setNewItem({ ...newItem, level: Number(val) || 1 })}
|
||||||
/>
|
/>
|
||||||
@@ -378,6 +385,13 @@ function EditAPBDes() {
|
|||||||
{ value: 'belanja', label: 'Belanja' },
|
{ value: 'belanja', label: 'Belanja' },
|
||||||
{ value: 'pembiayaan', label: 'Pembiayaan' },
|
{ value: 'pembiayaan', label: 'Pembiayaan' },
|
||||||
]}
|
]}
|
||||||
|
styles={{
|
||||||
|
option: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
}}
|
||||||
value={newItem.tipe}
|
value={newItem.tipe}
|
||||||
onChange={(val) => setNewItem({ ...newItem, tipe: (val as any) || 'pendapatan' })}
|
onChange={(val) => setNewItem({ ...newItem, tipe: (val as any) || 'pendapatan' })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -353,6 +353,13 @@ function CreateAPBDes() {
|
|||||||
{ value: '2', label: 'Level 2 (Sub-kelompok)' },
|
{ value: '2', label: 'Level 2 (Sub-kelompok)' },
|
||||||
{ value: '3', label: 'Level 3 (Detail)' },
|
{ value: '3', label: 'Level 3 (Detail)' },
|
||||||
]}
|
]}
|
||||||
|
styles={{
|
||||||
|
option: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
}}
|
||||||
value={String(newItem.level)}
|
value={String(newItem.level)}
|
||||||
onChange={(val) => setNewItem({ ...newItem, level: Number(val) || 1 })}
|
onChange={(val) => setNewItem({ ...newItem, level: Number(val) || 1 })}
|
||||||
/>
|
/>
|
||||||
@@ -363,6 +370,13 @@ function CreateAPBDes() {
|
|||||||
{ value: 'belanja', label: 'Belanja' },
|
{ value: 'belanja', label: 'Belanja' },
|
||||||
{ value: 'pembiayaan', label: 'Pembiayaan' },
|
{ value: 'pembiayaan', label: 'Pembiayaan' },
|
||||||
]}
|
]}
|
||||||
|
styles={{
|
||||||
|
option: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
}}
|
||||||
value={newItem.level === 1 ? null : newItem.tipe}
|
value={newItem.level === 1 ? null : newItem.tipe}
|
||||||
onChange={(val) => setNewItem({ ...newItem, tipe: val as any })}
|
onChange={(val) => setNewItem({ ...newItem, tipe: val as any })}
|
||||||
disabled={newItem.level === 1}
|
disabled={newItem.level === 1}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconFile, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconFile, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -45,12 +45,13 @@ function APBDes() {
|
|||||||
function ListAPBDes({ search }: { search: string }) {
|
function ListAPBDes({ search }: { search: string }) {
|
||||||
const listState = useProxy(apbdes);
|
const listState = useProxy(apbdes);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = listState.findMany;
|
const { data, page, totalPages, loading, load } = listState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
@@ -197,17 +198,17 @@ function ListAPBDes({ search }: { search: string }) {
|
|||||||
<Text fz="sm" fw={600} lh={1.4}>
|
<Text fz="sm" fw={600} lh={1.4}>
|
||||||
APBDes {item.tahun}
|
APBDes {item.tahun}
|
||||||
</Text>
|
</Text>
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Box>
|
||||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
<Text fz="sm"fw={600} lh={1.4}>
|
||||||
Tahun
|
Tahun
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz="sm" fw={500} lh={1.4}>
|
<Text fz="sm" fw={500} lh={1.4}>
|
||||||
{item.tahun || '-'}
|
{item.tahun || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Box>
|
||||||
|
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Box>
|
||||||
<Text fz="sm" c="dimmed" lh={1.4}>
|
<Text fz="sm"fw={600} lh={1.4}>
|
||||||
Dokumen
|
Dokumen
|
||||||
</Text>
|
</Text>
|
||||||
{item.file?.link ? (
|
{item.file?.link ? (
|
||||||
@@ -230,7 +231,7 @@ function ListAPBDes({ search }: { search: string }) {
|
|||||||
Tidak ada
|
Tidak ada
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd w={60}>
|
<TableTd w={120}>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="green"
|
color="green"
|
||||||
@@ -161,7 +161,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
<IconEdit size={18} />
|
<IconEdit size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd w={60}>
|
<TableTd w={120}>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color="red"
|
||||||
@@ -191,7 +191,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={{ base: 'xl', md: 'xl' }}>
|
<Box py={{ base: 20, md: 20 }}>
|
||||||
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="md" radius="md">
|
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'xl' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
|
<Group justify="space-between" mb={{ base: 'md', md: 'lg' }}>
|
||||||
<Title order={2} lh={1.2}>
|
<Title order={2} lh={1.2}>
|
||||||
|
|||||||
@@ -150,12 +150,18 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<Paper key={item.id} p="sm" radius="md" withBorder shadow="xs">
|
<Paper key={item.id} p="sm" radius="md" withBorder shadow="xs">
|
||||||
<Stack gap="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}>
|
<Text fw={500} fz="sm" lh={1.5} lineClamp={1}>
|
||||||
{item.name || '-'}
|
{item.name || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Kategori</Text>
|
||||||
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1}>
|
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1}>
|
||||||
Kategori: {item.kategori?.name || '-'}
|
{item.kategori?.name || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
<Group justify="flex-end">
|
<Group justify="flex-end">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -32,6 +32,7 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
|||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -50,8 +51,8 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
|||||||
} = stateKategori.findMany
|
} = stateKategori.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, debouncedSearch)
|
||||||
}, [page, search])
|
}, [page, debouncedSearch])
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || []
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -29,6 +29,7 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
const listState = useProxy(prestasiState.prestasiDesa)
|
const listState = useProxy(prestasiState.prestasiDesa)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -39,8 +40,8 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
} = listState.findMany
|
} = listState.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, []);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || []
|
||||||
|
|
||||||
@@ -71,36 +72,37 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Desktop Table */}
|
{/* Desktop Table */}
|
||||||
<Box visibleFrom="md">
|
<Box visibleFrom="md" style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover miw={0}>
|
<Table highlightOnHover striped verticalSpacing="sm" miw={800}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh w="25%">Nama Prestasi</TableTh>
|
<TableTh>Nama Prestasi</TableTh>
|
||||||
<TableTh w="25%">Deskripsi</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh w="25%">Kategori</TableTh>
|
<TableTh>Kategori</TableTh>
|
||||||
<TableTh w="25%" ta="center">Aksi</TableTh>
|
<TableTh ta="center">Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd w="25%">
|
<TableTd style={{ maxWidth: 250 }}>
|
||||||
<Text truncate="end" fz="md" lh={1.5}>
|
<Text truncate="end" fz="md" lh={1.5}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd w="25%">
|
<TableTd style={{ maxWidth: 250 }}>
|
||||||
<Text lineClamp={1} fz="md" c="dimmed" lh={1.5} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text lineClamp={1} fz="md" c="dimmed" lh={1.5} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd w="25%">
|
<TableTd>
|
||||||
<Text truncate="end" fz="md" lh={1.5}>
|
<Text truncate="end" fz="md" lh={1.5}>
|
||||||
{item.kategori?.name || 'Tidak ada kategori'}
|
{item.kategori?.name || 'Tidak ada kategori'}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd w="25%" ta="center">
|
<TableTd ta="center">
|
||||||
|
<Center>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="sm"
|
||||||
radius="md"
|
radius="md"
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -109,6 +111,7 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
))
|
||||||
@@ -130,14 +133,23 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<Paper key={item.id} withBorder p="sm" radius="sm">
|
<Paper key={item.id} withBorder p="sm" radius="sm">
|
||||||
<Stack gap={4}>
|
<Stack gap={"xs"}>
|
||||||
<Text fz="sm" fw={600} lh={1.4}>
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama Prestasi</Text>
|
||||||
|
<Text fz="sm" fw={500} lh={1.4} lineClamp={2}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz="xs" c="dimmed" lh={1.5} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
</Box>
|
||||||
<Text fz="xs" c="dimmed" lh={1.4}>
|
<Box>
|
||||||
Kategori: {item.kategori?.name || 'Tidak ada kategori'}
|
<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>
|
</Text>
|
||||||
|
</Box>
|
||||||
<Group justify="flex-end" mt="xs">
|
<Group justify="flex-end" mt="xs">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -48,6 +48,7 @@ function MediaSosial() {
|
|||||||
function ListMediaSosial({ search }: { search: string }) {
|
function ListMediaSosial({ search }: { search: string }) {
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const getIconSource = (item: any) => {
|
const getIconSource = (item: any) => {
|
||||||
if (item.image?.link) return item.image.link;
|
if (item.image?.link) return item.image.link;
|
||||||
@@ -66,8 +67,8 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
} = stateMediaSosial.findMany;
|
} = stateMediaSosial.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
@@ -195,12 +196,15 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<Paper key={item.id} withBorder p="sm" radius="md">
|
<Paper key={item.id} withBorder p="sm" radius="md">
|
||||||
<Group justify="space-between" wrap="nowrap" align='center'>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} fz="sm" lh={1.45}>
|
<Text fz="sm" fw={600} lh={1.4}>Nama Media Sosial / Kontak</Text>
|
||||||
|
<Text fw={500} fz="sm" lh={1.45}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Gambar</Text>
|
||||||
|
</Box>
|
||||||
<Box w={40} h={40} style={{ borderRadius: 6, overflow: 'hidden' }}>
|
<Box w={40} h={40} style={{ borderRadius: 6, overflow: 'hidden' }}>
|
||||||
{(() => {
|
{(() => {
|
||||||
const src = getIconSource(item);
|
const src = getIconSource(item);
|
||||||
@@ -217,8 +221,8 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
return <Box bg={colors['blue-button']} w="100%" h="100%" />;
|
return <Box bg={colors['blue-button']} w="100%" h="100%" />;
|
||||||
})()}
|
})()}
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
|
||||||
<Box>
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Link / No. Telepon</Text>
|
||||||
<a
|
<a
|
||||||
href={item.link}
|
href={item.link}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
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 { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -29,12 +29,13 @@ function ProgramInovasi() {
|
|||||||
function ListProgramInovasi({ search }: { search: string }) {
|
function ListProgramInovasi({ search }: { search: string }) {
|
||||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000); // 500ms delay
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = stateProgramInovasi.findMany;
|
const { data, page, totalPages, loading, load } = stateProgramInovasi.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || [];
|
const filteredData = data || [];
|
||||||
|
|
||||||
@@ -135,15 +136,22 @@ function ListProgramInovasi({ search }: { search: string }) {
|
|||||||
>
|
>
|
||||||
<Stack gap={6}>
|
<Stack gap={6}>
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<Text fw={600}>{item.name}</Text>
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Nama Program</Text>
|
||||||
|
<Text fw={500} lh={1.4}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
|
||||||
<Text fz="sm" c="gray.7" lineClamp={2}>
|
<Text fz="sm" c="gray.7" lineClamp={2}>
|
||||||
{item.description || '-'}
|
{item.description || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Link */}
|
{/* Link */}
|
||||||
<Box>
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} lh={1.4}>Link</Text>
|
||||||
<a
|
<a
|
||||||
href={item.link}
|
href={item.link}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ function EditDaftarInformasiPublik() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function DetailDaftarInformasiPublik() {
|
|||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: '100%', md: '60%' }}
|
w={{ base: '100%', md: '70%' }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -83,11 +83,11 @@ function DetailDaftarInformasiPublik() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold" mb={4}>Deskripsi</Text>
|
<Text fz="lg" fw="bold" mb={4}>Deskripsi</Text>
|
||||||
<Box
|
<Text
|
||||||
|
px={"xs"}
|
||||||
fz="md"
|
fz="md"
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
className="prose max-w-none"
|
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function CreateDaftarInformasi() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -1,7 +1,24 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import {
|
||||||
import { useShallowEffect, useViewportSize } from '@mantine/hooks';
|
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 { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -10,7 +27,7 @@ import HeaderSearch from '../../_com/header';
|
|||||||
import daftarInformasiPublik from '../../_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
import daftarInformasiPublik from '../../_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||||
|
|
||||||
function DaftarInformasiPublik() {
|
function DaftarInformasiPublik() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
@@ -26,32 +43,35 @@ function DaftarInformasiPublik() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListDaftarInformasi({ search }: { search: string }) {
|
function ListDaftarInformasi({ search }: { search: string }) {
|
||||||
const listData = useProxy(daftarInformasiPublik)
|
const listData = useProxy(daftarInformasiPublik);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = listData.findMany
|
const { data, page, totalPages, loading, load } = listData.findMany;
|
||||||
const { width } = useViewportSize()
|
const { width } = useViewportSize();
|
||||||
const isMobile = width < 768
|
const isMobile = width < 768;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search])
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="md">
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py="md">
|
<Box py={{ base: 'md', md: 'lg' }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'sm', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>List Daftar Informasi Publik</Title>
|
<Title order={2} lh={1.2}>
|
||||||
|
List Daftar Informasi Publik
|
||||||
|
</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -65,10 +85,14 @@ function ListDaftarInformasi({ search }: { search: string }) {
|
|||||||
{filteredData.length === 0 ? (
|
{filteredData.length === 0 ? (
|
||||||
<Stack align="center" py="xl">
|
<Stack align="center" py="xl">
|
||||||
<IconDeviceImacCog size={40} stroke={1.5} color={colors['blue-button']} />
|
<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>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<>
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md" style={{ overflowX: 'auto' }}>
|
||||||
<Table
|
<Table
|
||||||
highlightOnHover
|
highlightOnHover
|
||||||
striped
|
striped
|
||||||
@@ -77,39 +101,46 @@ function ListDaftarInformasi({ search }: { search: string }) {
|
|||||||
>
|
>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
<TableTh w="25%">
|
||||||
<TableTh style={{ width: '25%' }}>Jenis Informasi</TableTh>
|
<Text fw={600} lh={1.4}>
|
||||||
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
|
Jenis Informasi
|
||||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Aksi</TableTh>
|
</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>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item, index) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ textAlign: 'center' }}>
|
|
||||||
<Text fz="sm">{(page - 1) * 10 + index + 1}</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="sm" fw={600} lh={1.5} lineClamp={1}>
|
||||||
<Text fw={500} lineClamp={1}>{item.jenisInformasi}</Text>
|
{item.jenisInformasi}
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Box w={200}>
|
|
||||||
<Text lineClamp={1} fz="sm" c="dimmed">
|
|
||||||
{item.deskripsi?.replace(/<[^>]*>?/gm, '').substring(0, 80) + '...'}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
|
||||||
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ textAlign: 'center' }}>
|
<TableTd>
|
||||||
|
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1}>
|
||||||
|
{item.deskripsi?.replace(/<[^>]*>?/gm, '').substring(0, 80)}...
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd ta="center">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
radius="md"
|
radius="md"
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
leftSection={<IconDeviceImacCog size={16} />}
|
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
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
@@ -119,9 +150,51 @@ function ListDaftarInformasi({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</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>
|
</Paper>
|
||||||
<Center mt="lg">
|
|
||||||
|
<Center mt={{ base: 'lg', md: 'xl' }}>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
@@ -129,14 +202,12 @@ function ListDaftarInformasi({ search }: { search: string }) {
|
|||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
color="blue"
|
color="blue"
|
||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DaftarInformasiPublik;
|
export default DaftarInformasiPublik;
|
||||||
@@ -8,11 +8,11 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import stateDasarHukumPPID from '../../_state/ppid/dasar_hukum/dasarHukum';
|
import stateDasarHukumPPID from '../../_state/ppid/dasar_hukum/dasarHukum';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listDasarHukum = useProxy(stateDasarHukumPPID)
|
const listDasarHukum = useProxy(stateDasarHukumPPID);
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listDasarHukum.findById.load('1')
|
listDasarHukum.findById.load('1');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (listDasarHukum.findById.loading) {
|
if (listDasarHukum.findById.loading) {
|
||||||
return (
|
return (
|
||||||
@@ -41,6 +41,7 @@ function Page() {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
<Button
|
<Button
|
||||||
|
w={{ base: '100%', md: "110%" }}
|
||||||
c="green"
|
c="green"
|
||||||
variant="light"
|
variant="light"
|
||||||
leftSection={<IconEdit size={18} stroke={2} />}
|
leftSection={<IconEdit size={18} stroke={2} />}
|
||||||
@@ -57,33 +58,39 @@ function Page() {
|
|||||||
<Grid>
|
<Grid>
|
||||||
<GridCol span={12}>
|
<GridCol span={12}>
|
||||||
<Center>
|
<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>
|
</Center>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={12}>
|
<GridCol span={12}>
|
||||||
<Text
|
<Title
|
||||||
|
order={3}
|
||||||
ta="center"
|
ta="center"
|
||||||
fz={{ base: '1.5rem', md: '2rem' }}
|
lh={{ base: 1.15, md: 1.1 }}
|
||||||
fw="bold"
|
fw="bold"
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.judul }}
|
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.judul }}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Divider my="xl" color={colors['blue-button']} />
|
<Divider my="xl" color={colors['blue-button']} />
|
||||||
|
|
||||||
<Box
|
<Text
|
||||||
className="prose max-w-none"
|
|
||||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.content }}
|
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>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client';
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title} from '@mantine/core';
|
||||||
import { IconChartBar, IconUsers } from '@tabler/icons-react';
|
import { IconChartBar, IconUsers } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -56,6 +56,8 @@ function LayoutTabsIKM({ children }: { children: React.ReactNode }) {
|
|||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
|
<Box visibleFrom='md' pb={10}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
<TabsList
|
<TabsList
|
||||||
p="sm"
|
p="sm"
|
||||||
@@ -69,29 +71,62 @@ function LayoutTabsIKM({ children }: { children: React.ReactNode }) {
|
|||||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map((e, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<Tooltip
|
|
||||||
key={i}
|
|
||||||
label={e.description}
|
|
||||||
withArrow
|
|
||||||
position="bottom"
|
|
||||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
|
||||||
>
|
|
||||||
<TabsTab
|
<TabsTab
|
||||||
value={e.value}
|
key={i}
|
||||||
leftSection={e.icon}
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
style={{
|
style={{
|
||||||
fontWeight: 500,
|
fontWeight: 600,
|
||||||
fontSize: "0.9rem",
|
fontSize: "0.9rem",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
|
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{e.label}
|
{tab.label}
|
||||||
</TabsTab>
|
</TabsTab>
|
||||||
</Tooltip>
|
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</ScrollArea>
|
</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) => (
|
{tabs.map((e, i) => (
|
||||||
<TabsPanel key={i} value={e.value} mt="md">
|
<TabsPanel key={i} value={e.value} mt="md">
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function EditResponden() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function DetailResponden() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function RespondenCreate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack size={20} />
|
<IconArrowBack size={20} />
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
'use client';
|
'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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -9,11 +25,11 @@ import HeaderSearch from '../../../_com/header';
|
|||||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
||||||
|
|
||||||
function Responden() {
|
function Responden() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title="Data Responden"
|
title="Responden"
|
||||||
placeholder="Cari nama responden..."
|
placeholder="Cari nama responden..."
|
||||||
searchIcon={<IconSearch size={18} />}
|
searchIcon={<IconSearch size={18} />}
|
||||||
value={search}
|
value={search}
|
||||||
@@ -33,17 +49,13 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10);
|
||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
|
const filteredData = (data || []).filter((item) => {
|
||||||
const filteredData = (data || []).filter(item => {
|
|
||||||
const keyword = search.toLowerCase();
|
const keyword = search.toLowerCase();
|
||||||
return (
|
return item.name.toLowerCase().includes(keyword);
|
||||||
item.name.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
@@ -56,21 +68,25 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
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">
|
<Stack gap="md">
|
||||||
<Title order={3}>Data Responden</Title>
|
<Title order={2} lh={1.2}>
|
||||||
|
Data Responden
|
||||||
|
</Title>
|
||||||
|
<Box visibleFrom="md">
|
||||||
<Table striped withRowBorders>
|
<Table striped withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ textAlign: 'center' }}>No</TableTh>
|
<TableTh ta="center">No</TableTh>
|
||||||
<TableTh>Nama</TableTh>
|
<TableTh>Nama</TableTh>
|
||||||
<TableTh>Tanggal</TableTh>
|
<TableTh>Tanggal</TableTh>
|
||||||
<TableTh>Jenis Kelamin</TableTh>
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
<TableTh style={{ textAlign: 'center' }}>Aksi</TableTh>
|
<TableTh ta="center">Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
</Table>
|
</Table>
|
||||||
<Text c="dimmed" ta="center" py="md">
|
</Box>
|
||||||
|
<Text c="dimmed" ta="center" py="md" fz={{ base: 'sm', md: 'md' }} lh={1.4}>
|
||||||
Belum ada data responden yang tersedia
|
Belum ada data responden yang tersedia
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -79,24 +95,33 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<Stack gap="md">
|
||||||
<Title order={3}>Data Responden</Title>
|
<Title order={2} lh={1.2}>
|
||||||
|
Data Responden
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
<Table striped withRowBorders>
|
<Table striped withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
<TableTh w="5%" ta="center">
|
||||||
<TableTh style={{ width: '25%' }}>Nama</TableTh>
|
No
|
||||||
<TableTh style={{ width: '25%' }}>Tanggal</TableTh>
|
</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Jenis Kelamin</TableTh>
|
<TableTh w="25%">Nama</TableTh>
|
||||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
<TableTh w="25%">Tanggal</TableTh>
|
||||||
|
<TableTh w="20%">Jenis Kelamin</TableTh>
|
||||||
|
<TableTh w="15%" ta="center">
|
||||||
|
Aksi
|
||||||
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length === 0 ? (
|
{filteredData.length === 0 ? (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={5}>
|
<TableTd colSpan={5}>
|
||||||
<Text c="dimmed" ta="center" py="md">
|
<Text c="dimmed" ta="center" py="md" fz="sm" lh={1.4}>
|
||||||
Tidak ada data yang cocok dengan pencarian
|
Tidak ada data yang cocok dengan pencarian
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -104,20 +129,22 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
) : (
|
) : (
|
||||||
filteredData.map((item, index) => (
|
filteredData.map((item, index) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTd ta="center">{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '25%' }}>{item.name}</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd>
|
||||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID') : '-'}
|
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID') : '-'}
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>{item.jenisKelamin.name}</TableTd>
|
<TableTd>{item.jenisKelamin.name}</TableTd>
|
||||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
<TableTd ta="center">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
radius="md"
|
radius="md"
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
leftSection={<IconDeviceImacCog size={16} />}
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)}
|
onClick={() =>
|
||||||
|
router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
@@ -127,6 +154,74 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
)}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</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 && (
|
{filteredData.length > 0 && (
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
@@ -138,7 +233,6 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
}}
|
}}
|
||||||
size="md"
|
size="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
mt="md"
|
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
@@ -148,5 +242,3 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Responden;
|
export default Responden;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function DetailPermohonanInformasiPublik() {
|
|||||||
const data = state.findUnique.data;
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
@@ -39,7 +39,7 @@ function DetailPermohonanInformasiPublik() {
|
|||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: '100%', md: '60%' }}
|
w={{ base: '100%', md: '70%' }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -1,82 +1,182 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors'
|
import colors from '@/con/colors'
|
||||||
import { Box, Button, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'
|
import {
|
||||||
import { useShallowEffect } from '@mantine/hooks'
|
Box,
|
||||||
import { IconDeviceImacCog, IconId, IconInfoCircle, IconPhone, IconUser } from '@tabler/icons-react'
|
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 { useRouter } from 'next/navigation'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
import { useProxy } from 'valtio/utils'
|
import { useProxy } from 'valtio/utils'
|
||||||
import statepermohonanInformasiPublikForm from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik'
|
import statepermohonanInformasiPublikForm from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik'
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks'
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const permohonanInformasiPublikState = useProxy(statepermohonanInformasiPublikForm)
|
const permohonanInformasiPublikState = useProxy(statepermohonanInformasiPublikForm)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { data, page, totalPages, loading, load } = permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useEffect(() => {
|
||||||
permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany.load()
|
load(page, 10, debouncedSearch);
|
||||||
}, [])
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
if (!permohonanInformasiPublikState.statepermohonanInformasiPublik.findMany.data) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} p="lg" align="center">
|
<Stack pos="relative" p="lg" align="center">
|
||||||
<Skeleton radius="md" h={40} w="60%" />
|
|
||||||
<Skeleton radius="md" h={200} w="100%" />
|
<Skeleton radius="md" h={200} w="100%" />
|
||||||
</Stack>
|
</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 (
|
return (
|
||||||
<Box py="md">
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
<Paper bg={colors['white-1']} p="lg" radius="xl" shadow="sm" withBorder>
|
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="xl" shadow="sm" withBorder>
|
||||||
<Stack gap="md">
|
<Stack gap={'sm'}>
|
||||||
<Group justify="space-between">
|
<Grid mb={10}>
|
||||||
<Title order={2} c="dark">Daftar Permohonan Informasi Publik</Title>
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<IconInfoCircle size={20} stroke={1.5} />
|
<Title order={2} lh={1.2} c="dark">
|
||||||
</Group>
|
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 ? (
|
{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']} />
|
<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>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<>
|
||||||
<Table
|
{/* Desktop Table */}
|
||||||
highlightOnHover
|
<Box visibleFrom="md">
|
||||||
withRowBorders
|
<Table highlightOnHover>
|
||||||
withColumnBorders
|
|
||||||
withTableBorder
|
|
||||||
striped
|
|
||||||
stickyHeader
|
|
||||||
>
|
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>No</TableTh>
|
<TableTh fz="sm" fw={600} ta="center" w={60}>
|
||||||
<TableTh><Group gap={5}><IconUser size={16} /> Nama</Group></TableTh>
|
No
|
||||||
<TableTh><Group gap={5}><IconId size={16} /> NIK</Group></TableTh>
|
</TableTh>
|
||||||
<TableTh><Group gap={5}><IconPhone size={16} /> Telepon</Group></TableTh>
|
<TableTh fz="sm" fw={600}>
|
||||||
<TableTh><Group gap={5}><IconInfoCircle size={16} /> Detail</Group></TableTh>
|
<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>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{data.map((item, index) => (
|
{data.map((item, index) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{index + 1}</TableTd>
|
<TableTd ta="center" fz="sm" lh={1.5}>
|
||||||
<TableTd>
|
{index + 1}
|
||||||
<Box w={200}>
|
|
||||||
<Text lineClamp={1} fw={500}>{item.name}</Text>
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="sm" fw={500} lh={1.5} lineClamp={1}>
|
||||||
{item.nik}
|
{item.name}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="sm" lh={1.5}>{item.nik}</Text>
|
||||||
{item.notelp}
|
</TableTd>
|
||||||
</Box>
|
<TableTd>
|
||||||
|
<Text fz="sm" lh={1.5}>{item.notelp}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
@@ -85,7 +185,9 @@ function Page() {
|
|||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
leftSection={<IconDeviceImacCog size={16} />}
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
onClick={() => router.push(`/admin/ppid/permohonan-informasi-publik/${item.id}`)}
|
onClick={() =>
|
||||||
|
router.push(`/admin/ppid/permohonan-informasi-publik/${item.id}`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
@@ -95,8 +197,78 @@ function Page() {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</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}`)
|
||||||
|
}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
Lihat Detail
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</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>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function DetailPermohonanKeberatanInformasiPublik() {
|
|||||||
const data = state.findUnique.data;
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py="md">
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
@@ -40,7 +40,7 @@ function DetailPermohonanKeberatanInformasiPublik() {
|
|||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: '100%', md: '60%' }}
|
w={{ base: '100%', md: '70%' }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -1,75 +1,185 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors'
|
import colors from '@/con/colors'
|
||||||
import { Box, Button, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'
|
import {
|
||||||
import { useShallowEffect } from '@mantine/hooks'
|
Box,
|
||||||
import { IconDeviceImacCog, IconInfoCircle, IconMail, IconPhone, IconUser } from '@tabler/icons-react'
|
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 { useRouter } from 'next/navigation'
|
||||||
import { useProxy } from 'valtio/utils'
|
import { useProxy } from 'valtio/utils'
|
||||||
import statePermohonanKeberatan from '../../_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi'
|
import statePermohonanKeberatan from '../../_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks'
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const listState = useProxy(statePermohonanKeberatan)
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
useShallowEffect(() => {
|
const listState = useProxy(statePermohonanKeberatan)
|
||||||
listState.findMany.load()
|
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 (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} p="lg" align="center">
|
<Stack pos="relative" p="lg" align="center">
|
||||||
<Skeleton radius="md" h={40} w="60%" />
|
|
||||||
<Skeleton radius="md" h={200} w="100%" />
|
<Skeleton radius="md" h={200} w="100%" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (!data || data.length === 0) {
|
||||||
const data = listState.findMany.data
|
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 (
|
return (
|
||||||
<Box py="md">
|
<Box py={{ base: 'md', md: 'lg' }}>
|
||||||
<Paper bg={colors['white-1']} p="lg" radius="xl" shadow="sm" withBorder>
|
<Paper bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} radius="xl" shadow="sm" withBorder>
|
||||||
<Stack gap="md">
|
<Stack gap={'sm'}>
|
||||||
<Group justify="space-between">
|
<Grid mb={10}>
|
||||||
<Title order={2} c="dark">Daftar Permohonan Keberatan Informasi Publik</Title>
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<IconInfoCircle size={20} stroke={1.5} />
|
<Title order={2} lh={1.2} c="dark">
|
||||||
</Group>
|
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 ? (
|
{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']} />
|
<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>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<>
|
||||||
<Table
|
{/* Desktop Table */}
|
||||||
highlightOnHover
|
<Box visibleFrom="md">
|
||||||
withRowBorders
|
<Table highlightOnHover>
|
||||||
withColumnBorders
|
|
||||||
withTableBorder
|
|
||||||
striped
|
|
||||||
stickyHeader
|
|
||||||
>
|
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>No</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4} ta="center">
|
||||||
<TableTh><Group gap={5}><IconUser size={16} /> Nama</Group></TableTh>
|
No
|
||||||
<TableTh><Group gap={5}><IconMail size={16} /> Email</Group></TableTh>
|
</TableTh>
|
||||||
<TableTh><Group gap={5}><IconPhone size={16} /> Telepon</Group></TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||||
<TableTh><Group gap={5}><IconInfoCircle size={16} /> Detail</Group></TableTh>
|
<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>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{data.map((item, index) => (
|
{data.map((item, index) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{index + 1}</TableTd>
|
<TableTd ta="center" fz="sm" lh={1.5}>
|
||||||
<TableTd>
|
{index + 1}
|
||||||
<Text lineClamp={1} fw={500}>{item.name}</Text>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text size="sm">{item.email || '-'}</Text>
|
<Text fz="sm" fw={500} lh={1.5} lineClamp={1}>
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text>{item.notelp || '-'}</Text>
|
<Text fz="sm" lh={1.5}>
|
||||||
|
{item.email || '-'}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" lh={1.5}>
|
||||||
|
{item.notelp || '-'}
|
||||||
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
@@ -78,7 +188,11 @@ function Page() {
|
|||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
leftSection={<IconDeviceImacCog size={16} />}
|
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}`
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
@@ -88,10 +202,84 @@ function Page() {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</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}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mt={4}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</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>
|
</Box>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
export default Page;
|
|
||||||
|
export default Page
|
||||||
@@ -138,7 +138,7 @@ function EditProfilePPID() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={handleBack} p="xs" radius="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 { useProxy } from 'valtio/utils';
|
||||||
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
|
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const allList = useProxy(stateProfilePPID)
|
const allList = useProxy(stateProfilePPID);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
allList.profile.load("edit") // Assuming "1" is your default ID, adjust as needed
|
allList.profile.load("edit");
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!allList.profile.data) {
|
if (!allList.profile.data) {
|
||||||
return (
|
return (
|
||||||
@@ -32,7 +32,7 @@ function Page() {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Grid>
|
<Grid>
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<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>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
<Button
|
<Button
|
||||||
@@ -57,9 +57,14 @@ function Page() {
|
|||||||
</Center>
|
</Center>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={12}>
|
<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
|
PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA
|
||||||
</Text>
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -87,34 +92,77 @@ function Page() {
|
|||||||
className="glass3"
|
className="glass3"
|
||||||
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
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}
|
{item.name}
|
||||||
</Text>
|
</Title>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Box mt="lg">
|
<Box mt="lg">
|
||||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Biodata</Text>
|
<Title order={3} lh={1.2} mb={4}>
|
||||||
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.biodata }} />
|
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>
|
||||||
|
|
||||||
<Box mt="xl">
|
<Box mt="xl">
|
||||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Riwayat Karir</Text>
|
<Title order={3} lh={1.2} mb={4}>
|
||||||
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.riwayat }} />
|
Riwayat Karir
|
||||||
</Box>
|
</Title>
|
||||||
|
|
||||||
<Box mt="xl">
|
|
||||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Pengalaman Organisasi</Text>
|
|
||||||
<Box px={20}>
|
<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">
|
||||||
|
<Title order={3} lh={1.2} mb={4}>
|
||||||
|
Pengalaman Organisasi
|
||||||
|
</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.pengalaman }}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box mt="xl" mb="lg">
|
<Box mt="xl" mb="lg">
|
||||||
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Program Kerja Unggulan</Text>
|
<Title order={3} lh={1.2} mb={4}>
|
||||||
|
Program Kerja Unggulan
|
||||||
|
</Title>
|
||||||
<Box px={20}>
|
<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.unggulan }}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -122,9 +170,7 @@ function Page() {
|
|||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
import { IconBuildingCommunity, IconHierarchy2, IconUsers } from '@tabler/icons-react';
|
import { IconBuildingCommunity, IconHierarchy2, IconUsers } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -63,6 +63,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
{/* ✅ Scroll horizontal wrapper */}
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
|
<Box visibleFrom='md' pb={10}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
<TabsList
|
<TabsList
|
||||||
p="sm"
|
p="sm"
|
||||||
@@ -85,6 +86,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: "0.9rem",
|
fontSize: "0.9rem",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
|
flexShrink: 0, // ✅ jangan mengecil aneh-aneh
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -92,6 +94,45 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
|||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</ScrollArea>
|
</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) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export default function EditPegawaiPPID() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function DetailPegawai() {
|
|||||||
const data = statePegawai.findUnique.data;
|
const data = statePegawai.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
@@ -59,7 +59,7 @@ function DetailPegawai() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w={{ base: "100%", md: "70%" }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ function CreatePegawaiPPID() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -1,13 +1,32 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title } from '@mantine/core';
|
import {
|
||||||
import { IconCheck, IconDeviceImacCog, IconPlus, IconSearch, IconX } from '@tabler/icons-react';
|
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 { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
|
||||||
function PegawaiPPID() {
|
function PegawaiPPID() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -28,6 +47,7 @@ function PegawaiPPID() {
|
|||||||
function ListPegawaiPPID({ search }: { search: string }) {
|
function ListPegawaiPPID({ search }: { search: string }) {
|
||||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai);
|
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -38,26 +58,28 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
} = stateOrganisasi.findMany;
|
} = stateOrganisasi.findMany;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
// Handle loading state
|
// Handle loading state
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py="xl">
|
||||||
<Skeleton height={300} />
|
<Skeleton height={300} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="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
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -68,17 +90,22 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<Center py="xl">
|
<Center py="xl">
|
||||||
<Text c="dimmed">Tidak ada data pegawai yang ditemukan</Text>
|
<Text c="dimmed" fz={{ base: 'sm', md: 'md' }} lh={1.5}>
|
||||||
|
Tidak ada data pegawai yang ditemukan
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py="xl">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="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
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -88,52 +115,43 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
|
||||||
<Table highlightOnHover>
|
{/* Desktop: Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Table highlightOnHover miw={0}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Nama Lengkap</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>
|
||||||
<TableTh style={{ width: '20%' }}>Posisi</TableTh>
|
Nama Lengkap
|
||||||
<TableTh style={{ width: '10%' }}>Status</TableTh>
|
</TableTh>
|
||||||
<TableTh style={{ width: '10%' }}>Aksi</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>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={150}>
|
<Text fz="md" fw={500} lh={1.5} truncate="end">
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
|
||||||
{item.namaLengkap}
|
{item.namaLengkap}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={150}>
|
<Badge variant="light" color="blue" fz="sm" lh={1.4}>
|
||||||
<Badge variant="light" color="blue">
|
|
||||||
{item.posisi?.nama || 'Belum diatur'}
|
{item.posisi?.nama || 'Belum diatur'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Group gap="xs" wrap="nowrap">
|
<Badge color={item.isActive ? "green" : "red"} fz="sm" lh={1.4}>
|
||||||
<Box visibleFrom="sm">
|
|
||||||
<Badge color={item.isActive ? "green" : "red"}>
|
|
||||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||||
</Badge>
|
</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>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
@@ -143,6 +161,7 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
color="blue"
|
color="blue"
|
||||||
leftSection={<IconDeviceImacCog size={16} />}
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}
|
onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}
|
||||||
|
fz="sm"
|
||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
@@ -152,7 +171,47 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</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
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ function EditPosisiOrganisasiPPID() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ function CreatePosisiOrganisasiPPID() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -31,6 +31,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -41,8 +42,8 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
} = stateOrganisasi.findMany;
|
} = stateOrganisasi.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, search]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
const handleHapus = async () => {
|
const handleHapus = async () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -56,17 +57,17 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={{ base: 'sm', md: 'md' }}>
|
||||||
<Skeleton height={600} radius="md" />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={{ base: 'sm', md: 'md' }}>
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={{ base: 'md', md: 'lg' }} shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb={{ base: 'sm', md: 'md' }}>
|
||||||
<Title order={4}>Daftar Posisi Organisasi PPID</Title>
|
<Title order={2}>Daftar Posisi Organisasi PPID</Title>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -76,33 +77,33 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
|
||||||
|
{/* Desktop Table */}
|
||||||
|
<Box visibleFrom="md">
|
||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '20%' }}>Nama Posisi</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>Nama Posisi</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Deskripsi</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>Deskripsi</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Hierarki</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>Hierarki</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>Edit</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
|
<TableTh fz="sm" fw={600} lh={1.4}>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
<Text fz="md" fw={600} lh={1.5} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd w={200}>
|
||||||
<Box w={200}>
|
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd>
|
||||||
<Text>{item.hierarki || '-'}</Text>
|
<Text fz="md" lh={1.5}>{item.hierarki || '-'}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="green"
|
color="green"
|
||||||
@@ -112,7 +113,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
<IconEdit size={18} />
|
<IconEdit size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color="red"
|
||||||
@@ -129,9 +130,11 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={5}>
|
||||||
<Center py={20}>
|
<Center py={{ base: 'sm', md: 'md' }}>
|
||||||
<Text color="dimmed">Tidak ada data posisi organisasi yang cocok</Text>
|
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
|
||||||
|
Tidak ada data posisi organisasi yang cocok
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -139,7 +142,59 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</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>
|
</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>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -154,6 +209,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function VisiMisiPPIDEdit() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateVisiMisiPPID from '../../_state/ppid/visi_misi_ppid/visimisiPPID';
|
import stateVisiMisiPPID from '../../_state/ppid/visi_misi_ppid/visimisiPPID';
|
||||||
|
|
||||||
|
|
||||||
function VisiMisiPPIDList() {
|
function VisiMisiPPIDList() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listVisiMisi = useProxy(stateVisiMisiPPID);
|
const listVisiMisi = useProxy(stateVisiMisiPPID);
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listVisiMisi.findById.load('1');
|
listVisiMisi.findById.load('1');
|
||||||
@@ -42,6 +41,7 @@ function VisiMisiPPIDList() {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
<Button
|
<Button
|
||||||
|
w={{ base: '100%', md: "110%" }}
|
||||||
c="green"
|
c="green"
|
||||||
variant="light"
|
variant="light"
|
||||||
leftSection={<IconEdit size={18} stroke={2} />}
|
leftSection={<IconEdit size={18} stroke={2} />}
|
||||||
@@ -58,14 +58,25 @@ function VisiMisiPPIDList() {
|
|||||||
<Grid>
|
<Grid>
|
||||||
<GridCol span={12}>
|
<GridCol span={12}>
|
||||||
<Center>
|
<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>
|
</Center>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={12}>
|
<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
|
MOTO PPID DESA DARMASABA
|
||||||
</Text>
|
</Title>
|
||||||
<Text ta="center" fz={{ base: '1rem', md: '1.2rem' }} mt="sm">
|
<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
|
MEMBERIKAN INFORMASI YANG CEPAT, MUDAH, TEPAT DAN TRANSPARAN
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
@@ -74,26 +85,50 @@ function VisiMisiPPIDList() {
|
|||||||
<Divider my="xl" color={colors['blue-button']} />
|
<Divider my="xl" color={colors['blue-button']} />
|
||||||
|
|
||||||
<Box>
|
<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
|
VISI PPID
|
||||||
</Text>
|
</Title>
|
||||||
<Box
|
<Text
|
||||||
className="prose max-w-none"
|
ta={{ base: "center", md: "justify" }}
|
||||||
dangerouslySetInnerHTML={{ __html: listVisiMisi.findById.data.visi }}
|
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>
|
</Box>
|
||||||
|
|
||||||
<Divider my="xl" color={colors['blue-button']} />
|
<Divider my="xl" color={colors['blue-button']} />
|
||||||
|
|
||||||
<Box mt="xl">
|
<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
|
MISI PPID
|
||||||
</Text>
|
</Title>
|
||||||
<Box
|
<Text
|
||||||
className="prose max-w-none"
|
ta={"justify"}
|
||||||
dangerouslySetInnerHTML={{ __html: listVisiMisi.findById.data.misi }}
|
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>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,14 +1,56 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function permohonanInformasiPublikFindMany() {
|
export default async function permohonanInformasiPublikFindMany(
|
||||||
const res = await prisma.permohonanInformasiPublik.findMany({
|
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: {
|
include: {
|
||||||
jenisInformasiDiminta: true,
|
jenisInformasiDiminta: true,
|
||||||
caraMemperolehInformasi: true,
|
caraMemperolehInformasi: true,
|
||||||
caraMemperolehSalinanInformasi: true,
|
caraMemperolehSalinanInformasi: true,
|
||||||
}
|
},
|
||||||
});
|
take: limit,
|
||||||
|
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||||
|
}),
|
||||||
|
prisma.permohonanInformasiPublik.count({
|
||||||
|
where: { isActive: true },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: res,
|
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 prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
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 },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
export default async function permohonanKeberatanInformasiPublikFindMany() {
|
|
||||||
const res = await prisma.formulirPermohonanKeberatan.findMany();
|
|
||||||
return {
|
return {
|
||||||
data: res,
|
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",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -216,6 +216,8 @@ export default function ModernNewsNotification({
|
|||||||
{/* Widget Panel */}
|
{/* Widget Panel */}
|
||||||
<Transition mounted={widgetOpen} transition="slide-up" duration={300}>
|
<Transition mounted={widgetOpen} transition="slide-up" duration={300}>
|
||||||
{(styles) => (
|
{(styles) => (
|
||||||
|
<Box>
|
||||||
|
<Box hiddenFrom="md">
|
||||||
<Paper
|
<Paper
|
||||||
style={{
|
style={{
|
||||||
...styles,
|
...styles,
|
||||||
@@ -308,10 +310,108 @@ export default function ModernNewsNotification({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Paper
|
||||||
|
style={{
|
||||||
|
...styles,
|
||||||
|
position: "fixed",
|
||||||
|
bottom: "100px",
|
||||||
|
right: "24px",
|
||||||
|
width: "90vw",
|
||||||
|
maxWidth: 380,
|
||||||
|
maxHeight: "500px",
|
||||||
|
boxShadow: "0 8px 32px rgba(0,0,0,0.12)",
|
||||||
|
borderRadius: "16px",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #1e5a7e 0%, #2c7da0 100%)",
|
||||||
|
padding: "16px 20px",
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Group gap="xs">
|
||||||
|
<IconBell size={20} />
|
||||||
|
<Text c="white" fw={600} size="md">
|
||||||
|
Berita & Pengumuman
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<CloseButton
|
||||||
|
onClick={() => {
|
||||||
|
setWidgetOpen(false);
|
||||||
|
onSeen?.();
|
||||||
|
}}
|
||||||
|
variant="transparent"
|
||||||
|
c="white"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box style={{ maxHeight: "400px", overflowY: "auto", padding: "12px" }}>
|
||||||
|
{news.length === 0 ? (
|
||||||
|
<Box p="xl" style={{ textAlign: "center" }}>
|
||||||
|
<Text c="dimmed" size="sm">
|
||||||
|
Tidak ada berita terbaru
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Stack gap="xs">
|
||||||
|
{news.map((item) => (
|
||||||
|
<Paper
|
||||||
|
key={item.id}
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
style={{
|
||||||
|
border: "1px solid #e9ecef",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = "#1e5a7e";
|
||||||
|
e.currentTarget.style.transform = "translateY(-2px)";
|
||||||
|
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.08)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = "#e9ecef";
|
||||||
|
e.currentTarget.style.transform = "translateY(0)";
|
||||||
|
e.currentTarget.style.boxShadow = "none";
|
||||||
|
}}
|
||||||
|
onClick={() => handleNotificationClick(item)}
|
||||||
|
>
|
||||||
|
<Group justify="space-between" mb="xs">
|
||||||
|
<Badge
|
||||||
|
size="sm"
|
||||||
|
color={item.type === "berita" ? "blue" : "orange"}
|
||||||
|
variant="light"
|
||||||
|
>
|
||||||
|
{item.type === "berita" ? "Berita" : "Pengumuman"}
|
||||||
|
</Badge>
|
||||||
|
<IconChevronRight size={16} color="#adb5bd" />
|
||||||
|
</Group>
|
||||||
|
<Text fw={600} size="sm" mb={4} lineClamp={2}>
|
||||||
|
{item.title || "Tanpa Judul"}
|
||||||
|
</Text>
|
||||||
|
<Text size="xs" c="dimmed" lineClamp={2}>
|
||||||
|
{stripHtml(item.content).substring(0, 100)}...
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
{/* Toast Notification */}
|
{/* Toast Notification */}
|
||||||
|
<Box>
|
||||||
|
<Box hiddenFrom="md">
|
||||||
<Transition
|
<Transition
|
||||||
mounted={toastVisible && !!currentNews}
|
mounted={toastVisible && !!currentNews}
|
||||||
transition="slide-left"
|
transition="slide-left"
|
||||||
@@ -387,6 +487,85 @@ export default function ModernNewsNotification({
|
|||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
</Box>
|
||||||
|
<Box visibleFrom="md">
|
||||||
|
<Transition
|
||||||
|
mounted={toastVisible && !!currentNews}
|
||||||
|
transition="slide-right"
|
||||||
|
duration={300}
|
||||||
|
>
|
||||||
|
{(styles) => (
|
||||||
|
<Paper
|
||||||
|
style={{
|
||||||
|
...styles,
|
||||||
|
position: "fixed",
|
||||||
|
bottom: "100px",
|
||||||
|
right: "24px",
|
||||||
|
width: "90vw",
|
||||||
|
maxWidth: 380,
|
||||||
|
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
overflow: "hidden",
|
||||||
|
border: "1px solid #e9ecef",
|
||||||
|
}}
|
||||||
|
onClick={handleLihatSemua}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: "3px",
|
||||||
|
background: "#1e5a7e",
|
||||||
|
animation: "shrink 8s linear forwards",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<style>{`
|
||||||
|
@keyframes shrink {
|
||||||
|
from { width: 100%; }
|
||||||
|
to { width: 0%; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<Box p="md">
|
||||||
|
<Group justify="space-between" mb="xs">
|
||||||
|
<Badge
|
||||||
|
size="md"
|
||||||
|
color={currentNews?.type === "berita" ? "blue" : "orange"}
|
||||||
|
variant="light"
|
||||||
|
>
|
||||||
|
{currentNews?.type === "berita"
|
||||||
|
? "Berita Terbaru"
|
||||||
|
: "Pengumuman"}
|
||||||
|
</Badge>
|
||||||
|
<CloseButton onClick={handleDismissToast} size="sm" />
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Text fw={600} size="sm" mb={6}>
|
||||||
|
{currentNews?.title || "Informasi Terbaru"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="xs" c="dimmed" lineClamp={3}>
|
||||||
|
{stripHtml(currentNews?.content || "")}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group justify="space-between" mt="md">
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
{news.length > 1 ? `${news.length} berita tersedia` : "1 berita"}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
fw={500}
|
||||||
|
c="#1e5a7e"
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={handleLihatSemua}
|
||||||
|
>
|
||||||
|
Lihat Semua →
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3,18 +3,17 @@
|
|||||||
import colors from "@/con/colors"
|
import colors from "@/con/colors"
|
||||||
import stateNav from "@/state/state-nav"
|
import stateNav from "@/state/state-nav"
|
||||||
import { ActionIcon, Button, Container, Flex, Image, Menu, MenuTarget, Stack, Tooltip } from "@mantine/core"
|
import { ActionIcon, Button, Container, Flex, Image, Menu, MenuTarget, Stack, Tooltip } from "@mantine/core"
|
||||||
import { IconSearch, IconUser } from "@tabler/icons-react"
|
import { IconSearch, IconUserCog } from "@tabler/icons-react"
|
||||||
import { useTransitionRouter } from 'next-view-transitions'
|
import { useTransitionRouter } from 'next-view-transitions'
|
||||||
import { usePathname, useRouter } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from "valtio"
|
||||||
import { MenuItem } from "../../../../types/menu-item"
|
import { MenuItem } from "../../../../types/menu-item"
|
||||||
import { NavbarSearch } from "./NavBarSearch"
|
import { NavbarSearch } from "./NavBarSearch"
|
||||||
import { NavbarSubMenu } from "./NavbarSubMenu"
|
import { NavbarSubMenu } from "./NavbarSubMenu"
|
||||||
|
import { authStore } from "@/store/authStore";
|
||||||
|
|
||||||
// contoh state auth (dummy aja dulu, bisa diganti sesuai sistem auth kamu)
|
// contoh state auth (dummy aja dulu, bisa diganti sesuai sistem auth kamu)
|
||||||
const stateAuth = {
|
const isAdmin = authStore.user?.roleId === 0 || authStore.user?.roleId === 1 || authStore.user?.roleId === 2 || authStore.user?.roleId === 3 || authStore.user?.roleId === 4;
|
||||||
role: "admin", // coba ubah ke "user" buat test
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
||||||
const { item, isSearch } = useSnapshot(stateNav)
|
const { item, isSearch } = useSnapshot(stateNav)
|
||||||
@@ -70,8 +69,8 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* hanya tampil kalau role = admin */}
|
{/* hanya tampil kalau role = admin */}
|
||||||
{stateAuth.role === "admin" && (
|
{isAdmin && (
|
||||||
<Tooltip label="Profil Saya" position="bottom" withArrow>
|
<Tooltip label="Kembali ke Admin" position="bottom" withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
next.push("/admin/landing-page/profil/program-inovasi")
|
next.push("/admin/landing-page/profil/program-inovasi")
|
||||||
@@ -80,7 +79,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
|||||||
radius="xl"
|
radius="xl"
|
||||||
variant="light"
|
variant="light"
|
||||||
>
|
>
|
||||||
<IconUser size={22} />
|
<IconUserCog size={22} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user