Fix semua tulisan profile jadi profil, mulai dari navbar, dan route
This commit is contained in:
15
src/app/darmasaba/(pages)/desa/profil/_com/BackButto.tsx
Normal file
15
src/app/darmasaba/(pages)/desa/profil/_com/BackButto.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import colors from "@/con/colors";
|
||||
import { ActionIcon } from "@mantine/core";
|
||||
import { IconArrowLeft } from "@tabler/icons-react";
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
|
||||
export default function BackButton() {
|
||||
const router = useTransitionRouter()
|
||||
return (
|
||||
<ActionIcon bg={colors["blue-button"]} onClick={() => router.back()}>
|
||||
<IconArrowLeft />
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
48
src/app/darmasaba/(pages)/desa/profil/page.tsx
Normal file
48
src/app/darmasaba/(pages)/desa/profil/page.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Stack, Text } from '@mantine/core';
|
||||
import BackButton from './_com/BackButto';
|
||||
import ProfileDesa from './ui/profileDesa';
|
||||
import SejarahDesa from './ui/sejarahDesa';
|
||||
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 MotoDesa from './ui/motoDesa';
|
||||
import SemuaPerbekel from './ui/semuaPerbekel';
|
||||
import ScrollToTopButton from '@/app/darmasaba/_com/scrollToTopButton';
|
||||
import StrukturPerangkatDesa from './struktur-perangkat-desa/page';
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container w={{ base: "100%", md: "50%" }}>
|
||||
<Stack align='center' gap={0}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Profile Desa
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
<Stack px={{ base: "md", md: 100 }} gap={"xl"}>
|
||||
<ProfileDesa />
|
||||
<SejarahDesa />
|
||||
<VisimisiDesa />
|
||||
<LambangDesa />
|
||||
<MaskotDesa />
|
||||
<ProfilPerbekel />
|
||||
<StrukturPerangkatDesa/>
|
||||
<MotoDesa />
|
||||
<SemuaPerbekel />
|
||||
</Stack>
|
||||
</Stack>
|
||||
{/* Tombol Scroll ke Atas */}
|
||||
<ScrollToTopButton />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,143 @@
|
||||
'use client';
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Group,
|
||||
Image,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../_com/BackButto';
|
||||
|
||||
function DetailPegawaiUser() {
|
||||
const statePegawai = useProxy(stateStrukturPPID.pegawai);
|
||||
const params = useParams();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateStrukturPPID.posisiOrganisasi.findMany.load();
|
||||
statePegawai.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
|
||||
if (!statePegawai.findUnique.data) {
|
||||
return (
|
||||
<Stack py="lg">
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = statePegawai.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'md', md: 100 }} py="xl">
|
||||
{/* Back button */}
|
||||
<Group mb="lg" px={{ base: 'md', md: 100 }}>
|
||||
<BackButton/>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
mx="auto"
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
bg="white"
|
||||
style={{
|
||||
border: '1px solid #eaeaea',
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap="md">
|
||||
{/* Foto Profil */}
|
||||
<Image
|
||||
src={data.image?.link || '/placeholder-profile.png'}
|
||||
alt={data.namaLengkap || 'Foto Profil'}
|
||||
w={160}
|
||||
h={160}
|
||||
radius={100}
|
||||
fit="cover"
|
||||
style={{ border: `2px solid ${colors['blue-button']}` }}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{/* Nama & Jabatan */}
|
||||
<Stack align="center" gap={2}>
|
||||
<Title order={3} fw={700} c={colors['blue-button']}>
|
||||
{data.namaLengkap || '-'} {data.gelarAkademik || ''}
|
||||
</Title>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{data.posisi?.nama || 'Posisi tidak tersedia'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Divider my="lg" />
|
||||
|
||||
{/* Informasi Detail */}
|
||||
<Stack gap="md">
|
||||
<InfoRow label="Email" value={data.email} />
|
||||
<InfoRow label="Telepon" value={data.telepon} />
|
||||
<InfoRow label="Alamat" value={data.alamat} multiline />
|
||||
<InfoRow
|
||||
label="Tanggal Masuk"
|
||||
value={
|
||||
data.tanggalMasuk
|
||||
? new Date(data.tanggalMasuk).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '-'
|
||||
}
|
||||
/>
|
||||
<InfoRow
|
||||
label="Status"
|
||||
value={data.isActive ? 'Aktif' : 'Tidak Aktif'}
|
||||
valueColor={data.isActive ? 'green' : 'red'}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/* Komponen kecil untuk menampilkan baris informasi */
|
||||
function InfoRow({
|
||||
label,
|
||||
value,
|
||||
valueColor,
|
||||
multiline = false,
|
||||
}: {
|
||||
label: string;
|
||||
value?: string | null;
|
||||
valueColor?: string;
|
||||
multiline?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Box>
|
||||
<Text fz="sm" fw={600} c="dark">
|
||||
{label}
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
c={valueColor || 'dimmed'}
|
||||
style={{
|
||||
whiteSpace: multiline ? 'normal' : 'nowrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{value || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailPegawaiUser;
|
||||
@@ -0,0 +1,514 @@
|
||||
/* 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 ScrollToTopButton from '@/app/darmasaba/_com/scrollToTopButton'
|
||||
import colors from '@/con/colors'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTab,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Transition
|
||||
} from '@mantine/core'
|
||||
import {
|
||||
IconArrowsMaximize,
|
||||
IconArrowsMinimize,
|
||||
IconRefresh,
|
||||
IconSearch,
|
||||
IconUsers,
|
||||
IconZoomIn,
|
||||
IconZoomOut,
|
||||
} from '@tabler/icons-react'
|
||||
import { debounce } from 'lodash'
|
||||
import { useTransitionRouter } from 'next-view-transitions'
|
||||
import { OrganizationChart } from 'primereact/organizationchart'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
import './struktur.css'
|
||||
import BackButton from '../_com/BackButto'
|
||||
import { useMediaQuery } from '@mantine/hooks'
|
||||
|
||||
export default function StrukturPerangkatDesa() {
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
background: colors['Bg'],
|
||||
color: '#E6F0FF',
|
||||
paddingBottom: 48,
|
||||
}}
|
||||
>
|
||||
<Box px={{ base: 'md', md: 100 }} py={"xl"}>
|
||||
<BackButton />
|
||||
|
||||
<Stack align="center" gap="xl" mt="xl">
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
fz={{ base: 28, md: 36, lg: 44 }}
|
||||
>
|
||||
Struktur Perangkat Desa
|
||||
</Title>
|
||||
<Text ta="center" c="black" maw={800}>
|
||||
Gambaran visual peran dan pegawai yang ditugaskan. Arahkan kursor
|
||||
untuk melihat detail atau klik node untuk fokus tampilan.
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Box mt="lg">
|
||||
<StrukturPerangkatDesaNode />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ScrollToTopButton />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function StrukturPerangkatDesaNode() {
|
||||
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 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
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Center py={48}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader size="lg" />
|
||||
<Text fw={600}>Memuat struktur organisasi…</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
Mengambil data pegawai dan posisi. Mohon tunggu sebentar.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
const data = stateOrganisasi.findMany.data || []
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Center py={40}>
|
||||
<Stack align="center" gap="md">
|
||||
<Paper
|
||||
radius="md"
|
||||
p="xl"
|
||||
style={{
|
||||
width: 560,
|
||||
background: 'rgba(28,110,164,0.2)',
|
||||
border: `1px solid rgba(255,255,255,0.1)`,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Center>
|
||||
<IconUsers size={56} />
|
||||
</Center>
|
||||
<Title order={3} mt="md">
|
||||
Data pegawai belum tersedia
|
||||
</Title>
|
||||
<Text c="dimmed" mt="xs">
|
||||
Belum ada data pegawai yang tercatat untuk PPID.
|
||||
</Text>
|
||||
<Group justify="center" mt="lg">
|
||||
<Button
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
variant="gradient"
|
||||
gradient={{ from: 'indigo', to: 'cyan' }}
|
||||
onClick={() => stateOrganisasi.findMany.load()}
|
||||
>
|
||||
Muat Ulang
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
// 🧩 buat struktur organisasi
|
||||
const posisiMap = new Map<string, any>()
|
||||
const aktifPegawai = data.filter((p: any) => p.isActive)
|
||||
|
||||
for (const pegawai of aktifPegawai) {
|
||||
const posisiId = pegawai.posisi.id
|
||||
if (!posisiMap.has(posisiId)) {
|
||||
posisiMap.set(posisiId, {
|
||||
...pegawai.posisi,
|
||||
pegawaiList: [],
|
||||
children: [],
|
||||
})
|
||||
}
|
||||
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)
|
||||
})
|
||||
|
||||
const toOrgChartFormat = (node: any): any => {
|
||||
const pegawai = node.pegawaiList?.[0]
|
||||
return {
|
||||
expanded: true,
|
||||
data: {
|
||||
id: pegawai?.id,
|
||||
name: pegawai?.namaLengkap || 'Belum Ditugaskan',
|
||||
title: node.nama || 'Tanpa Jabatan',
|
||||
image: pegawai?.image?.link || '/img/default.png',
|
||||
},
|
||||
children: node.children?.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)
|
||||
}
|
||||
|
||||
// 🎬 fullscreen & zoom control
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
chartContainerRef.current?.requestFullscreen()
|
||||
setFullscreen(true)
|
||||
} else {
|
||||
document.exitFullscreen()
|
||||
setFullscreen(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleZoomIn = () => setScale((s) => Math.min(s + 0.1, 2))
|
||||
const handleZoomOut = () => setScale((s) => Math.max(s - 0.1, 0.5))
|
||||
const resetZoom = () => setScale(1)
|
||||
|
||||
return (
|
||||
<Stack align="center" mt="xl">
|
||||
{/* 🔍 Controls */}
|
||||
<Paper
|
||||
shadow="xs"
|
||||
p="md"
|
||||
radius="md"
|
||||
style={{
|
||||
background: colors['blue-button'],
|
||||
width: '100%', // ⬅️ penting
|
||||
maxWidth: '100%', // ⬅️ penting
|
||||
overflowX: 'auto' // ⬅️ untuk mencegah overflow
|
||||
}}
|
||||
>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
placeholder="Cari nama atau jabatan..."
|
||||
leftSection={<IconSearch size={16} />}
|
||||
onChange={(e) => debouncedSearch(e.target.value)}
|
||||
styles={{
|
||||
input: {
|
||||
minWidth: 250,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Tabs
|
||||
defaultValue="zoom-out"
|
||||
variant="outline"
|
||||
radius="md"
|
||||
styles={{
|
||||
panel: { display: 'none' },
|
||||
tab: {
|
||||
color: colors['blue-button'],
|
||||
backgroundColor: colors['blue-button-2'],
|
||||
border: 'none',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.875rem',
|
||||
padding: '6px 12px',
|
||||
minHeight: 'auto',
|
||||
flexShrink: 0, // 👈 PENTING: mencegah tab mengecil
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TabsList
|
||||
style={{
|
||||
display: 'flex',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden', // 👈 tambahkan ini
|
||||
gap: '4px',
|
||||
paddingBottom: '4px',
|
||||
flexWrap: 'nowrap',
|
||||
WebkitOverflowScrolling: 'touch', // 👈 smooth scroll di iOS
|
||||
scrollbarWidth: 'thin', // 👈 scrollbar tipis di Firefox
|
||||
msOverflowStyle: '-ms-autohiding-scrollbar', // 👈 untuk IE/Edge
|
||||
}}
|
||||
>
|
||||
<TabsTab
|
||||
value="zoom-out"
|
||||
onClick={handleZoomOut}
|
||||
leftSection={<IconZoomOut size={16} />}
|
||||
style={{ flexShrink: 0 }} // 👈 pastikan tidak mengecil
|
||||
>
|
||||
Zoom Out
|
||||
</TabsTab>
|
||||
|
||||
<Box
|
||||
bg={colors['blue-button-2']}
|
||||
c={colors['blue-button']}
|
||||
px={12}
|
||||
py={6}
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
borderRadius: '6px',
|
||||
minWidth: 60,
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
whiteSpace: 'nowrap', // 👈 mencegah text wrap
|
||||
}}
|
||||
>
|
||||
{Math.round(scale * 100)}%
|
||||
</Box>
|
||||
|
||||
<TabsTab
|
||||
value="zoom-in"
|
||||
onClick={handleZoomIn}
|
||||
leftSection={<IconZoomIn size={16} />}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Zoom In
|
||||
</TabsTab>
|
||||
|
||||
<TabsTab
|
||||
value="reset"
|
||||
onClick={resetZoom}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Reset
|
||||
</TabsTab>
|
||||
|
||||
<TabsTab
|
||||
value="fullscreen"
|
||||
onClick={toggleFullscreen}
|
||||
leftSection={
|
||||
isFullscreen ? (
|
||||
<IconArrowsMinimize size={16} />
|
||||
) : (
|
||||
<IconArrowsMaximize size={16} />
|
||||
)
|
||||
}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{isFullscreen ? 'Exit' : 'Fullscreen'}
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* 🧩 Chart Container */}
|
||||
<Center style={{ width: '100%' }}>
|
||||
<Box
|
||||
ref={chartContainerRef}
|
||||
style={{
|
||||
overflowX: 'auto',
|
||||
overflowY: 'auto',
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
padding: '32px 16px',
|
||||
transition: 'transform 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<Box style={{
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'center top',
|
||||
display: 'inline-block', // 👈 agar tidak memenuhi lebar parent
|
||||
minWidth: 'min-content', // 👈 penting agar chart tidak dipaksa muat di width 100%
|
||||
}}>
|
||||
<OrganizationChart
|
||||
value={chartData}
|
||||
nodeTemplate={(node) => <NodeCard node={node} router={router} />}
|
||||
className="p-organizationchart p-organizationchart-horizontal"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Center>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function NodeCard({ node, router }: any) {
|
||||
const imageSrc = node?.data?.image || '/img/default.png'
|
||||
const name = node?.data?.name || 'Tanpa Nama'
|
||||
const title = node?.data?.title || 'Tanpa Jabatan'
|
||||
const hasId = Boolean(node?.data?.id)
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
return (
|
||||
<Transition mounted transition="pop" duration={300}>
|
||||
{(styles) => (
|
||||
<Card
|
||||
shadow="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
style={{
|
||||
...styles,
|
||||
width: '100%',
|
||||
maxWidth: isMobile ? 200 : 240, // lebih kecil di mobile
|
||||
minHeight: isMobile ? 240 : 280,
|
||||
padding: isMobile ? 16 : 20,
|
||||
background: 'linear-gradient(135deg, rgba(28,110,164,0.15) 0%, rgba(255,255,255,0.95) 100%)',
|
||||
borderColor: 'rgba(28, 110, 164, 0.3)',
|
||||
borderWidth: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: hasId ? 'pointer' : 'default',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(-4px)'
|
||||
e.currentTarget.style.boxShadow = '0 8px 24px rgba(28, 110, 164, 0.25)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (hasId) {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap={12}>
|
||||
{/* Photo */}
|
||||
<Box
|
||||
style={{
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
border: '3px solid rgba(28, 110, 164, 0.4)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
background: 'white',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
width={96}
|
||||
height={96}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Name */}
|
||||
<Text
|
||||
fw={700}
|
||||
size="sm"
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
lineClamp={2}
|
||||
style={{
|
||||
minHeight: 40,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.3,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
|
||||
{/* Title/Position */}
|
||||
<Text
|
||||
size="xs"
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
fw={500}
|
||||
lineClamp={2}
|
||||
style={{
|
||||
minHeight: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{/* Detail Button */}
|
||||
{hasId && (
|
||||
<Button
|
||||
variant="gradient"
|
||||
gradient={{ from: 'blue', to: 'cyan' }}
|
||||
size="xs"
|
||||
fullWidth
|
||||
mt={8}
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/desa/profile/struktur-perangkat-desa/${node.data.id}`)
|
||||
}
|
||||
style={{
|
||||
height: 32,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* ============================================
|
||||
STRUKTUR ORGANISASI PPID - STYLING
|
||||
============================================ */
|
||||
|
||||
/* Tabel chart selalu center */
|
||||
.p-organizationchart-table {
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
/* Jarak vertikal antar level - lebih lega */
|
||||
.p-organizationchart-line-down {
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
/* Padding di dalam node - lebih rapi */
|
||||
.p-organizationchart-node-content {
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Garis connector antar node - lebih tebal dan jelas */
|
||||
.p-organizationchart-line-down,
|
||||
.p-organizationchart-line-left,
|
||||
.p-organizationchart-line-right,
|
||||
.p-organizationchart-line-top {
|
||||
border-color: rgba(28, 110, 164, 0.4) !important;
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
/* Garis horizontal */
|
||||
.p-organizationchart-line-left,
|
||||
.p-organizationchart-line-right {
|
||||
border-top-width: 2px !important;
|
||||
}
|
||||
|
||||
/* Jarak horizontal antar node - lebih proporsional */
|
||||
.p-organizationchart-table > tbody > tr > td {
|
||||
padding: 0 24px !important;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
/* Node container spacing */
|
||||
.p-organizationchart-node {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.p-organizationchart-table > tbody > tr > td {
|
||||
padding: 0 12px !important;
|
||||
}
|
||||
|
||||
.p-organizationchart-line-down {
|
||||
height: 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth transitions untuk zoom */
|
||||
.p-organizationchart {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* Fullscreen mode adjustments */
|
||||
.p-organizationchart-table:fullscreen {
|
||||
background: rgba(230, 240, 255, 0.98);
|
||||
padding: 40px;
|
||||
}
|
||||
75
src/app/darmasaba/(pages)/desa/profil/ui/lambangDesa.tsx
Normal file
75
src/app/darmasaba/(pages)/desa/profil/ui/lambangDesa.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
|
||||
import colors from '@/con/colors'
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'
|
||||
import { useEffect } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
|
||||
function LambangDesa() {
|
||||
const state = useProxy(stateProfileDesa.lambangDesa)
|
||||
|
||||
useEffect(() => {
|
||||
state.findUnique.load("edit")
|
||||
}, [])
|
||||
|
||||
const { data, loading } = state.findUnique
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py="lg">
|
||||
<Skeleton h={420} radius="xl" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="lg">
|
||||
<Box pb="lg">
|
||||
<Center>
|
||||
<Image
|
||||
src={"/darmasaba-icon.png"}
|
||||
alt="Lambang resmi desa"
|
||||
w={{ base: 180, md: 280 }}
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Center>
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={800}
|
||||
fz={{ base: 28, md: 40 }}
|
||||
mt="sm"
|
||||
style={{ letterSpacing: '-0.5px' }}
|
||||
>
|
||||
Lambang Desa
|
||||
</Text>
|
||||
</Box>
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="xl"
|
||||
withBorder
|
||||
shadow="sm"
|
||||
w="100%"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #ffffff 0%, #f3f7ff 100%)',
|
||||
borderColor: '#e0e9ff',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
||||
lh={1.8}
|
||||
c="dark"
|
||||
ta="justify"
|
||||
style={{ fontWeight: 400, wordBreak: "break-word", whiteSpace: "normal", }}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default LambangDesa
|
||||
96
src/app/darmasaba/(pages)/desa/profil/ui/maskotDesa.tsx
Normal file
96
src/app/darmasaba/(pages)/desa/profil/ui/maskotDesa.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
|
||||
import { IconPhoto } from '@tabler/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function MaskotDesa() {
|
||||
const state = useProxy(stateProfileDesa.maskotDesa);
|
||||
|
||||
useEffect(() => {
|
||||
state.findUnique.load('edit');
|
||||
}, []);
|
||||
|
||||
const { data, loading } = state.findUnique;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Center mih={500}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Loader size="lg" color="blue" />
|
||||
<Text c="dimmed" fz="sm">Sedang memuat data maskot desa...</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="xl">
|
||||
<Stack align="center" gap={10}>
|
||||
<Image src="/pudak-icon.png" alt="Ikon Desa" w={{ base: 160, md: 240 }} loading="lazy"/>
|
||||
<Text c={colors['blue-button']} ta="center" fw={700} fz={{ base: 28, md: 36 }}>Maskot Desa</Text>
|
||||
</Stack>
|
||||
|
||||
<Paper
|
||||
p={{ base: 'md', md: 'xl' }}
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
withBorder
|
||||
style={{ background: 'linear-gradient(145deg, #ffffff, #f8f9fa)' }}
|
||||
>
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'lg' }}
|
||||
lh={1.7}
|
||||
ta="justify"
|
||||
c="dark"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
|
||||
<Group justify="center" gap="lg" mt="lg">
|
||||
{data.images.length > 0 ? (
|
||||
data.images.map((img, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w={220}
|
||||
p="sm"
|
||||
style={{
|
||||
transition: 'transform 200ms ease, box-shadow 200ms ease',
|
||||
}}
|
||||
className="hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
alt={img.label}
|
||||
w="100%"
|
||||
h={200}
|
||||
fit="cover"
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text ta="center" mt="sm" fw={600} fz="sm" c="dark">
|
||||
{img.label}
|
||||
</Text>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<Stack align="center" gap="xs" mt="lg">
|
||||
<IconPhoto size={48} stroke={1.5} color="gray" />
|
||||
<Text c="dimmed" fz="sm">Belum ada gambar maskot yang ditambahkan</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Group>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MaskotDesa;
|
||||
136
src/app/darmasaba/(pages)/desa/profil/ui/motoDesa.tsx
Normal file
136
src/app/darmasaba/(pages)/desa/profil/ui/motoDesa.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
'use client'
|
||||
import { ActionIcon, Box, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { motion } from 'framer-motion';
|
||||
import { IconSparkles } from '@tabler/icons-react';
|
||||
import colors from '@/con/colors';
|
||||
|
||||
const dataText = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Santun",
|
||||
description: "Pelayanan ramah, penuh empati, sopan, dan beretika."
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Adaptif",
|
||||
description: "Cepat menyesuaikan diri terhadap perubahan dan selalu proaktif."
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Inovatif",
|
||||
description: "Berani menciptakan pembaruan dan ide-ide kreatif."
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Profesional",
|
||||
description: "Berpengetahuan luas, terampil, dan bertanggung jawab."
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Gesit",
|
||||
description: "Cekatan, sigap, dan penuh inisiatif dalam bekerja."
|
||||
},
|
||||
];
|
||||
|
||||
const letters = ["S", "I", "G", "A", "P"];
|
||||
|
||||
function MotoDesa() {
|
||||
return (
|
||||
<Box px={{ base: "md", md: "xl" }}>
|
||||
<Stack align="center" gap="lg">
|
||||
<Box>
|
||||
<Text
|
||||
ta="center"
|
||||
fw={800}
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #0D5594FF, #094678FF)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
}}
|
||||
>
|
||||
Moto Desa Darmasaba
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Flex gap={30} pb={40} pt={10} wrap="wrap" justify="center">
|
||||
{letters.map((letter, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
whileHover={{ scale: 1.15, rotate: 5 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
transition={{ type: "spring", stiffness: 300 }}
|
||||
>
|
||||
<ActionIcon
|
||||
radius="xl"
|
||||
size={90}
|
||||
variant="gradient"
|
||||
gradient={{ from: "blue", to: "cyan" }}
|
||||
style={{
|
||||
boxShadow: `0 0 18px ${colors['blue-button']}`,
|
||||
backdropFilter: "blur(6px)",
|
||||
}}
|
||||
>
|
||||
<Text c="white" fw={800} fz="xl">
|
||||
{letter}
|
||||
</Text>
|
||||
</ActionIcon>
|
||||
</motion.div>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
<Paper
|
||||
radius="lg"
|
||||
p="xl"
|
||||
withBorder
|
||||
style={{
|
||||
background: "linear-gradient(145deg, #ffffffcc, #f5faffcc)",
|
||||
boxShadow: "0 8px 24px rgba(0,0,0,0.08)",
|
||||
}}
|
||||
>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
||||
{dataText.map((v) => (
|
||||
<motion.div
|
||||
key={v.id}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Stack gap={4}>
|
||||
<Flex align="center" gap="sm">
|
||||
<IconSparkles size={20} color={colors['blue-button']} />
|
||||
<Text fw={700} fz={{ base: "lg", md: "xl" }} c={colors['blue-button']}>
|
||||
{v.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text fz={{ base: "sm", md: "md" }} c="gray.7">
|
||||
{v.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
</motion.div>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
<Text
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: "md", md: "xl" }}
|
||||
c="blue.8"
|
||||
mt="md"
|
||||
style={{
|
||||
maxWidth: 720,
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
"Berkomitmen menghadirkan pelayanan terbaik dengan semangat{" "}
|
||||
<Text span fw={800} c="cyan.6">
|
||||
SIGAP
|
||||
</Text>{" "}
|
||||
untuk masyarakat Desa Darmasaba."
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MotoDesa;
|
||||
174
src/app/darmasaba/(pages)/desa/profil/ui/profilPerbekel.tsx
Normal file
174
src/app/darmasaba/(pages)/desa/profil/ui/profilPerbekel.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Divider, Image, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { IconBriefcase, IconTargetArrow, IconUser, IconUsers } from '@tabler/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function ProfilPerbekel() {
|
||||
const state = useProxy(stateProfileDesa.profilPerbekel)
|
||||
|
||||
useEffect(() => {
|
||||
state.findUnique.load("edit")
|
||||
}, [])
|
||||
|
||||
const { data, loading } = state.findUnique
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py={20} px="md">
|
||||
<Skeleton h={500} radius="lg" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px="md">
|
||||
<Stack align="center" gap={0} mb={40}>
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw="bold"
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
style={{ letterSpacing: "0.5px" }}
|
||||
>
|
||||
Profil Perbekel
|
||||
</Text>
|
||||
<Divider w={120} size="sm" color={colors['blue-button']} mt={10} />
|
||||
</Stack>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
|
||||
<Box>
|
||||
<Paper
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
>
|
||||
<Stack gap={0}>
|
||||
<Image
|
||||
pt={{ base: 0, md: 100 }}
|
||||
px="lg"
|
||||
src={data.image?.link || "/perbekel.png"}
|
||||
alt="Foto Perbekel"
|
||||
radius="lg"
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = "/perbekel.png";
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
px="lg"
|
||||
radius="0 0 var(--mantine-radius-xl) var(--mantine-radius-xl)"
|
||||
className="glass3"
|
||||
py={{ base: 20, md: 50 }}
|
||||
>
|
||||
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
|
||||
Perbekel Desa Darmasaba
|
||||
</Text>
|
||||
<Text
|
||||
c={colors['white-1']}
|
||||
fw="bolder"
|
||||
fz={{ base: "xl", md: "h2" }}
|
||||
mt={8}
|
||||
>
|
||||
{"I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P."}
|
||||
</Text>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
p="xl"
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
>
|
||||
<Stack gap="xl">
|
||||
<Box>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconUser size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.biodata }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconBriefcase size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="left"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
|
||||
<Paper
|
||||
p="xl"
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
>
|
||||
<Stack gap="xl">
|
||||
<Box>
|
||||
<Stack align="center" gap={6} >
|
||||
<IconUsers size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman Organisasi</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Stack align="center" gap={6} mb={6}>
|
||||
<IconTargetArrow size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Program Kerja Unggulan</Text>
|
||||
</Stack>
|
||||
<Box px={10}>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilPerbekel;
|
||||
18
src/app/darmasaba/(pages)/desa/profil/ui/profileDesa.tsx
Normal file
18
src/app/darmasaba/(pages)/desa/profil/ui/profileDesa.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Paper } from '@mantine/core';
|
||||
|
||||
function ProfileDesa() {
|
||||
return (
|
||||
<Box>
|
||||
<Center>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
||||
<Center>
|
||||
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d15780.3751228292!2d115.19559210934409!3d-8.586981700000006!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23eb932812c81%3A0x5b5ac2759cc5fe4b!2sKantor%20Perbekel%20Darmasaba!5e0!3m2!1sid!2sid!4v1741922475983!5m2!1sid!2sid" width="600" height="450" style={{ border: 2, width: "100%" }} loading="lazy" />
|
||||
</Center>
|
||||
</Paper>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileDesa;
|
||||
77
src/app/darmasaba/(pages)/desa/profil/ui/sejarahDesa.tsx
Normal file
77
src/app/darmasaba/(pages)/desa/profil/ui/sejarahDesa.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function SejarahDesa() {
|
||||
const state = useProxy(stateProfileDesa.sejarahDesa);
|
||||
|
||||
useEffect(() => {
|
||||
state.findUnique.load('edit');
|
||||
}, []);
|
||||
|
||||
const { data, loading } = state.findUnique;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py="lg">
|
||||
<Skeleton h={500} radius="lg" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<Center>
|
||||
<Image
|
||||
src="/darmasaba-icon.png"
|
||||
alt="Ikon Desa Darmasaba"
|
||||
w={{ base: 180, md: 260 }}
|
||||
radius="md"
|
||||
style={{ filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Center>
|
||||
<Center>
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: '2rem', md: '2.8rem' }}
|
||||
style={{ letterSpacing: '-0.5px' }}
|
||||
>
|
||||
Sejarah Desa
|
||||
</Text>
|
||||
</Center>
|
||||
</Stack>
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="lg"
|
||||
bg="white"
|
||||
shadow="sm"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #ffffff 0%, #f9fbfd 100%)',
|
||||
border: `1px solid ${colors['blue-button']}20`,
|
||||
}}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={1.8}
|
||||
ta="justify"
|
||||
style={{ color: '#2a2a2a', wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default SejarahDesa;
|
||||
101
src/app/darmasaba/(pages)/desa/profil/ui/semuaPerbekel.tsx
Normal file
101
src/app/darmasaba/(pages)/desa/profil/ui/semuaPerbekel.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import { Box, Center, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconUser } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function SemuaPerbekel() {
|
||||
const state = useProxy(stateProfileDesa.mantanPerbekel);
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findMany.load();
|
||||
}, []);
|
||||
|
||||
const { data, loading } = state.findMany;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py="xl">
|
||||
<Skeleton h={500} radius="xl" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<IconUser size={48} stroke={1.5} />
|
||||
<Title fw="bold" order={2}>Belum ada data Perbekel</Title>
|
||||
<Text c="dimmed" fz="sm" ta="center">Data mantan Perbekel akan muncul di sini ketika sudah tersedia</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="lg">
|
||||
<Box>
|
||||
<Text
|
||||
ta="center"
|
||||
fw={900}
|
||||
fz={{ base: "2rem", md: "2.5rem" }}
|
||||
variant="gradient"
|
||||
gradient={{ from: "blue", to: "cyan", deg: 45 }}
|
||||
>
|
||||
Perbekel Dari Masa ke Masa
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="xl" w="100%">
|
||||
{data.map((v: any, k: number) => (
|
||||
<Paper
|
||||
key={k}
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
p="lg"
|
||||
bg="white"
|
||||
style={{
|
||||
transition: "all 250ms ease",
|
||||
}}
|
||||
className="hover:shadow-xl hover:scale-[1.02]"
|
||||
>
|
||||
<Stack gap="md" align="center">
|
||||
<Box w="100%" h={500} style={{ overflow: "hidden", borderRadius: "1rem" }}>
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.nama || "Foto Perbekel"}
|
||||
fit="cover"
|
||||
h="100%"
|
||||
w="100%"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Stack gap={4} align="center">
|
||||
<Text fw={700} fz="lg" ta="center">
|
||||
{v.nama}
|
||||
</Text>
|
||||
|
||||
<Text c="dimmed" fz="sm" ta="center">
|
||||
{v.daerah}
|
||||
</Text>
|
||||
|
||||
<Text c="blue" fw={600} fz="sm" ta="center">
|
||||
{v.periode}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default SemuaPerbekel;
|
||||
98
src/app/darmasaba/(pages)/desa/profil/ui/visimisiDesa.tsx
Normal file
98
src/app/darmasaba/(pages)/desa/profil/ui/visimisiDesa.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function VisiMisiDesa() {
|
||||
const state = useProxy(stateProfileDesa.visiMisiDesa);
|
||||
|
||||
useEffect(() => {
|
||||
state.findUnique.load('edit');
|
||||
}, []);
|
||||
|
||||
const { data, loading } = state.findUnique;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Box py="xl">
|
||||
<Skeleton h={500} radius="lg" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack align="center" gap="xl">
|
||||
<Image
|
||||
src="/darmasaba-icon.png"
|
||||
alt="Lambang Desa Darmasaba"
|
||||
w={{ base: 160, md: 240 }}
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w="100%"
|
||||
style={{
|
||||
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: '2rem', md: '2.5rem' }}
|
||||
mb="md"
|
||||
>
|
||||
Visi Desa
|
||||
</Text>
|
||||
<Text
|
||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
||||
ta="center"
|
||||
fw={500}
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.visi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
<Paper
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
w="100%"
|
||||
style={{
|
||||
background: 'linear-gradient(145deg, #ffffff, #f5f7fa)',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw={700}
|
||||
fz={{ base: '2rem', md: '2.5rem' }}
|
||||
mb="md"
|
||||
>
|
||||
Misi Desa
|
||||
</Text>
|
||||
<Text
|
||||
fz={{ base: '1.125rem', md: '1.375rem' }}
|
||||
fw={500}
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.misi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default VisiMisiDesa;
|
||||
Reference in New Issue
Block a user