PPID > Profile PPID

Desa >  Profile Visi Misi Desa
Keamanan >  Pencegahan Kriminalitas  Grid Kiri  Datanya  Mepet
Keamanan >  Laporan  Publik
Ekonomi  >  Sektor Unggulan Desa Coba tampilin 3  Aja
This commit is contained in:
2026-02-04 16:59:49 +08:00
parent bbd52fb6f5
commit 25000d0b0f
12 changed files with 164 additions and 148 deletions

View File

@@ -20,12 +20,25 @@ export default async function profilePerbekelFindById(request: Request) {
}, { status: 400 }); }, { status: 400 });
} }
const data = await prisma.profilPerbekel.findUnique({ let data;
// Special handling for 'edit' - get the first/only record
if (id === 'edit') {
data = await prisma.profilPerbekel.findFirst({
where: { isActive: true },
include: {
image: true,
},
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
} else {
data = await prisma.profilPerbekel.findUnique({
where: { id }, where: { id },
include: { include: {
image: true, image: true,
} }
}); });
}
if (!data) { if (!data) {
return Response.json({ return Response.json({

View File

@@ -26,9 +26,19 @@ export default async function lambangDesaFindById(request: Request) {
); );
} }
const data = await prisma.lambangDesa.findUnique({ let data;
where: { id },
}); // Special handling for 'edit' - get the first/only record
if (id === 'edit') {
data = await prisma.lambangDesa.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
} else {
data = await prisma.lambangDesa.findUnique({
where: { id },
});
}
if (!data) { if (!data) {
return Response.json( return Response.json(

View File

@@ -4,7 +4,7 @@ export default async function maskotDesaFindById(request: Request){
const url = new URL(request.url); const url = new URL(request.url);
const pathSegments = url.pathname.split('/'); const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1]; const id = pathSegments[pathSegments.length - 1];
if (!id) { if (!id) {
return Response.json({ return Response.json({
success: false, success: false,
@@ -20,16 +20,33 @@ export default async function maskotDesaFindById(request: Request){
}, {status: 400}) }, {status: 400})
} }
const data = await prisma.maskotDesa.findUnique({ let data;
where: { id },
include: { // Special handling for 'edit' - get the first/only record
images: { if (id === 'edit') {
include: { data = await prisma.maskotDesa.findFirst({
image: true, where: { isActive: true },
include: {
images: {
include: {
image: true,
}
}
},
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
} else {
data = await prisma.maskotDesa.findUnique({
where: { id },
include: {
images: {
include: {
image: true,
}
} }
} }
} })
}) }
if(!data) { if(!data) {
return Response.json({ return Response.json({

View File

@@ -4,7 +4,7 @@ export default async function sejarahDesaFindById(request: Request) {
const url = new URL(request.url); const url = new URL(request.url);
const pathSegments = url.pathname.split('/'); const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1]; const id = pathSegments[pathSegments.length - 1];
if (!id) { if (!id) {
return Response.json({ return Response.json({
success: false, success: false,
@@ -20,9 +20,19 @@ export default async function sejarahDesaFindById(request: Request) {
}, {status: 400}) }, {status: 400})
} }
const data = await prisma.sejarahDesa.findUnique({ let data;
where: { id },
}) // Special handling for 'edit' - get the first/only record
if (id === 'edit') {
data = await prisma.sejarahDesa.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
} else {
data = await prisma.sejarahDesa.findUnique({
where: { id },
})
}
if (!data) { if (!data) {
return Response.json({ return Response.json({

View File

@@ -4,7 +4,7 @@ export default async function visiMisiDesaFindById(request: Request) {
const url = new URL(request.url); const url = new URL(request.url);
const pathSegments = url.pathname.split('/'); const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1]; const id = pathSegments[pathSegments.length - 1];
if (!id) { if (!id) {
return Response.json({ return Response.json({
success: false, success: false,
@@ -20,9 +20,19 @@ export default async function visiMisiDesaFindById(request: Request) {
}, {status: 400}) }, {status: 400})
} }
const data = await prisma.visiMisiDesa.findUnique({ let data;
where: { id },
}) // Special handling for 'edit' - get the first/only record
if (id === 'edit') {
data = await prisma.visiMisiDesa.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
} else {
data = await prisma.visiMisiDesa.findUnique({
where: { id },
})
}
if (!data) { if (!data) {
return Response.json({ return Response.json({

View File

@@ -20,12 +20,25 @@ export default async function handler(request: Request) {
}, { status: 400 }); }, { status: 400 });
} }
const data = await prisma.profilePPID.findUnique({ let data;
where: { id },
include: { // Special handling for 'edit' - get the first/only record
image: true, if (id === 'edit') {
} data = await prisma.profilePPID.findFirst({
}); where: { isActive: true },
include: {
image: true,
},
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
} else {
data = await prisma.profilePPID.findUnique({
where: { id },
include: {
image: true,
}
});
}
if (!data) { if (!data) {
return Response.json({ return Response.json({

View File

@@ -1,7 +1,16 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Box, Text, Paper, Skeleton, Center, Title } from '@mantine/core'; import {
import React from 'react'; Stack,
Box,
Text,
Paper,
Skeleton,
Center,
Title,
Pagination
} from '@mantine/core';
import React, { useState } from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { BarChart } from '@mantine/charts'; import { BarChart } from '@mantine/charts';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
@@ -13,11 +22,15 @@ function Page() {
const { const {
data, data,
loading, loading
} = state.findMany } = state.findMany
const [activePage, setActivePage] = useState(1);
const itemsPerPage = 3;
useShallowEffect(() => { useShallowEffect(() => {
state.findMany.load() // Muat semua data tanpa batasan jumlah per halaman
state.findMany.load() // Ambil banyak data sekaligus
}, []) }, [])
if (loading || !data) { if (loading || !data) {
@@ -44,6 +57,11 @@ function Page() {
); );
} }
// Filter data untuk halaman saat ini
const startIndex = (activePage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentData = data.slice(startIndex, endIndex);
const chartData = data const chartData = data
.filter(item => item?.name && typeof item.value === 'number') .filter(item => item?.name && typeof item.value === 'number')
.map((item) => ({ .map((item) => ({
@@ -74,9 +92,9 @@ function Page() {
</Box> </Box>
<Box px={{ base: 'md', md: 100 }}> <Box px={{ base: 'md', md: 100 }}>
<Stack gap="lg" justify="center"> <Stack gap="lg" justify="center">
{data.map((v, k) => { {currentData.map((v, k) => {
return ( return (
<Paper p="xl" key={k}> <Paper p="xl" key={`${startIndex + k}`}>
<Title order={3} fw="bold"> <Title order={3} fw="bold">
{v.name} {v.name}
</Title> </Title>
@@ -90,6 +108,18 @@ function Page() {
</Paper> </Paper>
); );
})} })}
{/* Pagination */}
<Center mt="xl">
<Pagination
total={Math.ceil(data.length / itemsPerPage)}
value={activePage}
onChange={setActivePage}
size="lg"
radius="md"
/>
</Center>
<Box style={{ width: '100%', overflowX: 'auto' }}> <Box style={{ width: '100%', overflowX: 'auto' }}>
<Paper p="xl"> <Paper p="xl">
<Title order={3} fw="bold" pb="md"> <Title order={3} fw="bold" pb="md">

View File

@@ -162,6 +162,9 @@ function Page() {
p="lg" p="lg"
shadow="sm" shadow="sm"
style={{ style={{
height: '100%',
display: 'flex',
flexDirection: 'column',
'&:hover': { '&:hover': {
transform: 'translateY(-4px)', transform: 'translateY(-4px)',
boxShadow: '0 8px 20px rgba(0,0,0,0.1)', boxShadow: '0 8px 20px rgba(0,0,0,0.1)',
@@ -169,7 +172,7 @@ function Page() {
transition: 'transform 0.2s ease, box-shadow 0.2s ease', transition: 'transform 0.2s ease, box-shadow 0.2s ease',
}} }}
> >
<Stack gap="sm"> <Stack gap="sm" style={{ flex: 1 }}>
<Text <Text
c={colors['blue-button']} c={colors['blue-button']}
lineClamp={2} lineClamp={2}
@@ -190,7 +193,7 @@ function Page() {
: '-'} : '-'}
</Text> </Text>
<Box> <Box style={{ flex: 1 }}>
<Text fw="bold" fz="sm"> <Text fw="bold" fz="sm">
Penanganan: Penanganan:
</Text> </Text>
@@ -257,6 +260,7 @@ function Page() {
onClick={() => router.push(`/darmasaba/keamanan/laporan-publik/${v.id}`)} onClick={() => router.push(`/darmasaba/keamanan/laporan-publik/${v.id}`)}
size={mobile ? 'sm' : 'md'} size={mobile ? 'sm' : 'md'}
fullWidth fullWidth
style={{ marginTop: 'auto' }}
> >
{mobile ? 'Detail' : 'Lihat Detail Kronologi'} {mobile ? 'Detail' : 'Lihat Detail Kronologi'}
</Button> </Button>

View File

@@ -55,9 +55,9 @@ function Page() {
<SimpleGrid <SimpleGrid
px={{ base: 20, md: 100 }} px={{ base: 20, md: 100 }}
cols={{ base: 1, md: 2 }} cols={{ base: 1, md: 2 }}
spacing="xl" spacing={{ base: 'md', md: 'xl' }}
> >
<Paper p="xl" radius="xl" shadow="lg" > <Paper p="xl" radius="xl" shadow="lg" bg="white">
<Title order={2} c={colors['blue-button']} fw="bold" lh={1.2}> <Title order={2} c={colors['blue-button']} fw="bold" lh={1.2}>
Program Keamanan Berjalan Program Keamanan Berjalan
</Title> </Title>
@@ -85,9 +85,9 @@ function Page() {
<SimpleGrid <SimpleGrid
px={{ base: 20, md: 100 }} px={{ base: 20, md: 100 }}
cols={{ base: 1, md: 2 }} cols={{ base: 1, md: 2 }}
spacing="xl" spacing={{ base: 'md', md: 'xl' }}
> >
<Paper p="xl" radius="xl" shadow="lg" > <Paper p="xl" radius="xl" shadow="lg" bg="white">
<Title order={2} c={colors['blue-button']} fw="bold" lh={1.2}> <Title order={2} c={colors['blue-button']} fw="bold" lh={1.2}>
Program Keamanan Berjalan Program Keamanan Berjalan
</Title> </Title>
@@ -108,6 +108,7 @@ function Page() {
cursor: 'pointer', cursor: 'pointer',
backgroundColor: colors['blue-button'], backgroundColor: colors['blue-button'],
transition: 'all 0.2s ease', transition: 'all 0.2s ease',
marginBottom: '10px' // Add space between items
}} }}
onClick={() => onClick={() =>
router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`) router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`)
@@ -160,7 +161,7 @@ function Page() {
{findFirst.loading ? ( {findFirst.loading ? (
<Center><Skeleton h={400} /></Center> <Center><Skeleton h={400} /></Center>
) : findFirst.data ? ( ) : findFirst.data ? (
<Paper p="xl" radius="xl" shadow="lg"> <Paper p="xl" radius="xl" shadow="lg" bg="white">
{findFirst.data?.linkVideo ? ( {findFirst.data?.linkVideo ? (
<Box <Box
component="iframe" component="iframe"
@@ -168,7 +169,7 @@ function Page() {
width="100%" width="100%"
height={300} height={300}
allowFullScreen allowFullScreen
style={{ borderRadius: 8 }} style={{ borderRadius: 8, marginBottom: 15 }}
/> />
) : ( ) : (
<Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.4}> <Text fz={{ base: 'xs', md: 'sm' }} c="dimmed" lh={1.4}>

View File

@@ -1,109 +1,17 @@
import { Mutex } from 'async-mutex'; // Function to get the static authentication token from environment variables
// Store the token and its expiration time
let authToken: string | null = null;
let tokenExpirationTime: number | null = null;
// Mutex to prevent multiple simultaneous token refresh attempts
const mutex = new Mutex();
// Function to authenticate with Seafile and get a new token
async function authenticateWithSeafile(): Promise<{token: string, expirationTime: number}> {
// First, check if we have a static token as fallback
const staticToken = process.env.SEAFILE_TOKEN;
if (staticToken) {
console.log("Using static SEAFILE_TOKEN from environment variables");
// For static tokens, we'll set a conservative expiration (e.g., 6 days) to force periodic refresh attempts
const conservativeExpiration = Date.now() + (6 * 24 * 60 * 60 * 1000); // 6 days from now
return {
token: staticToken,
expirationTime: conservativeExpiration
};
}
// Otherwise, use username/password to get a new token
const username = process.env.SEAFILE_USERNAME;
const password = process.env.SEAFILE_PASSWORD;
const baseUrl = process.env.SEAFILE_URL;
if (!username || !password || !baseUrl) {
throw new Error('Missing required Seafile environment variables (either SEAFILE_TOKEN or SEAFILE_USERNAME/SEAFILE_PASSWORD/SEAFILE_URL)');
}
const response = await fetch(`${baseUrl}/api2/auth-token/`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
username,
password,
// Note: Seafile tokens typically last 7 days by default, but we'll refresh earlier
}).toString(),
});
if (!response.ok) {
throw new Error(`Authentication failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Calculate expiration time (set to refresh 1 hour before actual expiration)
// Seafile tokens typically last 7 days (604800 seconds), so we refresh after 6 days and 23 hours (601200 seconds)
const refreshTokenThreshold = 601200; // 6 days and 23 hours in seconds
const expirationTime = Date.now() + (refreshTokenThreshold * 1000);
return {
token: data.token,
expirationTime
};
}
// Function to get a valid authentication token
export async function getValidAuthToken(): Promise<string> { export async function getValidAuthToken(): Promise<string> {
// Check if we have a valid token that hasn't expired const staticToken = process.env.SEAFILE_TOKEN;
if (authToken && tokenExpirationTime && Date.now() < tokenExpirationTime) {
return authToken; if (!staticToken) {
throw new Error('SEAFILE_TOKEN environment variable is not set');
} }
// Acquire lock to prevent multiple simultaneous refresh attempts console.log("Using static SEAFILE_TOKEN from environment variables");
return mutex.runExclusive(async () => {
// Double-check after acquiring the lock
if (authToken && tokenExpirationTime && Date.now() < tokenExpirationTime) {
return authToken;
}
// Get a new token return staticToken;
const { token, expirationTime } = await authenticateWithSeafile();
// Update the stored token and expiration time
authToken = token;
tokenExpirationTime = expirationTime;
console.log('New Seafile token acquired and cached');
return token;
});
} }
// Function to force refresh the token (useful for manual refresh or testing) // Function to check if the token is set (always true since we're using static token)
export async function refreshAuthToken(): Promise<string> {
return mutex.runExclusive(async () => {
const { token, expirationTime } = await authenticateWithSeafile();
// Update the stored token and expiration time
authToken = token;
tokenExpirationTime = expirationTime;
console.log('Seafile token refreshed');
return token;
});
}
// Function to check if the token is still valid
export function isTokenValid(): boolean { export function isTokenValid(): boolean {
return authToken !== null && return !!process.env.SEAFILE_TOKEN;
tokenExpirationTime !== null &&
Date.now() < tokenExpirationTime;
} }

2
x.json
View File

@@ -66,4 +66,4 @@
"version": 1, "version": 1,
"salt": "" "salt": ""
} }
] ]

6
x.sh
View File

@@ -1,8 +1,8 @@
# ambil token # ambil token
# curl -X POST https://cld-dkr-makuro-seafile.wibudev.com/api2/auth-token/ \ curl -X POST https://cld-dkr-makuro-seafile.wibudev.com/api2/auth-token/ \
# -d "username=nico@bip.com" \ -d "username=nico@bip.com" \
# -d "password=Production_123" -d "password=Production_123"
# ambil list repo / library # ambil list repo / library