Fix QC Kak Inno & Kak Ayu Tanggal 15 Oct
This commit is contained in:
@@ -28,6 +28,32 @@ function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
// Add this check before the return statement
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
|
||||
Sektor Unggulan Desa Darmasaba
|
||||
</Text>
|
||||
<Text c="dimmed" mt="md">
|
||||
Data sektor unggulan belum tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const chartData = data
|
||||
.filter(item => item?.name && typeof item.value === 'number')
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
sektor: item.name,
|
||||
Ton: item.value,
|
||||
}));
|
||||
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
@@ -54,11 +80,7 @@ function Page() {
|
||||
<BarChart
|
||||
p={10}
|
||||
h={300}
|
||||
data={data.map((item) => ({
|
||||
id: item.id,
|
||||
sektor: item.name,
|
||||
Ton: item.value,
|
||||
}))}
|
||||
data={chartData}
|
||||
dataKey="sektor"
|
||||
series={[
|
||||
{ name: 'Ton', color: colors['blue-button'] },
|
||||
|
||||
@@ -41,6 +41,37 @@ function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Text fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
|
||||
Pencegahan Kriminalitas
|
||||
</Text>
|
||||
<Text c={colors['blue-button']} fz={{ base: 'h4', md: 'h3' }}>
|
||||
Keamanan Komunitas & Pencegahan Kriminal
|
||||
</Text>
|
||||
</Box>
|
||||
<SimpleGrid
|
||||
px={{ base: 20, md: 100 }}
|
||||
cols={{ base: 1, md: 2 }}
|
||||
spacing="xl"
|
||||
>
|
||||
<Paper p="xl" radius="xl" shadow="lg" >
|
||||
<Text fz={{ base: 'h3', md: 'h2' }} c={colors['blue-button']} fw="bold">
|
||||
Program Keamanan Berjalan
|
||||
</Text>
|
||||
<Stack pt={30} gap="lg">
|
||||
<Text c="dimmed">
|
||||
Tidak ada data pencegahan kriminalitas yang cocok
|
||||
</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
|
||||
@@ -141,7 +141,7 @@ function Page() {
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>Angka Kematian</Text>
|
||||
<ColorSwatch color="#EF3E3E" size={30} />
|
||||
<ColorSwatch color="#EF3E3E" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
@@ -101,27 +101,42 @@ function Page() {
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap="sm">
|
||||
<Image
|
||||
src={v.image.link}
|
||||
alt={v.name}
|
||||
w={140}
|
||||
h={140}
|
||||
fit="contain"
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
width: '100%',
|
||||
aspectRatio: '16/9',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={v.image.link}
|
||||
alt={v.name}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transition: 'transform 0.4s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}>
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed" ta="center" lineClamp={3}>
|
||||
<span style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Text>
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<IconBrandWhatsapp size={18} />}
|
||||
component="a"
|
||||
href={`https://wa.me/${v.whatsapp.replace(/\D/g, '')}`}
|
||||
target="_blank"
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<IconBrandWhatsapp size={18} />}
|
||||
component="a"
|
||||
href={`https://wa.me/${v.whatsapp.replace(/\D/g, '')}`}
|
||||
target="_blank"
|
||||
aria-label="Hubungi WhatsApp"
|
||||
>WhatsApp</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat'
|
||||
import colors from '@/con/colors'
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Center,
|
||||
Grid,
|
||||
@@ -106,15 +105,30 @@ function Page() {
|
||||
>
|
||||
<Stack align="center" gap="md">
|
||||
<Center>
|
||||
<Image
|
||||
src={v.image.link}
|
||||
alt={v.name}
|
||||
h={180}
|
||||
w="100%"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
style={{ aspectRatio: '4/3' }}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
width: '100%',
|
||||
aspectRatio: '16/9',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={v.image.link}
|
||||
alt={v.name}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transition: 'transform 0.4s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
</Center>
|
||||
<Stack gap={4} w="100%">
|
||||
<Text
|
||||
@@ -136,9 +150,6 @@ function Page() {
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Badge radius="md" color="blue" variant="light" mt="sm">
|
||||
Darurat
|
||||
</Badge>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
@@ -160,7 +171,7 @@ function Page() {
|
||||
'&:hover': { backgroundColor: colors['blue-button'], color: 'white' },
|
||||
},
|
||||
}}
|
||||
|
||||
|
||||
/>
|
||||
</Center>
|
||||
|
||||
|
||||
@@ -7,16 +7,18 @@ import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/ico
|
||||
import { useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import BackButton from "../../desa/layanan/_com/BackButto";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
|
||||
export default function Page() {
|
||||
const state = useProxy(posyandustate);
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||
|
||||
const { data, page, totalPages, loading, load } = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 6, search);
|
||||
}, [page, search]);
|
||||
load(page, 6, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
|
||||
@@ -78,9 +78,7 @@ function Page() {
|
||||
<Tooltip label={materi.data?.judul} position="top" withArrow>
|
||||
<Stack gap={4} align="center">
|
||||
<IconRecycle size={28} color={colors['blue-button']} />
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{materi.data?.judul}
|
||||
</Text>
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center" dangerouslySetInnerHTML={{ __html: materi.data?.judul || '' }} />
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID'
|
||||
import colors from '@/con/colors'
|
||||
import {
|
||||
@@ -15,17 +16,27 @@ import {
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
Transition,
|
||||
} from '@mantine/core'
|
||||
import { IconRefresh, IconSearch, IconUsers } from '@tabler/icons-react'
|
||||
import {
|
||||
IconRefresh,
|
||||
IconSearch,
|
||||
IconUsers,
|
||||
IconZoomIn,
|
||||
IconZoomOut,
|
||||
IconArrowsMaximize,
|
||||
IconArrowsMinimize,
|
||||
} from '@tabler/icons-react'
|
||||
import { OrganizationChart } from 'primereact/organizationchart'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import BackButton from '../../desa/layanan/_com/BackButto'
|
||||
import { useTransitionRouter } from 'next-view-transitions'
|
||||
import ScrollToTopButton from '@/app/darmasaba/_com/scrollToTopButton'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -47,7 +58,6 @@ export default function Page() {
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 28, md: 36, lg: 44 }}
|
||||
|
||||
>
|
||||
Struktur Organisasi PPID
|
||||
</Title>
|
||||
@@ -61,8 +71,8 @@ export default function Page() {
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
{/* Tombol Scroll ke Atas */}
|
||||
<ScrollToTopButton />
|
||||
{/* Tombol Scroll ke Atas */}
|
||||
<ScrollToTopButton />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -70,14 +80,24 @@ export default function Page() {
|
||||
function StrukturOrganisasiPPID() {
|
||||
const stateOrganisasi: any = useProxy(stateStrukturPPID.pegawai)
|
||||
const router = useTransitionRouter()
|
||||
const chartContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [scale, setScale] = useState(1)
|
||||
const [isFullscreen, setFullscreen] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
// debounce untuk pencarian
|
||||
const debouncedSearch = useRef(
|
||||
debounce((value: string) => {
|
||||
setSearchQuery(value)
|
||||
}, 400)
|
||||
).current
|
||||
|
||||
useEffect(() => {
|
||||
void stateOrganisasi.findMany.load()
|
||||
}, [])
|
||||
|
||||
const isLoading =
|
||||
!stateOrganisasi.findMany.data &&
|
||||
stateOrganisasi.findMany.loading !== false
|
||||
!stateOrganisasi.findMany.data && stateOrganisasi.findMany.loading !== false
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -93,10 +113,7 @@ function StrukturOrganisasiPPID() {
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
!stateOrganisasi.findMany.data ||
|
||||
stateOrganisasi.findMany.data.length === 0
|
||||
) {
|
||||
if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) {
|
||||
return (
|
||||
<Center py={40}>
|
||||
<Stack align="center" gap="md">
|
||||
@@ -117,8 +134,7 @@ function StrukturOrganisasiPPID() {
|
||||
Data pegawai belum tersedia
|
||||
</Title>
|
||||
<Text c="dimmed" mt="xs">
|
||||
Belum ada data pegawai yang tercatat untuk PPID. Silakan coba
|
||||
muat ulang atau periksa sumber data.
|
||||
Belum ada data pegawai yang tercatat untuk PPID.
|
||||
</Text>
|
||||
<Group justify="center" mt="lg">
|
||||
<Button
|
||||
@@ -129,15 +145,6 @@ function StrukturOrganisasiPPID() {
|
||||
>
|
||||
Muat Ulang
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<IconSearch size={16} />}
|
||||
variant="subtle"
|
||||
onClick={() =>
|
||||
stateOrganisasi.findMany.load({ query: { q: '' } })
|
||||
}
|
||||
>
|
||||
Cari Pegawai
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
</Stack>
|
||||
@@ -145,44 +152,39 @@ function StrukturOrganisasiPPID() {
|
||||
)
|
||||
}
|
||||
|
||||
// Buat struktur organisasi
|
||||
const posisiMap = new Map<string, any>()
|
||||
|
||||
const aktifPegawai = stateOrganisasi.findMany.data.filter((p: any) => p.isActive);
|
||||
const aktifPegawai = stateOrganisasi.findMany.data.filter((p: any) => p.isActive)
|
||||
|
||||
for (const pegawai of aktifPegawai) {
|
||||
const posisiId = pegawai.posisi.id;
|
||||
const posisiId = pegawai.posisi.id
|
||||
if (!posisiMap.has(posisiId)) {
|
||||
posisiMap.set(posisiId, {
|
||||
...pegawai.posisi,
|
||||
pegawaiList: [],
|
||||
children: [],
|
||||
});
|
||||
})
|
||||
}
|
||||
posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
|
||||
posisiMap.get(posisiId)!.pegawaiList.push(pegawai)
|
||||
}
|
||||
|
||||
const root: any[] = []
|
||||
posisiMap.forEach((posisi) => {
|
||||
if (posisi.parentId) {
|
||||
const parent = posisiMap.get(posisi.parentId)
|
||||
if (parent) {
|
||||
parent.children.push(posisi)
|
||||
} else {
|
||||
root.push(posisi)
|
||||
}
|
||||
} else {
|
||||
root.push(posisi)
|
||||
}
|
||||
if (parent) parent.children.push(posisi)
|
||||
else root.push(posisi)
|
||||
} else root.push(posisi)
|
||||
})
|
||||
|
||||
function toOrgChartFormat(node: any): any {
|
||||
const pegawai = node.pegawaiList?.[0];
|
||||
const pegawai = node.pegawaiList?.[0]
|
||||
return {
|
||||
expanded: true,
|
||||
type: 'person',
|
||||
styleClass: 'p-person',
|
||||
data: {
|
||||
id: pegawai?.id || null, // tambahin ini bro
|
||||
id: pegawai?.id || null,
|
||||
name: pegawai?.namaLengkap || 'Belum ditugaskan',
|
||||
title: node.nama || 'Tanpa jabatan',
|
||||
image: pegawai?.image?.link || '/img/default.png',
|
||||
@@ -190,28 +192,90 @@ function StrukturOrganisasiPPID() {
|
||||
positionId: node.id || null,
|
||||
},
|
||||
children: node.children?.map(toOrgChartFormat) || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const chartData = root.map(toOrgChartFormat)
|
||||
let chartData = root.map(toOrgChartFormat)
|
||||
|
||||
// 🔍 filter by search
|
||||
if (searchQuery) {
|
||||
const filterNodes = (nodes: any[]): any[] =>
|
||||
nodes
|
||||
.map((n) => ({
|
||||
...n,
|
||||
children: filterNodes(n.children || []),
|
||||
}))
|
||||
.filter(
|
||||
(n) =>
|
||||
n.data.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
n.data.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
n.children.length > 0
|
||||
)
|
||||
chartData = filterNodes(chartData)
|
||||
}
|
||||
|
||||
// 🧭 fungsi fullscreen
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
chartContainerRef.current?.requestFullscreen()
|
||||
setFullscreen(true)
|
||||
} else {
|
||||
document.exitFullscreen()
|
||||
setFullscreen(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 🧭 fungsi zoom
|
||||
const handleZoomIn = () => setScale((prev) => Math.min(prev + 0.1, 2))
|
||||
const handleZoomOut = () => setScale((prev) => Math.max(prev - 0.1, 0.5))
|
||||
const resetZoom = () => setScale(1)
|
||||
|
||||
return (
|
||||
<Box py={16} >
|
||||
<Paper
|
||||
radius="md"
|
||||
p="md"
|
||||
<Stack align="center" mt="xl">
|
||||
{/* 🔍 Search + Zoom + Fullscreen controls */}
|
||||
<Group mb="md" justify="center" gap="sm">
|
||||
<TextInput
|
||||
placeholder="Cari nama atau jabatan..."
|
||||
leftSection={<IconSearch size={16} />}
|
||||
onChange={(e) => debouncedSearch(e.target.value)}
|
||||
/>
|
||||
<Button variant="light" size="sm" onClick={handleZoomOut}>
|
||||
<IconZoomOut size={16} />
|
||||
</Button>
|
||||
<Button variant="light" size="sm" onClick={resetZoom}>
|
||||
100%
|
||||
</Button>
|
||||
<Button variant="light" size="sm" onClick={handleZoomIn}>
|
||||
<IconZoomIn size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
size="sm"
|
||||
onClick={toggleFullscreen}
|
||||
leftSection={
|
||||
isFullscreen ? <IconArrowsMinimize size={16} /> : <IconArrowsMaximize size={16} />
|
||||
}
|
||||
>
|
||||
{isFullscreen ? 'Keluar' : 'Fullscreen'}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{/* Chart Container */}
|
||||
<Box
|
||||
ref={chartContainerRef}
|
||||
style={{
|
||||
background: 'rgba(28,110,164,0.2)',
|
||||
border: `1px solid rgba(255,255,255,0.1)`,
|
||||
overflowX: 'auto',
|
||||
overflow: 'auto',
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'center top',
|
||||
transition: 'transform 0.25s ease',
|
||||
}}
|
||||
>
|
||||
<OrganizationChart
|
||||
value={chartData}
|
||||
nodeTemplate={(node) => nodeTemplate(node, router)}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -221,7 +285,6 @@ function nodeTemplate(node: any, router: ReturnType<typeof useTransitionRouter>)
|
||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||
const description = node?.data?.description || ''
|
||||
|
||||
|
||||
return (
|
||||
<Transition mounted transition="pop" duration={240}>
|
||||
{(styles) => (
|
||||
@@ -244,15 +307,15 @@ function nodeTemplate(node: any, router: ReturnType<typeof useTransitionRouter>)
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
radius="md"
|
||||
width={120}
|
||||
height={120}
|
||||
width={60}
|
||||
height={60}
|
||||
fit="cover"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
border: '2px solid rgba(255,255,255,0.2)',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
loading='lazy'
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text fw={700}>{name}</Text>
|
||||
<Text size="sm" c="dimmed" mt={4}>
|
||||
|
||||
Reference in New Issue
Block a user