Fix Admin Menu PPID, Submenu IKM

This commit is contained in:
2025-08-13 00:07:57 +08:00
parent c1583c21b1
commit a1d55e2b0a
64 changed files with 2865 additions and 1829 deletions

View File

@@ -124,7 +124,7 @@ export default function Content({ kategori }: { kategori: string }) {
p="lg"
radius="md"
withBorder
onClick={() => router.push(`/desa/berita/${item.id}`)}
onClick={() => router.push(`/darmasaba/desa/berita/${item.id}`)}
style={{ cursor: 'pointer' }}
>
<Card.Section>

View File

@@ -94,7 +94,7 @@ function Semua() {
<Button
variant="light"
rightSection={<IconArrowRight size={16} />}
onClick={() => router.push(`/desa/berita/${featuredData.id}`)}
onClick={() => router.push(`/darmasaba/desa/berita/${featuredData.kategoriBerita?.name}/${featuredData.id}`)}
>
Baca Selengkapnya
</Button>
@@ -155,7 +155,7 @@ function Semua() {
})}
</Text>
<Button p="xs" variant="light" rightSection={<IconArrowRight size={16} />} onClick={() => router.push(`/desa/berita/${item.id}`)}>Baca Selengkapnya</Button>
<Button p="xs" variant="light" rightSection={<IconArrowRight size={16} />} onClick={() => router.push(`/darmasaba/desa/berita/${item.kategoriBerita?.name}/${item.id}`)}>Baca Selengkapnya</Button>
</Flex>
</Card>
))}

View File

