Fix Admin Menu PPID, Submenu IKM
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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={{
|
||||
|
||||
Reference in New Issue
Block a user