Compare commits
7 Commits
nico/20-ok
...
nico/28-ok
| Author | SHA1 | Date | |
|---|---|---|---|
| a6663bbcee | |||
| ed371bd0d9 | |||
| f82c7b86e0 | |||
| b5d6585cd5 | |||
| aa98359ef7 | |||
| 0ff0d5234a | |||
| 827c1c191a |
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "bun --bun next dev --hostname 0.0.0.0",
|
||||
"dev": "bun --bun next dev",
|
||||
"build": "bun --bun next build",
|
||||
"start": "bun --bun next start"
|
||||
},
|
||||
@@ -43,6 +43,7 @@
|
||||
"@types/bun": "^1.2.2",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/nodemailer": "^7.0.2",
|
||||
"add": "^2.0.6",
|
||||
"adm-zip": "^0.5.16",
|
||||
"animate.css": "^4.1.1",
|
||||
@@ -52,6 +53,7 @@
|
||||
"classnames": "^2.5.1",
|
||||
"colors": "^1.4.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^17.2.3",
|
||||
"elysia": "^1.3.5",
|
||||
"embla-carousel-autoplay": "^8.5.2",
|
||||
"embla-carousel-react": "^7.1.0",
|
||||
@@ -71,6 +73,7 @@
|
||||
"next": "^15.5.2",
|
||||
"next-view-transitions": "^0.3.4",
|
||||
"node-fetch": "^3.3.2",
|
||||
"nodemailer": "^7.0.10",
|
||||
"p-limit": "^6.2.0",
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.6",
|
||||
|
||||
@@ -55,9 +55,9 @@ function EditProgramKemiskinan() {
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
|
||||
stateProgram.findUnique
|
||||
.load(id)
|
||||
.then(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
await stateProgram.findUnique.load(id);
|
||||
const data = stateProgram.findUnique.data;
|
||||
if (data) {
|
||||
setFormData({
|
||||
@@ -70,12 +70,16 @@ function EditProgramKemiskinan() {
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
} catch (err) {
|
||||
console.error('Error load data:', err);
|
||||
toast.error('Gagal mengambil data program');
|
||||
});
|
||||
}, [id, stateProgram.findUnique]);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]); // ✅ hanya trigger saat id berubah
|
||||
|
||||
|
||||
// generic handler untuk field top-level
|
||||
const handleChange = useCallback(
|
||||
|
||||
@@ -183,7 +183,7 @@ function EditArtikelKesehatan() {
|
||||
{/* Gambar */}
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Gambar Berita
|
||||
Gambar Artikel Kesehatan
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={handleFileChange}
|
||||
@@ -240,15 +240,15 @@ function EditArtikelKesehatan() {
|
||||
/>
|
||||
|
||||
{/* Pendahuluan */}
|
||||
<InputText
|
||||
label="Pendahuluan"
|
||||
value={formData.introduction.content}
|
||||
onChange={(value) =>
|
||||
setFormData((prev) => ({ ...prev, introduction: { content: value } }))
|
||||
}
|
||||
placeholder="Masukkan pendahuluan"
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold">Pendahuluan</Text>
|
||||
<EditEditor
|
||||
value={formData.introduction.content}
|
||||
onChange={(value) =>
|
||||
setFormData((prev) => ({ ...prev, introduction: { ...prev.introduction, content: value } }))
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
{/* Gejala */}
|
||||
<Box>
|
||||
<Text fw="bold">Gejala</Text>
|
||||
|
||||
@@ -115,7 +115,7 @@ function CreateArtikelKesehatan() {
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Gambar Berita
|
||||
Gambar Artikel Kesehatan
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
@@ -163,7 +163,7 @@ function CreateArtikelKesehatan() {
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
<TextInput
|
||||
label={"Judul"}
|
||||
placeholder="Masukkan judul"
|
||||
@@ -182,16 +182,15 @@ function CreateArtikelKesehatan() {
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label={"Pendahuluan"}
|
||||
placeholder="Masukkan pendahuluan"
|
||||
required
|
||||
defaultValue={stateArtikelKesehatan.create.form.introduction.content}
|
||||
onChange={(e) => {
|
||||
stateArtikelKesehatan.create.form.introduction.content = e.target.value;
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">Pendahuluan</Text>
|
||||
<CreateEditor
|
||||
value={stateArtikelKesehatan.create.form.introduction.content}
|
||||
onChange={(e) => {
|
||||
stateArtikelKesehatan.create.form.introduction.content = e;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Gejala */}
|
||||
<Box>
|
||||
<Text fz="md" fw="bold">Gejala</Text>
|
||||
|
||||
54
src/app/api/subscribe/route.ts
Normal file
54
src/app/api/subscribe/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { email } = await request.json();
|
||||
|
||||
// Input validation
|
||||
if (!email) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Email is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Email regex validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Invalid email format' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Configure nodemailer
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS,
|
||||
},
|
||||
});
|
||||
|
||||
// Send email
|
||||
await transporter.sendMail({
|
||||
from: `"Tim Info" <${process.env.EMAIL_USER}>`,
|
||||
to: email,
|
||||
subject: '✅ Berhasil Berlangganan!',
|
||||
html: `<p>Terima kasih telah berlangganan info terbaru dari kami!</p>`,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Subscription successful! Please check your email.',
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in subscribe API:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -62,11 +62,23 @@ function Page() {
|
||||
Informasi dan Pelayanan Administrasi Digital
|
||||
</Text>
|
||||
</Box>
|
||||
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy"/>
|
||||
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy" />
|
||||
</Container>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.content || '' }} />
|
||||
<Text
|
||||
py={20}
|
||||
fz={{ base: "sm", md: "lg" }}
|
||||
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif
|
||||
ta="justify"
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: state.findUnique.data?.content || "",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -186,12 +186,11 @@ function Page() {
|
||||
stateCreate.create.form.kategoriId = '';
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
// searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
required
|
||||
/>
|
||||
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||
Simpan
|
||||
</Button>
|
||||
|
||||
@@ -7,34 +7,52 @@ import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function PelayananPerizinanBerusaha() {
|
||||
const state = useProxy(stateLayananDesa)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [active, setActive] = useState(1);
|
||||
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
|
||||
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
|
||||
const state = useProxy(stateLayananDesa);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [active, setActive] = useState(0);
|
||||
|
||||
const totalSteps = 6;
|
||||
|
||||
const nextStep = () => {
|
||||
if (active < totalSteps - 1) {
|
||||
setActive(active + 1);
|
||||
} else if (active === totalSteps - 1) {
|
||||
setActive(totalSteps); // Mark as completed
|
||||
}
|
||||
};
|
||||
|
||||
const prevStep = () => {
|
||||
if (active > 0) {
|
||||
setActive(active - 1);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.pelayananPerizinanBerusaha.findById.load('edit')
|
||||
await state.pelayananPerizinanBerusaha.findById.load('edit');
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}, [])
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const data = state.pelayananPerizinanBerusaha.findById.data;
|
||||
|
||||
|
||||
if (!data && !loading) {
|
||||
return (
|
||||
<Center mih={300}>
|
||||
<Stack align="center" gap="sm">
|
||||
<Text fz="lg" fw={500} c="dimmed">Belum ada informasi layanan yang tersedia</Text>
|
||||
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">Kunjungi OSS</Button>
|
||||
<Text fz="lg" fw={500} c="dimmed">
|
||||
Belum ada informasi layanan yang tersedia
|
||||
</Text>
|
||||
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
|
||||
Kunjungi OSS
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
@@ -47,72 +65,111 @@ function PelayananPerizinanBerusaha() {
|
||||
<Loader size="lg" color="blue" />
|
||||
</Center>
|
||||
) : (
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
|
||||
Perizinan Berusaha Berbasis Risiko melalui OSS
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
|
||||
</Text>
|
||||
</Box>
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Title order={2} fw={700} fz={{ base: 22, md: 32 }} mb="sm">
|
||||
Perizinan Berusaha Berbasis Risiko melalui OSS
|
||||
</Title>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
Sistem Online Single Submission (OSS) untuk pendaftaran NIB
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Text fz={{ base: 'sm', md: 'md' }} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '' }} />
|
||||
<Text
|
||||
fz={{ base: 'sm', md: 'md' }}
|
||||
ta="justify"
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: data?.deskripsi || '' }}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>Alur pendaftaran NIB:</Text>
|
||||
<Stepper active={active} onStepClick={setActive} orientation="vertical" color="blue" radius="md"
|
||||
styles={{
|
||||
step: { padding: '14px 0' },
|
||||
stepBody: { marginLeft: 8 }
|
||||
}}
|
||||
>
|
||||
<StepperStep label="Langkah 1" description="Daftar Akun">
|
||||
<Text fz="sm">Membuat akun di portal OSS</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
|
||||
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 3" description="Pilih KBLI">
|
||||
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 4" description="Unggah Dokumen">
|
||||
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
|
||||
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 6" description="Terbit NIB">
|
||||
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text>
|
||||
</StepperStep>
|
||||
<StepperCompleted>
|
||||
<Center>
|
||||
<Stack align="center" gap="xs">
|
||||
<IconCheck size={40} color="green" />
|
||||
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</StepperCompleted>
|
||||
</Stepper>
|
||||
<Box>
|
||||
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>
|
||||
Alur pendaftaran NIB:
|
||||
</Text>
|
||||
<Stepper
|
||||
active={active}
|
||||
onStepClick={(step) => {
|
||||
if (step <= active) { // Only allow clicking on previous or current steps
|
||||
setActive(step);
|
||||
}
|
||||
}}
|
||||
orientation="vertical"
|
||||
color="blue"
|
||||
radius="md"
|
||||
styles={{
|
||||
step: { padding: '14px 0' },
|
||||
stepBody: { marginLeft: 8 }
|
||||
}}
|
||||
>
|
||||
<StepperStep label="Langkah 1" description="Daftar Akun">
|
||||
<Text fz="sm">Membuat akun di portal OSS</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 2" description="Isi Data Perusahaan">
|
||||
<Text fz="sm">Lengkapi informasi perusahaan, data pemegang saham, dan alamat</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 3" description="Pilih KBLI">
|
||||
<Text fz="sm">Menentukan kode KBLI sesuai jenis usaha</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 4" description="Unggah Dokumen">
|
||||
<Text fz="sm">Unggah akta pendirian, surat izin, dan dokumen wajib lainnya</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 5" description="Verifikasi Instansi">
|
||||
<Text fz="sm">Menunggu verifikasi dan persetujuan dari pihak berwenang</Text>
|
||||
</StepperStep>
|
||||
<StepperStep label="Langkah 6" description="Terbit NIB">
|
||||
<Text fz="sm">Menerima NIB sebagai identitas resmi usaha</Text>
|
||||
</StepperStep>
|
||||
<StepperCompleted>
|
||||
<Center>
|
||||
<Stack align="center" gap="xs">
|
||||
<IconCheck size={40} color="green" />
|
||||
<Text fz="sm" fw={500}>Proses pendaftaran selesai</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</StepperCompleted>
|
||||
</Stepper>
|
||||
|
||||
{active < totalSteps && (
|
||||
<Group justify="center" mt="lg">
|
||||
<Button variant="light" leftSection={<IconArrowLeft size={18} />} onClick={prevStep} disabled={active === 0}>
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<IconArrowLeft size={18} />}
|
||||
onClick={prevStep}
|
||||
disabled={active === 0}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button rightSection={<IconArrowRight size={18} />} onClick={nextStep}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Text fz="sm" ta="justify" c="dimmed" mt="md">
|
||||
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{" "}
|
||||
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">oss.go.id</a> atau hubungi instansi pemerintah terkait.
|
||||
</Text>
|
||||
</Stack>
|
||||
{active < totalSteps ? (
|
||||
<Button
|
||||
rightSection={active < totalSteps - 1 ? <IconArrowRight size={18} /> : null}
|
||||
onClick={nextStep}
|
||||
>
|
||||
{active === totalSteps - 1 ? 'Selesai' : 'Lanjut'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={() => setActive(0)}
|
||||
>
|
||||
Mulai Lagi
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Text fz="sm" ta="justify" c="dimmed" mt="md">
|
||||
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
|
||||
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
|
||||
oss.go.id
|
||||
</a>{' '}
|
||||
atau hubungi instansi pemerintah terkait.
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default PelayananPerizinanBerusaha;
|
||||
export default PelayananPerizinanBerusaha;
|
||||
@@ -47,13 +47,13 @@ function PelayananSuratKeterangan({ search }: { search: string }) {
|
||||
<Box pb="xl">
|
||||
<Group justify="space-between" align="center" mb="md">
|
||||
<Group gap="xs">
|
||||
<IconFileDescription size={28} stroke={1.8} color={colors["blue-button"]} />
|
||||
<IconFileDescription size={28} stroke={1.8} />
|
||||
<Text fz={{ base: "h4", md: "h2" }} fw={700}>
|
||||
Layanan Surat Keterangan
|
||||
</Text>
|
||||
</Group>
|
||||
<Tooltip label="Pilih layanan surat keterangan sesuai kebutuhan Anda" withArrow>
|
||||
<IconInfoCircle size={22} stroke={1.8} color={colors["blue-button"]} />
|
||||
<IconInfoCircle size={22} stroke={1.8} />
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
|
||||
import colors from '@/con/colors';
|
||||
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { IconEye } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -43,7 +43,7 @@ function Page() {
|
||||
<Text fz={{ base: "2rem", md: "3rem" }} fw={900} c={colors["blue-button"]} lh={1.2}>
|
||||
Potensi Desa Darmasaba
|
||||
</Text>
|
||||
<Text fz="lg" c="dimmed" ta="justify">
|
||||
<Text fz="lg" ta="justify">
|
||||
Temukan berbagai potensi unggulan, peluang, dan daya tarik yang menjadikan Desa Darmasaba istimewa.
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -114,7 +114,6 @@ function Page() {
|
||||
</Text>
|
||||
</Box>
|
||||
<Group justify="center">
|
||||
<Tooltip label="Lihat detail potensi" withArrow>
|
||||
<Button
|
||||
radius="xl"
|
||||
size="md"
|
||||
@@ -126,7 +125,6 @@ function Page() {
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile'
|
||||
import colors from '@/con/colors'
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'
|
||||
import { Box, Center, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'
|
||||
import { useEffect } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
|
||||
@@ -58,7 +58,6 @@ function LambangDesa() {
|
||||
borderColor: '#e0e9ff',
|
||||
}}
|
||||
>
|
||||
<Tooltip label="Deskripsi lambang desa" position="top-start" withArrow>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={1.8}
|
||||
@@ -67,7 +66,6 @@ function LambangDesa() {
|
||||
style={{ fontWeight: 400, wordBreak: "break-word", whiteSpace: "normal", }}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import { Box, Card, Center, Group, Image, Loader, Paper, Stack, Text, Tooltip } from '@mantine/core';
|
||||
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';
|
||||
import { IconPhoto } from '@tabler/icons-react';
|
||||
import colors from '@/con/colors';
|
||||
|
||||
function MaskotDesa() {
|
||||
const state = useProxy(stateProfileDesa.maskotDesa);
|
||||
@@ -54,8 +54,8 @@ function MaskotDesa() {
|
||||
<Group justify="center" gap="lg" mt="lg">
|
||||
{data.images.length > 0 ? (
|
||||
data.images.map((img, index) => (
|
||||
<Tooltip key={index} label={img.label} position="bottom" withArrow>
|
||||
<Card
|
||||
key={index}
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
@@ -79,7 +79,6 @@ function MaskotDesa() {
|
||||
{img.label}
|
||||
</Text>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
))
|
||||
) : (
|
||||
<Stack align="center" gap="xs" mt="lg">
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Image, Paper, SimpleGrid, Skeleton, Stack, Text, Divider, Tooltip } from '@mantine/core';
|
||||
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';
|
||||
import { IconUser, IconBriefcase, IconUsers, IconTargetArrow } from '@tabler/icons-react';
|
||||
|
||||
function ProfilPerbekel() {
|
||||
const state = useProxy(stateProfileDesa.profilPerbekel)
|
||||
@@ -27,10 +27,10 @@ function ProfilPerbekel() {
|
||||
return (
|
||||
<Box pb={80} px="md">
|
||||
<Stack align="center" gap={0} mb={40}>
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw="bold"
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
ta="center"
|
||||
fw="bold"
|
||||
fz={{ base: "2rem", md: "2.8rem" }}
|
||||
style={{ letterSpacing: "0.5px" }}
|
||||
>
|
||||
@@ -41,11 +41,11 @@ function ProfilPerbekel() {
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl" pb={50}>
|
||||
<Box>
|
||||
<Paper
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
<Paper
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
>
|
||||
<Stack gap={0}>
|
||||
@@ -70,9 +70,9 @@ function ProfilPerbekel() {
|
||||
<Text c={colors['white-1']} fz={{ base: "lg", md: "h3" }}>
|
||||
Perbekel Desa Darmasaba
|
||||
</Text>
|
||||
<Text
|
||||
c={colors['white-1']}
|
||||
fw="bolder"
|
||||
<Text
|
||||
c={colors['white-1']}
|
||||
fw="bolder"
|
||||
fz={{ base: "xl", md: "h2" }}
|
||||
mt={8}
|
||||
>
|
||||
@@ -83,89 +83,85 @@ function ProfilPerbekel() {
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
p="xl"
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
<Paper
|
||||
p="xl"
|
||||
bg={colors['white-trans-1']}
|
||||
w="100%"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
>
|
||||
<Stack gap="xl">
|
||||
<Box>
|
||||
<Tooltip label="Informasi pribadi perbekel" withArrow>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconUser size={22} color={colors['blue-button']} />
|
||||
<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 gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconUser size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Biodata</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
<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>
|
||||
<Tooltip label="Pengalaman kerja perbekel" withArrow>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconBriefcase size={22} color={colors['blue-button']} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalaman }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
<Stack gap={6}>
|
||||
<Stack align="center" gap={6}>
|
||||
<IconBriefcase size={22} />
|
||||
<Text fz={{ base: "1.2rem", md: "1.5rem" }} fw="bold">Pengalaman</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
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"
|
||||
<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} color={colors['blue-button']} />
|
||||
<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"
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
dangerouslySetInnerHTML={{ __html: data.pengalamanOrganisasi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Stack align="center" gap={6} mb={6}>
|
||||
<IconTargetArrow size={22} color={colors['blue-button']} />
|
||||
<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"
|
||||
<Text
|
||||
fz={{ base: "1rem", md: "1.2rem" }}
|
||||
ta="justify"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
dangerouslySetInnerHTML={{ __html: data.programUnggulan }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* 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, Tooltip } from '@mantine/core';
|
||||
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';
|
||||
@@ -77,23 +77,17 @@ function SemuaPerbekel() {
|
||||
</Box>
|
||||
|
||||
<Stack gap={4} align="center">
|
||||
<Tooltip label="Nama Perbekel" withArrow>
|
||||
<Text fw={700} fz="lg" ta="center">
|
||||
{v.nama}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Wilayah menjabat" withArrow>
|
||||
<Text c="dimmed" fz="sm" ta="center">
|
||||
<Text c="dimmed" fz="sm" ta="center">
|
||||
{v.daerah}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Periode jabatan" withArrow>
|
||||
<Text c="blue" fw={600} fz="sm" ta="center">
|
||||
<Text c="blue" fw={600} fz="sm" ta="center">
|
||||
{v.periode}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -1,167 +1,3 @@
|
||||
// 'use client'
|
||||
// import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
// import colors from '@/con/colors';
|
||||
// import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||
// import { useProxy } from 'valtio/utils';
|
||||
// import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
// import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
|
||||
// function Page() {
|
||||
// const state = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||
|
||||
// useShallowEffect(() => {
|
||||
// state.findMany.load();
|
||||
// }, []);
|
||||
|
||||
// useShallowEffect(() => {
|
||||
// PendapatanAsliDesa.pembiayaan.findMany.load();
|
||||
// PendapatanAsliDesa.belanja.findMany.load();
|
||||
// PendapatanAsliDesa.pendapatan.findMany.load();
|
||||
// }, []);
|
||||
|
||||
// // Get the latest APB data
|
||||
// const latestApb = state.findMany.data?.[0];
|
||||
|
||||
// // Calculate totals
|
||||
// const totalPendapatan = latestApb?.pendapatan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||
// const totalBelanja = latestApb?.belanja?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||
// const totalPembiayaan = latestApb?.pembiayaan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0;
|
||||
|
||||
|
||||
// return (
|
||||
// <Stack pos="relative" bg={colors.Bg} py="xl" gap="lg">
|
||||
// <Box px={{ base: 'md', md: 100 }}>
|
||||
// <BackButton />
|
||||
// </Box>
|
||||
// <Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||
// Pendapatan Asli Desa
|
||||
// </Text>
|
||||
// <Box px={{ base: "md", md: 100 }}>
|
||||
// <Stack gap="lg" justify="center">
|
||||
// <Paper bg={colors['white-1']} p="xl">
|
||||
// <SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||
// {/* Pendapatan Card */}
|
||||
// <Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
// <Stack gap={"xs"}>
|
||||
// <Title order={3}>Pendapatan</Title>
|
||||
// {PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => (
|
||||
// <Box key={item.id}>
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="md" fw={500}>{item.name}</Text>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0
|
||||
// }).format(item.value)}</Text>
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Box>
|
||||
// ))}
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="xl" fw={700} c={colors['blue-button']}>
|
||||
// {new Intl.NumberFormat('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0
|
||||
// }).format(totalPendapatan)}
|
||||
// </Text>
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Stack>
|
||||
// </Box>
|
||||
|
||||
// {/* Belanja Card */}
|
||||
// <Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
// <Stack gap={"xs"}>
|
||||
// <Title order={3}>Belanja</Title>
|
||||
// {PendapatanAsliDesa.belanja.findMany.data?.map((item) => (
|
||||
// <Box key={item.id}>
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="md" fw={500}>{item.name}</Text>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0
|
||||
// }).format(item.value)}</Text>
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Box>
|
||||
// ))}
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="xl" fw={700} c="orange">
|
||||
// {new Intl.NumberFormat('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0
|
||||
// }).format(totalBelanja)}
|
||||
// </Text>
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Stack>
|
||||
// </Box>
|
||||
|
||||
// {/* Pembiayaan Card */}
|
||||
// <Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
// <Stack gap={"xs"}>
|
||||
// <Title order={3}>Pembiayaan</Title>
|
||||
// {PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => (
|
||||
// <Box key={item.id}>
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="md" fw={500}>{item.name}</Text>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0
|
||||
// }).format(item.value)}</Text>
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Box>
|
||||
// ))}
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 6 }}>
|
||||
// <Text fz="xl" fw={700} c="green">
|
||||
// {new Intl.NumberFormat('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0
|
||||
// }).format(totalPembiayaan)}
|
||||
// </Text>
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Stack>
|
||||
// </Box>
|
||||
|
||||
// </SimpleGrid>
|
||||
// </Paper>
|
||||
// </Stack>
|
||||
// </Box>
|
||||
// </Stack>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default Page;
|
||||
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
@@ -206,32 +42,41 @@ function Page() {
|
||||
<Stack gap="lg" justify="center">
|
||||
<Paper bg={colors['white-1']} p="xl">
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||
{/* Pendapatan Card */}
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
{/* Pendapatan Card */}
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Pendapatan</Title>
|
||||
{PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => (
|
||||
{latestApb?.pendapatan?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="md" fw={500}>{item.name}</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}</Text>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text
|
||||
fz="md"
|
||||
fw={500}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
{new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(item.value)}
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="xl" fw={700} c={colors['blue-button']}>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}} fz="xl" fw={700} c={colors['blue-button']}>
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
@@ -247,18 +92,28 @@ function Page() {
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Belanja</Title>
|
||||
{PendapatanAsliDesa.belanja.findMany.data?.map((item) => (
|
||||
{latestApb?.belanja?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="md" fw={500}>{item.name}</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}</Text>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text
|
||||
fz="md"
|
||||
fw={500}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
@@ -284,18 +139,28 @@ function Page() {
|
||||
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Pembiayaan</Title>
|
||||
{PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => (
|
||||
{latestApb?.pembiayaan?.map((item) => (
|
||||
<Box key={item.id}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text fz="md" fw={500}>{item.name}</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}</Text>
|
||||
<GridCol span={{ base: 12, md: 6 }} style={{ maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<Text
|
||||
fz="md"
|
||||
fw={500}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
{new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(item.value)}
|
||||
</Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
@@ -366,5 +231,4 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,136 @@
|
||||
'use client'
|
||||
|
||||
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconBrandWhatsapp, IconBriefcase, IconCurrencyDollar, IconMapPin, IconPhone } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailLowonganKerjaUser() {
|
||||
const state = useProxy(lowonganKerjaState);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useShallowEffect(() => {
|
||||
const loadData = async () => {
|
||||
await state.findUnique.load(params?.id as string);
|
||||
setLoading(false);
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const data = state.findUnique.data;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
<Skeleton height={500} w={{ base: '90%', md: '70%' }} radius="lg" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack bg={colors.Bg} py="xl" px={{ base: 'md', md: 100 }} align="center">
|
||||
<Box w={{ base: '100%', md: '70%' }}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
leftSection={<IconArrowBack size={20} />}
|
||||
mb="md"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
withBorder
|
||||
p="xl"
|
||||
bg={colors['white-1']}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Judul */}
|
||||
<Text fz={{ base: '1.6rem', md: '2rem' }} fw={700} c={colors['blue-button']}>
|
||||
{data.posisi}
|
||||
</Text>
|
||||
<Text c="dimmed" fz="sm">
|
||||
Diposting: {new Date(data.createdAt).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</Text>
|
||||
|
||||
{/* Info Ringkas */}
|
||||
<Stack gap="sm" mt="md">
|
||||
<Group gap="xs">
|
||||
<IconBriefcase size={20} color={colors['blue-button']} />
|
||||
<Text fz="md" fw={600}>{data.namaPerusahaan}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.lokasi}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconPhone size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.notelp}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconCurrencyDollar size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.gaji || '-'}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconBriefcase size={20} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.tipePekerjaan}</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" mb={4}>
|
||||
Deskripsi Pekerjaan
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
lh={1.6}
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={600} fz="lg" mb={4}>
|
||||
Kualifikasi
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
lh={1.6}
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
dangerouslySetInnerHTML={{ __html: data.kualifikasi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Center>
|
||||
<Button
|
||||
radius="md"
|
||||
size="md"
|
||||
mt="md"
|
||||
bg={colors['blue-button']}
|
||||
onClick={() => window.open(`https://wa.me/${data.notelp}`, '_blank')}
|
||||
leftSection={<IconBrandWhatsapp size={20} />}
|
||||
>
|
||||
Hubungi Sekarang
|
||||
</Button>
|
||||
</Center>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailLowonganKerjaUser;
|
||||
@@ -12,13 +12,13 @@ import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
const formatCurrency = (value: string | number) => {
|
||||
// Convert to string if it's a number
|
||||
const numStr = typeof value === 'number' ? value.toString() : value;
|
||||
|
||||
|
||||
// Remove all non-digit characters
|
||||
const digitsOnly = numStr.replace(/\D/g, '');
|
||||
|
||||
|
||||
// Format with thousand separators
|
||||
const formatted = digitsOnly.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
|
||||
|
||||
return `Rp.${formatted}`;
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ function Page() {
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Box px={{ base: 'md', md: 100 }} pb={80}>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Lowongan Kerja Lokal
|
||||
</Text>
|
||||
@@ -103,7 +103,7 @@ function Page() {
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Button onClick={() => router.push(`https://wa.me/${v.notelp?.replace(/\D/g, '')}`)}>Lamar Sekarang</Button>
|
||||
<Button onClick={() => router.push(`/darmasaba/ekonomi/lowongan-kerja-lokal/${v.id}`)}>Detail</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
157
src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx
Normal file
157
src/app/darmasaba/(pages)/ekonomi/pasar-desa/[id]/page.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, Image, Skeleton, Group, Badge, Divider } from '@mantine/core';
|
||||
import { IconArrowBack, IconMapPin, IconPhone, IconStar } from '@tabler/icons-react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pasar-desa';
|
||||
|
||||
function DetailProdukPasarUser() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const statePasar = useProxy(pasarDesaState);
|
||||
|
||||
useShallowEffect(() => {
|
||||
statePasar.pasarDesa.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const data = statePasar.pasarDesa.findUnique.data;
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={400} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={20}>
|
||||
{/* Tombol kembali */}
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={20} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali ke daftar produk
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '70%' }}
|
||||
mx="auto"
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
bg={colors['white-1']}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Gambar Produk */}
|
||||
{data.image?.link ? (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.nama}
|
||||
radius="md"
|
||||
h={250}
|
||||
w="100%"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
h={300}
|
||||
bg="gray.1"
|
||||
display="flex"
|
||||
style={{ alignItems: 'center', justifyContent: 'center', borderRadius: 'md' }}
|
||||
>
|
||||
<Text c="dimmed">Tidak ada gambar</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Detail Produk */}
|
||||
<Stack gap="xs">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
{data.nama || 'Produk Tanpa Nama'}
|
||||
</Text>
|
||||
<Group>
|
||||
<Badge color="green" size="lg" radius="md">
|
||||
Rp {data.harga?.toLocaleString('id-ID')}
|
||||
</Badge>
|
||||
{data.rating && (
|
||||
<Group gap={4}>
|
||||
<IconStar size={18} color="#FFD43B" />
|
||||
<Text fz="md" fw={500}>{data.rating}</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Divider my="sm" />
|
||||
|
||||
{/* Info Tambahan */}
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz="lg" fw={600}>Kategori</Text>
|
||||
<Group gap="xs" mt={4}>
|
||||
{data.KategoriToPasar && data.KategoriToPasar.length > 0 ? (
|
||||
data.KategoriToPasar.map((kategori) => (
|
||||
<Badge key={kategori.id} color="blue" variant="light">
|
||||
{kategori.kategori.nama}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada kategori</Text>
|
||||
)}
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{data.alamatUsaha && (
|
||||
<Group gap={6}>
|
||||
<IconMapPin size={18} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.alamatUsaha}</Text>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{data.kontak && (
|
||||
<Group gap={6}>
|
||||
<IconPhone size={18} color={colors['blue-button']} />
|
||||
<Text fz="md">{data.kontak}</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<Divider my="sm" />
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fz="lg" fw={600}>Deskripsi Produk</Text>
|
||||
<Text fz="md" c="dimmed" mt={4}>
|
||||
Tidak ada deskripsi.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Tombol Aksi User */}
|
||||
{data.kontak && (
|
||||
<Button
|
||||
mt="md"
|
||||
color="green"
|
||||
size="lg"
|
||||
radius="md"
|
||||
component="a"
|
||||
href={`https://wa.me/${data.kontak.replace(/[^0-9]/g, '')}`}
|
||||
target="_blank"
|
||||
>
|
||||
Hubungi Penjual via WhatsApp
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailProdukPasarUser;
|
||||
@@ -71,8 +71,11 @@ function Page() {
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz={{ base: "h4", md: "h3" }} >
|
||||
Pasar Desa Online merupakan Media Promosi yang bertujuan untuk membantu warga desa dalam memasarkan dan memperkenalkan produknya kepada masyarakat.
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
||||
Pasar Desa Online adalah media promosi untuk membantu warga memasarkan
|
||||
</Text>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" >
|
||||
dan memperkenalkan produk mereka.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
@@ -105,7 +108,7 @@ function Page() {
|
||||
return (
|
||||
<Stack key={k}>
|
||||
<motion.div
|
||||
onClick={() => router.push(`https://wa.me/${v.kontak?.replace(/\D/g, '')}`)}
|
||||
onClick={() => router.push(`/darmasaba/ekonomi/pasar-desa/${v.id}`)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.8 }}
|
||||
>
|
||||
@@ -117,7 +120,7 @@ function Page() {
|
||||
h={200}
|
||||
w='100%'
|
||||
style={{ objectFit: 'cover' }}
|
||||
loading="lazy"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text py={10} fw={'bold'} fz={'lg'}>{v.nama}</Text>
|
||||
<Text fz={'md'}>Rp {v.harga.toLocaleString('id-ID')}</Text>
|
||||
|
||||
@@ -55,6 +55,7 @@ function Page() {
|
||||
}}
|
||||
>
|
||||
<Paper p={'xl'} >
|
||||
<Stack gap={"xs"}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>Tujuan Ide Inovatif Ini</Text>
|
||||
<List>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mendorong partisipasi aktif masyarakat</ListItem>
|
||||
@@ -62,6 +63,7 @@ function Page() {
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Memecahkan tantangan komunal</ListItem>
|
||||
<ListItem ta={'justify'} fz={{ base: 'h4', md: 'lg' }}>Mengembangkan potensi kreativitas warga</ListItem>
|
||||
</List>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Paper p={'xl'} >
|
||||
<Flex align={'center'} justify={'space-between'}>
|
||||
|
||||
@@ -56,7 +56,8 @@ function Page() {
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text fz={'h4'}>Mewujudkan Desa Darmasaba sebagai pusat inovasi digital yang memberdayakan masyarakat, meningkatkan kesejahteraan, dan menciptakan peluang ekonomi berbasis teknologi.</Text>
|
||||
<Text fz={'md'}>Menjadikan Desa Darmasaba pusat inovasi digital untuk pemberdayaan masyarakat</Text>
|
||||
<Text fz={'md'}>dan peningkatan ekonomi berbasis teknologi.</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
|
||||
@@ -71,11 +71,22 @@ function Page() {
|
||||
{filteredData.map((v, k) => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Image src={v.image.link || ''} pb={10} radius={10} alt='' loading="lazy"/>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.name}</Text>
|
||||
<Box pr={'lg'} pb={10}>
|
||||
<Text fz={'h4'} fw={'bold'} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Image src={v.image.link || ''} pb={10} radius={10} alt='' loading="lazy" />
|
||||
<Text fz={'h3'} fw={'bold'}>{v.name}</Text>
|
||||
<Box pr={'lg'} pb={10}>
|
||||
<Text
|
||||
size="md"
|
||||
ta="justify"
|
||||
lh={1} // line height biar enak dibaca
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -75,18 +75,18 @@ function AdministrasiOnline() {
|
||||
<Title order={3}>Ajukan Administrasi Online</Title>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
||||
placeholder="masukkan nama"
|
||||
placeholder="Masukkan nama"
|
||||
onChange={(val) => (state.administrasiOnline.create.form.name = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
||||
placeholder="masukkan alamat"
|
||||
placeholder="Masukkan alamat"
|
||||
onChange={(val) => (state.administrasiOnline.create.form.alamat = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
label={<Text fz="sm" fw="bold">Nomor Telepon</Text>}
|
||||
placeholder="masukkan nomor telepon"
|
||||
placeholder="Masukkan nomor telepon"
|
||||
onChange={(val) => (state.administrasiOnline.create.form.nomorTelepon = val.target.value)}
|
||||
/>
|
||||
<Select
|
||||
@@ -95,7 +95,7 @@ function AdministrasiOnline() {
|
||||
state.administrasiOnline.create.form.jenisLayananId = val ?? "";
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Jenis Layanan</Text>}
|
||||
placeholder="Pilih kategori produk"
|
||||
placeholder="Pilih jenis layanan"
|
||||
data={
|
||||
state.jenisLayanan.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function Page() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||
const state = useProxy(stateDashboardBerita.berita)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findUnique.load(id);
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}, [id])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Center>
|
||||
<Skeleton height={500} />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Center>
|
||||
<Text>Data tidak ditemukan</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container w={{ base: "100%", md: "50%" }} >
|
||||
<Box pb={20}>
|
||||
<Text ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||
{state.findUnique.data?.judul}
|
||||
</Text>
|
||||
</Box>
|
||||
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} loading="lazy" />
|
||||
</Container>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text
|
||||
py={20}
|
||||
fz={{ base: "sm", md: "lg" }}
|
||||
lh={{ base: 1.6, md: 1.8 }} // ✅ line-height lebih rapat dan responsif
|
||||
ta="justify"
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: state.findUnique.data?.content || "",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function Page() {
|
||||
const detail = useProxy(stateDesaPengumuman.pengumuman.findUnique)
|
||||
|
||||
const params = useParams()
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDesaPengumuman.pengumuman.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
if (!detail.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={400} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
||||
{/* Header */}
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack gap="xs" >
|
||||
<Group justify={"space-between"} align={"center"}>
|
||||
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
||||
{detail.data?.judul}
|
||||
</Text>
|
||||
<Group justify='end'>
|
||||
<Paper bg={colors['blue-button']} p={5}>
|
||||
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
</Group>
|
||||
<Paper bg={colors["white-1"]} p="md">
|
||||
<Text fz={"md"} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
||||
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
|
||||
{new Date(detail.data?.createdAt).toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</Text>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -1,20 +1,23 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Card, Divider, Grid, GridCol, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { Badge, Box, Card, Divider, Grid, GridCol, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../../desa/layanan/_com/BackButto';
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
function InformasiDesa() {
|
||||
const stateBerita = useProxy(stateDashboardBerita.berita)
|
||||
const statePengumuman = useProxy(stateDesaPengumuman.pengumuman)
|
||||
const router = useTransitionRouter()
|
||||
const stateBerita = useProxy(stateDashboardBerita.berita);
|
||||
const statePengumuman = useProxy(stateDesaPengumuman.pengumuman);
|
||||
|
||||
useEffect(() => {
|
||||
stateBerita.findFirst.load();
|
||||
@@ -23,116 +26,216 @@ function InformasiDesa() {
|
||||
statePengumuman.findRecent.load();
|
||||
}, []);
|
||||
|
||||
const dataBerita = stateBerita.findFirst.data
|
||||
const dataPengumuman = statePengumuman.findFirst.data
|
||||
|
||||
const dataBerita = stateBerita.findFirst.data;
|
||||
const dataPengumuman = statePengumuman.findFirst.data;
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Title ta="center" fz={{ base: 'h1', md: '2.5rem' }} c={colors['blue-button']} fw="bold">
|
||||
Informasi Desa
|
||||
</Text>
|
||||
</Title>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={10}>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap={30}>
|
||||
|
||||
{/* === BERITA UTAMA === */}
|
||||
{dataBerita && (
|
||||
<Paper shadow="md" radius="md" p="md">
|
||||
<Grid>
|
||||
<GridCol span={{ md: 6, base: 12 }}>
|
||||
<Image
|
||||
src={dataBerita.image?.link || "/fallback.jpg"}
|
||||
alt={dataBerita.judul}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
height={250}
|
||||
maw={600}
|
||||
loading="lazy"
|
||||
/>
|
||||
</GridCol>
|
||||
<GridCol span={{ md: 6, base: 12 }}>
|
||||
<Box>
|
||||
<Text fz="sm" c="dimmed">{dataBerita.kategoriBerita?.name} • {dayjs(dataBerita.createdAt).fromNow()}</Text>
|
||||
<Title order={1} fw="bold">{dataBerita.judul}</Title>
|
||||
<Text ta={"justify"} mt="xs" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: dataBerita.content }} />
|
||||
</Box>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => router.push(`/darmasaba/inovasi/layanan-online-desa/informasi-desa/detail-berita/${dataBerita.id}`)}
|
||||
>
|
||||
<Paper shadow="md" radius="lg" p="lg" withBorder>
|
||||
<Grid align="center" gutter="xl">
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Image
|
||||
src={dataBerita.image?.link || '/fallback.jpg'}
|
||||
alt={dataBerita.judul}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
height={280}
|
||||
loading="lazy"
|
||||
style={{ objectPosition: 'center' }}
|
||||
/>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Stack gap="xs">
|
||||
<Title order={2} fw={800}>
|
||||
{dataBerita.judul}
|
||||
</Title>
|
||||
<Group justify='space-between'>
|
||||
<Badge bg={colors['blue-button']}>
|
||||
{dataBerita.kategoriBerita?.name}
|
||||
</Badge>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{dayjs(dataBerita.createdAt).fromNow()}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text
|
||||
ta="justify"
|
||||
mt="xs"
|
||||
fz="md"
|
||||
lh={1.7}
|
||||
lineClamp={6}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: dataBerita.content }}
|
||||
/>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
)}
|
||||
<Stack py={10}>
|
||||
<Title order={3}>Berita Terbaru</Title>
|
||||
<Grid>
|
||||
|
||||
{/* === BERITA TERBARU === */}
|
||||
<Stack>
|
||||
<Title order={3} fw={700}>
|
||||
Berita Terbaru
|
||||
</Title>
|
||||
<Grid gutter="xl">
|
||||
{stateBerita.findRecent.data.map((item) => (
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }} key={item.id}>
|
||||
<Card shadow="sm" radius="md" withBorder h="100%">
|
||||
<Card.Section>
|
||||
<Image
|
||||
src={item.image?.link || "/placeholder.jpg"}
|
||||
alt={item.judul}
|
||||
height={160} // gambar fix height
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Card.Section>
|
||||
<Stack gap="xs" mt="sm">
|
||||
<Text fw={600} lineClamp={2}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Text size="sm" color="dimmed" lineClamp={2}>
|
||||
{item.deskripsi}
|
||||
</Text>
|
||||
<Text size="xs" c="gray">
|
||||
{dayjs(item.createdAt).fromNow()}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => router.push(`/darmasaba/inovasi/layanan-online-desa/informasi-desa/detail-berita/${item.id}`)}
|
||||
>
|
||||
<Card
|
||||
shadow="sm"
|
||||
radius="md"
|
||||
withBorder
|
||||
h="100%"
|
||||
>
|
||||
<Card.Section>
|
||||
<Image
|
||||
src={item.image?.link || '/placeholder.jpg'}
|
||||
alt={item.judul}
|
||||
height={160}
|
||||
fit="cover"
|
||||
radius="sm"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Card.Section>
|
||||
<Stack gap={4} mt="sm">
|
||||
<Text ta="justify"
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "normal",
|
||||
lineHeight: 1.5,
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||
<Text size="xs" c="gray">
|
||||
{dayjs(item.createdAt).fromNow()}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
</Stack>
|
||||
<Divider color={colors['blue-button']} my="md" />
|
||||
<Grid>
|
||||
<GridCol span={{ md: 6, base: 12 }}>
|
||||
|
||||
<Divider color={colors['blue-button']} my="lg" />
|
||||
|
||||
{/* === PENGUMUMAN === */}
|
||||
<Grid gutter="xl" align="stretch">
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
{dataPengumuman && (
|
||||
<Paper h={"97%"} shadow="md" radius="md" p="md">
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={1} fw="bold">{dataPengumuman.judul}</Title>
|
||||
<Text fz="sm" c="dimmed">{dataPengumuman.CategoryPengumuman?.name} • {dayjs(dataPengumuman.createdAt).fromNow()}</Text>
|
||||
<Box>
|
||||
<Text ta={"justify"} mt="xs" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: dataPengumuman.content }} />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<motion.div
|
||||
onClick={() => router.push(`/darmasaba/inovasi/layanan-online-desa/informasi-desa/detail-pengumuman/${dataPengumuman.id}`)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<Paper shadow="md" radius="lg" p="lg" h="100%" withBorder>
|
||||
<Stack gap="xs">
|
||||
<Title order={2} fw={800}>
|
||||
{dataPengumuman.judul}
|
||||
</Title>
|
||||
<Group justify='space-between'>
|
||||
<Badge bg={colors['blue-button']}>
|
||||
{dataPengumuman.CategoryPengumuman?.name}
|
||||
</Badge>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{dayjs(dataPengumuman.createdAt).fromNow()}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text
|
||||
ta="justify"
|
||||
mt="xs"
|
||||
fz="md"
|
||||
lh={1.7}
|
||||
lineClamp={8}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: dataPengumuman.content }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
)}
|
||||
</GridCol>
|
||||
<GridCol span={{ md: 6, base: 12 }}>
|
||||
<Stack py={10}>
|
||||
<Title order={3}>Pengumuman Terbaru</Title>
|
||||
<Grid>
|
||||
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Stack>
|
||||
<Title order={3} fw={700}>
|
||||
Pengumuman Terbaru
|
||||
</Title>
|
||||
<Grid gutter="lg">
|
||||
{statePengumuman.findRecent.data.map((item) => (
|
||||
<GridCol span={{ base: 12, sm: 8, md: 6 }} key={item.id}>
|
||||
<Card shadow="sm" radius="md" withBorder h="100%">
|
||||
<Stack gap="xs" mt="sm">
|
||||
<Text fw={600} lineClamp={2}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Text size="sm" color="dimmed" lineClamp={2}>
|
||||
{item.deskripsi}
|
||||
</Text>
|
||||
<Text size="xs" c="gray">
|
||||
{dayjs(item.createdAt).fromNow()}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
<GridCol span={{ base: 12, sm: 6 }} key={item.id}>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => router.push(`/darmasaba/inovasi/layanan-online-desa/informasi-desa/detail-pengumuman/${item.id}`)}
|
||||
>
|
||||
<Card
|
||||
shadow="xs"
|
||||
radius="md"
|
||||
withBorder
|
||||
h="100%"
|
||||
p="md"
|
||||
style={{ transition: '0.2s ease' }}
|
||||
className="hover:shadow-md"
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} lineClamp={2}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Text
|
||||
ta="justify"
|
||||
mt="xs"
|
||||
fz="md"
|
||||
lh={1.7}
|
||||
lineClamp={2}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
dangerouslySetInnerHTML={{ __html: item.content }}
|
||||
/>
|
||||
<Text size="xs" c="gray">
|
||||
{dayjs(item.createdAt).fromNow()}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
@@ -100,33 +100,33 @@ function PengaduanMasyarakat() {
|
||||
<Title order={3}>Ajukan Pengaduan Masyarakat</Title>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
||||
placeholder="masukkan nama"
|
||||
placeholder="Masukkan nama"
|
||||
onChange={(val) => (state.pengaduanMasyarakat.create.form.name = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Email</Text>}
|
||||
placeholder="masukkan email"
|
||||
placeholder="Masukkan email"
|
||||
onChange={(val) => (state.pengaduanMasyarakat.create.form.email = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
label={<Text fz="sm" fw="bold">Nomor Telepon</Text>}
|
||||
placeholder="masukkan nomor telepon"
|
||||
placeholder="Masukkan nomor telepon"
|
||||
onChange={(val) => (state.pengaduanMasyarakat.create.form.nomorTelepon = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">NIK</Text>}
|
||||
placeholder="masukkan nik"
|
||||
placeholder="Masukkan nik"
|
||||
onChange={(val) => (state.pengaduanMasyarakat.create.form.nik = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Judul Pengaduan</Text>}
|
||||
placeholder="masukkan judul pengaduan"
|
||||
placeholder="Masukkan judul pengaduan"
|
||||
onChange={(val) => (state.pengaduanMasyarakat.create.form.judulPengaduan = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Lokasi Kejadian</Text>}
|
||||
placeholder="masukkan lokasi kejadian"
|
||||
placeholder="Masukkan lokasi kejadian"
|
||||
onChange={(val) => (state.pengaduanMasyarakat.create.form.lokasiKejadian = val.target.value)}
|
||||
/>
|
||||
<Box>
|
||||
@@ -144,7 +144,7 @@ function PengaduanMasyarakat() {
|
||||
state.pengaduanMasyarakat.create.form.jenisPengaduanId = val ?? "";
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Jenis Pengaduan</Text>}
|
||||
placeholder="Pilih kategori produk"
|
||||
placeholder="Pilih jenis pengaduan"
|
||||
data={
|
||||
state.jenisPengaduan.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap';
|
||||
import programKreatifState from '@/app/admin/(dashboard)/_state/inovasi/program-kreatif';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Button, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap';
|
||||
|
||||
// const data = [
|
||||
// {
|
||||
@@ -75,17 +75,23 @@ function Page() {
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Group justify="space-between" mb="md" align='center'>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Program Kreatif Desa
|
||||
</Text>
|
||||
<TextInput
|
||||
placeholder="Cari program kreatif..."
|
||||
leftSection={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Group>
|
||||
<Grid align='center'>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Program Kreatif Desa
|
||||
</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
placeholder='Cari Program Kreatif'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
|
||||
@@ -64,8 +64,8 @@ function Page() {
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz={{ base: "h4", md: "h3" }} >
|
||||
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal). Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz="md" mt={4} >
|
||||
Pecalang dan Patwal (Patroli Pengawal) bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, ColorSwatch, Flex, Group, Modal, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Center, ColorSwatch, Flex, Group, Modal, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { DateTimePicker } from '@mantine/dates';
|
||||
import { useDebouncedValue, useDisclosure, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowRight, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
|
||||
function Page() {
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -53,7 +53,7 @@ function Page() {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Group justify="space-between" align="center">
|
||||
<BackButton />
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
@@ -61,9 +61,9 @@ function Page() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
w={{ base: "50%", md: "100%" }}
|
||||
w={{ base: "100%", md: "30%" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Group>
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Group justify="space-between">
|
||||
@@ -118,7 +118,7 @@ function Page() {
|
||||
return (
|
||||
<Paper radius={'lg'} key={k} bg={colors['white-trans-1']} p={'xl'}>
|
||||
<Stack>
|
||||
<Title c={colors['blue-button']} order={1}>{v.judul}</Title>
|
||||
<Text c={colors['blue-button']} lineClamp={3} truncate="end" fz="h4" fw="bold">{v.judul}</Text>
|
||||
<Text fs={'italic'} fz={'xl'}>
|
||||
{v.tanggalWaktu
|
||||
? new Date(v.tanggalWaktu).toLocaleString('id-ID')
|
||||
|
||||
@@ -45,7 +45,7 @@ function DetailPencegahanKriminalitas() {
|
||||
const data = kriminalitasState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py="md" px="md">
|
||||
<Box py="md" px={{ base: 'md', md: 100 }}>
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="light"
|
||||
|
||||
@@ -48,7 +48,7 @@ function Page() {
|
||||
<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' }}>
|
||||
<Text fz='md'>
|
||||
Keamanan Komunitas & Pencegahan Kriminal
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -78,7 +78,7 @@ function Page() {
|
||||
<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' }}>
|
||||
<Text fz='md'>
|
||||
Keamanan Komunitas & Pencegahan Kriminal
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -92,31 +92,63 @@ function Page() {
|
||||
Program Keamanan Berjalan
|
||||
</Text>
|
||||
<Stack pt={30} gap="lg">
|
||||
{data.length > 0 ? (
|
||||
data.map((item) => (
|
||||
<a key={item.id} href={`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`}>
|
||||
<Paper p="md" bg={colors['blue-button']} radius="md" shadow="sm">
|
||||
<Stack gap={"xs"}>
|
||||
<Box
|
||||
style={{
|
||||
minHeight: 300, // sesuaikan: tinggi area yg muat 3 item
|
||||
}}
|
||||
>
|
||||
{data.length > 0 ? (
|
||||
data.map((item) => (
|
||||
<Paper
|
||||
key={item.id}
|
||||
p="md"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
backgroundColor: colors['blue-button'],
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`)
|
||||
}
|
||||
onMouseEnter={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = '#1a3e7a')
|
||||
}
|
||||
onMouseLeave={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = colors['blue-button'])
|
||||
}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fz="h3" c={colors['white-1']}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</a>
|
||||
))
|
||||
) : (
|
||||
<Text color="dimmed">
|
||||
Tidak ada data pencegahan kriminalitas yang cocok
|
||||
</Text>
|
||||
)}
|
||||
))
|
||||
) : (
|
||||
<Text c="dimmed">Tidak ada data pencegahan kriminalitas yang cocok</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Button
|
||||
mt={20}
|
||||
fullWidth
|
||||
radius="xl"
|
||||
size="md"
|
||||
bg={colors['blue-button']}
|
||||
rightSection={<IconArrowRight size={20} color={colors['white-1']} />}
|
||||
onClick={() => router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/program-lainnya`)}
|
||||
variant="outline"
|
||||
color="blue"
|
||||
rightSection={<IconArrowRight size={20} />}
|
||||
styles={{
|
||||
root: {
|
||||
fontWeight: 600,
|
||||
borderWidth: 2,
|
||||
},
|
||||
}}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/darmasaba/keamanan/pencegahan-kriminalitas/program-lainnya`
|
||||
)
|
||||
}
|
||||
>
|
||||
Jelajahi Program Lainnya
|
||||
</Button>
|
||||
@@ -142,9 +174,7 @@ function Page() {
|
||||
<Text py={10} fz={{ base: 'h3', md: 'h2' }} fw="bold" c={colors['blue-button']}>
|
||||
{findFirst.data?.judul}
|
||||
</Text>
|
||||
<Text fz="h4" c={colors['blue-button']}>
|
||||
{findFirst.data?.deskripsiSingkat}
|
||||
</Text>
|
||||
<Text fz="h4" dangerouslySetInnerHTML={{ __html: findFirst.data?.deskripsiSingkat }} />
|
||||
</Paper>
|
||||
) : null}
|
||||
</Box>
|
||||
|
||||
@@ -21,12 +21,23 @@ import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
|
||||
import { IconArrowLeft } from '@tabler/icons-react';
|
||||
|
||||
function PencegahanKriminalitas() {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box>
|
||||
<Box pt={20} px={{ base: 'md', md: 100 }}>
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowLeft size={20} />}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
</Group>
|
||||
<HeaderSearch
|
||||
title="Program Pencegahan Kriminalitas"
|
||||
placeholder="Cari program atau deskripsi..."
|
||||
@@ -82,7 +93,7 @@ function ListPencegahanKriminalitas({ search }: { search: string }) {
|
||||
c="dimmed"
|
||||
lineClamp={2}
|
||||
dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat || '' }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
<Group justify="flex-end" mt="sm">
|
||||
<Tooltip label="Lihat detail program" withArrow>
|
||||
|
||||
@@ -43,7 +43,7 @@ function Page() {
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Kantor Polisi Terdekat
|
||||
</Text>
|
||||
<Text pb={15} fz={'h4'} >
|
||||
<Text pb={15} fz={'md'} >
|
||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -78,15 +78,12 @@ function Page() {
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">Pendahuluan</Text>
|
||||
<Divider my="xs" />
|
||||
<Text fz="md" lh={1.6} ta="justify">
|
||||
{state.findUnique.data.introduction?.content}
|
||||
</Text>
|
||||
<Text fz="md" lh={1.6} ta="justify" dangerouslySetInnerHTML={{ __html: state.findUnique.data.introduction?.content }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="h4" fw="bold">Kenali Gejala DBD</Text>
|
||||
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text>
|
||||
<Divider my="xs" />
|
||||
<Text fz="md" fw="semibold">{state.findUnique.data.symptom?.title}</Text>
|
||||
<Text fz="md" lh={1.6} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import artikelKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import { Anchor, Box, Card, Divider, Group, Image, Loader, Paper, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { Box, Button, Card, Divider, Group, Image, Loader, Paper, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -28,9 +28,9 @@ function ArtikelKesehatanPage() {
|
||||
<Box>
|
||||
<Paper p="xl" bg={colors['white-trans-1']} radius="xl" shadow="md">
|
||||
<Stack gap="lg">
|
||||
<Title order={2} ta="center" c={colors['blue-button']}>
|
||||
<Text ta="center" fw={700} fz="32px" c={colors['blue-button']}>
|
||||
Artikel Kesehatan
|
||||
</Title>
|
||||
</Text>
|
||||
<Divider size="sm" color={colors['blue-button']} />
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
@@ -51,31 +51,30 @@ function ArtikelKesehatanPage() {
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
|
||||
>
|
||||
<Card.Section>
|
||||
<Image src={item.image?.link} alt={item.title} height={200} fit="cover" loading="lazy"/>
|
||||
<Image style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px' }} src={item.image?.link} alt={item.title} height={200} fit="cover" loading="lazy" />
|
||||
</Card.Section>
|
||||
<Stack gap="xs" mt="md">
|
||||
<Text fw="bold" fz="xl" c="dark">{item.title}</Text>
|
||||
<Text fw="bold" fz="xl" c={colors['blue-button']}>{item.title}</Text>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={16} color={colors['blue-button']} />
|
||||
<IconCalendar size={16} color='gray' />
|
||||
<Text fz="sm" c="dimmed">
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} • Dinas Kesehatan
|
||||
</Text>
|
||||
</Group>
|
||||
<Text fz="md" c="dark" lineClamp={3}>
|
||||
<Text fz="md" lineClamp={3}>
|
||||
{item.content}
|
||||
</Text>
|
||||
<Tooltip label="Baca artikel lengkap">
|
||||
<Anchor
|
||||
<Group justify="flex-start">
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
radius="lg"
|
||||
size="sm"
|
||||
rightSection={<IconChevronRight size={18} />}
|
||||
onClick={() => router.push(`/darmasaba/kesehatan/data-kesehatan-warga/artikel-kesehatan-page/${item.id}`)}
|
||||
variant="light"
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
<Group gap="xs">
|
||||
<Text fw="bold" fz="md">Baca Selengkapnya</Text>
|
||||
<IconChevronRight size={18} />
|
||||
</Group>
|
||||
</Anchor>
|
||||
</Tooltip>
|
||||
Baca Selengkapnya
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
))
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, Anchor, AspectRatio, Badge, Box, Button, Card, Chip, CopyButton, Divider, Grid, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title, Tooltip } from '@mantine/core';
|
||||
import { ActionIcon, AspectRatio, Badge, Box, Button, Card, CopyButton, Divider, Grid, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, ThemeIcon, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconBrandWhatsapp, IconCheck, IconCopy, IconDeviceLandlinePhone, IconHeart, IconInfoCircle, IconMail, IconMapPin, IconMoodEmpty, IconSearch, IconStethoscope, IconUser, IconUsersGroup, IconWallet } from '@tabler/icons-react';
|
||||
import { IconBrandWhatsapp, IconCheck, IconCopy, IconDeviceLandlinePhone, IconHeart, IconInfoCircle, IconMail, IconMapPin, IconMoodEmpty, IconSearch, IconUser } from '@tabler/icons-react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useMemo } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -149,11 +149,6 @@ function Page() {
|
||||
</CopyButton>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group gap="xs" mt="sm" wrap="wrap">
|
||||
<Chip defaultChecked radius="xl" variant="light" icon={<IconStethoscope size={16} />}>Layanan Medis</Chip>
|
||||
<Chip radius="xl" variant="light" icon={<IconUsersGroup size={16} />}>Ramah Keluarga</Chip>
|
||||
<Chip radius="xl" variant="light" icon={<IconWallet size={16} />}>Pembayaran Non-Tunai</Chip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Box>
|
||||
@@ -210,7 +205,6 @@ function Page() {
|
||||
<Button variant="light" leftSection={<IconBrandWhatsapp size={18} />} component="a" href={`https://wa.me/${kontak.whatsapp.replace(/\D/g, '')}`} target="_blank" aria-label="Hubungi WhatsApp">WhatsApp</Button>
|
||||
<Button variant="light" leftSection={<IconMail size={18} />} component="a" href={`mailto:${kontak.email}`} aria-label="Kirim Email">Email</Button>
|
||||
</Group>
|
||||
<Anchor target="_blank" underline="hover">Kunjungi situs resmi</Anchor>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
@@ -246,15 +240,8 @@ function Page() {
|
||||
</Table>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Grid gutter="lg">
|
||||
<Grid.Col span={{ base: 12, md: 8 }}>
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Fasilitas Pendukung</Title>
|
||||
<Divider />
|
||||
@@ -270,8 +257,7 @@ function Page() {
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
|
||||
<Card radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={3}>Layanan & Tarif</Title>
|
||||
@@ -309,10 +295,11 @@ function Page() {
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }} pb="xl">
|
||||
<Paper radius="xl" p="lg" withBorder>
|
||||
<Stack gap="md">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
|
||||
import colors from '@/con/colors';
|
||||
import { Anchor, Badge, Box, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Badge, Box, Button, Card, Divider, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconChevronRight, IconClock, IconMapPin } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { IconMapPin, IconClock, IconArrowRight } from '@tabler/icons-react';
|
||||
|
||||
function FasilitasKesehatanPage() {
|
||||
const state = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
|
||||
@@ -36,72 +36,73 @@ function FasilitasKesehatanPage() {
|
||||
</Text>
|
||||
<Divider size="sm" color={colors['blue-button']} />
|
||||
<Stack gap="lg">
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
<Text fz="lg" c="dimmed">
|
||||
Belum ada fasilitas kesehatan yang tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
state.findMany.data.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
withBorder
|
||||
radius="xl"
|
||||
shadow="sm"
|
||||
p="lg"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #fdfdfd, #f7faff)',
|
||||
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.transform = 'translateY(-4px)';
|
||||
(e.currentTarget as HTMLElement).style.boxShadow = '0 8px 20px rgba(0,0,0,0.08)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.transform = 'translateY(0px)';
|
||||
(e.currentTarget as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.05)';
|
||||
}}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text fw={700} fz="lg" c={colors['blue-button']}>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Badge color="blue" radius="sm" variant="light" fz="xs">
|
||||
Aktif
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={18} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fz="sm" c="dimmed">
|
||||
{item.informasiumum.alamat}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconClock size={18} stroke={1.5} color={colors['blue-button']} />
|
||||
<Text fz="sm" c="dimmed">
|
||||
{item.informasiumum.jamOperasional}
|
||||
</Text>
|
||||
</Group>
|
||||
<Anchor
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/darmasaba/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/${item.id}`
|
||||
)
|
||||
}
|
||||
c={colors['blue-button']}
|
||||
fz="sm"
|
||||
fw={600}
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: '4px' }}
|
||||
>
|
||||
Lihat Detail
|
||||
<IconArrowRight size={16} stroke={1.5} />
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
{state.findMany.data.length === 0 ? (
|
||||
<Box py="xl" ta="center">
|
||||
<Text fz="lg" c="dimmed">
|
||||
Belum ada fasilitas kesehatan yang tersedia
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
state.findMany.data.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
withBorder
|
||||
radius="xl"
|
||||
shadow="sm"
|
||||
p="lg"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #fdfdfd, #f7faff)',
|
||||
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.transform = 'translateY(-4px)';
|
||||
(e.currentTarget as HTMLElement).style.boxShadow = '0 8px 20px rgba(0,0,0,0.08)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.transform = 'translateY(0px)';
|
||||
(e.currentTarget as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.05)';
|
||||
}}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text fw={700} fz="lg" c={colors['blue-button']}>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Badge color="blue" radius="sm" variant="light" fz="xs">
|
||||
Aktif
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={18} stroke={1.5} />
|
||||
<Text fz="sm">
|
||||
{item.informasiumum.alamat}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconClock size={18} stroke={1.5} />
|
||||
<Text fz="sm">
|
||||
{item.informasiumum.jamOperasional}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group justify="flex-start">
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
radius="lg"
|
||||
size="sm"
|
||||
rightSection={<IconChevronRight size={18} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/darmasaba/kesehatan/data-kesehatan-warga/fasilitas-kesehatan-page/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -4,14 +4,16 @@ import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Group,
|
||||
Modal,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useDisclosure, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconMail, IconPhone, IconUser } from '@tabler/icons-react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -21,6 +23,7 @@ import CreatePendaftaran from '../create/page';
|
||||
function Page() {
|
||||
const params = useParams();
|
||||
const state = useProxy(jadwalkegiatanState);
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findUnique.load(params?.id as string);
|
||||
@@ -66,28 +69,38 @@ function Page() {
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Deskripsi Kegiatan</Text>
|
||||
<Divider />
|
||||
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsijadwalkegiatan.deskripsi }} />
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Layanan yang Tersedia</Text>
|
||||
<Divider />
|
||||
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.layananjadwalkegiatan.content }} />
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Syarat & Ketentuan</Text>
|
||||
<Divider />
|
||||
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.syaratketentuanjadwalkegiatan.content }} />
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Dokumen yang Perlu Dibawa</Text>
|
||||
<Divider />
|
||||
<Text ta="justify" fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
|
||||
<Text ta="justify" fz="md" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: state.findUnique.data.dokumenjadwalkegiatan.content }} />
|
||||
</Stack>
|
||||
|
||||
<CreatePendaftaran />
|
||||
<Stack gap="sm">
|
||||
<Text fz="lg" fw="bold">Pendaftaran Kegiatan</Text>
|
||||
<Divider />
|
||||
<Group>
|
||||
<Button onClick={open}>Buat Pendaftaran</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Modal opened={opened} onClose={close}>
|
||||
<CreatePendaftaran />
|
||||
</Modal>
|
||||
|
||||
<Paper p="lg" radius="md" bg={colors['blue-button-trans']} shadow="sm">
|
||||
<Stack gap="xs">
|
||||
|
||||
@@ -49,7 +49,7 @@ function JadwalKegiatanPage() {
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between">
|
||||
<Text fw={700} fz="xl">
|
||||
<Text fw={700} fz="xl" c={colors['blue-button']}>
|
||||
{item.informasijadwalkegiatan.name}
|
||||
</Text>
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']}>
|
||||
@@ -62,20 +62,20 @@ function JadwalKegiatanPage() {
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconClockHour4 size={18} color={colors['blue-button']} />
|
||||
<IconClockHour4 size={18} />
|
||||
<Text fz="sm">{item.informasijadwalkegiatan.waktu}</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={18} color={colors['blue-button']} />
|
||||
<IconMapPin size={18} />
|
||||
<Text fz="sm">{item.informasijadwalkegiatan.lokasi}</Text>
|
||||
</Group>
|
||||
|
||||
<Divider my="sm" />
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Group justify="flex-start">
|
||||
<Button
|
||||
variant="light"
|
||||
bg={colors['blue-button']}
|
||||
radius="lg"
|
||||
size="sm"
|
||||
rightSection={<IconChevronRight size={18} />}
|
||||
@@ -84,14 +84,6 @@ function JadwalKegiatanPage() {
|
||||
`/darmasaba/kesehatan/data-kesehatan-warga/jadwal-kegiatan-page/${item.id}`
|
||||
)
|
||||
}
|
||||
styles={{
|
||||
root: {
|
||||
background: colors['blue-button'],
|
||||
color: 'white',
|
||||
boxShadow: '0 0 12px rgba(0, 123, 255, 0.4)',
|
||||
transition: 'all 0.2s ease',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Lihat Detail & Daftar
|
||||
</Button>
|
||||
|
||||
@@ -28,16 +28,18 @@ function DetailInfoWabahPenyakitUser() {
|
||||
const data = state.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Box py={10} px={{ base: 'md', md: 100 }}>
|
||||
{/* Tombol Back */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Box>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Wrapper Detail */}
|
||||
<Paper
|
||||
@@ -71,7 +73,6 @@ function DetailInfoWabahPenyakitUser() {
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
|
||||
@@ -61,7 +61,7 @@ function Page() {
|
||||
>
|
||||
Informasi Wabah & Penyakit
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed" mt={4}>
|
||||
<Text fz="md" mt={4}>
|
||||
Dapatkan informasi terbaru mengenai wabah dan penyakit yang sedang
|
||||
diawasi.
|
||||
</Text>
|
||||
@@ -84,7 +84,7 @@ function Page() {
|
||||
<Center py="6xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<IconInfoCircle size={50} color={colors['blue-button']} />
|
||||
<Text fz="lg" fw={500} c="dimmed">
|
||||
<Text fz="lg" fw={500} >
|
||||
Tidak ada data yang cocok dengan pencarian Anda.
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -101,17 +101,35 @@ function Page() {
|
||||
bg={colors['white-trans-1']}
|
||||
style={{
|
||||
transition: 'transform 200ms ease, box-shadow 200ms ease',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
<Stack gap="sm" style={{ flex: 1 }}>
|
||||
{/* Gambar */}
|
||||
<Box
|
||||
h={180}
|
||||
src={v.image.link}
|
||||
alt={v.name}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
w="100%"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.name}
|
||||
fit="cover"
|
||||
w="100%"
|
||||
h="100%"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Judul dan badge */}
|
||||
<Group justify="space-between" mt="sm">
|
||||
<Text fw={700} fz="lg" c={colors['blue-button']}>
|
||||
{v.name}
|
||||
@@ -120,25 +138,46 @@ function Page() {
|
||||
Wabah
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Text fz="sm" c="dimmed">
|
||||
Diposting: {new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||
Diposting:{' '}
|
||||
{new Date(v.createdAt).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
|
||||
</Text>
|
||||
<Divider />
|
||||
<Text fz="sm" lh={1.5} lineClamp={3} truncate="end">
|
||||
{v.deskripsiSingkat}
|
||||
</Text>
|
||||
<Button variant="light" radius="md" size="md" onClick={() => router.push(`/darmasaba/kesehatan/info-wabah-penyakit/${v.id}`)}>
|
||||
Selengkapnya
|
||||
</Button>
|
||||
|
||||
{/* Bagian deskripsi dan tombol */}
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
|
||||
<Text
|
||||
fz="sm"
|
||||
lh={1.5}
|
||||
lineClamp={3}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsiSingkat }}
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
mt="md"
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/kesehatan/info-wabah-penyakit/${v.id}`)
|
||||
}
|
||||
>
|
||||
Selengkapnya
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ function Page() {
|
||||
<Text fz={{ base: '2rem', md: '2.8rem' }} c={colors['blue-button']} fw={800}>
|
||||
Kontak Darurat
|
||||
</Text>
|
||||
<Text c="dimmed" fz="md" mt={4}>
|
||||
<Text fz="md" mt={4}>
|
||||
Hubungi layanan penting dengan cepat dan mudah
|
||||
</Text>
|
||||
</GridCol>
|
||||
@@ -128,7 +128,7 @@ function Page() {
|
||||
<Text ta="center" fw={700} fz="lg" c={colors['blue-button']}>
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed" ta="center" lineClamp={3}>
|
||||
<Text fz="sm" ta="center" lineClamp={3}>
|
||||
<span style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Text>
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailPenangananDaruratUser() {
|
||||
const state = useProxy(penangananDarurat);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Stack py={40}>
|
||||
<Skeleton height={400} radius="md" />
|
||||
<Skeleton height={20} width="80%" radius="md" />
|
||||
<Skeleton height={20} width="60%" radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = state.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={20}>
|
||||
{/* Tombol Back */}
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}
|
||||
mb={20}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Wrapper Detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '70%', lg: '60%' }}
|
||||
mx="auto"
|
||||
bg={colors['white-1']}
|
||||
p="xl"
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md" align="center" ta="center">
|
||||
<Text fz="xl" fw={700} c={colors['blue-button']}>
|
||||
{data.name || 'Penanganan Darurat'}
|
||||
</Text>
|
||||
|
||||
{data.image?.link && (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.name}
|
||||
radius="md"
|
||||
mah={300}
|
||||
fit="contain"
|
||||
loading="lazy"
|
||||
mb="md"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<Text
|
||||
fz="md"
|
||||
ta="justify"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailPenangananDaruratUser;
|
||||
@@ -3,6 +3,7 @@ import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penangan
|
||||
import colors from '@/con/colors'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Grid,
|
||||
GridCol,
|
||||
@@ -51,7 +52,7 @@ function Page() {
|
||||
<Text fz={{ base: 30, md: 40 }} c={colors['blue-button']} fw={800} lh={1.2}>
|
||||
Penanganan Darurat
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed" mt={4}>
|
||||
<Text fz="md" mt={4}>
|
||||
Informasi cepat dan jelas untuk situasi darurat kesehatan
|
||||
</Text>
|
||||
</GridCol>
|
||||
@@ -104,32 +105,28 @@ function Page() {
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'translateY(0)')}
|
||||
>
|
||||
<Stack align="center" gap="md">
|
||||
<Center>
|
||||
<Box
|
||||
<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%',
|
||||
aspectRatio: '16/9',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
transition: 'transform 0.4s ease',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
/>
|
||||
</Box>
|
||||
|
||||
</Center>
|
||||
<Stack gap={4} w="100%">
|
||||
<Text
|
||||
fz="lg"
|
||||
@@ -142,13 +139,22 @@ function Page() {
|
||||
</Text>
|
||||
<Box>
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
lineClamp={4}
|
||||
fz="md"
|
||||
lineClamp={3}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
radius="xl"
|
||||
size="md"
|
||||
component="a"
|
||||
href={`/darmasaba/kesehatan/penanganan-darurat/${v.id}`}
|
||||
bg={colors['blue-button']}
|
||||
c="white"
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -42,6 +42,7 @@ export default function DetailPosyanduUser() {
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{/* Konten utama */}
|
||||
<Paper
|
||||
withBorder
|
||||
p="xl"
|
||||
@@ -85,15 +86,15 @@ export default function DetailPosyanduUser() {
|
||||
|
||||
{/* Info utama */}
|
||||
<Stack gap="sm" mt="md">
|
||||
<Flex align="flex-start" gap="xs">
|
||||
<IconPhone size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||
<Flex align="center" gap="xs">
|
||||
<IconPhone size={18} stroke={1.5} />
|
||||
<Text fz="sm" c="dimmed">
|
||||
{data.nomor || 'Nomor tidak tersedia'}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex align="flex-start" gap="xs">
|
||||
<IconCalendar size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||
<Flex align="center" gap="xs">
|
||||
<IconCalendar size={18} stroke={1.5} />
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
@@ -102,8 +103,8 @@ export default function DetailPosyanduUser() {
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex align="flex-start" gap="xs">
|
||||
<IconInfoCircle size={18} stroke={1.5} style={{ marginTop: 3 }} />
|
||||
<Flex align="center" gap="xs">
|
||||
<IconInfoCircle size={18} stroke={1.5} />
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
|
||||
@@ -28,10 +28,11 @@ function Page() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
|
||||
<Stack bg={colors.Bg} py="xl" gap="xl">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Paper
|
||||
px={{ base: 'md', md: 100 }}
|
||||
py="xl"
|
||||
@@ -70,7 +71,7 @@ function Page() {
|
||||
<Group gap="xl">
|
||||
<Group gap="xs">
|
||||
<Tooltip label="Tanggal dibuat" withArrow>
|
||||
<IconCalendar size={20} stroke={1.5} />
|
||||
<IconCalendar color='gray' size={20} stroke={1.5} />
|
||||
</Tooltip>
|
||||
<Text size="sm" c="dimmed">
|
||||
{state.findUnique.data.createdAt
|
||||
@@ -84,13 +85,14 @@ function Page() {
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<Tooltip label="Dibuat oleh" withArrow>
|
||||
<IconUser size={20} stroke={1.5} />
|
||||
<IconUser color='gray' size={20} stroke={1.5} />
|
||||
</Tooltip>
|
||||
<Text size="sm" c="dimmed">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import programKesehatan from "@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan";
|
||||
import colors from "@/con/colors";
|
||||
import {
|
||||
Box,
|
||||
@@ -15,9 +16,9 @@ import {
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
Transition,
|
||||
Transition
|
||||
} from "@mantine/core";
|
||||
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
|
||||
import {
|
||||
IconBarbell,
|
||||
IconCalendar,
|
||||
@@ -26,12 +27,10 @@ import {
|
||||
IconUser,
|
||||
IconUsersGroup,
|
||||
} from "@tabler/icons-react";
|
||||
import BackButton from "../../desa/layanan/_com/BackButto";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import programKesehatan from "@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan";
|
||||
import { useState } from "react";
|
||||
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
import BackButton from "../../desa/layanan/_com/BackButto";
|
||||
|
||||
const manfaatProgram = [
|
||||
{
|
||||
@@ -88,7 +87,7 @@ export default function Page() {
|
||||
>
|
||||
Program Kesehatan Desa
|
||||
</Text>
|
||||
<Text fz="lg" c="dimmed" mt="xs">
|
||||
<Text fz="lg" mt="xs">
|
||||
Temukan berbagai program kesehatan untuk mendukung kualitas hidup
|
||||
masyarakat Darmasaba.
|
||||
</Text>
|
||||
@@ -126,17 +125,36 @@ export default function Page() {
|
||||
className="hover-scale"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box h={180} w="100%">
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.name}
|
||||
radius="xl"
|
||||
w="100%"
|
||||
h="100%"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
<Center>
|
||||
<Box
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 180, // 🔥 tinggi fix biar semua seragam
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
backgroundColor: '#f0f2f5', // fallback kalau gambar loading
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={v.image?.link || '/img/default.png'}
|
||||
alt={v.name}
|
||||
fit="cover"
|
||||
width="100%"
|
||||
height="100%"
|
||||
loading="lazy"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
transition: 'transform 0.4s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
</Center>
|
||||
|
||||
<Box px="lg" pb="lg">
|
||||
<Text
|
||||
@@ -149,7 +167,7 @@ export default function Page() {
|
||||
</Text>
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
ta={"justify"}
|
||||
lineClamp={3}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
@@ -175,7 +193,6 @@ export default function Page() {
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Tooltip label="Lihat detail program" withArrow>
|
||||
<Button
|
||||
mt="lg"
|
||||
fullWidth
|
||||
@@ -192,7 +209,6 @@ export default function Page() {
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -230,7 +246,7 @@ export default function Page() {
|
||||
>
|
||||
Manfaat Program Kesehatan
|
||||
</Text>
|
||||
<Text fz="lg" c="dimmed" maw={700}>
|
||||
<Text fz="lg" maw={700}>
|
||||
Program kesehatan Desa Darmasaba berperan penting dalam meningkatkan
|
||||
kesejahteraan dan kualitas hidup warganya.
|
||||
</Text>
|
||||
@@ -260,7 +276,7 @@ export default function Page() {
|
||||
<Text ta="center" fw="bold" fz="xl" c={colors["blue-button"]}>
|
||||
{v.title}
|
||||
</Text>
|
||||
<Text ta="center" fz="sm" c="dimmed">
|
||||
<Text ta="center" fz="sm">
|
||||
{v.desc}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -43,7 +43,7 @@ function Page() {
|
||||
<Text fz={{ base: "2rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||
Daftar Puskesmas
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Text fz="md">
|
||||
Temukan informasi lengkap mengenai layanan, kontak, dan lokasi Puskesmas Darmasaba
|
||||
</Text>
|
||||
</GridCol>
|
||||
@@ -93,20 +93,23 @@ function Page() {
|
||||
<Text fw={600} fz="lg" lineClamp={1}>{v.name}</Text>
|
||||
<Badge color="blue" variant="light" radius="sm" fz="xs">Aktif</Badge>
|
||||
</Group>
|
||||
<Stack gap={4}>
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={16} />
|
||||
<Text fz="sm" c="dimmed" lineClamp={2}>{v.alamat}</Text>
|
||||
<Stack gap={6}>
|
||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||
<Box pt={2}><IconMapPin size={16} /></Box>
|
||||
<Text fz="sm" c="dimmed">{v.alamat}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconPhone size={16} />
|
||||
|
||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||
<Box pt={2}><IconPhone size={16} /></Box>
|
||||
<Text fz="sm" c="dimmed">{v.kontak.kontakPuskesmas}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconMail size={16} />
|
||||
|
||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||
<Box pt={2}><IconMail size={16} /></Box>
|
||||
<Text fz="sm" c="dimmed">{v.kontak.email}</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Anchor
|
||||
href={`/darmasaba/kesehatan/puskesmas/${v.id}`}
|
||||
fz="sm"
|
||||
|
||||
@@ -71,8 +71,11 @@ function Page() {
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Group>
|
||||
<Text fz="lg" c={'black'}>
|
||||
Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya. Fokus utama meliputi penghijauan, pengelolaan sampah, dan perlindungan kawasan hijau.
|
||||
<Text fz="md" >
|
||||
Desa Darmasaba menjaga dan mengembangkan lingkungan demi kesejahteraan warganya.
|
||||
</Text>
|
||||
<Text fz="md">
|
||||
Fokus utama meliputi penghijauan, pengelolaan sampah, dan perlindungan kawasan hijau.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Create a new component: components/EdukasiCard.tsx
|
||||
'use client';
|
||||
|
||||
import { Box, Paper, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface EdukasiCardProps {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export function EdukasiCard({ icon, title, description, color = '#1e88e5' }: EdukasiCardProps) {
|
||||
return (
|
||||
<Paper
|
||||
p={{ base: 'md', md: 'lg' }}
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
withBorder
|
||||
style={{
|
||||
height: '100%',
|
||||
transition: 'transform 0.2s, box-shadow 0.2s',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack h="100%" justify="space-between" gap="md">
|
||||
<Box>
|
||||
<Stack align="center" gap="xs" mb="md">
|
||||
<Box style={{ color }}>{icon}</Box>
|
||||
<Tooltip label={title} maw={250} multiline withArrow position="top">
|
||||
<Text
|
||||
fz={{ base: 'h5', md: 'h4' }}
|
||||
fw={700}
|
||||
c={color}
|
||||
ta="center"
|
||||
lineClamp={2}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
minHeight: '3.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: title }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Text
|
||||
size="sm"
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.6,
|
||||
color: 'var(--mantine-color-gray-7)'
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -1,128 +1,104 @@
|
||||
'use client'
|
||||
import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
'use client';
|
||||
|
||||
import { Box, Container, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconLeaf, IconPlant2, IconRecycle } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
function Page() {
|
||||
const tujuan = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi.findById)
|
||||
const materi = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan.findById)
|
||||
const contoh = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan.findById)
|
||||
import stateEdukasiLingkungan from '@/app/admin/(dashboard)/_state/lingkungan/edukasi-lingkungan';
|
||||
import colors from '@/con/colors';
|
||||
import { EdukasiCard } from './component/edukasiCard';
|
||||
|
||||
function LoadingSkeleton() {
|
||||
return (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
|
||||
{[1, 2, 3].map((item) => (
|
||||
<Skeleton key={item} height={300} radius="md" />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EdukasiLingkunganPage() {
|
||||
const tujuan = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi.findById);
|
||||
const materi = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan.findById);
|
||||
const contoh = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan.findById);
|
||||
|
||||
useShallowEffect(() => {
|
||||
tujuan.load('edit')
|
||||
materi.load('edit')
|
||||
contoh.load('edit')
|
||||
}, [])
|
||||
tujuan.load('edit');
|
||||
materi.load('edit');
|
||||
contoh.load('edit');
|
||||
}, []);
|
||||
|
||||
if (tujuan.loading || !tujuan.data || materi.loading || !materi.data || contoh.loading || !contoh.data) {
|
||||
const isLoading = tujuan.loading || !tujuan.data ||
|
||||
materi.loading || !materi.data ||
|
||||
contoh.loading || !contoh.data;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
<Stack py="xl" px={{ base: 'md', md: 'xl' }}>
|
||||
<BackButton />
|
||||
<LoadingSkeleton />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Stack bg={colors.Bg} py="xl" gap="xl" px={{ base: 'md', md: 'xl' }}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }} pb={20}>
|
||||
<Text ta={'center'} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||
<Container size="lg" ta="center">
|
||||
<Text
|
||||
component="h1"
|
||||
fz={{ base: 'h2', md: '2.5rem' }}
|
||||
c={colors['blue-button']}
|
||||
fw={700}
|
||||
mb="md"
|
||||
>
|
||||
Edukasi Lingkungan
|
||||
</Text>
|
||||
<Text ta={'center'} fz="h4" c="black">
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
c="dimmed"
|
||||
maw={800}
|
||||
mx="auto"
|
||||
>
|
||||
Program edukasi ini membimbing masyarakat untuk peduli dan bertanggung jawab terhadap alam,
|
||||
meningkatkan kesehatan, kenyamanan, dan keberlanjutan hidup bersama.
|
||||
</Text>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg" style={{ alignItems: 'stretch' }}>
|
||||
{/* Tujuan Edukasi Lingkungan */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md" style={{ width: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Tooltip label={tujuan.data?.judul} position="top" withArrow>
|
||||
<Stack gap={4} align="center">
|
||||
<IconLeaf size={28} color={colors['blue-button']} />
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{tujuan.data?.judul}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Text
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: tujuan.data?.deskripsi || '' }}
|
||||
/>
|
||||
<Box style={{ flexGrow: 1 }} />
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Materi Edukasi Lingkungan */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<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" dangerouslySetInnerHTML={{ __html: materi.data?.judul || '' }} />
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Text
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: materi.data?.deskripsi || '' }}
|
||||
/>
|
||||
<Box style={{ flexGrow: 1 }} />
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Contoh Edukasi Lingkungan */}
|
||||
<Box style={{ display: 'flex', height: '100%' }}>
|
||||
<Paper p={20} bg={colors['white-trans-1']} shadow="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Tooltip label={contoh.data?.judul} position="top" withArrow>
|
||||
<Stack gap={4} align="center">
|
||||
<IconPlant2 size={28} color={colors['blue-button']} />
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{contoh.data?.judul}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Text
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
flexGrow: 1
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: contoh.data?.deskripsi || '' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Container size="xl">
|
||||
<SimpleGrid
|
||||
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||
spacing="xl"
|
||||
verticalSpacing={{ base: 'md', md: 'xl' }}
|
||||
>
|
||||
<EdukasiCard
|
||||
icon={<IconLeaf size={32} />}
|
||||
title={tujuan.data?.judul || ''}
|
||||
description={tujuan.data?.deskripsi || ''}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
|
||||
<EdukasiCard
|
||||
icon={<IconRecycle size={32} />}
|
||||
title={materi.data?.judul || ''}
|
||||
description={materi.data?.deskripsi || ''}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
|
||||
<EdukasiCard
|
||||
icon={<IconPlant2 size={32} />}
|
||||
title={contoh.data?.judul || ''}
|
||||
description={contoh.data?.deskripsi || ''}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Container>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
}
|
||||
@@ -1,323 +1,5 @@
|
||||
// 'use client'
|
||||
// import colors from '@/con/colors';
|
||||
// import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
// import { IconSearch } from '@tabler/icons-react';
|
||||
// import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
// import React, { useEffect, useState } from 'react';
|
||||
// import BackButton from '../../../desa/layanan/_com/BackButto';
|
||||
|
||||
|
||||
// type HeaderSearchProps = {
|
||||
// placeholder?: string;
|
||||
// searchIcon?: React.ReactNode;
|
||||
// value?: string;
|
||||
// onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
// children?: React.ReactNode;
|
||||
// };
|
||||
|
||||
// function LayoutTabsGotongRoyong({
|
||||
// children,
|
||||
// placeholder = "pencarian",
|
||||
// searchIcon = <IconSearch size={20} />
|
||||
// }: HeaderSearchProps) {
|
||||
// const router = useRouter();
|
||||
// const pathname = usePathname();
|
||||
// const searchParams = useSearchParams();
|
||||
|
||||
// // Get active tab from URL path
|
||||
// const activeTab = pathname.split('/').pop() || 'semua';
|
||||
|
||||
// // Get initial search value from URL
|
||||
// const initialSearch = searchParams.get('search') || '';
|
||||
// const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
// const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// // Update active tab state when pathname changes
|
||||
// const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
// useEffect(() => {
|
||||
// setActiveTabState(activeTab);
|
||||
// }, [activeTab]);
|
||||
|
||||
// // Clean up timeouts on unmount
|
||||
// useEffect(() => {
|
||||
// return () => {
|
||||
// if (searchTimeout !== null) {
|
||||
// clearTimeout(searchTimeout);
|
||||
// }
|
||||
// };
|
||||
// }, [searchTimeout]);
|
||||
|
||||
// // Handle search input change with debounce
|
||||
// const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = event.target.value;
|
||||
// setSearchValue(value);
|
||||
|
||||
// // Clear previous timeout
|
||||
// if (searchTimeout !== null) {
|
||||
// clearTimeout(searchTimeout);
|
||||
// }
|
||||
|
||||
// // Set new timeout
|
||||
// const newTimeout = window.setTimeout(() => {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
// if (value) {
|
||||
// params.set('search', value);
|
||||
// } else {
|
||||
// params.delete('search');
|
||||
// }
|
||||
|
||||
// // Only update URL if the search value has actually changed
|
||||
// if (params.toString() !== searchParams.toString()) {
|
||||
// router.push(`/darmasaba/lingkungan/gotong-royong/${activeTab}?${params.toString()}`);
|
||||
// }
|
||||
// }, 500); // 500ms debounce delay
|
||||
|
||||
// setSearchTimeout(newTimeout);
|
||||
// };
|
||||
// const tabs = [
|
||||
// {
|
||||
// label: "Semua",
|
||||
// value: "semua",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/semua"
|
||||
// },
|
||||
// {
|
||||
// label: "Kebersihan",
|
||||
// value: "kebersihan",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/kebersihan"
|
||||
// },
|
||||
// {
|
||||
// label: "Infrastruktur",
|
||||
// value: "infrastruktur",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/infrastruktur"
|
||||
// },
|
||||
// {
|
||||
// label: "Sosial",
|
||||
// value: "sosial",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/sosial"
|
||||
// },
|
||||
// {
|
||||
// label: "Lingkungan",
|
||||
// value: "lingkungan",
|
||||
// href: "/darmasaba/lingkungan/gotong-royong/lingkungan"
|
||||
// }
|
||||
// ];
|
||||
// const handleTabChange = (value: string | null) => {
|
||||
// if (!value) return;
|
||||
// const tab = tabs.find(t => t.value === value);
|
||||
// if (tab) {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
// router.push(`/darmasaba/lingkungan/gotong-royong/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
// {/* Header */}
|
||||
// <Box px={{ base: "md", md: 100 }}>
|
||||
// <BackButton />
|
||||
// </Box>
|
||||
// <Container size="lg" px="md">
|
||||
// <Stack align="center" gap="0" >
|
||||
// <Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
// Gotong Royong Desa Darmasaba
|
||||
// </Text>
|
||||
// <Text ta="center" px="md">
|
||||
// Gotong royong rutin dilakukan oleh warga desa untuk meningkatkan kualitas hidup dan kesejahteraan masyarakat Desa Darmasaba
|
||||
// </Text>
|
||||
// </Stack>
|
||||
// </Container>
|
||||
|
||||
// <Tabs
|
||||
// color={colors['blue-button']}
|
||||
// variant="pills"
|
||||
// value={activeTabState}
|
||||
// onChange={handleTabChange}
|
||||
// >
|
||||
// <Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
// <Grid>
|
||||
// <GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
// <TabsList>
|
||||
// {tabs.map((tab, index) => (
|
||||
// <TabsTab
|
||||
// key={index}
|
||||
// value={tab.value}
|
||||
// onClick={() => router.push(tab.href)}
|
||||
// >
|
||||
// {tab.label}
|
||||
// </TabsTab>
|
||||
// ))}
|
||||
// </TabsList>
|
||||
// </GridCol>
|
||||
// <GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
// <TextInput
|
||||
// radius="lg"
|
||||
// placeholder={placeholder}
|
||||
// leftSection={searchIcon}
|
||||
// w="100%"
|
||||
// value={searchValue}
|
||||
// onChange={handleSearchChange}
|
||||
// />
|
||||
// </GridCol>
|
||||
// </Grid>
|
||||
// </Box>
|
||||
|
||||
// {children}
|
||||
// </Tabs>
|
||||
// </Stack>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default LayoutTabsGotongRoyong;
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// 'use client'
|
||||
// import colors from '@/con/colors';
|
||||
// import { Box, Group, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
// import { IconSearch } from '@tabler/icons-react';
|
||||
// import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
// import React, { useEffect, useState } from 'react';
|
||||
// import BackButton from '../../layanan/_com/BackButto';
|
||||
|
||||
// type HeaderSearchProps = {
|
||||
// placeholder?: string;
|
||||
// searchIcon?: React.ReactNode;
|
||||
// value?: string;
|
||||
// onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
// children?: React.ReactNode;
|
||||
// };
|
||||
|
||||
// function LayoutTabsBerita({
|
||||
// children,
|
||||
// placeholder = "pencarian",
|
||||
// searchIcon = <IconSearch size={20} />
|
||||
// }: HeaderSearchProps) {
|
||||
// const router = useRouter();
|
||||
// const pathname = usePathname();
|
||||
// const searchParams = useSearchParams();
|
||||
|
||||
// const activeTab = pathname.split('/').pop() || 'semua';
|
||||
// const initialSearch = searchParams.get('search') || '';
|
||||
// const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
// const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
// useEffect(() => {
|
||||
// setActiveTabState(activeTab);
|
||||
// }, [activeTab]);
|
||||
|
||||
// useEffect(() => {
|
||||
// return () => {
|
||||
// if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
// };
|
||||
// }, [searchTimeout]);
|
||||
|
||||
// const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = event.target.value;
|
||||
// setSearchValue(value);
|
||||
|
||||
// if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
|
||||
// const newTimeout = window.setTimeout(() => {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
// if (value) params.set('search', value);
|
||||
// else params.delete('search');
|
||||
|
||||
// if (params.toString() !== searchParams.toString()) {
|
||||
// router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||
// }
|
||||
// }, 500);
|
||||
|
||||
// setSearchTimeout(newTimeout);
|
||||
// };
|
||||
|
||||
// const tabs = [
|
||||
// { label: "Semua", value: "semua", href: "/darmasaba/desa/berita/semua" },
|
||||
// { label: "Budaya", value: "budaya", href: "/darmasaba/desa/berita/budaya" },
|
||||
// { label: "Pemerintahan", value: "pemerintahan", href: "/darmasaba/desa/berita/pemerintahan" },
|
||||
// { label: "Ekonomi", value: "ekonomi", href: "/darmasaba/desa/berita/ekonomi" },
|
||||
// { label: "Pembangunan", value: "pembangunan", href: "/darmasaba/desa/berita/pembangunan" },
|
||||
// { label: "Sosial", value: "sosial", href: "/darmasaba/desa/berita/sosial" },
|
||||
// { label: "Teknologi", value: "teknologi", href: "/darmasaba/desa/berita/teknologi" },
|
||||
// ];
|
||||
|
||||
// const handleTabChange = (value: string | null) => {
|
||||
// if (!value) return;
|
||||
// const tab = tabs.find(t => t.value === value);
|
||||
// if (tab) {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
// router.push(`/darmasaba/desa/berita/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
// {/* Header */}
|
||||
// <Box px={{ base: "md", md: 100 }}>
|
||||
// <BackButton />
|
||||
// </Box>
|
||||
|
||||
// <Box px={{ base: 'md', md: 100 }}>
|
||||
// <Group justify='space-between' align="center">
|
||||
// <Stack gap="0">
|
||||
// <Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" >
|
||||
// Portal Berita Darmasaba
|
||||
// </Text>
|
||||
// <Text>
|
||||
// Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
// </Text>
|
||||
// </Stack>
|
||||
// <Box>
|
||||
// <TextInput
|
||||
// radius="lg"
|
||||
// placeholder={placeholder}
|
||||
// leftSection={searchIcon}
|
||||
// w="100%"
|
||||
// value={searchValue}
|
||||
// onChange={handleSearchChange}
|
||||
// />
|
||||
// </Box>
|
||||
// </Group>
|
||||
// </Box>
|
||||
|
||||
// <Tabs
|
||||
// color={colors['blue-button']}
|
||||
// variant="pills"
|
||||
// value={activeTabState}
|
||||
// onChange={handleTabChange}
|
||||
// >
|
||||
// <Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
// {/* SCROLLABLE TABS */}
|
||||
// <Box style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}>
|
||||
// <TabsList style={{ display: 'flex', flexWrap: 'nowrap', gap: '0.5rem' }}>
|
||||
// {tabs.map((tab, index) => (
|
||||
// <TabsTab
|
||||
// key={index}
|
||||
// value={tab.value}
|
||||
// onClick={() => router.push(tab.href)}
|
||||
// style={{
|
||||
// flex: '0 0 auto', // Prevent shrinking
|
||||
// minWidth: 100, // optional: makes them touch-friendly
|
||||
// textAlign: 'center'
|
||||
// }}
|
||||
// >
|
||||
// {tab.label}
|
||||
// </TabsTab>
|
||||
// ))}
|
||||
// </TabsList>
|
||||
// </Box>
|
||||
// </Box>
|
||||
|
||||
// {children}
|
||||
// </Tabs>
|
||||
// </Stack>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default LayoutTabsBerita;
|
||||
|
||||
|
||||
'use client'
|
||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||
@@ -402,7 +84,7 @@ function LayoutTabsGotongRoyong({ children }: { children: React.ReactNode }) {
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold">
|
||||
Portal Gotong royong Darmasaba
|
||||
</Text>
|
||||
<Text>Temukan berbagai kegiatan lingkungan yang dimiliki Desa Darmasaba</Text>
|
||||
<Text fz="md">Temukan berbagai kegiatan lingkungan yang dimiliki Desa Darmasaba</Text>
|
||||
</Stack>
|
||||
<Box>
|
||||
<TextInput
|
||||
|
||||
@@ -66,8 +66,11 @@ function Page() {
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
<Text c="dimmed" fz={{ base: 'sm', md: 'lg' }} mt="sm">
|
||||
Mari berpartisipasi menanam dan merawat pohon untuk menciptakan lingkungan hijau, sehat, dan seimbang bagi seluruh warga desa.
|
||||
<Text fz="md" mt="sm">
|
||||
Mari berpartisipasi menanam dan merawat pohon untuk menciptakan lingkungan hijau,
|
||||
</Text>
|
||||
<Text fz="md">
|
||||
sehat, dan seimbang bagi seluruh warga desa.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} pb={60}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Image, Modal, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Stepper, StepperStep, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Center, Group, Image, Modal, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Stepper, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useDisclosure, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowRight, IconCoin, IconInfoCircle, IconSchool, IconUsers } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
|
||||
const dataBeasiswa = [
|
||||
{ id: 1, nama: 'Penerima Beasiswa', jumlah: '250+', icon: IconUsers },
|
||||
@@ -27,7 +27,7 @@ function Page() {
|
||||
tempatLahir: "",
|
||||
tanggalLahir: "",
|
||||
jenisKelamin: "",
|
||||
kewarganegaraan: "",
|
||||
kewarganegaraan: "WNI",
|
||||
agama: "",
|
||||
alamatKTP: "",
|
||||
alamatDomisili: "",
|
||||
@@ -50,9 +50,21 @@ function Page() {
|
||||
close();
|
||||
};
|
||||
|
||||
const timeline = [
|
||||
{ label: "1 Maret 2025", desc: "Pembukaan Pendaftaran", date: new Date("2025-03-01") },
|
||||
{ label: "15 Maret 2025", desc: "Seleksi Administrasi", date: new Date("2025-03-15") },
|
||||
{ label: "1 April 2025", desc: "Tes Potensi Akademik", date: new Date("2025-04-01") },
|
||||
{ label: "15 April 2025", desc: "Wawancara", date: new Date("2025-04-15") },
|
||||
{ label: "1 Mei 2025", desc: "Pengumuman Hasil", date: new Date("2025-05-01") },
|
||||
];
|
||||
|
||||
const [active, setActive] = useState(1);
|
||||
const nextStep = () => setActive((current) => (current < 5 ? current + 1 : current));
|
||||
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
|
||||
useShallowEffect(() => {
|
||||
const today = new Date();
|
||||
// cari berapa banyak tanggal yang sudah lewat
|
||||
const doneSteps = timeline.filter(item => today >= item.date).length;
|
||||
setActive(doneSteps); // active step diset sesuai tanggal
|
||||
}, []);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -74,7 +86,7 @@ function Page() {
|
||||
<Title fz={55} fw={900} c={colors['blue-button']}>
|
||||
Wujudkan Mimpi Pendidikanmu di Desa Darmasaba
|
||||
</Title>
|
||||
<Text fz="lg" mt="md" c="dimmed">
|
||||
<Text fz="lg" mt="md" fw={"bold"}>
|
||||
Program beasiswa untuk mendukung pendidikan berkualitas bagi generasi muda Desa Darmasaba.
|
||||
</Text>
|
||||
<Group mt="xl">
|
||||
@@ -115,7 +127,7 @@ function Page() {
|
||||
{data.map((v, k) => (
|
||||
<Paper key={k} p="xl" radius="xl" shadow="sm" bg={colors['white-trans-1']}>
|
||||
<Title order={3} fw={700} c={colors['blue-button']} mb="xs">{v.judul}</Title>
|
||||
<Text fz="sm" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }}/>
|
||||
<Text fz="sm" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Paper>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
@@ -139,19 +151,22 @@ function Page() {
|
||||
Timeline Pendaftaran
|
||||
</Title>
|
||||
<Center>
|
||||
<Stepper mt={20} active={active} onStepClick={setActive} orientation="vertical" allowNextStepsSelect={false}>
|
||||
<StepperStep label="1 Maret 2025" description="Pembukaan Pendaftaran" />
|
||||
<StepperStep label="15 Maret 2025" description="Seleksi Administrasi" />
|
||||
<StepperStep label="1 April 2025" description="Tes Potensi Akademik" />
|
||||
<StepperStep label="15 April 2025" description="Wawancara" />
|
||||
<StepperStep label="1 Mei 2025" description="Pengumuman Hasil" />
|
||||
<Stepper
|
||||
mt={20}
|
||||
active={active}
|
||||
onStepClick={setActive}
|
||||
orientation="vertical"
|
||||
allowNextStepsSelect={false}
|
||||
>
|
||||
{timeline.map((item, index) => (
|
||||
<Stepper.Step
|
||||
key={index}
|
||||
label={item.label}
|
||||
description={item.desc}
|
||||
/>
|
||||
))}
|
||||
</Stepper>
|
||||
</Center>
|
||||
|
||||
<Group justify="center" mt="xl">
|
||||
<Button variant="default" radius="xl" onClick={prevStep}>Kembali</Button>
|
||||
<Button radius="xl" bg={colors['blue-button']} onClick={nextStep}>Lanjut</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Modal
|
||||
@@ -194,7 +209,11 @@ function Page() {
|
||||
<TextInput
|
||||
label="Kewarganegaraan"
|
||||
placeholder="Masukkan kewarganegaraan"
|
||||
onChange={(val) => { beasiswaDesa.create.form.kewarganegaraan = val.target.value }} />
|
||||
value={beasiswaDesa.create.form.kewarganegaraan || "WNI"} // tampilkan WNI kalau kosong
|
||||
onChange={(e) => {
|
||||
beasiswaDesa.create.form.kewarganegaraan = e.target.value;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
label="Agama"
|
||||
placeholder="Pilih agama"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Timeline,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { IconArrowLeft } from '@tabler/icons-react';
|
||||
import { IconArrowLeft, IconChecklist, IconInfoCircle, IconQuote, IconSchool, IconTimeline, IconUserPlus } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -34,7 +34,7 @@ export default function BeasiswaPage() {
|
||||
tempatLahir: "",
|
||||
tanggalLahir: "",
|
||||
jenisKelamin: "",
|
||||
kewarganegaraan: "",
|
||||
kewarganegaraan: "WNI",
|
||||
agama: "",
|
||||
alamatKTP: "",
|
||||
alamatDomisili: "",
|
||||
@@ -69,10 +69,11 @@ export default function BeasiswaPage() {
|
||||
{/* Hero Section */}
|
||||
<Container size="lg" py="xl">
|
||||
<Stack gap="md" maw={600}>
|
||||
<Title order={2} c="blue.9">
|
||||
Program Beasiswa Pendidikan Desa Darmasaba
|
||||
</Title>
|
||||
<Text c="dimmed">
|
||||
<Group>
|
||||
<IconSchool size={30} color={colors["blue-button"]} />
|
||||
<Title order={2} c={colors["blue-button"]}>Program Beasiswa Pendidikan Desa Darmasaba</Title>
|
||||
</Group>
|
||||
<Text>
|
||||
Program ini bertujuan untuk mendukung pendidikan generasi muda di Desa Darmasaba
|
||||
agar dapat melanjutkan studi ke jenjang lebih tinggi dengan dukungan finansial dan pendampingan.
|
||||
</Text>
|
||||
@@ -81,21 +82,34 @@ export default function BeasiswaPage() {
|
||||
|
||||
{/* Tentang Program */}
|
||||
<Container size="lg" py="xl">
|
||||
<Title order={3} mb="sm">
|
||||
Tentang Program
|
||||
</Title>
|
||||
<Group mb="sm">
|
||||
<IconInfoCircle size={24} color={colors["blue-button"]} />
|
||||
<Title order={3} c={colors["blue-button"]}>Tentang Program</Title>
|
||||
</Group>
|
||||
<Text>
|
||||
Program Beasiswa Desa Darmasaba adalah inisiatif pemerintah desa untuk meningkatkan akses
|
||||
pendidikan bagi siswa berprestasi dan kurang mampu. Melalui program ini, desa memberikan bantuan
|
||||
biaya sekolah, bimbingan akademik, serta pelatihan soft skill bagi peserta terpilih.
|
||||
</Text>
|
||||
|
||||
{/* Tambahkan info tahun berjalan di sini */}
|
||||
<Paper mt="md" p="md" radius="lg" shadow="xs" bg="#f8fbff" withBorder>
|
||||
<Text fw={500}>
|
||||
Periode Beasiswa Tahun 2025
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
Pendaftaran beasiswa dibuka mulai <strong>1 Januari 2025</strong> dan ditutup pada <strong>31 Mei 2025</strong>.
|
||||
Pengumuman hasil seleksi akan diumumkan pada pertengahan Juni 2025 melalui website resmi Desa Darmasaba.
|
||||
</Text>
|
||||
</Paper>
|
||||
</Container>
|
||||
|
||||
{/* Syarat dan Ketentuan */}
|
||||
<Container size="lg" py="xl">
|
||||
<Title order={3} mb="sm">
|
||||
Syarat Pendaftaran
|
||||
</Title>
|
||||
<Group mb="sm">
|
||||
<IconChecklist size={24} color={colors["blue-button"]} />
|
||||
<Title order={3} c={colors["blue-button"]}>Syarat Pendaftaran</Title>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="lg">
|
||||
<Paper shadow="sm" p="md" radius="lg" withBorder>
|
||||
@@ -123,42 +137,61 @@ export default function BeasiswaPage() {
|
||||
|
||||
{/* Proses Seleksi */}
|
||||
<Container size="lg" py="xl">
|
||||
<Title order={3} mb="sm">
|
||||
Proses Seleksi
|
||||
</Title>
|
||||
<Group mb="sm">
|
||||
<IconTimeline size={24} color={colors["blue-button"]} />
|
||||
<Title order={3} c={colors["blue-button"]}>Proses Seleksi</Title>
|
||||
</Group>
|
||||
|
||||
<Timeline active={4} bulletSize={24} lineWidth={2}>
|
||||
<Timeline.Item title="Pendaftaran Online">
|
||||
<Text c="dimmed" size="sm">
|
||||
Calon peserta mengisi formulir pendaftaran dan mengunggah dokumen pendukung.
|
||||
</Text>
|
||||
<Text size="sm" fw={500} mt={4}>
|
||||
Estimasi waktu: 1 Februari – 31 Mei 2025
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
|
||||
<Timeline.Item title="Seleksi Administrasi">
|
||||
<Text c="dimmed" size="sm">
|
||||
Panitia memverifikasi kelengkapan dan validitas berkas.
|
||||
</Text>
|
||||
<Text size="sm" fw={500} mt={4}>
|
||||
Estimasi waktu: 5–7 hari kerja setelah penutupan pendaftaran
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
|
||||
<Timeline.Item title="Wawancara dan Penilaian">
|
||||
<Text c="dimmed" size="sm">
|
||||
Peserta yang lolos administrasi akan diundang untuk wawancara langsung dengan tim seleksi.
|
||||
</Text>
|
||||
<Text size="sm" fw={500} mt={4}>
|
||||
Estimasi waktu: 7–10 hari kerja setelah pengumuman seleksi administrasi
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
|
||||
<Timeline.Item title="Pengumuman Penerima">
|
||||
<Text c="dimmed" size="sm">
|
||||
Daftar penerima beasiswa diumumkan melalui website resmi Desa Darmasaba.
|
||||
</Text>
|
||||
<Text size="sm" fw={500} mt={4}>
|
||||
Estimasi waktu: 5 hari kerja setelah tahap wawancara selesai
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
</Timeline>
|
||||
|
||||
<Text c="dimmed" size="sm" mt="lg" ta="center">
|
||||
Total estimasi keseluruhan proses: sekitar 3–4 minggu setelah penutupan pendaftaran
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
|
||||
{/* Testimoni */}
|
||||
<Container size="lg" py="xl">
|
||||
<Title order={3} mb="sm">
|
||||
Cerita Sukses Penerima Beasiswa
|
||||
</Title>
|
||||
<Group mb="sm">
|
||||
<IconQuote size={24} color={colors["blue-button"]} />
|
||||
<Title order={3} c={colors["blue-button"]}>Cerita Sukses Penerima Beasiswa</Title>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
|
||||
<Paper shadow="md" p="lg" radius="lg">
|
||||
@@ -183,11 +216,14 @@ export default function BeasiswaPage() {
|
||||
|
||||
{/* CTA Akhir */}
|
||||
<Container size="lg" py="xl" ta="center">
|
||||
<Title order={3}>Siap Bergabung dengan Program Ini?</Title>
|
||||
<Group justify="center" mb="sm">
|
||||
<IconUserPlus size={28} color={colors["blue-button"]} />
|
||||
<Title order={3} c={colors["blue-button"]}>Siap Bergabung dengan Program Ini?</Title>
|
||||
</Group>
|
||||
<Text c="dimmed" mb="md">
|
||||
Segera daftar dan wujudkan mimpimu bersama Desa Darmasaba.
|
||||
</Text>
|
||||
<Button onClick={open} size="lg" radius="xl" color="blue">
|
||||
<Button onClick={open} size="lg" radius="xl" bg={colors["blue-button"]}>
|
||||
Daftar Sekarang
|
||||
</Button>
|
||||
</Container>
|
||||
@@ -232,7 +268,11 @@ export default function BeasiswaPage() {
|
||||
<TextInput
|
||||
label="Kewarganegaraan"
|
||||
placeholder="Masukkan kewarganegaraan"
|
||||
onChange={(val) => { beasiswaDesa.create.form.kewarganegaraan = val.target.value }} />
|
||||
value={beasiswaDesa.create.form.kewarganegaraan || "WNI"} // tampilkan WNI kalau kosong
|
||||
onChange={(e) => {
|
||||
beasiswaDesa.create.form.kewarganegaraan = e.target.value;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
label="Agama"
|
||||
placeholder="Pilih agama"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip, Divider, Badge, Group } from '@mantine/core';
|
||||
import { Box, Divider, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconBook2, IconCalendarTime, IconMapPin } from '@tabler/icons-react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { IconMapPin, IconCalendarTime, IconBook2 } from '@tabler/icons-react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
function Page() {
|
||||
@@ -55,9 +55,9 @@ function Page() {
|
||||
<IconBook2 size={36} stroke={1.5} color={colors['blue-button']} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Badge variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="lg" radius="sm" mb="sm">
|
||||
<Title order={3} fw={700} c={colors['blue-button']} mb="xs">
|
||||
{stateTujuanProgram.findById.data?.judul}
|
||||
</Badge>
|
||||
</Title>
|
||||
</Group>
|
||||
<Text fz="md" style={{wordBreak: "break-word", whiteSpace: "normal"}} lh={1.6} dangerouslySetInnerHTML={{ __html: stateTujuanProgram.findById.data?.deskripsi }} />
|
||||
</Stack>
|
||||
@@ -70,9 +70,9 @@ function Page() {
|
||||
<IconMapPin size={36} stroke={1.5} color={colors['blue-button']} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Badge variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="lg" radius="sm" mb="sm">
|
||||
<Title order={3} fw={700} c={colors['blue-button']} mb="xs">
|
||||
{stateLokasiDanJadwal.findById.data?.judul}
|
||||
</Badge>
|
||||
</Title>
|
||||
</Group>
|
||||
<Text fz="md" lh={1.6} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: stateLokasiDanJadwal.findById.data?.deskripsi }} />
|
||||
</Stack>
|
||||
@@ -85,9 +85,9 @@ function Page() {
|
||||
<IconCalendarTime size={36} stroke={1.5} color={colors['blue-button']} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Badge variant="gradient" gradient={{ from: colors['blue-button'], to: 'cyan' }} size="lg" radius="sm" mb="sm">
|
||||
<Title order={3} fw={700} c={colors['blue-button']} mb="xs">
|
||||
{stateFasilitas.findById.data?.judul}
|
||||
</Badge>
|
||||
</Title>
|
||||
</Group>
|
||||
<Text fz="md" lh={1.6} style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: stateFasilitas.findById.data?.deskripsi }} />
|
||||
</Stack>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client';
|
||||
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Container, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Tooltip, ActionIcon } from '@mantine/core';
|
||||
import { IconChalkboard, IconMicroscope, IconProps, IconRefresh, IconSchool, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { motion } from 'framer-motion';
|
||||
@@ -23,19 +24,19 @@ export default function KategoriPage({ jenjangPendidikan }: { jenjangPendidikan:
|
||||
const router = useTransitionRouter();
|
||||
const [stats, setStats] = useState<Stat[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
|
||||
// Decode the URL parameter
|
||||
const decodedJenjangPendidikan = decodeURIComponent(jenjangPendidikan);
|
||||
const jenjangFilter = decodedJenjangPendidikan.toLowerCase() === 'semua'
|
||||
? undefined
|
||||
const jenjangFilter = decodedJenjangPendidikan.toLowerCase() === 'semua'
|
||||
? undefined
|
||||
: decodedJenjangPendidikan;
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
if (!decodedJenjangPendidikan) return;
|
||||
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
// Load all data in parallel with the jenjang filter
|
||||
await Promise.all([
|
||||
infoSekolahPaud.lembagaPendidikan.findMany.load(1, 100, '', jenjangFilter),
|
||||
@@ -50,7 +51,7 @@ export default function KategoriPage({ jenjangPendidikan }: { jenjangPendidikan:
|
||||
|
||||
setStats([
|
||||
{
|
||||
|
||||
|
||||
icon: IconChalkboard,
|
||||
jumlah: totalLembaga,
|
||||
nama: 'Lembaga Pendidikan',
|
||||
@@ -119,11 +120,15 @@ export default function KategoriPage({ jenjangPendidikan }: { jenjangPendidikan:
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
leftSection={<IconRefresh color={colors['blue-button']} size={16} />}
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={handleRefresh}
|
||||
loading={stats.some(stat => stat.loading)}
|
||||
c={colors['blue-button']}
|
||||
style={{
|
||||
borderColor: colors['blue-button'],
|
||||
}}
|
||||
>
|
||||
Segarkan Data
|
||||
</Button>
|
||||
@@ -143,7 +148,7 @@ export default function KategoriPage({ jenjangPendidikan }: { jenjangPendidikan:
|
||||
aria-label="Tidak ada hasil"
|
||||
>
|
||||
<Center style={{ minHeight: 180, flexDirection: 'column' }}>
|
||||
<Text fz="lg" fw={800} c="#2563eb">
|
||||
<Text fz="lg" fw={800} c={colors['blue-button']}>
|
||||
Tidak ditemukan
|
||||
</Text>
|
||||
<Text c="dimmed" mt="6px">
|
||||
@@ -173,81 +178,81 @@ export default function KategoriPage({ jenjangPendidikan }: { jenjangPendidikan:
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Skeleton visible={v.loading}>
|
||||
<Paper
|
||||
p="lg"
|
||||
radius="lg"
|
||||
style={{
|
||||
background: 'white',
|
||||
border: '1px solid #e2e8f0',
|
||||
boxShadow: '0 8px 28px rgba(0,0,0,0.06)',
|
||||
minHeight: 260,
|
||||
}}
|
||||
role="article"
|
||||
aria-label={`${v.nama} kartu statistik`}
|
||||
>
|
||||
<Stack gap="sm" mb="md">
|
||||
<Center>
|
||||
<Box
|
||||
style={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#eff6ff',
|
||||
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.6)',
|
||||
}}
|
||||
aria-hidden
|
||||
>
|
||||
{React.createElement(v.icon, {
|
||||
color: '#2563eb',
|
||||
size: 34,
|
||||
stroke: 1.6,
|
||||
})}
|
||||
</Box>
|
||||
</Center>
|
||||
<Paper
|
||||
p="lg"
|
||||
radius="lg"
|
||||
style={{
|
||||
background: 'white',
|
||||
border: '1px solid #e2e8f0',
|
||||
boxShadow: '0 8px 28px rgba(0,0,0,0.06)',
|
||||
minHeight: 260,
|
||||
}}
|
||||
role="article"
|
||||
aria-label={`${v.nama} kartu statistik`}
|
||||
>
|
||||
<Stack gap="sm" mb="md">
|
||||
<Center>
|
||||
<Box
|
||||
style={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#eff6ff',
|
||||
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.6)',
|
||||
}}
|
||||
aria-hidden
|
||||
>
|
||||
{React.createElement(v.icon, {
|
||||
color: colors['blue-button'],
|
||||
size: 34,
|
||||
stroke: 1.6,
|
||||
})}
|
||||
</Box>
|
||||
</Center>
|
||||
|
||||
<Group justify="center" align="center" gap="xs">
|
||||
<Stack gap={0}>
|
||||
<Text ta={"center"} fz={{ base: 18, md: 22 }} fw={800} c="#0f172a">
|
||||
{v.jumlah.toLocaleString()}
|
||||
</Text>
|
||||
<Group gap={6} align="center">
|
||||
<Text ta={"center"} fz="sm" fw={700} c="#2563eb">
|
||||
{v.nama}
|
||||
<Group justify="center" align="center" gap="xs">
|
||||
<Stack gap={0}>
|
||||
<Text ta={"center"} fz={{ base: 18, md: 22 }} fw={800} c="#0f172a">
|
||||
{v.jumlah.toLocaleString()}
|
||||
</Text>
|
||||
<Tooltip label={v.helper ?? ''} position="right" withArrow>
|
||||
<ActionIcon aria-label={`Info ${v.nama}`} variant="transparent" size="xs">
|
||||
<IconInfoCircle size={16} style={{ color: '#2563eb' }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Group gap={6} align="center">
|
||||
<Text ta={"center"} fz="sm" fw={700} c={colors['blue-button']}>
|
||||
{v.nama}
|
||||
</Text>
|
||||
<Tooltip label={v.helper ?? ''} position="right" withArrow>
|
||||
<ActionIcon aria-label={`Info ${v.nama}`} variant="transparent" size="xs">
|
||||
<IconInfoCircle size={16} style={{ color: colors['blue-button'] }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Group justify="center" mt="8px">
|
||||
<Button
|
||||
radius="xl"
|
||||
variant="outline"
|
||||
aria-label={`Lihat detail ${v.nama}`}
|
||||
style={{
|
||||
borderColor: '#e2e8f0',
|
||||
color: '#2563eb',
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
}}
|
||||
onClick={() => {
|
||||
if (v.nama === "Lembaga Pendidikan") router.push(`/darmasaba/pendidikan/info-sekolah/${jenjangPendidikan}/lembaga`);
|
||||
if (v.nama === "Siswa Terdaftar") router.push(`/darmasaba/pendidikan/info-sekolah/${jenjangPendidikan}/siswa`);
|
||||
if (v.nama === "Tenaga Pengajar") router.push(`/darmasaba/pendidikan/info-sekolah/${jenjangPendidikan}/pengajar`);
|
||||
}}
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
<Group justify="center" mt="8px">
|
||||
<Button
|
||||
radius="xl"
|
||||
variant="outline"
|
||||
aria-label={`Lihat detail ${v.nama}`}
|
||||
style={{
|
||||
borderColor: colors['blue-button'],
|
||||
color: colors['blue-button'],
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
}}
|
||||
onClick={() => {
|
||||
if (v.nama === "Lembaga Pendidikan") router.push(`/darmasaba/pendidikan/info-sekolah/${jenjangPendidikan}/lembaga`);
|
||||
if (v.nama === "Siswa Terdaftar") router.push(`/darmasaba/pendidikan/info-sekolah/${jenjangPendidikan}/siswa`);
|
||||
if (v.nama === "Tenaga Pengajar") router.push(`/darmasaba/pendidikan/info-sekolah/${jenjangPendidikan}/pengajar`);
|
||||
}}
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
</Skeleton>
|
||||
</motion.div>
|
||||
))
|
||||
|
||||
@@ -84,7 +84,7 @@ function Page({ params }: PageProps) {
|
||||
<TableTr>
|
||||
<TableTh w="30%">Nama Pengajar</TableTh>
|
||||
<TableTh w="30%">Nama Lembaga</TableTh>
|
||||
<TableTh w="40%">Jenjang Pendidikan</TableTh>
|
||||
<TableTh w="40%">Mengajar Di Jenjang Pendidikan</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
@@ -95,7 +95,7 @@ function Page({ params }: PageProps) {
|
||||
<TableTd>{item.lembaga.jenjangPendidikan?.nama || '-'}</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
@@ -185,6 +185,8 @@ export default function LayoutSekolah({
|
||||
radius="xl"
|
||||
size="sm"
|
||||
variant={aktif ? 'filled' : 'light'}
|
||||
bg={colors['blue-button']}
|
||||
c={aktif ? colors['white-1'] : 'gray'}
|
||||
>
|
||||
{k}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
@@ -130,11 +131,15 @@ export default function SekolahPage() {
|
||||
<Box>
|
||||
<Group justify="start" mb="md">
|
||||
<Button
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
leftSection={<IconRefresh color={colors['blue-button']} size={16} />}
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={handleRefresh}
|
||||
loading={stats.some(stat => stat.loading)}
|
||||
c={colors['blue-button']}
|
||||
style={{
|
||||
borderColor: colors['blue-button'],
|
||||
}}
|
||||
>
|
||||
Segarkan Data
|
||||
</Button>
|
||||
@@ -154,7 +159,7 @@ export default function SekolahPage() {
|
||||
aria-label="Tidak ada hasil"
|
||||
>
|
||||
<Center style={{ minHeight: 180, flexDirection: 'column' }}>
|
||||
<Text fz="lg" fw={800} c="#2563eb">
|
||||
<Text fz="lg" fw={800} c={colors['blue-button']}>
|
||||
Tidak ditemukan
|
||||
</Text>
|
||||
<Text c="dimmed" mt="6px">
|
||||
@@ -212,7 +217,7 @@ export default function SekolahPage() {
|
||||
aria-hidden
|
||||
>
|
||||
{React.createElement(v.icon, {
|
||||
color: '#2563eb',
|
||||
color: colors['blue-button'],
|
||||
size: 34,
|
||||
stroke: 1.6,
|
||||
})}
|
||||
@@ -225,12 +230,12 @@ export default function SekolahPage() {
|
||||
{v.jumlah.toLocaleString()}
|
||||
</Text>
|
||||
<Group gap={6} align="center">
|
||||
<Text ta={"center"} fz="sm" fw={700} c="#2563eb">
|
||||
<Text ta={"center"} fz="sm" fw={700} c={colors['blue-button']}>
|
||||
{v.nama}
|
||||
</Text>
|
||||
<Tooltip label={v.helper ?? ''} position="right" withArrow>
|
||||
<ActionIcon aria-label={`Info ${v.nama}`} variant="transparent" size="xs">
|
||||
<IconInfoCircle size={16} style={{ color: '#2563eb' }} />
|
||||
<IconInfoCircle size={16} style={{ color: colors['blue-button'] }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
@@ -244,8 +249,8 @@ export default function SekolahPage() {
|
||||
variant="outline"
|
||||
aria-label={`Lihat detail ${v.nama}`}
|
||||
style={{
|
||||
borderColor: '#e2e8f0',
|
||||
color: '#2563eb',
|
||||
borderColor: colors['blue-button'],
|
||||
color: colors['blue-button'],
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
}}
|
||||
|
||||
@@ -43,7 +43,7 @@ function Page() {
|
||||
Pendidikan Non Formal
|
||||
</Title>
|
||||
<Text ta="center" fz="lg" lh={1.6} c="black" maw={800} mx="auto">
|
||||
Bentuk pendidikan di luar sekolah yang terstruktur, bertujuan memberikan keterampilan, pengetahuan, dan pengembangan karakter masyarakat dari berbagai usia serta latar belakang.
|
||||
Pendidikan non formal merupakan bentuk pendidikan di luar sekolah yang terstruktur, bertujuan untuk memberikan keterampilan, pengetahuan, serta pengembangan karakter masyarakat dari berbagai usia dan latar belakang.
|
||||
</Text>
|
||||
</Box>
|
||||
<SimpleGrid
|
||||
|
||||
@@ -16,16 +16,11 @@ import {
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { DateInput } from '@mantine/dates';
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconBook2,
|
||||
IconUser
|
||||
} from '@tabler/icons-react';
|
||||
import { IconArrowRight, IconBook2, IconUser } from '@tabler/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
|
||||
export interface ModalPeminjamanProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
@@ -45,11 +40,12 @@ export default function ModalPeminjaman({
|
||||
}: ModalPeminjamanProps) {
|
||||
const snap = useSnapshot(perpustakaanDigitalState.peminjamanBuku);
|
||||
|
||||
// reset form setiap modal dibuka
|
||||
const BATAS_HARI_PINJAM = 4;
|
||||
|
||||
// Reset form setiap modal dibuka
|
||||
useEffect(() => {
|
||||
if (opened && buku) {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form = {
|
||||
...perpustakaanDigitalState.peminjamanBuku.create.form,
|
||||
bukuId: buku.id,
|
||||
nama: '',
|
||||
noTelp: '',
|
||||
@@ -99,7 +95,14 @@ export default function ModalPeminjaman({
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<Text fz="sm" c="dimmed" lineClamp={3} dangerouslySetInnerHTML={{ __html: buku.deskripsi || 'Tidak ada deskripsi' }} />
|
||||
<Text
|
||||
fz="sm"
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: buku.deskripsi || 'Tidak ada deskripsi',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
@@ -112,7 +115,8 @@ export default function ModalPeminjaman({
|
||||
leftSection={<IconUser size={16} />}
|
||||
value={snap.create.form.nama}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.nama = e.currentTarget.value)
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.nama =
|
||||
e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
@@ -123,7 +127,8 @@ export default function ModalPeminjaman({
|
||||
leftSection={<IconUser size={16} />}
|
||||
value={snap.create.form.noTelp}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.noTelp = e.currentTarget.value)
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.noTelp =
|
||||
e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
@@ -134,11 +139,13 @@ export default function ModalPeminjaman({
|
||||
leftSection={<IconUser size={16} />}
|
||||
value={snap.create.form.alamat}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.alamat = e.currentTarget.value)
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.alamat =
|
||||
e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
{/* === OTOMATIS SET BATAS DAN TANGGAL KEMBALI === */}
|
||||
<DateInput
|
||||
label="Tanggal Pinjam"
|
||||
placeholder="Pilih tanggal pinjam"
|
||||
@@ -148,64 +155,83 @@ export default function ModalPeminjaman({
|
||||
: null
|
||||
}
|
||||
onChange={(date) => {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam =
|
||||
date ? new Date(date).toISOString() : '';
|
||||
if (date) {
|
||||
const tanggalPinjam = new Date(date);
|
||||
|
||||
// simpan tanggal pinjam
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam =
|
||||
tanggalPinjam.toISOString();
|
||||
|
||||
// hitung batas +4 hari
|
||||
const batasKembali = new Date(tanggalPinjam);
|
||||
batasKembali.setDate(batasKembali.getDate() + BATAS_HARI_PINJAM);
|
||||
|
||||
// set batas & tanggal kembali otomatis
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali =
|
||||
batasKembali.toISOString();
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali =
|
||||
batasKembali.toISOString();
|
||||
|
||||
toast.info(
|
||||
`Batas pengembalian otomatis diset ke ${batasKembali.toLocaleDateString('id-ID')} (+${BATAS_HARI_PINJAM} hari).`
|
||||
);
|
||||
} else {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalPinjam = '';
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali = '';
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali = '';
|
||||
}
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text>Catatan</Text>
|
||||
<Text fw={500}>Catatan</Text>
|
||||
<CreateEditor
|
||||
value={snap.create.form.catatan}
|
||||
onChange={(e) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.catatan = e)
|
||||
onChange={(val) =>
|
||||
(perpustakaanDigitalState.peminjamanBuku.create.form.catatan =
|
||||
val)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<DateInput
|
||||
label="Tanggal Kembali"
|
||||
placeholder="Pilih tanggal kembali"
|
||||
value={
|
||||
snap.create.form.tanggalKembali
|
||||
? new Date(snap.create.form.tanggalKembali)
|
||||
: null
|
||||
}
|
||||
onChange={(date) => {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.tanggalKembali =
|
||||
date ? new Date(date).toISOString() : '';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
label="Batas Pengembalian"
|
||||
placeholder="Pilih tanggal kembali"
|
||||
placeholder="Otomatis diatur +4 hari dari tanggal pinjam"
|
||||
value={
|
||||
snap.create.form.batasKembali
|
||||
? new Date(snap.create.form.batasKembali)
|
||||
: null
|
||||
}
|
||||
onChange={(date) => {
|
||||
perpustakaanDigitalState.peminjamanBuku.create.form.batasKembali =
|
||||
date ? new Date(date).toISOString() : '';
|
||||
}}
|
||||
required
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<DateInput
|
||||
label="Tanggal Kembali"
|
||||
placeholder="Otomatis sama dengan batas pengembalian"
|
||||
value={
|
||||
snap.create.form.tanggalKembali
|
||||
? new Date(snap.create.form.tanggalKembali)
|
||||
: null
|
||||
}
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
loading={snap.create.loading}
|
||||
disabled={
|
||||
!snap.create.form.nama ||
|
||||
!snap.create.form.tanggalPinjam ||
|
||||
!snap.create.form.batasKembali ||
|
||||
!snap.create.form.tanggalKembali
|
||||
!snap.create.form.nama || !snap.create.form.tanggalPinjam
|
||||
}
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
radius="xl"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Pinjam Buku
|
||||
</Button>
|
||||
|
||||
@@ -74,10 +74,10 @@ function Page() {
|
||||
<Stack gap="lg">
|
||||
<Paper p="lg" radius="xl" shadow="sm" withBorder>
|
||||
<Stack gap="sm">
|
||||
<Text fz={{ base: 'lg', md: 'xl' }} fw="bold" c={colors["blue-button"]}>
|
||||
<Text ta={"center"} fz={{ base: 'lg', md: 'xl' }} fw="bold" c={colors["blue-button"]}>
|
||||
Tentang Informasi Publik
|
||||
</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
<Text ta={"center"} fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
Daftar Informasi Publik Desa Darmasaba adalah kumpulan data yang dapat diakses oleh masyarakat sesuai dengan ketentuan peraturan yang berlaku.
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -117,41 +117,45 @@ function Page() {
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center">{(page - 1) * 5 + index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Box>
|
||||
<Badge variant="light" size="lg" color="blue">
|
||||
{item.jenisInformasi}
|
||||
<Text fw={650} fz={"sm"} c={'blue'} lineClamp={1}>
|
||||
{item.jenisInformasi}
|
||||
</Text>
|
||||
</Badge>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Text lineClamp={1} fz="sm" c="dark" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Box>
|
||||
<Text lineClamp={1} fz="sm" c="dark" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Box w={150}>
|
||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}) : '-'}
|
||||
<Box>
|
||||
<Text ta={"center"}>
|
||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}) : '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Box w={150}>
|
||||
<Box>
|
||||
<Tooltip label="Lihat Detail" withArrow>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/darmasaba/ppid/daftar-informasi-publik-desa-darmasaba/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/darmasaba/ppid/daftar-informasi-publik-desa-darmasaba/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
|
||||
@@ -42,7 +42,7 @@ function Page() {
|
||||
>
|
||||
Dasar Hukum
|
||||
</Text>
|
||||
<Text ta="center" fz="sm" c="dimmed">
|
||||
<Text ta="center" fz="md" >
|
||||
Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan";
|
||||
import colors from "@/con/colors";
|
||||
import { BarChart, PieChart } from '@mantine/charts';
|
||||
import { Box, Button, Center, Container, Flex, Group, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { useDisclosure, useMediaQuery, useShallowEffect } from "@mantine/hooks";
|
||||
import { Box, Button, Center, Container, Flex, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||
import { useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
|
||||
@@ -18,14 +18,13 @@ interface ChartDataItem {
|
||||
|
||||
|
||||
function Kepuasan() {
|
||||
const state = useProxy(indeksKepuasanState.responden);
|
||||
const state = useProxy(indeksKepuasanState.responden);
|
||||
const { data, loading } = state.findMany;
|
||||
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; Responden: number }>>([]);
|
||||
const [opened, { open, close }] = useDisclosure(false)
|
||||
|
||||
const resetForm = () => {
|
||||
state.create.form = {
|
||||
@@ -122,18 +121,18 @@ function Kepuasan() {
|
||||
|
||||
// Convert map to array and sort by date
|
||||
const barData = Array.from(monthYearMap.entries())
|
||||
.map(([key, count]) => {
|
||||
.map(([key, Responden]) => {
|
||||
const [year, month] = key.split('-');
|
||||
const monthName = new Date(Number(year), Number(month) - 1, 1)
|
||||
.toLocaleString('id-ID', { month: 'long' });
|
||||
return {
|
||||
month: `${monthName} ${year}`,
|
||||
count,
|
||||
Responden,
|
||||
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.sortKey - b.sortKey)
|
||||
.map(({ month, count }) => ({ month, count }));
|
||||
.map(({ month, Responden }) => ({ month, Responden }));
|
||||
|
||||
setBarChartData(barData);
|
||||
}
|
||||
@@ -141,12 +140,12 @@ function Kepuasan() {
|
||||
|
||||
if ((loading && !data) || !data) {
|
||||
return (
|
||||
<Stack py={10} px="sm">
|
||||
<Skeleton height={200} mb="md" />
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="md">
|
||||
<Skeleton height={200} />
|
||||
<Skeleton height={200} />
|
||||
<Skeleton height={200} />
|
||||
<Stack py={10} px="xl">
|
||||
<Skeleton height={300} mb="md" />
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={300} />
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
@@ -157,10 +156,16 @@ function Kepuasan() {
|
||||
<Stack p="sm">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Center>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
</Center>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||
<Center mt={10}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
<Button
|
||||
radius={"lg"}
|
||||
onClick={open}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
@@ -177,10 +182,10 @@ function Kepuasan() {
|
||||
</Box>
|
||||
</Flex>
|
||||
<BarChart
|
||||
h={300}
|
||||
h={window.innerWidth < 480 ? 200 : 300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
series={[{ name: 'Responden', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
@@ -191,12 +196,9 @@ function Kepuasan() {
|
||||
</Paper>
|
||||
<Box py={"xl"}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 1,
|
||||
lg: 1,
|
||||
xl: 3
|
||||
}}
|
||||
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||
spacing="md"
|
||||
verticalSpacing="md"
|
||||
>
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
@@ -215,7 +217,7 @@ function Kepuasan() {
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={200}
|
||||
size={250} // Fixed size in pixels
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
@@ -254,7 +256,7 @@ function Kepuasan() {
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={200}
|
||||
size={250}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
@@ -297,7 +299,7 @@ function Kepuasan() {
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={190}
|
||||
size={250}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
@@ -330,7 +332,7 @@ function Kepuasan() {
|
||||
<TextInput
|
||||
label="Nama"
|
||||
type='text'
|
||||
placeholder="masukkan nama"
|
||||
placeholder="Masukkan nama"
|
||||
defaultValue={state.create.form.name}
|
||||
onChange={(val) => {
|
||||
state.create.form.name = val.currentTarget.value;
|
||||
@@ -413,41 +415,57 @@ function Kepuasan() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack p="sm">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={isMobile ? "md" : "xl"}>
|
||||
<Stack gap="xs">
|
||||
<Text ta="center" fz={{ base: "2rem", md: "3rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Group justify="center">
|
||||
<Button radius="lg" bg={colors["blue-button"]} onClick={open}>
|
||||
Ajukan Responden
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Stack p={"sm"}>
|
||||
<Container size="lg" px="md">
|
||||
<Center>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
</Center>
|
||||
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||
<Center mt={10}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box px={isMobile ? "sm" : "xl"}>
|
||||
<Paper p="lg" bg={colors.Bg}>
|
||||
<Paper p={isMobile ? "sm" : "lg"}>
|
||||
<Stack gap="xs">
|
||||
<Flex direction={isMobile ? "column" : "row"} justify="space-between" align={isMobile ? "start" : "center"}>
|
||||
<Text fw="bold" mb={isMobile ? "sm" : 0}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold" c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta="end" fz="h1" fw="bold" c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString("id-ID")}
|
||||
<Box px={"xl"}>
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Flex
|
||||
direction={{ base: "column", sm: "row" }}
|
||||
justify="space-between"
|
||||
align={{ base: "flex-start", sm: "center" }}
|
||||
>
|
||||
<Text fw="bold" ta={{ base: "center", sm: "left" }}>
|
||||
Pelayanan Terhadap Publik Desa Darmasaba
|
||||
</Text>
|
||||
<Box mt={{ base: "sm", sm: 0 }}>
|
||||
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString('id-ID')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<BarChart
|
||||
h={isMobile ? 200 : 300}
|
||||
h={300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: "count", color: colors["blue-button"] }]}
|
||||
series={[{ name: 'Responden', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Box py="xl">
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, xl: 3 }} spacing="lg">
|
||||
<Box py={"xl"}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 1,
|
||||
lg: 1,
|
||||
xl: 3
|
||||
}}
|
||||
>
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
@@ -457,17 +475,28 @@ function Kepuasan() {
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md">
|
||||
<Stack>
|
||||
<Center>
|
||||
<PieChart
|
||||
size={isMobile ? 150 : 200}
|
||||
withLabels
|
||||
data={donutDataJenisKelamin}
|
||||
withTooltip
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={200}
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataJenisKelamin.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -482,18 +511,35 @@ function Kepuasan() {
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md">
|
||||
<Stack>
|
||||
<Center>
|
||||
<PieChart
|
||||
size={isMobile ? 150 : 200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
withLabelsLine
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={200}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataRating.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -508,18 +554,35 @@ function Kepuasan() {
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md">
|
||||
<Stack>
|
||||
<Center>
|
||||
<PieChart
|
||||
size={isMobile ? 150 : 200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
withLabelsLine
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={190}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataKelompokUmur.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -542,7 +605,7 @@ function Kepuasan() {
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal"
|
||||
label="Tanggal Pengisian"
|
||||
type="date"
|
||||
placeholder="masukkan tanggal"
|
||||
defaultValue={state.create.form.tanggal}
|
||||
|
||||
@@ -54,28 +54,34 @@ function Page() {
|
||||
<Center>
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" h={{ base: 70, md: 120 }} w={{ base: 70, md: 120 }} alt="Logo Desa" />
|
||||
</Center>
|
||||
<Text ta="center" fz={{ base: "1.2rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
|
||||
<Text ta="center" fz={{ base: "1.2rem", md: "2rem", lg: "2.5rem", xl: "3rem" }} fw="bold">
|
||||
Pejabat Pengelola Informasi dan Dokumentasi
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
<Divider my="lg" />
|
||||
|
||||
<Box px={{ base: 0, md: 50 }} pb={40}>
|
||||
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
|
||||
<Box px={{ base: 0, md: 50 }}>
|
||||
<Paper bg={colors['white-trans-1']} radius="lg" shadow="sm">
|
||||
<Stack gap="md">
|
||||
<Center>
|
||||
<Image
|
||||
loading='lazy'
|
||||
src={item.image?.link ? `${item.image.link}?t=${Date.now()}` : "/perbekel.png"}
|
||||
w={{ base: 220, md: 330 }}
|
||||
alt="Foto Pimpinan"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
<Paper bg={colors['blue-button']} py={25} radius="lg" className="glass3">
|
||||
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.5rem", md: "2rem" }}>
|
||||
<Paper bg={colors['white-trans-1']} radius="xl" shadow="md" withBorder>
|
||||
<Stack gap={0}>
|
||||
<Image
|
||||
pt={{ base: 0, md: 100 }}
|
||||
px="lg"
|
||||
src={item.image?.link ? `${item.image.link}?t=${Date.now()}` : "/perbekel.png"}
|
||||
alt="Foto Pimpinan"
|
||||
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 ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "xl", md: "h2" }}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Paper>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
'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,
|
||||
@@ -18,25 +19,23 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
Transition,
|
||||
Transition
|
||||
} from '@mantine/core'
|
||||
import {
|
||||
IconArrowsMaximize,
|
||||
IconArrowsMinimize,
|
||||
IconRefresh,
|
||||
IconSearch,
|
||||
IconUsers,
|
||||
IconZoomIn,
|
||||
IconZoomOut,
|
||||
IconArrowsMaximize,
|
||||
IconArrowsMinimize,
|
||||
} 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 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 (
|
||||
@@ -233,21 +232,42 @@ function StrukturOrganisasiPPID() {
|
||||
return (
|
||||
<Stack align="center" mt="xl">
|
||||
{/* 🔍 Search + Zoom + Fullscreen controls */}
|
||||
<Group mb="md" justify="center" gap="sm">
|
||||
<Group mb="md" justify="center" gap="sm" align="center">
|
||||
<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>
|
||||
|
||||
{/* 🔍 Tambahkan indikator zoom di sini */}
|
||||
{/* Floating Zoom Indicator */}
|
||||
<Box
|
||||
bg="#C3D0E8"
|
||||
c="blue"
|
||||
px={9}
|
||||
py={8}
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
borderRadius: '5px',
|
||||
}}
|
||||
>
|
||||
{Math.round(scale * 100)}%
|
||||
</Box>
|
||||
|
||||
|
||||
<Button variant="light" size="sm" onClick={handleZoomIn}>
|
||||
<IconZoomIn size={16} />
|
||||
</Button>
|
||||
|
||||
<Button variant="light" size="sm" onClick={resetZoom}>
|
||||
Reset
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
size="sm"
|
||||
@@ -260,6 +280,7 @@ function StrukturOrganisasiPPID() {
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
|
||||
{/* Chart Container */}
|
||||
<Box
|
||||
ref={chartContainerRef}
|
||||
@@ -324,7 +345,6 @@ function nodeTemplate(node: any, router: ReturnType<typeof useTransitionRouter>)
|
||||
<Text size="xs" c="dimmed" mt={8} lineClamp={3}>
|
||||
{description || 'Belum ada deskripsi.'}
|
||||
</Text>
|
||||
<Tooltip label="Lihat Detail" withArrow position="bottom">
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
@@ -336,7 +356,6 @@ function nodeTemplate(node: any, router: ReturnType<typeof useTransitionRouter>)
|
||||
>
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
@@ -54,12 +54,11 @@ function Page() {
|
||||
ta="center"
|
||||
fz={{ base: 28, md: 36 }}
|
||||
fw={800}
|
||||
variant="gradient"
|
||||
gradient={{ from: colors['blue-button'], to: 'cyan', deg: 45 }}
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
Moto PPID Desa Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" fz={{ base: 16, md: 20 }} mt="xs" c="dimmed">
|
||||
<Text ta="center" fz={{ base: 16, md: 20 }} mt="xs">
|
||||
Memberikan informasi yang cepat, mudah, tepat, dan transparan
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -67,7 +66,8 @@ function Page() {
|
||||
<Divider my="sm" labelPosition="center" label={<IconSparkles size={18} />} />
|
||||
|
||||
<Box>
|
||||
<Text ta="center" fz={{ base: 24, md: 30 }} fw={700} mb="sm">
|
||||
<Text ta="center" fz={{ base: 24, md: 30 }} fw={800}
|
||||
c={colors['blue-button']} mb="sm">
|
||||
Visi PPID
|
||||
</Text>
|
||||
<Text
|
||||
@@ -82,13 +82,13 @@ function Page() {
|
||||
<Divider my="sm" />
|
||||
|
||||
<Box>
|
||||
<Text ta="center" fz={{ base: 24, md: 30 }} fw={700} mb="sm">
|
||||
<Text ta="center" fz={{ base: 24, md: 30 }} fw={800}
|
||||
c={colors['blue-button']} mb="sm">
|
||||
Misi PPID
|
||||
</Text>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={1.7}
|
||||
ta="center"
|
||||
dangerouslySetInnerHTML={{ __html: item.misi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
'use client';
|
||||
import penghargaanState from "@/app/admin/(dashboard)/_state/desa/penghargaan";
|
||||
import colors from "@/con/colors";
|
||||
import { Carousel, CarouselSlide } from "@mantine/carousel";
|
||||
import { Box, Button, Container, Group, Paper, Stack, Text, useMantineTheme, Skeleton } from "@mantine/core";
|
||||
import { Carousel } from "@mantine/carousel";
|
||||
import { Box, Button, Container, Group, Paper, Skeleton, Stack, Text, useMantineTheme } from "@mantine/core";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { IconArrowRight, IconAward } from "@tabler/icons-react";
|
||||
import Autoplay from "embla-carousel-autoplay";
|
||||
import { IconAward, IconArrowRight } from "@tabler/icons-react";
|
||||
import { useTransitionRouter } from "next-view-transitions";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
@@ -18,7 +18,8 @@ export default function Page() {
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container w={{ base: "100%", md: "60%" }}>
|
||||
<Container w={{ base: "100%", md: "90%", lg: "60%" }}>
|
||||
|
||||
<Stack align="center" gap="sm">
|
||||
<Group gap="xs">
|
||||
<IconAward size={40} color={colors["blue-button"]} />
|
||||
@@ -37,11 +38,10 @@ export default function Page() {
|
||||
}
|
||||
|
||||
function Slider() {
|
||||
const height = 500;
|
||||
const width = 1200;
|
||||
const theme = useMantineTheme();
|
||||
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
|
||||
const autoplay = useRef(Autoplay({ delay: 3000 }));
|
||||
const tablet = useMediaQuery(`(max-width: ${theme.breakpoints.md})`);
|
||||
const autoplay = useRef(Autoplay({ delay: 3000, stopOnInteraction: false }));
|
||||
const state = useProxy(penghargaanState);
|
||||
const router = useTransitionRouter();
|
||||
|
||||
@@ -54,7 +54,7 @@ function Slider() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Group justify="center" py="xl">
|
||||
<Group justify="center" py="xl" gap="md">
|
||||
<Skeleton w={300} h={200} radius="lg" />
|
||||
<Skeleton w={300} h={200} radius="lg" visibleFrom="sm" />
|
||||
<Skeleton w={300} h={200} radius="lg" visibleFrom="md" />
|
||||
@@ -74,31 +74,49 @@ function Slider() {
|
||||
}
|
||||
|
||||
const slides = data.map((item) => (
|
||||
<CarouselSlide key={item.id}>
|
||||
<Carousel.Slide key={item.id}>
|
||||
<Paper
|
||||
h="100%"
|
||||
radius="lg"
|
||||
shadow="md"
|
||||
pos="relative"
|
||||
style={{
|
||||
height: "100%",
|
||||
backgroundImage: `url(${item.image?.link})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
transition: "transform 0.3s ease, box-shadow 0.3s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(-4px)";
|
||||
e.currentTarget.style.boxShadow = "0 8px 20px rgba(0,0,0,0.2)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(0)";
|
||||
e.currentTarget.style.boxShadow = "none";
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
pos="absolute"
|
||||
inset={0}
|
||||
bg="linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0.3))"
|
||||
bg="linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0.2))"
|
||||
style={{ borderRadius: 16 }}
|
||||
/>
|
||||
<Stack justify="flex-end" h="100%" gap="sm" p="lg" pos="relative">
|
||||
<Text fz="xl" fw={700} ta="center" c="white">
|
||||
<Text
|
||||
fz={{ base: "md", sm: "lg", md: "xl" }}
|
||||
fw={700}
|
||||
ta="center"
|
||||
c="white"
|
||||
lineClamp={3}
|
||||
style={{ textShadow: "0 2px 4px rgba(0,0,0,0.6)" }}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Group justify="center">
|
||||
<Button
|
||||
onClick={() => router.push(`/darmasaba/penghargaan/${item.id}`)}
|
||||
onClick={() =>
|
||||
router.push(`/darmasaba/penghargaan/${item.id}`)
|
||||
}
|
||||
size="md"
|
||||
radius="xl"
|
||||
rightSection={<IconArrowRight size={18} />}
|
||||
@@ -110,24 +128,83 @@ function Slider() {
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</CarouselSlide>
|
||||
</Carousel.Slide>
|
||||
));
|
||||
|
||||
return (
|
||||
<Carousel
|
||||
py="xl"
|
||||
plugins={[autoplay.current]}
|
||||
onMouseEnter={autoplay.current.stop}
|
||||
onMouseLeave={autoplay.current.reset}
|
||||
w={{ base: "100%", sm: "90%", md: "80%", lg: width }}
|
||||
h={height}
|
||||
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
|
||||
slideGap="md"
|
||||
loop
|
||||
align="start"
|
||||
slidesToScroll={mobile ? 1 : 2}
|
||||
<Box
|
||||
pos="relative"
|
||||
w="100%"
|
||||
mx="auto"
|
||||
px={{ base: "md", sm: "xl", md: "2rem", lg: "3rem" }}
|
||||
style={{
|
||||
maxWidth: 1300,
|
||||
}}
|
||||
>
|
||||
{slides}
|
||||
</Carousel>
|
||||
<Carousel
|
||||
py="xl"
|
||||
w="100%"
|
||||
h={{ base: 320, sm: 380, md: 420, lg: 450 }}
|
||||
slideSize={{
|
||||
base: "100%", // Mobile: 1
|
||||
sm: "50%", // Tablet kecil (≥768): 2
|
||||
md: "50%", // 1024px: tetap 2
|
||||
lg: "33.333%", // Desktop besar: 3
|
||||
}}
|
||||
slideGap={{ base: "md", sm: "md", md: "lg" }}
|
||||
loop
|
||||
align="start"
|
||||
slidesToScroll={mobile ? 1 : tablet ? 2 : 3}
|
||||
plugins={[autoplay.current]}
|
||||
onMouseEnter={autoplay.current.stop}
|
||||
onMouseLeave={autoplay.current.reset}
|
||||
withControls={data.length > 3}
|
||||
draggable={data.length > 1}
|
||||
styles={{
|
||||
root: {
|
||||
position: "relative",
|
||||
},
|
||||
viewport: {
|
||||
overflow: "hidden",
|
||||
},
|
||||
container: {
|
||||
alignItems: "stretch",
|
||||
},
|
||||
control: {
|
||||
zIndex: 20,
|
||||
backgroundColor: "rgba(255,255,255,0.95)",
|
||||
color: colors["blue-button"],
|
||||
border: `2px solid ${colors["blue-button"]}`,
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: "50%",
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
||||
transition: "all 0.2s ease",
|
||||
'&:hover': {
|
||||
backgroundColor: colors["blue-button"],
|
||||
color: "white",
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
'&[data-inactive]': {
|
||||
opacity: 0,
|
||||
cursor: 'default',
|
||||
},
|
||||
},
|
||||
controls: {
|
||||
position: "absolute",
|
||||
top: mobile ? "70%" : tablet ? "65%" : "60%",
|
||||
transform: "translateY(-50%)",
|
||||
width: mobile ? "100%" : tablet ? "calc(100% + 60px)" : "calc(100% + 100px)",
|
||||
left: mobile ? "0" : tablet ? "-30px" : "-50px",
|
||||
right: mobile ? "0" : tablet ? "-30px" : "-50px",
|
||||
padding: "0",
|
||||
justifyContent: "space-between",
|
||||
zIndex: 30,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{slides}
|
||||
</Carousel>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client'
|
||||
import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTiktok, IconBrandYoutube } from '@tabler/icons-react';
|
||||
import { useRef } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const sosialMedia = [
|
||||
{
|
||||
@@ -60,6 +62,39 @@ const tautanPenting = [
|
||||
]
|
||||
|
||||
function Footer() {
|
||||
|
||||
const emailRef = useRef<HTMLInputElement>(null)
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = emailRef.current?.value.trim();
|
||||
if (!email) return toast.error('Email wajib diisi!');
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) return toast.error('Format email tidak valid!');
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok && data.success) {
|
||||
toast.success('Berhasil! Cek email Anda untuk konfirmasi.');
|
||||
emailRef.current!.value = '';
|
||||
} else {
|
||||
toast.error(data.message || 'Gagal berlangganan.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('Gagal menghubungi server. Coba lagi nanti.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Stack bg="linear-gradient(180deg, #1C6EA4, #124170)" c="white">
|
||||
<Box w="100%" p="xl">
|
||||
@@ -166,8 +201,9 @@ function Footer() {
|
||||
w="70%"
|
||||
placeholder="Masukkan email Anda"
|
||||
rightSection={<IconAt size={16} />}
|
||||
ref={emailRef} // ini aja cukup
|
||||
/>
|
||||
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
|
||||
<Button onClick={handleSubmit} variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function Navbar() {
|
||||
stateNav.mobileOpen = false;
|
||||
}}
|
||||
>
|
||||
<Tooltip label="Go to homepage" position="bottom" withArrow>
|
||||
<Tooltip label="Kembali ke Beranda" position="bottom" withArrow>
|
||||
<Image
|
||||
src="/darmasaba-icon.png"
|
||||
alt="Village Logo"
|
||||
|
||||
@@ -26,7 +26,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
||||
<Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
|
||||
<Container pos="relative" w={{ base: '100%', md: '80%' }} fluid>
|
||||
<Flex align="center" justify="space-between" wrap={{ base: "wrap", md: "nowrap" }}>
|
||||
<Tooltip label="Go to Homepage" position="bottom" withArrow>
|
||||
<Tooltip label="Kembali ke Beranda" position="bottom" withArrow>
|
||||
<ActionIcon
|
||||
radius="xl"
|
||||
variant="transparent"
|
||||
@@ -50,12 +50,12 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
||||
<MenuItemCom
|
||||
key={k}
|
||||
item={item}
|
||||
isActive={pathname === item.href ||
|
||||
(item.children?.some(child => child.href === pathname))}
|
||||
isActive={item.href && pathname.startsWith(item.href) ||
|
||||
(item.children?.some(child => child.href && pathname.startsWith(child.href)))}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Tooltip label="Search content" position="bottom" withArrow>
|
||||
<Tooltip label="Cari Konten" position="bottom" withArrow>
|
||||
<ActionIcon
|
||||
variant="transparent"
|
||||
c={isSearch ? 'gray' : colors["blue-button"]}
|
||||
@@ -71,7 +71,7 @@ export function NavbarMainMenu({ listNavbar }: { listNavbar: MenuItem[] }) {
|
||||
|
||||
{/* hanya tampil kalau role = admin */}
|
||||
{stateAuth.role === "admin" && (
|
||||
<Tooltip label="My Profile" position="bottom" withArrow>
|
||||
<Tooltip label="Profil Saya" position="bottom" withArrow>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
next.push("/admin/landing-page/profile/program-inovasi")
|
||||
|
||||
@@ -38,7 +38,7 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
|
||||
justify="space-between"
|
||||
size="lg"
|
||||
radius="md"
|
||||
color={pathname === link.href ? 'blue' : 'gray'}
|
||||
color={link.href && pathname.startsWith(link.href) ? 'blue' : 'gray'}
|
||||
onClick={() => {
|
||||
if (link.href) {
|
||||
router.push(link.href);
|
||||
@@ -49,12 +49,12 @@ export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
|
||||
rightSection={<IconArrowRight size={18} />}
|
||||
styles={(theme) => ({
|
||||
root: {
|
||||
background: pathname === link.href ? theme.colors.blue[0] : 'transparent',
|
||||
color: pathname === link.href ? theme.colors.blue[7] : colors['blue-button'],
|
||||
fontWeight: pathname === link.href ? 600 : 500,
|
||||
background: link.href && pathname.startsWith(link.href) ? theme.colors.blue[0] : 'transparent',
|
||||
color: link.href && pathname.startsWith(link.href) ? theme.colors.blue[7] : colors['blue-button'],
|
||||
fontWeight: link.href && pathname.startsWith(link.href) ? 600 : 500,
|
||||
transition: "all 0.2s ease",
|
||||
"&:hover": {
|
||||
background: pathname === link.href ? theme.colors.blue[1] : theme.colors.gray[0],
|
||||
background: link.href && pathname.startsWith(link.href) ? theme.colors.blue[1] : theme.colors.gray[0],
|
||||
}
|
||||
},
|
||||
})}
|
||||
|
||||
@@ -34,10 +34,10 @@ function Apbdes() {
|
||||
const data = (state.findMany.data || []).slice(0, 3)
|
||||
|
||||
return (
|
||||
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
||||
<Stack p="sm" gap="xl" bg={colors.Bg}>
|
||||
<Box>
|
||||
<Stack gap="sm">
|
||||
<Text ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
<Text c={colors["blue-button"]} ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
@@ -117,7 +117,7 @@ function Apbdes() {
|
||||
)}
|
||||
</SimpleGrid>
|
||||
|
||||
<Group pb={80} justify="center">
|
||||
<Group justify="center" pb={10}>
|
||||
<Button
|
||||
component={Link}
|
||||
href="/darmasaba/apbdes"
|
||||
|
||||
@@ -30,9 +30,9 @@ function DesaAntiKorupsi() {
|
||||
const data = (state.desaAntikorupsi.findMany.data || []).slice(0, 6);
|
||||
return (
|
||||
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||
<Center>
|
||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
||||
<Text fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
||||
</Center>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
|
||||
<Center py={20}>
|
||||
|
||||
@@ -23,7 +23,7 @@ function Kepuasan() {
|
||||
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; Responden: number }>>([]);
|
||||
const [opened, { open, close }] = useDisclosure(false)
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -121,18 +121,18 @@ function Kepuasan() {
|
||||
|
||||
// Convert map to array and sort by date
|
||||
const barData = Array.from(monthYearMap.entries())
|
||||
.map(([key, count]) => {
|
||||
.map(([key, Responden]) => {
|
||||
const [year, month] = key.split('-');
|
||||
const monthName = new Date(Number(year), Number(month) - 1, 1)
|
||||
.toLocaleString('id-ID', { month: 'long' });
|
||||
return {
|
||||
month: `${monthName} ${year}`,
|
||||
count,
|
||||
Responden,
|
||||
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.sortKey - b.sortKey)
|
||||
.map(({ month, count }) => ({ month, count }));
|
||||
.map(({ month, Responden }) => ({ month, Responden }));
|
||||
|
||||
setBarChartData(barData);
|
||||
}
|
||||
@@ -140,7 +140,7 @@ function Kepuasan() {
|
||||
|
||||
if ((loading && !data) || !data) {
|
||||
return (
|
||||
<Stack py={10} px="xl">
|
||||
<Stack py={10} px="sm">
|
||||
<Skeleton height={300} mb="md" />
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
<Skeleton height={300} />
|
||||
@@ -154,9 +154,15 @@ function Kepuasan() {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack p="sm">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"sm"}>
|
||||
<Center>
|
||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: '2rem', md: '2.8rem' }}
|
||||
c={colors['blue-button']}
|
||||
fw={800}
|
||||
style={{ letterSpacing: '-0.5px' }}
|
||||
>Indeks Kepuasan Masyarakat</Text>
|
||||
</Center>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||
<Center mt={10}>
|
||||
@@ -168,7 +174,7 @@ function Kepuasan() {
|
||||
>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
<Box px={"sm"}>
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
@@ -185,7 +191,7 @@ function Kepuasan() {
|
||||
h={window.innerWidth < 480 ? 200 : 300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
series={[{ name: 'Responden', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
@@ -416,16 +422,22 @@ function Kepuasan() {
|
||||
}
|
||||
return (
|
||||
<Stack p={"sm"}>
|
||||
<Container size="lg" px="md">
|
||||
<Container size="lg" px="sm">
|
||||
<Center>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Text
|
||||
ta="center"
|
||||
fz={{ base: '2rem', md: '2.8rem' }}
|
||||
c={colors['blue-button']}
|
||||
fw={800}
|
||||
style={{ letterSpacing: '-0.5px' }}
|
||||
>Indeks Kepuasan Masyarakat</Text>
|
||||
</Center>
|
||||
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||
<Center mt={10}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
<Box px={"md"}>
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
@@ -448,7 +460,7 @@ function Kepuasan() {
|
||||
h={300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
series={[{ name: 'Responden', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
@@ -605,7 +617,7 @@ function Kepuasan() {
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal"
|
||||
label="Tanggal Pengisian"
|
||||
type="date"
|
||||
placeholder="masukkan tanggal"
|
||||
defaultValue={state.create.form.tanggal}
|
||||
|
||||
@@ -121,7 +121,7 @@ function LandingPage() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack bg={colors.Bg} p="md" gap="xl">
|
||||
<Stack bg={colors.Bg} p="md" gap="lg">
|
||||
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
|
||||
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
|
||||
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
||||
|
||||
@@ -30,10 +30,10 @@ const textHeading = {
|
||||
function Layanan() {
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||
<Stack pos={"relative"} bg={colors.grey[1]} gap={"xl"} py={"md"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||
<Stack align="center" gap={"0"}>
|
||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
<Text fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
|
||||
@@ -49,9 +49,9 @@ function Potensi() {
|
||||
const data = (state.findMany.data || []).slice(0, 4);
|
||||
|
||||
return (
|
||||
<Stack p="sm" gap="4rem">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||
<Text ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
<Stack p="sm" gap="xl">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||
<Text ta={"center"} fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||
{textHeading.title}
|
||||
</Text>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||
|
||||
@@ -35,7 +35,7 @@ function Prestasi() {
|
||||
<Stack align="center" gap="sm">
|
||||
<Group gap="xs">
|
||||
<IconTrophy size={36} color={colors["blue-button"]} />
|
||||
<Text ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
||||
<Text c={colors["blue-button"]} ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
||||
Prestasi Desa
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -55,7 +55,7 @@ function Prestasi() {
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
<Box py={50}>
|
||||
<Box py="lg">
|
||||
{loading ? (
|
||||
<Center mih={200}>
|
||||
<Loader color={colors["blue-button"]} size="xl" />
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMediaQuery } from "@mantine/hooks"
|
||||
import { Prisma } from "@prisma/client"
|
||||
import Link from "next/link"
|
||||
import { IconMoodSad } from "@tabler/icons-react"
|
||||
import colors from "@/con/colors"
|
||||
|
||||
export default function SDGS() {
|
||||
const theme = useMantineTheme()
|
||||
@@ -41,11 +42,7 @@ export default function SDGS() {
|
||||
order={1}
|
||||
fz={{ base: "2.4rem", md: "3.6rem" }}
|
||||
fw={900}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #1A5F7A, #159895)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
}}
|
||||
c={colors["blue-button"]}
|
||||
>
|
||||
SDGs Desa
|
||||
</Title>
|
||||
@@ -54,7 +51,7 @@ export default function SDGS() {
|
||||
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
|
||||
</Text>
|
||||
|
||||
<Box py={50}>
|
||||
<Box py="lg">
|
||||
<Paper
|
||||
p={{ base: "md", md: "xl" }}
|
||||
radius="2xl"
|
||||
@@ -156,7 +153,7 @@ export default function SDGS() {
|
||||
href="/darmasaba/sdgs-desa"
|
||||
radius="xl"
|
||||
size="lg"
|
||||
mt={40}
|
||||
mt="md"
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
style={{ boxShadow: "0 6px 14px rgba(18,65,112,0.25)"}}
|
||||
|
||||
@@ -17,7 +17,10 @@ import ScrollToTopButton from "./_com/scrollToTopButton";
|
||||
export default function Page() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack bg={colors.grey[1]} gap={"4rem"}>
|
||||
<Stack
|
||||
bg={colors.grey[1]}
|
||||
gap={"1.5rem"}
|
||||
>
|
||||
<LandingPage />
|
||||
<Penghargaan />
|
||||
<Layanan />
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
const colors = {
|
||||
"orange": "#FCAE00",
|
||||
"blue-button": "#0A4E78",
|
||||
"blue-button-1": "#E5F2FA",
|
||||
"blue-button-2": "#B8DAEF",
|
||||
"blue-button-3": "#8AC1E3",
|
||||
"blue-button-4": "#5DA9D8",
|
||||
"blue-button-5": "#2F91CC",
|
||||
"blue-button-6": "#083F61",
|
||||
"blue-button-7": "#062F49",
|
||||
"blue-button-8": "#041F32",
|
||||
"blue-button-trans": "#628EC6",
|
||||
"white-1": "#FBFBFC",
|
||||
"white-trans-1": "rgba(255, 255, 255, 0.5)",
|
||||
|
||||
Reference in New Issue
Block a user