@@ -4,8 +4,8 @@
import { TextInput } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react';
import { useRouter, useSearchParams } from 'next/navigation';
import React, { useState } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';
export type SearchBarProps = {
placeholder?: string;
@@ -16,31 +16,54 @@ export function SearchBar({
placeholder = "pencarian",
searchIcon = <IconSearch size={20} />,
}: SearchBarProps) {
const router = useRouter();
const searchParams = useSearchParams();
// Get initial search value from URL
const [searchValue, setSearchValue] = useState(searchParams.get('search') || '');
// Handle search input change with debounce
const pathname = usePathname();
const typingTimeoutRef = useRef<number | null>(null);
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setSearchValue(value);
// Update URL with debounce
const params = new URLSearchParams(searchParams.toString());
if (value) {
params.set('search', value);
} else {
params.delete('search');
// Clear previous timeout
if (typingTimeoutRef.current) {
window.clearTimeout(typingTimeoutRef.current);
}
// Only update URL if the search value has actually changed
if (params.toString() !== searchParams.toString()) {
router.push(`?${params.toString()}`);
}
// Set new timeout
typingTimeoutRef.current = window.setTimeout(() => {
const params = new URLSearchParams(searchParams.toString());
// Always reset to first page when search changes
params.set('page', '1');
if (value) {
params.set('search', value);
} else {
params.delete('search');
}
// Update URL without adding to history
const newUrl = `${pathname}?${params.toString()}`;
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, '', newUrl);
// Dispatch a custom event that content components can listen to
window.dispatchEvent(new CustomEvent('searchUpdate', { detail: { search: value } }));
}, 500);
};
// Clean up timeout on unmount
useEffect(() => {
return () => {
if (typingTimeoutRef.current) {
window.clearTimeout(typingTimeoutRef.current);
}
};
}, []);
return (
<TextInput
radius="lg"

View File

@@ -2,7 +2,7 @@
import colors from '@/con/colors';
import { Box, Center, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ApiFetch from '@/lib/api-fetch';
interface FileItem {
@@ -23,18 +23,20 @@ interface FileItem {
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
// ✅ Load data function
const load = async (pageNum: number, limit: number, searchTerm: string) => {
setLoading(true);
// Handle search and pagination changes
const loadData = useCallback((pageNum: number, searchTerm: string) => {
setLoading(true);
// Using the load function from the component's scope
const loadFn = async () => {
try {
const query: Record<string, string> = {
category: 'image',
page: pageNum.toString(),
limit: limit.toString(),
};
if (searchTerm) query.search = searchTerm;
const response = await ApiFetch.api.fileStorage.findMany.get({ query });
const response = await ApiFetch.api.fileStorage.findMany.get({
query: {
category: 'image',
page: pageNum.toString(),
limit: '10',
...(searchTerm && { search: searchTerm })
}
});
if (response.status === 200 && response.data) {
setFiles(response.data.data || []);
@@ -49,14 +51,44 @@ interface FileItem {
setLoading(false);
}
};
// ✅ Baca dari URL — AMAN karena ssr: false
useEffect(() => {
loadFn();
}, []);
// Initial load and URL change handler
useEffect(() => {
const handleRouteChange = () => {
const urlParams = new URLSearchParams(window.location.search);
const urlSearch = urlParams.get('search') || '';
const urlPage = parseInt(urlParams.get('page') || '1');
setSearch(urlSearch);
load(1, 10, urlSearch.trim());
}, []);
setPage(urlPage);
loadData(urlPage, urlSearch);
};
// Handle search updates from the search bar
const handleSearchUpdate = (e: Event) => {
const { search } = (e as CustomEvent).detail;
setSearch(search);
setPage(1); // Reset to first page on new search
loadData(1, search);
};
// Initial load
handleRouteChange();
// Set up event listeners
window.addEventListener('popstate', handleRouteChange);
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
// Cleanup
return () => {
window.removeEventListener('popstate', handleRouteChange);
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
};
}, [loadData]);
// ✅ Fetch data
useEffect(() => {

View File

@@ -1,34 +1,60 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import colors from '@/con/colors';
import { Box, Center, Pagination, Paper, SimpleGrid, Spoiler, Stack, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useSnapshot } from 'valtio';
export default function VideoContent() {
const [expanded, setExpanded] = useState(false);
const [currentSearch, setCurrentSearch] = useState('');
const videoState = useSnapshot(stateGallery.video);
const {
data,
page,
totalPages,
loading,
load,
} = videoState.findMany;
// ✅ Baca dari URL hanya di client
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const urlSearch = urlParams.get('search') || '';
setCurrentSearch(urlSearch);
load(1, 10, urlSearch.trim());
// Handle search and pagination changes
const loadData = useCallback((pageNum: number, searchTerm: string) => {
stateGallery.video.findMany.load(pageNum, 10, searchTerm.trim());
}, []);
// Initial load and URL change handler
useEffect(() => {
const handleRouteChange = () => {
const urlParams = new URLSearchParams(window.location.search);
const urlSearch = urlParams.get('search') || '';
const urlPage = parseInt(urlParams.get('page') || '1');
loadData(urlPage, urlSearch);
};
// Handle search updates from the search bar
const handleSearchUpdate = (e: Event) => {
const { search } = (e as CustomEvent).detail;
loadData(1, search);
};
// Initial load
handleRouteChange();
// Set up event listeners
window.addEventListener('popstate', handleRouteChange);
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
// Cleanup
return () => {
window.removeEventListener('popstate', handleRouteChange);
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
};
}, [loadData]);
const handlePageChange = (newPage: number) => {
load(newPage, 10, currentSearch.trim());
const params = new URLSearchParams(window.location.search);
const search = params.get('search') || '';
loadData(newPage, search);
};
const dataVideo = data || [];

View File

@@ -7,7 +7,7 @@ import VisimisiDesa from './ui/visimisiDesa';
import LambangDesa from './ui/lambangDesa';
import MaskotDesa from './ui/maskotDesa';
import ProfilPerbekel from './ui/profilPerbekel';
import LembagaDesa from './ui/lembagaDesa';
// import LembagaDesa from './ui/lembagaDesa';
import MotoDesa from './ui/motoDesa';
import SemuaPerbekel from './ui/semuaPerbekel';
@@ -31,7 +31,7 @@ function Page() {
<LambangDesa />
<MaskotDesa />
<ProfilPerbekel />
<LembagaDesa />
{/* <LembagaDesa /> */}
<MotoDesa />
<SemuaPerbekel/>
</Box>

View File

@@ -97,7 +97,7 @@ function PengaduanMasyarakat() {
>
<Paper p="md" withBorder>
<Stack gap="xs">
<Title order={3}>Ajukan Administrasi Online</Title>
<Title order={3}>Ajukan Pengaduan Masyarakat</Title>
<TextInput
label={<Text fz="sm" fw="bold">Nama</Text>}
placeholder="masukkan nama"

View File

@@ -50,7 +50,7 @@ function Page() {
data={data}
dataKey="kategori"
series={[
{ name: 'jumlah', color: 'violet.6' },
{ name: 'jumlah', color: colors['blue-button'] },
]}
tickLine="y"
xAxisProps={{