Compare commits
3 Commits
nico/1-okt
...
nico/6-okt
| Author | SHA1 | Date | |
|---|---|---|---|
| cee0957e07 | |||
| 5c66eccf23 | |||
| f7fd9be255 |
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"id": "edit",
|
||||
"name": "Pelayanan Penduduk Non-Permanent",
|
||||
"deskripsi": "<p>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</p>"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"id": "edit",
|
||||
"name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)",
|
||||
"deskripsi": "<p>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</p>",
|
||||
"link" : "https://oss.go.id/"
|
||||
|
||||
@@ -1167,6 +1167,7 @@ model KontakDarurat {
|
||||
deskripsi String
|
||||
image FileStorage @relation(fields: [imageId], references: [id])
|
||||
imageId String
|
||||
whatsapp String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
@@ -1340,6 +1341,7 @@ model PasarDesa {
|
||||
harga Int
|
||||
rating Float
|
||||
alamatUsaha String
|
||||
kontak String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
@@ -1382,6 +1384,7 @@ model LowonganPekerjaan {
|
||||
gaji String
|
||||
deskripsi String
|
||||
kualifikasi String
|
||||
notelp String
|
||||
tanggalPosting DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -581,33 +581,24 @@ const pelayananPerizinanBerusaha = proxy({
|
||||
findById: {
|
||||
data: null as pelayananPerizinanBerusahaForm | null,
|
||||
loading: false,
|
||||
initialize() {
|
||||
pelayananPerizinanBerusaha.findById.data = {
|
||||
id: "",
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
link: "",
|
||||
} as pelayananPerizinanBerusahaForm;
|
||||
},
|
||||
async load(id: string) {
|
||||
try {
|
||||
pelayananPerizinanBerusaha.findById.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/desa/layanan/pelayananperizinanberusaha/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
pelayananPerizinanBerusaha.findById.data = data.data ?? null;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch pelayanan perizinan berusaha:",
|
||||
res.statusText
|
||||
);
|
||||
pelayananPerizinanBerusaha.findById.data = null;
|
||||
this.loading = true;
|
||||
const response = await fetch(`/api/desa/layanan/pelayananperizinanberusaha/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
this.data = result.data; // Make sure this matches your API response structure
|
||||
}
|
||||
return result?.data || null;
|
||||
} catch (error) {
|
||||
console.error("Error fetching pelayanan perizinan berusaha:", error);
|
||||
pelayananPerizinanBerusaha.findById.data = null;
|
||||
console.error('Error loading data:', error);
|
||||
toast.error('Gagal memuat data');
|
||||
return null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ const templateForm = z.object({
|
||||
gaji: z.string(),
|
||||
deskripsi: z.string(),
|
||||
kualifikasi: z.string(),
|
||||
notelp: z.string(),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
@@ -23,6 +24,7 @@ const defaultForm = {
|
||||
gaji: "",
|
||||
deskripsi: "",
|
||||
kualifikasi: "",
|
||||
notelp: "",
|
||||
};
|
||||
|
||||
const lowonganKerjaState = proxy({
|
||||
@@ -179,6 +181,7 @@ const lowonganKerjaState = proxy({
|
||||
gaji: data.gaji,
|
||||
deskripsi: data.deskripsi,
|
||||
kualifikasi: data.kualifikasi,
|
||||
notelp: data.notelp,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -218,6 +221,7 @@ const lowonganKerjaState = proxy({
|
||||
gaji: this.form.gaji,
|
||||
deskripsi: this.form.deskripsi,
|
||||
kualifikasi: this.form.kualifikasi,
|
||||
notelp: this.form.notelp,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -12,6 +12,7 @@ const templatePasarDesaForm = z.object({
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
rating: z.number().min(1, "Rating minimal 1"),
|
||||
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
|
||||
kontak: z.string().min(1, "Kontak wajib diisi"),
|
||||
});
|
||||
|
||||
const defaultPasarDesaForm = {
|
||||
@@ -21,6 +22,7 @@ const defaultPasarDesaForm = {
|
||||
imageId: "",
|
||||
rating: 0,
|
||||
kategoriId: [] as string[],
|
||||
kontak: "",
|
||||
};
|
||||
|
||||
const pasarDesa = proxy({
|
||||
@@ -188,6 +190,7 @@ const pasarDesa = proxy({
|
||||
imageId: data.imageId,
|
||||
rating: data.rating,
|
||||
kategoriId: data.kategoriId,
|
||||
kontak: data.kontak,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -225,6 +228,7 @@ const pasarDesa = proxy({
|
||||
imageId: this.form.imageId,
|
||||
rating: this.form.rating,
|
||||
kategoriId: this.form.kategoriId,
|
||||
kontak: this.form.kontak,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -336,6 +340,40 @@ const kategoriProduk = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
// ✅ Versi findManyAll (ambil semua tanpa pagination)
|
||||
findManyAll: {
|
||||
data: null as
|
||||
| Prisma.KategoriProdukGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (search = "") => {
|
||||
kategoriProduk.findManyAll.loading = true;
|
||||
kategoriProduk.findManyAll.search = search;
|
||||
|
||||
try {
|
||||
const query: any = {};
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many-all"].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriProduk.findManyAll.data = res.data.data ?? [];
|
||||
} else {
|
||||
kategoriProduk.findManyAll.data = [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori produk (all):", err);
|
||||
kategoriProduk.findManyAll.data = [];
|
||||
} finally {
|
||||
kategoriProduk.findManyAll.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.KategoriProdukGetPayload<{
|
||||
omit: { isActive: true };
|
||||
|
||||
@@ -9,12 +9,14 @@ const templateForm = z.object({
|
||||
name: z.string().min(3, "Judul minimal 3 karakter"),
|
||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
imageId: z.string().nonempty(),
|
||||
whatsapp: z.string().min(10, "Whatsapp minimal 10 karakter"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
imageId: "",
|
||||
whatsapp: "",
|
||||
};
|
||||
|
||||
const kontakDarurat = proxy({
|
||||
@@ -171,6 +173,7 @@ const kontakDarurat = proxy({
|
||||
name: data.name,
|
||||
deskripsi: data.deskripsi,
|
||||
imageId: data.imageId,
|
||||
whatsapp: data.whatsapp,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
@@ -207,6 +210,7 @@ const kontakDarurat = proxy({
|
||||
name: this.form.name,
|
||||
deskripsi: this.form.deskripsi,
|
||||
imageId: this.form.imageId,
|
||||
whatsapp: this.form.whatsapp,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ function PelayananPendudukNonPermanent() {
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/desa/layanan/pelayanan_penduduk_non_permanent/edit'
|
||||
`/admin/desa/layanan/pelayanan_penduduk_non_permanent/${data.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||
import colors from '@/con/colors';
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
@@ -21,66 +23,82 @@ import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditPelayananPerizinanBerusaha() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const statePerizinanBerusaha = useProxy(
|
||||
stateLayananDesa.pelayananPerizinanBerusaha
|
||||
);
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = params?.id; // ini langsung string
|
||||
const state = useProxy(stateLayananDesa.pelayananPerizinanBerusaha);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [formData, setFormData] = useState({
|
||||
id: '',
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
link: '',
|
||||
});
|
||||
|
||||
// load data pertama kali
|
||||
// Load data detail
|
||||
useEffect(() => {
|
||||
const loadPelayananPerizinan = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
return;
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const data = await statePerizinanBerusaha.update.load(id);
|
||||
setLoading(true);
|
||||
const data = await state.findById.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
link: data.link || '',
|
||||
id: data.id,
|
||||
name: data.name || "",
|
||||
deskripsi: data.deskripsi || "",
|
||||
link: data.link || "",
|
||||
});
|
||||
} else {
|
||||
toast.error("Data tidak ditemukan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading pelayanan perizinan berusaha:', error);
|
||||
toast.error('Gagal memuat data pelayanan perizinan berusaha');
|
||||
console.error("Error loading data:", error);
|
||||
toast.error("Gagal memuat data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
loadPelayananPerizinan();
|
||||
}, [params?.id]);
|
||||
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
|
||||
const handleChange =
|
||||
(field: keyof typeof formData) =>
|
||||
(value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
(value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const { name, deskripsi, link } = formData;
|
||||
if (statePerizinanBerusaha.findById.data) {
|
||||
const updatedData = {
|
||||
...statePerizinanBerusaha.findById.data,
|
||||
name,
|
||||
deskripsi,
|
||||
link,
|
||||
};
|
||||
await statePerizinanBerusaha.update.update(updatedData);
|
||||
try {
|
||||
await state.update.update(formData);
|
||||
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha');
|
||||
} catch (error) {
|
||||
console.error('Error updating pelayanan perizinan berusaha:', error);
|
||||
toast.error('Terjadi kesalahan saat update data');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Stack align="center" justify="center" py="xl">
|
||||
<Skeleton height={800} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
{/* Header Section */}
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button
|
||||
@@ -97,7 +115,7 @@ function EditPelayananPerizinanBerusaha() {
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Section */}
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
@@ -109,7 +127,6 @@ function EditPelayananPerizinanBerusaha() {
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Edit Pelayanan Perizinan Berusaha</Title>
|
||||
|
||||
{/* Nama Field */}
|
||||
<TextInput
|
||||
label="Judul"
|
||||
placeholder="Masukkan judul"
|
||||
@@ -118,7 +135,6 @@ function EditPelayananPerizinanBerusaha() {
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Link Field */}
|
||||
<TextInput
|
||||
label="Link"
|
||||
placeholder="Masukkan link terkait"
|
||||
@@ -126,7 +142,6 @@ function EditPelayananPerizinanBerusaha() {
|
||||
onChange={(e) => handleChange('link')(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* Deskripsi Field */}
|
||||
<Box>
|
||||
<Title order={6}>Deskripsi</Title>
|
||||
<EditEditor
|
||||
@@ -135,23 +150,20 @@ function EditPelayananPerizinanBerusaha() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
loading={statePerizinanBerusaha.update.loading}
|
||||
loading={state.update.loading}
|
||||
disabled={!formData.name}
|
||||
>
|
||||
{statePerizinanBerusaha.update.loading
|
||||
? 'Menyimpan...'
|
||||
: 'Simpan Perubahan'}
|
||||
{state.update.loading ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
disabled={statePerizinanBerusaha.update.loading}
|
||||
disabled={state.update.loading}
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
@@ -19,35 +20,58 @@ import {
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function PerizinanBerusaha() {
|
||||
const router = useRouter();
|
||||
const pelayananPerizinanBerusaha = useProxy(
|
||||
stateLayananDesa.pelayananPerizinanBerusaha
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [active, setActive] = useState(1);
|
||||
const nextStep = () =>
|
||||
setActive((current) => (current < 6 ? current + 1 : current));
|
||||
const prevStep = () =>
|
||||
setActive((current) => (current > 0 ? current - 1 : current));
|
||||
|
||||
useShallowEffect(() => {
|
||||
pelayananPerizinanBerusaha.findById.load('1');
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// You should get the ID from your router query or params
|
||||
const id = '1'; // Replace with actual ID or get from URL params
|
||||
await pelayananPerizinanBerusaha.findById.load(id);
|
||||
} catch (err) {
|
||||
setError('Gagal memuat data');
|
||||
console.error('Error:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
if (!pelayananPerizinanBerusaha.findById.data) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Stack align="center" justify="center" py="xl">
|
||||
<Skeleton radius="md" height={800} />
|
||||
<Skeleton height={800} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !pelayananPerizinanBerusaha.findById.data) {
|
||||
return (
|
||||
<Center h={200}>
|
||||
<Text>{error || 'Data tidak ditemukan'}</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const data = pelayananPerizinanBerusaha.findById.data;
|
||||
|
||||
return (
|
||||
@@ -69,7 +93,7 @@ function PerizinanBerusaha() {
|
||||
radius="md"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/desa/layanan/pelayanan_perizinan_berusaha/edit'
|
||||
`/admin/desa/layanan/pelayanan_perizinan_berusaha/${data.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -183,3 +207,4 @@ function PerizinanBerusaha() {
|
||||
}
|
||||
|
||||
export default PerizinanBerusaha;
|
||||
|
||||
|
||||
@@ -44,39 +44,37 @@ function EditSuratKeterangan() {
|
||||
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
|
||||
|
||||
// load data awal
|
||||
useEffect(() => {
|
||||
const loadSurat = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
useEffect(() => {
|
||||
const loadSurat = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await stateSurat.edit.load(id);
|
||||
if (!data) return;
|
||||
|
||||
try {
|
||||
const data = await stateSurat.edit.load(id);
|
||||
if (data) {
|
||||
// merge style -> isi hanya field kosong
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
name: prev.name || data.name || '',
|
||||
deskripsi: prev.deskripsi || data.deskripsi || '',
|
||||
imageId: prev.imageId || data.imageId || '',
|
||||
image2Id: prev.image2Id || data.image2Id || '',
|
||||
...{
|
||||
name: prev.name || data.name || "",
|
||||
deskripsi: prev.deskripsi || data.deskripsi || "",
|
||||
imageId: prev.imageId || data.imageId || "",
|
||||
image2Id: prev.image2Id || data.image2Id || "",
|
||||
},
|
||||
}));
|
||||
|
||||
if (data.image?.link && !previewImage) {
|
||||
setPreviewImage(data.image.link);
|
||||
}
|
||||
if (data.image2?.link && !previewImage2) {
|
||||
setPreviewImage2(data.image2.link);
|
||||
}
|
||||
if (data.image?.link && !previewImage) setPreviewImage(data.image.link);
|
||||
if (data.image2?.link && !previewImage2) setPreviewImage2(data.image2.link);
|
||||
} catch (error) {
|
||||
console.error("Error loading surat:", error);
|
||||
toast.error("Gagal memuat data surat");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading surat:", error);
|
||||
toast.error("Gagal memuat data surat");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
loadSurat();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [params?.id]);
|
||||
|
||||
loadSurat();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [params?.id]);
|
||||
|
||||
|
||||
// handler untuk submit
|
||||
|
||||
@@ -8,17 +8,15 @@ import {
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
|
||||
function EditPelayananTelunjukSakti() {
|
||||
const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa);
|
||||
@@ -111,21 +109,19 @@ function EditPelayananTelunjukSakti() {
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Deskripsi pakai editor */}
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(htmlContent) => handleChange('deskripsi', htmlContent)}
|
||||
/>
|
||||
</Box>
|
||||
{/* Deskripsi */}
|
||||
<TextInput
|
||||
value={formData.deskripsi}
|
||||
onChange={(e) => handleChange('deskripsi', e.target.value)}
|
||||
label="Judul Link"
|
||||
placeholder="Masukkan judul link"
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Link */}
|
||||
<TextInput
|
||||
label="Link"
|
||||
placeholder="Masukkan link terkait"
|
||||
placeholder="Masukkan alamat link"
|
||||
value={formData.link}
|
||||
onChange={(e) => handleChange('link', e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -82,8 +82,8 @@ function CreatePelayananTelunjukDesa() {
|
||||
onChange={(val) => {
|
||||
stateTelunjukDesa.create.form.deskripsi = val.target.value;
|
||||
}}
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi pelayanan"
|
||||
label="Judul Link"
|
||||
placeholder="Masukkan judul link"
|
||||
required
|
||||
/>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
||||
@@ -25,59 +25,65 @@ interface FormData {
|
||||
perempuan: number;
|
||||
}
|
||||
|
||||
function EditDemografiPekerjaan() {
|
||||
export default function EditDemografiPekerjaan() {
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const { id } = useParams() as { id: string };
|
||||
const stateDemografi = useProxy(demografiPekerjaan);
|
||||
|
||||
const id = params.id;
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
pekerjaan: '',
|
||||
lakiLaki: 0,
|
||||
perempuan: 0,
|
||||
});
|
||||
|
||||
// Load data sekali waktu
|
||||
// ✅ Load data hanya sekali di awal (tidak reset form)
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
stateDemografi.update.id = id;
|
||||
|
||||
stateDemografi.findUnique
|
||||
.load(id)
|
||||
.then(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
stateDemografi.update.id = id;
|
||||
await stateDemografi.findUnique.load(id);
|
||||
|
||||
const data = stateDemografi.findUnique.data;
|
||||
if (data) {
|
||||
setFormData({
|
||||
pekerjaan: String(data.pekerjaan || ''),
|
||||
lakiLaki: Number(data.lakiLaki || 0),
|
||||
perempuan: Number(data.perempuan || 0),
|
||||
pekerjaan: data.pekerjaan ?? '',
|
||||
lakiLaki: Number(data.lakiLaki ?? 0),
|
||||
perempuan: Number(data.perempuan ?? 0),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
toast.error('Gagal memuat data');
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const handleChange =
|
||||
(field: keyof FormData) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]:
|
||||
field === 'lakiLaki' || field === 'perempuan'
|
||||
? Number(e.currentTarget.value)
|
||||
: e.currentTarget.value,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
// ✅ Handler input terkontrol (tidak buat re-render berlebihan)
|
||||
const handleChange = useCallback(
|
||||
(field: keyof FormData) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value =
|
||||
field === 'lakiLaki' || field === 'perempuan'
|
||||
? Number(e.currentTarget.value)
|
||||
: e.currentTarget.value;
|
||||
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// ✅ Submit hanya update global state sekali
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
stateDemografi.update.id = id;
|
||||
stateDemografi.update.form = { ...formData };
|
||||
|
||||
await stateDemografi.update.submit();
|
||||
|
||||
toast.success('Data berhasil diperbarui');
|
||||
router.push('/admin/ekonomi/demografi-pekerjaan');
|
||||
} catch (error) {
|
||||
@@ -160,5 +166,3 @@ function EditDemografiPekerjaan() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditDemografiPekerjaan;
|
||||
|
||||
@@ -126,9 +126,9 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Pekerjaan</TableTh>
|
||||
<TableTh>Laki - Laki</TableTh>
|
||||
<TableTh>Perempuan</TableTh>
|
||||
<TableTh style={{ minWidth: 200 }}>Pekerjaan</TableTh>
|
||||
<TableTh style={{ minWidth: 200 }}>Laki - Laki</TableTh>
|
||||
<TableTh style={{ minWidth: 200 }}>Perempuan</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
@@ -137,9 +137,9 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.pekerjaan}</TableTd>
|
||||
<TableTd>{item.lakiLaki}</TableTd>
|
||||
<TableTd>{item.perempuan}</TableTd>
|
||||
<TableTd style={{ minWidth: 200 }}>{item.pekerjaan}</TableTd>
|
||||
<TableTd style={{ minWidth: 200 }}>{item.lakiLaki}</TableTd>
|
||||
<TableTd style={{ minWidth: 200 }}>{item.perempuan}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useShallowEffect, useMediaQuery } from '@mantine/hooks';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||
import { Bar, BarChart, Legend, XAxis, YAxis, Tooltip as RechartsTooltip } from 'recharts';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||
|
||||
// ✅ BarChart Mantine
|
||||
import { BarChart } from '@mantine/charts';
|
||||
|
||||
function JumlahPendudukMiskin() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Jumlah Penduduk Miskin'
|
||||
placeholder='Cari tahun atau jumlah penduduk miskin...'
|
||||
title="Jumlah Penduduk Miskin"
|
||||
placeholder="Cari tahun atau jumlah penduduk miskin..."
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -28,7 +48,7 @@ function JumlahPendudukMiskin() {
|
||||
}
|
||||
|
||||
function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
type JPMGrafik = { id: string; year: number; totalPoorPopulation: number }
|
||||
type JPMGrafik = { year: number; totalPoorPopulation: number };
|
||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||
const [chartData, setChartData] = useState<JPMGrafik[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -36,33 +56,27 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const isTablet = useMediaQuery('(max-width:1024px)');
|
||||
const isMobile = useMediaQuery('(max-width:768px)');
|
||||
const { data, page, loading, load, totalPages } = stateJPM.findMany;
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
loading,
|
||||
load,
|
||||
totalPages,
|
||||
} = stateJPM.findMany;
|
||||
// Load data
|
||||
// Load data awal
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
// Update chart data
|
||||
useEffect(() => {
|
||||
if (stateJPM.findMany.data) {
|
||||
setChartData(stateJPM.findMany.data.map(item => ({
|
||||
id: item.id,
|
||||
year: Number(item.year),
|
||||
totalPoorPopulation: Number(item.totalPoorPopulation)
|
||||
})));
|
||||
setChartData(
|
||||
stateJPM.findMany.data.map((item) => ({
|
||||
year: Number(item.year),
|
||||
totalPoorPopulation: Number(item.totalPoorPopulation),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [stateJPM.findMany.data]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
@@ -71,7 +85,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
setSelectedId(null);
|
||||
stateJPM.findMany.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -83,15 +97,18 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Tabel */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Jumlah Penduduk Miskin</Title>
|
||||
<Tooltip label="Tambah Data" withArrow>
|
||||
<Button
|
||||
leftSection={<IconEdit size={18} />}
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-miskin/create')}
|
||||
onClick={() =>
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-miskin/create')
|
||||
}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
@@ -109,22 +126,38 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? filteredData.map(item => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.year}</TableTd>
|
||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
||||
<TableTd>
|
||||
<Button variant='light' color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button variant='light' color="red" disabled={stateJPM.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true) }}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)) : (
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.year}</TableTd>
|
||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
disabled={stateJPM.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
@@ -138,6 +171,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -153,33 +187,38 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
{/* Bar Chart */}
|
||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||
<Stack>
|
||||
<Box mt="lg" style={{ width: '100%', minHeight: 350 }}>
|
||||
<Title order={4} mb="sm">Grafik Jumlah Penduduk Miskin</Title>
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData}>
|
||||
<XAxis dataKey="year" />
|
||||
<YAxis />
|
||||
<RechartsTooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="totalPoorPopulation" fill={colors['blue-button']} name="Jumlah Penduduk Miskin" />
|
||||
</BarChart>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Title order={4} mb="sm">
|
||||
Grafik Jumlah Penduduk Miskin
|
||||
</Title>
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<BarChart
|
||||
h={300}
|
||||
data={chartData.map((item) => ({
|
||||
name: item.year.toString(),
|
||||
value: item.totalPoorPopulation,
|
||||
}))}
|
||||
dataKey="name"
|
||||
series={[
|
||||
{ name: 'value', color: colors['blue-button'] },
|
||||
]}
|
||||
withTooltip
|
||||
valueFormatter={(v) => `${v.toLocaleString()} jiwa`}
|
||||
/>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus data ini?'
|
||||
text="Apakah anda yakin ingin menghapus data ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Flex,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { DonutChart } from '@mantine/charts';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||
|
||||
function GrafikBerdasarkanPendidikan() {
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Detail Data Pengangguran Berdasarkan Pendidikan'
|
||||
placeholder='Cari data pendidikan...'
|
||||
title="Detail Data Pengangguran Berdasarkan Pendidikan"
|
||||
placeholder="Cari data pendidikan..."
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -31,7 +49,6 @@ function GrafikBerdasarkanPendidikan() {
|
||||
function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
@@ -45,37 +62,45 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stategrafik.findMany;
|
||||
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stategrafik.findMany.data) {
|
||||
const SD = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SD || 0), 0);
|
||||
const SMP = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SMP || 0), 0);
|
||||
const SMA = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SMA || 0), 0);
|
||||
const D3 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
|
||||
const S1 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
|
||||
const SD = stategrafik.findMany.data.reduce(
|
||||
(acc: number, cur: any) => acc + Number(cur.SD || 0),
|
||||
0,
|
||||
);
|
||||
const SMP = stategrafik.findMany.data.reduce(
|
||||
(acc: number, cur: any) => acc + Number(cur.SMP || 0),
|
||||
0,
|
||||
);
|
||||
const SMA = stategrafik.findMany.data.reduce(
|
||||
(acc: number, cur: any) => acc + Number(cur.SMA || 0),
|
||||
0,
|
||||
);
|
||||
const D3 = stategrafik.findMany.data.reduce(
|
||||
(acc: number, cur: any) => acc + Number(cur.D3 || 0),
|
||||
0,
|
||||
);
|
||||
const S1 = stategrafik.findMany.data.reduce(
|
||||
(acc: number, cur: any) => acc + Number(cur.S1 || 0),
|
||||
0,
|
||||
);
|
||||
setDonutData([
|
||||
{ name: 'SD', value: SD, color: '#4b6Ef5', key: 'SD' },
|
||||
{ name: 'SMP', value: SMP, color: '#14b885', key: 'SMP' },
|
||||
{ name: 'SMA', value: SMA, color: '#E6A03B', key: 'SMA' },
|
||||
{ name: 'D3', value: D3, color: '#DB524D', key: 'D3' },
|
||||
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
|
||||
{ name: 'SD', value: SD, color: '#4b6Ef5' },
|
||||
{ name: 'SMP', value: SMP, color: '#14b885' },
|
||||
{ name: 'SMA', value: SMA, color: '#E6A03B' },
|
||||
{ name: 'D3', value: D3, color: '#DB524D' },
|
||||
{ name: 'S1', value: S1, color: '#1018A8FF' },
|
||||
]);
|
||||
}
|
||||
}, [stategrafik.findMany.data]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -87,21 +112,26 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Table Data */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
{/* Header */}
|
||||
<Flex justify="space-between" align="center" mb="md">
|
||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
<Title order={4}>List Pengangguran Berdasarkan Pendidikan</Title>
|
||||
<Tooltip label="Tambah Data" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create')}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create',
|
||||
)
|
||||
}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
@@ -120,7 +150,9 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
<TableTr>
|
||||
<TableTd colSpan={7}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Belum ada data grafik responden</Text>
|
||||
<Text color="dimmed">
|
||||
Belum ada data grafik responden
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -134,7 +166,15 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
<TableTd>{item.S1}</TableTd>
|
||||
<TableTd>
|
||||
<Tooltip label="Edit Data" withArrow>
|
||||
<Button color="green" variant="light" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`)}>
|
||||
<Button
|
||||
color="green"
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -148,7 +188,8 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -161,6 +202,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -176,51 +218,35 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box mt="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
||||
{mounted && donutData.length > 0 ? (
|
||||
<Box style={{ width: '100%', minHeight: 250 }}>
|
||||
<PieChart width={800} height={300} data={donutData}>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
cx={400}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={115}
|
||||
label
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Stack gap="xs" mt="sm">
|
||||
{donutData.map((entry) => (
|
||||
<Flex key={entry.key} gap="sm" align="center">
|
||||
<Box w={20} h={20} bg={entry.color} />
|
||||
<Text>{entry.name} : {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
) : (
|
||||
<Text color="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
{/* Donut Chart */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md" mt="md">
|
||||
<Stack>
|
||||
<Title order={3} pb={10}>
|
||||
Grafik Pengangguran Berdasarkan Pendidikan
|
||||
</Title>
|
||||
{donutData.length > 0 ? (
|
||||
<DonutChart
|
||||
data={donutData}
|
||||
withLabels
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
size={260}
|
||||
thickness={40}
|
||||
/>
|
||||
) : (
|
||||
<Text color="dimmed">
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan pendidikan ini?'
|
||||
text="Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan pendidikan ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Flex,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { DonutChart } from '@mantine/charts';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||
@@ -17,8 +34,8 @@ function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Detail Data Pengangguran'
|
||||
placeholder='Cari usia...'
|
||||
title="Detail Data Pengangguran"
|
||||
placeholder="Cari usia..."
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -31,7 +48,6 @@ function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: string }) {
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
@@ -45,17 +61,10 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stategrafik.findMany;
|
||||
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
load(page, 10, search)
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,16 +73,17 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
||||
const totalUsia26_35 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia26_35 || 0), 0);
|
||||
const totalUsia36_45 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia36_45 || 0), 0);
|
||||
const totalUsia46_keatas = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia46_keatas || 0), 0);
|
||||
|
||||
setDonutData([
|
||||
{ name: 'usia18_25', value: totalUsia18_25, color: colors['blue-button'], key: 'usia18_25' },
|
||||
{ name: 'usia26_35', value: totalUsia26_35, color: '#10A85AFF', key: 'usia26_35' },
|
||||
{ name: 'usia36_45', value: totalUsia36_45, color: '#C07B13FF', key: 'usia36_45' },
|
||||
{ name: 'usia46_keatas', value: totalUsia46_keatas, color: '#1094A8FF', key: 'usia46_keatas' },
|
||||
{ name: 'Usia 18-25', value: totalUsia18_25, color: colors['blue-button'] },
|
||||
{ name: 'Usia 26-35', value: totalUsia26_35, color: '#10A85AFF' },
|
||||
{ name: 'Usia 36-45', value: totalUsia36_45, color: '#C07B13FF' },
|
||||
{ name: 'Usia 46+', value: totalUsia46_keatas, color: '#1094A8FF' },
|
||||
]);
|
||||
}
|
||||
}, [stategrafik.findMany.data]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -85,24 +95,23 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Table */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Stack>
|
||||
{/* Header */}
|
||||
<Flex justify="space-between" align="center" mb="md">
|
||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
<Tooltip label="Tambah Data" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')
|
||||
}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{/* Table */}
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
@@ -110,26 +119,38 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
||||
<TableTh>Usia 18-25</TableTh>
|
||||
<TableTh>Usia 26-35</TableTh>
|
||||
<TableTh>Usia 36-45</TableTh>
|
||||
<TableTh>Usia 46 +</TableTh>
|
||||
<TableTh>Usia 46+</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map(item => (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.usia18_25}</TableTd>
|
||||
<TableTd>{item.usia26_35}</TableTd>
|
||||
<TableTd>{item.usia36_45}</TableTd>
|
||||
<TableTd>{item.usia46_keatas}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)}>
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" disabled={stategrafik.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true); }}>
|
||||
<Button
|
||||
color="red"
|
||||
disabled={stategrafik.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
@@ -147,10 +168,10 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -166,48 +187,29 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
{/* Donut Chart */}
|
||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||
<Stack>
|
||||
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
{mounted && donutData.length > 0 ? (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart width={800} height={300} data={donutData}>
|
||||
<Pie dataKey="value" nameKey="name" data={donutData} cx={400} cy={150} innerRadius={60} outerRadius={115} label>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Stack mt="sm" gap="xs">
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Usia 18-25 : {donutData.find((entry) => entry.name === 'usia18_25')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>Usia 26-35 : {donutData.find((entry) => entry.name === 'usia26_35')?.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#C07B13FF'} w={20} h={20} />
|
||||
<Text>Usia 36-45 : {donutData.find((entry) => entry.name === 'usia36_45')?.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#1094A8FF'} w={20} h={20} />
|
||||
<Text>Usia 46 + : {donutData.find((entry) => entry.name === 'usia46_keatas')?.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Title order={3} pb={10}>
|
||||
Grafik Pengangguran Berdasarkan Usia Kerja
|
||||
</Title>
|
||||
{donutData.length > 0 ? (
|
||||
<Center>
|
||||
<DonutChart
|
||||
data={donutData}
|
||||
withLabels
|
||||
withTooltip
|
||||
size={200}
|
||||
thickness={40}
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
</Center>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client';
|
||||
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
@@ -20,11 +21,18 @@ import { useEffect, useState, useCallback } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
// --- Helper konstanta
|
||||
const MONTHS = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des',
|
||||
];
|
||||
|
||||
function EditDetailDataPengangguran() {
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
// --- state lokal form
|
||||
const [formData, setFormData] = useState({
|
||||
month: '',
|
||||
year: new Date().getFullYear(),
|
||||
@@ -34,18 +42,13 @@ function EditDetailDataPengangguran() {
|
||||
percentageChange: 0,
|
||||
});
|
||||
|
||||
// Hitung total & perubahan otomatis
|
||||
// --- hitung total + persentase perubahan
|
||||
const calculateTotalAndChange = useCallback(
|
||||
async (data: typeof formData) => {
|
||||
const total = data.educatedUnemployment + data.uneducatedUnemployment;
|
||||
|
||||
let percentageChange = 0;
|
||||
const monthOrder = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des',
|
||||
];
|
||||
const currentMonthIndex = monthOrder.indexOf(data.month);
|
||||
|
||||
const currentMonthIndex = MONTHS.indexOf(data.month);
|
||||
if (currentMonthIndex !== -1) {
|
||||
let prevMonthIndex = currentMonthIndex - 1;
|
||||
let prevYear = data.year;
|
||||
@@ -55,17 +58,15 @@ function EditDetailDataPengangguran() {
|
||||
prevYear--;
|
||||
}
|
||||
|
||||
const prevMonth = monthOrder[prevMonthIndex];
|
||||
const prevData = await stateDetail.findByMonthYear.load({
|
||||
month: prevMonth,
|
||||
month: MONTHS[prevMonthIndex],
|
||||
year: prevYear,
|
||||
});
|
||||
|
||||
if (prevData && prevData.totalUnemployment > 0) {
|
||||
const change =
|
||||
((total - prevData.totalUnemployment) /
|
||||
prevData.totalUnemployment) *
|
||||
100;
|
||||
prevData.totalUnemployment) * 100;
|
||||
percentageChange = parseFloat(change.toFixed(1));
|
||||
}
|
||||
}
|
||||
@@ -75,67 +76,66 @@ function EditDetailDataPengangguran() {
|
||||
[stateDetail.findByMonthYear]
|
||||
);
|
||||
|
||||
// --- update state lokal
|
||||
const updateFormData = async (updates: Partial<typeof formData>) => {
|
||||
const newData = { ...formData, ...updates };
|
||||
const { total, percentageChange } = await calculateTotalAndChange(newData);
|
||||
setFormData({
|
||||
...newData,
|
||||
totalUnemployment: total,
|
||||
percentageChange,
|
||||
});
|
||||
setFormData({ ...newData, totalUnemployment: total, percentageChange });
|
||||
};
|
||||
|
||||
// Load detail hanya sekali
|
||||
// --- load detail by ID (sekali)
|
||||
useEffect(() => {
|
||||
const loadDetail = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
await stateDetail.findUnique.load(id); // ambil by ID
|
||||
await stateDetail.findUnique.load(id);
|
||||
const data = stateDetail.findUnique.data;
|
||||
if (!data) return;
|
||||
|
||||
if (data) {
|
||||
const yearValue =
|
||||
data.year && typeof data.year === 'object' && 'getFullYear' in data.year
|
||||
? (data.year as Date).getFullYear()
|
||||
: Number(data.year);
|
||||
const yearValue =
|
||||
data.year && typeof data.year === 'object' && 'getFullYear' in data.year
|
||||
? (data.year as Date).getFullYear()
|
||||
: Number(data.year);
|
||||
|
||||
stateDetail.update.id = id; // set ID untuk update
|
||||
stateDetail.update.id = id; // simpan id untuk update
|
||||
|
||||
setFormData({
|
||||
month: data.month,
|
||||
year: yearValue,
|
||||
totalUnemployment: data.totalUnemployment,
|
||||
educatedUnemployment: data.educatedUnemployment,
|
||||
uneducatedUnemployment: data.uneducatedUnemployment,
|
||||
percentageChange: data.percentageChange || 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading detail:', error);
|
||||
setFormData({
|
||||
month: data.month,
|
||||
year: yearValue,
|
||||
educatedUnemployment: data.educatedUnemployment,
|
||||
uneducatedUnemployment: data.uneducatedUnemployment,
|
||||
totalUnemployment: data.totalUnemployment,
|
||||
percentageChange: data.percentageChange || 0,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error loading detail:', err);
|
||||
toast.error('Gagal memuat data detail');
|
||||
}
|
||||
};
|
||||
|
||||
loadDetail();
|
||||
}, [params?.id, stateDetail.findUnique]);
|
||||
}, [params?.id]);
|
||||
|
||||
// --- submit form
|
||||
const handleSubmit = async () => {
|
||||
const { total, percentageChange } = await calculateTotalAndChange(formData);
|
||||
try {
|
||||
const { total, percentageChange } = await calculateTotalAndChange(formData);
|
||||
|
||||
stateDetail.update.form = {
|
||||
...formData,
|
||||
totalUnemployment: total,
|
||||
percentageChange,
|
||||
};
|
||||
|
||||
const success = await stateDetail.update.submit();
|
||||
if (success) {
|
||||
toast.success('Detail data pengangguran berhasil diperbarui!');
|
||||
router.push('/admin/ekonomi/jumlah-pengangguran');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating:', error);
|
||||
} catch (err) {
|
||||
console.error('Error updating:', err);
|
||||
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||
}
|
||||
};
|
||||
@@ -143,12 +143,7 @@ function EditDetailDataPengangguran() {
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm">
|
||||
@@ -167,10 +162,7 @@ function EditDetailDataPengangguran() {
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label="Bulan"
|
||||
data={[
|
||||
'Jan','Feb','Mar','Apr','Mei','Jun',
|
||||
'Jul','Agu','Sep','Okt','Nov','Des',
|
||||
]}
|
||||
data={MONTHS}
|
||||
value={formData.month}
|
||||
onChange={(val) => updateFormData({ month: val || '' })}
|
||||
/>
|
||||
@@ -184,8 +176,10 @@ function EditDetailDataPengangguran() {
|
||||
label="Pengangguran Terdidik"
|
||||
type="number"
|
||||
value={formData.educatedUnemployment}
|
||||
onChange={(val) =>
|
||||
updateFormData({ educatedUnemployment: Number(val.currentTarget.value) || 0 })
|
||||
onChange={(e) =>
|
||||
updateFormData({
|
||||
educatedUnemployment: Number(e.currentTarget.value) || 0,
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
@@ -193,8 +187,10 @@ function EditDetailDataPengangguran() {
|
||||
label="Pengangguran Tidak Terdidik"
|
||||
type="number"
|
||||
value={formData.uneducatedUnemployment}
|
||||
onChange={(val) =>
|
||||
updateFormData({ uneducatedUnemployment: Number(val.currentTarget.value) || 0 })
|
||||
onChange={(e) =>
|
||||
updateFormData({
|
||||
uneducatedUnemployment: Number(e.currentTarget.value) || 0,
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
@@ -33,6 +33,7 @@ function EditLowonganKerja() {
|
||||
gaji: '',
|
||||
deskripsi: '',
|
||||
kualifikasi: '',
|
||||
notelp: '',
|
||||
});
|
||||
|
||||
// load data sekali aja ketika mount / id berubah
|
||||
@@ -52,6 +53,7 @@ function EditLowonganKerja() {
|
||||
gaji: data.gaji || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
kualifikasi: data.kualifikasi || '',
|
||||
notelp: data.notelp || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -132,6 +134,14 @@ function EditLowonganKerja() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Nomor Yang Dapat Dihubungi"
|
||||
placeholder="Masukkan nomor yang dapat dihubungi"
|
||||
value={formData.notelp}
|
||||
onChange={(e) => handleChange("notelp", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Tipe Pekerjaan"
|
||||
placeholder="Masukkan tipe pekerjaan"
|
||||
|
||||
@@ -82,6 +82,11 @@ function DetailLowonganKerjaLokal() {
|
||||
<Text fz="md" c="dimmed">{data.lokasi || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Nomor Yang Dapat Dihubungi</Text>
|
||||
<Text fz="md" c="dimmed">{data.notelp || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Tipe Pekerjaan</Text>
|
||||
<Text fz="md" c="dimmed">{data.tipePekerjaan || '-'}</Text>
|
||||
|
||||
@@ -30,6 +30,7 @@ function CreateLowonganKerja() {
|
||||
gaji: '',
|
||||
deskripsi: '',
|
||||
kualifikasi: '',
|
||||
notelp: '',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -86,6 +87,15 @@ function CreateLowonganKerja() {
|
||||
placeholder="Masukkan nama perusahaan"
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
defaultValue={lowonganState.create.form.notelp}
|
||||
onChange={(val) =>
|
||||
(lowonganState.create.form.notelp = val.target.value)
|
||||
}
|
||||
label="Nomor Yang Dapat Dihubungi"
|
||||
placeholder="Masukkan nomor yang dapat dihubungi"
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
defaultValue={lowonganState.create.form.lokasi}
|
||||
onChange={(val) =>
|
||||
|
||||
@@ -31,6 +31,7 @@ type FormData = {
|
||||
imageId: string;
|
||||
rating: number;
|
||||
kategoriId: string[];
|
||||
kontak: string;
|
||||
};
|
||||
|
||||
function EditPasarDesa() {
|
||||
@@ -47,11 +48,12 @@ function EditPasarDesa() {
|
||||
imageId: '',
|
||||
rating: 0,
|
||||
kategoriId: [],
|
||||
kontak: '',
|
||||
});
|
||||
|
||||
// load data awal
|
||||
useEffect(() => {
|
||||
pasarState.kategoriProduk.findMany.load();
|
||||
pasarState.kategoriProduk.findManyAll.load();
|
||||
|
||||
const loadPasarDesa = async () => {
|
||||
const id = params?.id as string;
|
||||
@@ -67,6 +69,7 @@ function EditPasarDesa() {
|
||||
imageId: data.imageId || '',
|
||||
rating: data.rating || 0,
|
||||
kategoriId: data.KategoriToPasar?.map((k: any) => k.kategoriId) || [],
|
||||
kontak: data.kontak || '',
|
||||
});
|
||||
if (data.image?.link) setPreviewImage(data.image.link);
|
||||
}
|
||||
@@ -228,13 +231,21 @@ function EditPasarDesa() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Kontak"
|
||||
placeholder="Masukkan kontak"
|
||||
value={formData.kontak}
|
||||
onChange={(e) => handleChange('kontak', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
label="Kategori Produk"
|
||||
placeholder="Pilih kategori produk"
|
||||
value={formData.kategoriId}
|
||||
onChange={(val) => handleChange('kategoriId', val)}
|
||||
data={
|
||||
pasarState.kategoriProduk.findMany.data?.map((v) => ({
|
||||
pasarState.kategoriProduk.findManyAll.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama,
|
||||
})) || []
|
||||
|
||||
@@ -85,6 +85,11 @@ function DetailPasarDesa() {
|
||||
<Text fz="md" c="dimmed">{data.alamatUsaha || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Kontak</Text>
|
||||
<Text fz="md" c="dimmed">{data.kontak || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Gambar</Text>
|
||||
{data.image?.link ? (
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function CreatePasarDesa() {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
statePasar.kategoriProduk.findMany.load();
|
||||
statePasar.kategoriProduk.findManyAll.load();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -41,6 +41,7 @@ export default function CreatePasarDesa() {
|
||||
imageId: '',
|
||||
rating: 0,
|
||||
kategoriId: [],
|
||||
kontak: '',
|
||||
};
|
||||
setPreviewImage(null);
|
||||
setFile(null);
|
||||
@@ -184,6 +185,15 @@ export default function CreatePasarDesa() {
|
||||
onChange={(e) => (statePasar.pasarDesa.create.form.alamatUsaha = e.target.value)}
|
||||
/>
|
||||
|
||||
{/* Kontak */}
|
||||
<TextInput
|
||||
label="Kontak"
|
||||
type="number"
|
||||
placeholder="Masukkan kontak"
|
||||
defaultValue={statePasar.pasarDesa.create.form.kontak}
|
||||
onChange={(e) => (statePasar.pasarDesa.create.form.kontak = e.target.value)}
|
||||
/>
|
||||
|
||||
{/* Kategori Produk */}
|
||||
<MultiSelect
|
||||
label="Kategori Produk"
|
||||
@@ -191,7 +201,7 @@ export default function CreatePasarDesa() {
|
||||
value={statePasar.pasarDesa.create.form.kategoriId}
|
||||
onChange={(val) => (statePasar.pasarDesa.create.form.kategoriId = val)}
|
||||
data={
|
||||
statePasar.kategoriProduk.findMany.data?.map((v) => ({
|
||||
statePasar.kategoriProduk.findManyAll.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama,
|
||||
})) || []
|
||||
|
||||
@@ -18,84 +18,90 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
type Statistik = {
|
||||
tahun: string;
|
||||
jumlah: string;
|
||||
};
|
||||
|
||||
type FormData = {
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
icon: string;
|
||||
statistik: Statistik;
|
||||
};
|
||||
|
||||
const initialForm: FormData = {
|
||||
nama: '',
|
||||
deskripsi: '',
|
||||
icon: '',
|
||||
statistik: {
|
||||
tahun: string;
|
||||
jumlah: string;
|
||||
};
|
||||
tahun: '',
|
||||
jumlah: '',
|
||||
},
|
||||
};
|
||||
|
||||
function EditProgramKemiskinan() {
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const { id } = useParams() as { id: string };
|
||||
const stateProgram = useProxy(programKemiskinanState);
|
||||
const id = params.id;
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
nama: '',
|
||||
deskripsi: '',
|
||||
icon: '',
|
||||
statistik: {
|
||||
tahun: '',
|
||||
jumlah: '',
|
||||
},
|
||||
});
|
||||
const [formData, setFormData] = useState<FormData>(initialForm);
|
||||
|
||||
// load data ke local state sekali aja
|
||||
// Load data 1x dari global state → isi local state
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
stateProgram.findUnique
|
||||
.load(id)
|
||||
.then(() => {
|
||||
const data = stateProgram.findUnique.data;
|
||||
if (data) {
|
||||
setFormData({
|
||||
nama: data.nama || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
icon: data.icon || '',
|
||||
statistik: {
|
||||
tahun: data.statistik?.tahun?.toString() || '',
|
||||
jumlah: data.statistik?.jumlah?.toString() || '',
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error load data:', err);
|
||||
toast.error('Gagal mengambil data program');
|
||||
});
|
||||
}
|
||||
if (!id) return;
|
||||
|
||||
stateProgram.findUnique
|
||||
.load(id)
|
||||
.then(() => {
|
||||
const data = stateProgram.findUnique.data;
|
||||
if (data) {
|
||||
setFormData({
|
||||
nama: data.nama ?? '',
|
||||
deskripsi: data.deskripsi ?? '',
|
||||
icon: data.icon ?? '',
|
||||
statistik: {
|
||||
tahun: data.statistik?.tahun?.toString() ?? '',
|
||||
jumlah: data.statistik?.jumlah?.toString() ?? '',
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error load data:', err);
|
||||
toast.error('Gagal mengambil data program');
|
||||
});
|
||||
}, [id, stateProgram.findUnique]);
|
||||
|
||||
const handleChange = (field: keyof FormData, value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
// generic handler untuk field top-level
|
||||
const handleChange = useCallback(
|
||||
(field: keyof FormData, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleStatistikChange = (field: keyof FormData['statistik'], value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
statistik: {
|
||||
...prev.statistik,
|
||||
[field]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
// khusus nested statistik
|
||||
const handleStatistikChange = useCallback(
|
||||
(field: keyof Statistik, value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
statistik: { ...prev.statistik, [field]: value },
|
||||
}));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
stateProgram.update.id = id;
|
||||
stateProgram.update.form = formData;
|
||||
await stateProgram.update.update();
|
||||
|
||||
toast.success('Program berhasil diperbarui!');
|
||||
router.push('/admin/ekonomi/program-kemiskinan');
|
||||
} catch (error) {
|
||||
|
||||
@@ -29,10 +29,11 @@ function EditKontakDaruratKeamanan() {
|
||||
const kontakState = useProxy(kontakDarurat.kontakDaruratKeamananState);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
// Remove the dependency on data in the initial state
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
icon: "" as IconKey | "",
|
||||
kategoriId: [] as string[],
|
||||
kategoriId: [] as string[], // Initialize as empty array
|
||||
});
|
||||
|
||||
// Load data dari backend
|
||||
@@ -41,7 +42,7 @@ function EditKontakDaruratKeamanan() {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await kontakDarurat.kontakDaruratItem.findMany.load();
|
||||
|
||||
|
||||
const id = params?.id as string;
|
||||
if (id) {
|
||||
const data = await kontakState.update.load(id);
|
||||
@@ -49,7 +50,7 @@ function EditKontakDaruratKeamanan() {
|
||||
setFormData({
|
||||
name: data.nama || "",
|
||||
icon: (data.icon as IconKey) || "",
|
||||
kategoriId: data.kategoriId || [],
|
||||
kategoriId: Array.isArray(data.kategoriId) ? data.kategoriId : [],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -134,9 +135,9 @@ function EditKontakDaruratKeamanan() {
|
||||
data={
|
||||
Array.isArray(kontakDarurat.kontakDaruratItem.findMany.data)
|
||||
? kontakDarurat.kontakDaruratItem.findMany.data.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama,
|
||||
}))
|
||||
value: v.id,
|
||||
label: v.nama,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
clearable
|
||||
|
||||
@@ -48,27 +48,29 @@ function EditLaporanPublik() {
|
||||
const loadLaporanPublik = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
|
||||
try {
|
||||
const data = await stateLaporan.edit.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
judul: data.judul || '',
|
||||
lokasi: data.lokasi || '',
|
||||
tanggalWaktu: data.tanggalWaktu || '',
|
||||
status: data.status || '',
|
||||
penanganan: data.penanganan?.[0]?.deskripsi || '',
|
||||
kronologi: data.kronologi || '',
|
||||
});
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
judul: data.judul ?? prev.judul,
|
||||
lokasi: data.lokasi ?? prev.lokasi,
|
||||
tanggalWaktu: data.tanggalWaktu ?? prev.tanggalWaktu,
|
||||
status: (data.status as Status) ?? prev.status,
|
||||
penanganan: data.penanganan?.[0]?.deskripsi ?? prev.penanganan,
|
||||
kronologi: data.kronologi ?? prev.kronologi,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading laporan publik:', error);
|
||||
console.error("Error loading laporan publik:", error);
|
||||
toast.error("Gagal mengambil data laporan publik");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
loadLaporanPublik();
|
||||
}, [params?.id, stateLaporan.edit]);
|
||||
|
||||
|
||||
const handleChange = (field: string, value: string | Status) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
@@ -61,9 +62,9 @@ function EditPencegahanKriminalitas() {
|
||||
|
||||
const handleChange =
|
||||
(field: keyof typeof formData) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: e.target.value }));
|
||||
};
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: e.target.value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||
@@ -128,13 +129,17 @@ function EditPencegahanKriminalitas() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Deskripsi Singkat"
|
||||
placeholder="Masukkan deskripsi singkat"
|
||||
value={formData.deskripsiSingkat}
|
||||
onChange={handleChange('deskripsiSingkat')}
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsiSingkat}
|
||||
onChange={(val) =>
|
||||
setFormData((prev) => ({ ...prev, deskripsiSingkat: val }))
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Title order={6} fw="bold" fz="sm" mb={6}>
|
||||
|
||||
@@ -90,15 +90,17 @@ function CreatePencegahanKriminalitas() {
|
||||
/>
|
||||
|
||||
{/* Deskripsi Singkat */}
|
||||
<TextInput
|
||||
label="Deskripsi Singkat"
|
||||
placeholder="Masukkan deskripsi singkat"
|
||||
defaultValue={kriminalitasState.create.form.deskripsiSingkat}
|
||||
onChange={(e) => {
|
||||
kriminalitasState.create.form.deskripsiSingkat = e.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi Singkat
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={kriminalitasState.create.form.deskripsiSingkat}
|
||||
onChange={(val) => {
|
||||
kriminalitasState.create.form.deskripsiSingkat = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Deskripsi Panjang */}
|
||||
<Box>
|
||||
|
||||
@@ -105,9 +105,11 @@ function ListPencegahanKriminalitas({ search }: { search: string }) {
|
||||
data.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
<Box w={200}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
|
||||
@@ -127,7 +127,9 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
{item.tarifdanlayanan?.layanan || '-'}
|
||||
<Text truncate="end" lineClamp={1}>
|
||||
{item.tarifdanlayanan?.layanan || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
|
||||
@@ -140,15 +140,15 @@ function EditInfoWabahPenyakit() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
value={formData.deskripsiSingkat}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
updateField('deskripsiSingkat', e.target.value)
|
||||
}
|
||||
label="Deskripsi Singkat"
|
||||
placeholder="Masukkan deskripsi singkat"
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">
|
||||
Deskripsi Singkat
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsiSingkat}
|
||||
onChange={(val) => updateField('deskripsiSingkat', val)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">
|
||||
|
||||
@@ -84,7 +84,7 @@ function DetailInfoWabahPenyakit() {
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }}>{data.deskripsiSingkat || '-'}</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.deskripsiSingkat }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -100,15 +100,15 @@ function CreateInfoWabahPenyakit() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
defaultValue={infoWabahPenyakitState.create.form.deskripsiSingkat}
|
||||
onChange={(val) => {
|
||||
infoWabahPenyakitState.create.form.deskripsiSingkat = val.target.value;
|
||||
}}
|
||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||
placeholder="Masukkan deskripsi singkat"
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">Deskripsi Singkat</Text>
|
||||
<CreateEditor
|
||||
value={infoWabahPenyakitState.create.form.deskripsiSingkat}
|
||||
onChange={(val) => {
|
||||
infoWabahPenyakitState.create.form.deskripsiSingkat = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||
|
||||
@@ -33,6 +33,7 @@ function EditKontakDarurat() {
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
imageId: '',
|
||||
whatsapp: '',
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -49,6 +50,7 @@ function EditKontakDarurat() {
|
||||
name: data.name || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
imageId: data.imageId || '',
|
||||
whatsapp: data.whatsapp || '',
|
||||
});
|
||||
if (data?.image?.link) setPreviewImage(data.image.link);
|
||||
}
|
||||
@@ -124,6 +126,14 @@ function EditKontakDarurat() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
value={formData.whatsapp}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, whatsapp: e.target.value }))}
|
||||
label="Whatsapp"
|
||||
placeholder="Masukkan whatsapp"
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||
<EditEditor
|
||||
|
||||
@@ -72,6 +72,11 @@ function DetailKontakDarurat() {
|
||||
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Whatsapp</Text>
|
||||
<Text fz="md" c="dimmed">{data.whatsapp || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text
|
||||
|
||||
@@ -38,6 +38,7 @@ function CreateKontakDarurat() {
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
imageId: '',
|
||||
whatsapp: '',
|
||||
};
|
||||
setPreviewImage(null);
|
||||
setFile(null);
|
||||
@@ -105,6 +106,17 @@ function CreateKontakDarurat() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
type='number'
|
||||
defaultValue={kontakDaruratState.create.form.whatsapp}
|
||||
onChange={(val) => {
|
||||
kontakDaruratState.create.form.whatsapp = val.target.value;
|
||||
}}
|
||||
label={<Text fz="sm" fw="bold">Whatsapp</Text>}
|
||||
placeholder="Masukkan whatsapp"
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||
<CreateEditor
|
||||
|
||||
@@ -117,7 +117,6 @@ function EditProgramKesehatan() {
|
||||
<Stack gap="md">
|
||||
{[
|
||||
{ label: 'Judul', key: 'name', placeholder: 'Masukkan judul' },
|
||||
{ label: 'Deskripsi Singkat', key: 'deskripsiSingkat', placeholder: 'Masukkan deskripsi singkat' },
|
||||
].map((field) => (
|
||||
<TextInput
|
||||
key={field.key}
|
||||
@@ -129,6 +128,16 @@ function EditProgramKesehatan() {
|
||||
/>
|
||||
))}
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Deskripsi Singkat
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsiSingkat}
|
||||
onChange={(val) => handleChange('deskripsiSingkat', val)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
|
||||
@@ -73,7 +73,7 @@ function DetailProgramKesehatan() {
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }}>{data?.deskripsiSingkat || '-'}</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data?.deskripsiSingkat || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -101,15 +101,17 @@ function CreateProgramKesehatan() {
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
defaultValue={programKesehatanState.create.form.deskripsiSingkat}
|
||||
onChange={(val) => {
|
||||
programKesehatanState.create.form.deskripsiSingkat = val.target.value;
|
||||
}}
|
||||
label="Deskripsi Singkat"
|
||||
placeholder="Masukkan deskripsi singkat"
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Title order={6} mb={6}>
|
||||
Deskripsi Singkat
|
||||
</Title>
|
||||
<CreateEditor
|
||||
value={programKesehatanState.create.form.deskripsiSingkat}
|
||||
onChange={(val) => {
|
||||
programKesehatanState.create.form.deskripsiSingkat = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Title order={6} mb={6}>
|
||||
|
||||
@@ -40,7 +40,7 @@ const DetailDataPengangguran = new Elysia({
|
||||
percentageChange: t.Optional(t.Number()),
|
||||
}),
|
||||
})
|
||||
.delete("/:id", detailDataPengangguranDelete, {
|
||||
.delete("/del/:id", detailDataPengangguranDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
|
||||
@@ -9,6 +9,7 @@ type FormCreate = {
|
||||
gaji: string;
|
||||
deskripsi: string;
|
||||
kualifikasi: string;
|
||||
notelp: string;
|
||||
}
|
||||
|
||||
export default async function lowonganKerjaCreate(context: Context) {
|
||||
@@ -23,6 +24,7 @@ export default async function lowonganKerjaCreate(context: Context) {
|
||||
gaji: body.gaji,
|
||||
deskripsi: body.deskripsi,
|
||||
kualifikasi: body.kualifikasi,
|
||||
notelp: body.notelp,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const LowonganKerja = new Elysia({prefix: "/lowongankerja", tags: ["Ekonomi/Lowo
|
||||
gaji: t.String(),
|
||||
deskripsi: t.String(),
|
||||
kualifikasi: t.String(),
|
||||
notelp: t.String(),
|
||||
})
|
||||
})
|
||||
.get("/find-many", lowonganKerjaFindMany)
|
||||
@@ -35,6 +36,7 @@ const LowonganKerja = new Elysia({prefix: "/lowongankerja", tags: ["Ekonomi/Lowo
|
||||
gaji: t.String(),
|
||||
deskripsi: t.String(),
|
||||
kualifikasi: t.String(),
|
||||
notelp: t.String(),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ type FormUpdate = {
|
||||
gaji: string;
|
||||
deskripsi: string;
|
||||
kualifikasi: string;
|
||||
notelp: string;
|
||||
}
|
||||
|
||||
export default async function lowonganKerjaUpdate(context: Context){
|
||||
@@ -16,7 +17,7 @@ export default async function lowonganKerjaUpdate(context: Context){
|
||||
const id = context.params?.id;
|
||||
const body = context.body as FormUpdate;
|
||||
|
||||
const { posisi, namaPerusahaan, lokasi, tipePekerjaan, gaji, deskripsi, kualifikasi } = body;
|
||||
const { posisi, namaPerusahaan, lokasi, tipePekerjaan, gaji, deskripsi, kualifikasi, notelp } = body;
|
||||
|
||||
if (!id) {
|
||||
return Response.json({
|
||||
@@ -46,6 +47,7 @@ export default async function lowonganKerjaUpdate(context: Context){
|
||||
gaji,
|
||||
deskripsi,
|
||||
kualifikasi,
|
||||
notelp,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ type FormCreate = {
|
||||
alamatUsaha: string;
|
||||
imageId: string;
|
||||
rating: number;
|
||||
kategoriId: string[]; // Array of KategoriProduk IDs
|
||||
kategoriId: string[];
|
||||
kontak: string;
|
||||
// Array of KategoriProduk IDs
|
||||
};
|
||||
|
||||
export default async function pasarDesaCreate(context: Context) {
|
||||
@@ -28,7 +30,9 @@ export default async function pasarDesaCreate(context: Context) {
|
||||
alamatUsaha: body.alamatUsaha,
|
||||
imageId: body.imageId,
|
||||
rating: Number(body.rating),
|
||||
kategoriProdukId: body.kategoriId[0], // Use the first category as the main one
|
||||
kategoriProdukId: body.kategoriId[0],
|
||||
kontak: body.kontak
|
||||
// Use the first category as the main one
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const PasarDesa = new Elysia({
|
||||
imageId: t.String(),
|
||||
rating: t.Number(),
|
||||
kategoriId: t.Array(t.String()),
|
||||
kontak: t.String(),
|
||||
}),
|
||||
}
|
||||
)
|
||||
@@ -79,6 +80,7 @@ const PasarDesa = new Elysia({
|
||||
imageId: t.String(),
|
||||
rating: t.Number(),
|
||||
kategoriId: t.Array(t.String()),
|
||||
kontak: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyAll.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function kategoriProdukFindManyAll(context: Context) {
|
||||
// Ambil query search (opsional)
|
||||
const search2 = (context.query.search as string) || "";
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
if (search2) {
|
||||
where.OR = [
|
||||
{ nama: { contains: search2, mode: "insensitive" } },
|
||||
{
|
||||
KategoriToPasar: {
|
||||
some: {
|
||||
kategori: {
|
||||
nama: { contains: search2, mode: "insensitive" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await prisma.kategoriProduk.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil semua kategori produk",
|
||||
data,
|
||||
total: data.length,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findManyAll:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data kategori produk",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default kategoriProdukFindManyAll;
|
||||
@@ -5,12 +5,14 @@ import kategoriProdukDelete from "./del";
|
||||
import kategoriProdukCreate from "./create";
|
||||
import kategoriProdukUpdate from "./updt";
|
||||
import { t } from "elysia";
|
||||
import kategoriProdukFindManyAll from "./findManyAll";
|
||||
|
||||
const KategoriProduk = new Elysia({
|
||||
prefix: "/kategoriproduk",
|
||||
tags: ["Ekonomi/Kategori Produk"],
|
||||
})
|
||||
.get("/find-many", kategoriProdukFindMany)
|
||||
.get("/find-many-all", kategoriProdukFindManyAll)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await kategoriProdukFindUnique(context);
|
||||
return response;
|
||||
|
||||
@@ -9,6 +9,7 @@ type FormUpdate = {
|
||||
imageId: string;
|
||||
rating: number;
|
||||
kategoriId: string[]; // Array of KategoriProduk IDs
|
||||
kontak: string;
|
||||
};
|
||||
|
||||
export default async function pasarDesaUpdate(context: Context) {
|
||||
@@ -31,6 +32,7 @@ export default async function pasarDesaUpdate(context: Context) {
|
||||
alamatUsaha: body.alamatUsaha,
|
||||
imageId: body.imageId,
|
||||
rating: Number(body.rating),
|
||||
kontak: body.kontak
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export default async function kontakDaruratKeamananFindMany(context: Context) {
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.kontakDaruratKeamanan.findMany({
|
||||
where,
|
||||
include: {
|
||||
kontakItems: {
|
||||
include: {
|
||||
|
||||
@@ -25,6 +25,7 @@ export default async function kontakItemFindMany(context: Context) {
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
where,
|
||||
}),
|
||||
prisma.kontakItem.count({ where }),
|
||||
]);
|
||||
|
||||
@@ -7,6 +7,7 @@ type FormCreate = Prisma.KontakDaruratGetPayload<{
|
||||
name: true;
|
||||
deskripsi: true;
|
||||
imageId: true;
|
||||
whatsapp: true;
|
||||
};
|
||||
}>;
|
||||
export default async function kontakDaruratCreate(context: Context) {
|
||||
@@ -17,6 +18,7 @@ export default async function kontakDaruratCreate(context: Context) {
|
||||
name: body.name,
|
||||
deskripsi: body.deskripsi,
|
||||
imageId: body.imageId,
|
||||
whatsapp: body.whatsapp,
|
||||
}
|
||||
})
|
||||
return {
|
||||
|
||||
@@ -14,6 +14,7 @@ const KontakDarurat = new Elysia({
|
||||
name: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imageId: t.String(),
|
||||
whatsapp: t.String(),
|
||||
})
|
||||
})
|
||||
.get("/find-many", kontakDaruratFindMany)
|
||||
@@ -33,6 +34,7 @@ const KontakDarurat = new Elysia({
|
||||
name: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imageId: t.String(),
|
||||
whatsapp: t.String(),
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ type FormUpdate = Prisma.KontakDaruratGetPayload<{
|
||||
name: true;
|
||||
deskripsi: true;
|
||||
imageId: true;
|
||||
whatsapp: true;
|
||||
}
|
||||
}>
|
||||
export default async function kontakDaruratUpdate(context: Context) {
|
||||
@@ -21,6 +22,7 @@ export default async function kontakDaruratUpdate(context: Context) {
|
||||
name,
|
||||
deskripsi,
|
||||
imageId,
|
||||
whatsapp,
|
||||
} = body;
|
||||
|
||||
if(!id) {
|
||||
@@ -75,6 +77,7 @@ export default async function kontakDaruratUpdate(context: Context) {
|
||||
name,
|
||||
deskripsi,
|
||||
imageId,
|
||||
whatsapp,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,83 +1,52 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Container, Grid, GridCol, Group, Paper, TextInput, Text, Image, Flex, Button } from '@mantine/core';
|
||||
import { IconCalendar, IconMapPin, IconSearch, IconUsersGroup } from '@tabler/icons-react';
|
||||
import { Stack, Container, Box, List, ListItem, Text, Image } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
||||
{/* Header */}
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap={0} mb="xl">
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Program Gotong Royong
|
||||
<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={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||
Bumdes Pudak Mesari
|
||||
</Text>
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Desa Darmasaba
|
||||
<Text
|
||||
ta={"center"}
|
||||
fw={"bold"}
|
||||
fz={"1.5rem"}
|
||||
>
|
||||
Informasi dan Pelayanan Administrasi Digital
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Image src="/api/img/ack.png" alt='' w={"100%"} />
|
||||
</Container>
|
||||
|
||||
{/* Tabs Menu */}
|
||||
<Box px={{ base: "md", md: "xl" }} py="md" bg={colors['BG-trans']} mb="md">
|
||||
<Grid align="center" justify="space-between" mb={20}>
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Group gap="md" wrap="wrap">
|
||||
<Paper bg={colors['blue-button']} radius="xl" py={5} px={20}>
|
||||
<Text c={colors['white-1']} size="sm">
|
||||
Semua
|
||||
</Text>
|
||||
</Paper>
|
||||
{['Kebersihan', 'Infrastruktur', 'Sosial', 'Lingkungan'].map((kategori) => (
|
||||
<Paper key={kategori} bg={colors['blue-button-trans']} radius="xl" py={5} px={20}>
|
||||
<Text size="sm">
|
||||
{kategori}
|
||||
</Text>
|
||||
</Paper>
|
||||
))}
|
||||
</Group>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder="Cari Program Gotong Royong"
|
||||
leftSection={<IconSearch size={18} />}
|
||||
w="100%"
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Image radius={20} src={'/api/img/gotong-royong.png'} w={'100%'} alt='' />
|
||||
<Text fw={"bold"} fz={{ base: "h2", md: "h1" }}>Membangun Fasilitas Desa</Text>
|
||||
<Group>
|
||||
<Paper py={5} px={20} bg={colors['blue-button-trans']} radius={20}>
|
||||
<Text c={colors['white-1']}>Sosial</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "h4", md: "h3" }}>
|
||||
Program Pembangunan Fasilitas Desa Maju, Masyarakat Sejahtera.
|
||||
</Text>
|
||||
<Flex gap={5} align={'center'}>
|
||||
<IconCalendar color={colors['blue-button-trans']} size={45} />
|
||||
<Text fz={{ base: "h4", md: "h3" }}>1 April 2025</Text>
|
||||
</Flex>
|
||||
<Flex gap={5} align={'center'}>
|
||||
<IconMapPin color={colors['blue-button-trans']} size={45} />
|
||||
<Text fz={{ base: "h4", md: "h3" }}>Banjar Desa Darmasaba</Text>
|
||||
</Flex>
|
||||
<Flex gap={5} align={'center'}>
|
||||
<IconUsersGroup color={colors['blue-button-trans']} size={45} />
|
||||
<Text fz={{ base: "h4", md: "h3" }}>30 Partisipan</Text>
|
||||
</Flex>
|
||||
<Text fw={'bold'} fz={'md'}>Deskripsi : Program pembangunan Pura sebagai pusat spiritual dan budaya desa, melibatkan gotong royong masyarakat dalam pembangunan struktur utama serta ornamen tradisional.</Text>
|
||||
<Group py={20} justify='center'>
|
||||
<Button component={Link} href={'https://www.whatsapp.com/?lang=id'} bg={colors['blue-button']} >Daftar Sebagai Relawan</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
Badan Usaha Milik Desa (BUMDes) Pudak Mesari adalah lembaga ekonomi desa yang berperan penting dalam pengembangan potensi dan kesejahteraan masyarakat Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali. BUMDes ini berfungsi sebagai motor penggerak perekonomian desa melalui berbagai unit usaha yang dikelola secara profesional.
|
||||
</Text>
|
||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
Potensi dan Peran BUMDes Pudak Mesari:
|
||||
</Text>
|
||||
<List py={20} type='ordered'>
|
||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengembangan Usaha Mikro dan Kecil:</Text>BUMDes Pudak Mesari menyediakan layanan bagi pelaku usaha mikro dan kecil di desa, seperti penyediaan konsumsi dan snack kotak untuk berbagai acara.
|
||||
</ListItem>
|
||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengelolaan Sampah Berbasis Masyarakat:</Text>Melalui kolaborasi dengan komunitas pemuda peduli lingkungan, BUMDes Pudak Mesari aktif dalam pengelolaan sampah berbasis masyarakat.
|
||||
</ListItem>
|
||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Peningkatan Kapasitas dan Transparansi:</Text>Untuk memastikan pengelolaan yang akuntabel, BUMDes Pudak Mesari rutin mengadakan rapat koordinasi dan pendampingan penyusunan laporan pertanggungjawaban.
|
||||
</ListItem>
|
||||
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Kolaborasi Internasional:</Text>Desa Darmasaba, melalui BUMDes Pudak Mesari, menerima kunjungan dari tim Osaki Jepang untuk memperkuat pengelolaan sampah dan lingkungan.
|
||||
</ListItem>
|
||||
</List>
|
||||
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||
Dengan berbagai inisiatif tersebut, BUMDes Pudak Mesari menunjukkan perannya sebagai pilar utama dalam pengembangan ekonomi dan kesejahteraan masyarakat Desa Darmasaba, sekaligus menjaga kelestarian lingkungan melalui program-program inovatif dan kolaboratif.
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
@@ -206,8 +206,117 @@ function Page() {
|
||||
<Stack gap="lg" justify="center">
|
||||
<Paper bg={colors['white-1']} p="xl">
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||
{/* Pendapatan, Belanja, Pembiayaan Card sama seperti sebelumnya */}
|
||||
{/* ... */}
|
||||
{/* 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>
|
||||
|
||||
@@ -218,7 +327,7 @@ function Page() {
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Keterangan</Table.Th>
|
||||
<Table.Th align="right">Jumlah</Table.Th>
|
||||
<Table.Th ta={"right"}>Jumlah</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
|
||||
@@ -86,38 +86,41 @@ function Page() {
|
||||
<Paper p={'lg'}>
|
||||
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Usia</Text>
|
||||
{mounted && donutGrafikNganggurData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart
|
||||
size={300}
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurData}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
mx="auto" />
|
||||
<Box w="100%" style={{ maxWidth: 400, margin: "0 auto" }}>
|
||||
<PieChart
|
||||
w="100%"
|
||||
h={250} // lebih kecil biar aman di mobile
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurData}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
</Box>
|
||||
</Box>) : <Skeleton h={500} />}
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'} wrap="wrap">
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>18-25</Text>
|
||||
<ColorSwatch color="#4b6Ef5" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>26-35</Text>
|
||||
<ColorSwatch color="#14b885" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>36-45</Text>
|
||||
<ColorSwatch color="#E6A03B" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>46+</Text>
|
||||
<ColorSwatch color="#DB524D" size={30} />
|
||||
</Flex>
|
||||
@@ -127,44 +130,47 @@ function Page() {
|
||||
<Paper p={'lg'}>
|
||||
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Pendidikan</Text>
|
||||
{mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (<Center>
|
||||
<PieChart
|
||||
size={300}
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurDataPendidikan}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
mx="auto" />
|
||||
<Box w="100%" style={{ maxWidth: 400, margin: "0 auto" }}>
|
||||
<PieChart
|
||||
w="100%"
|
||||
h={250} // lebih kecil biar aman di mobile
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabels
|
||||
data={donutGrafikNganggurDataPendidikan}
|
||||
withTooltip
|
||||
tooltipDataSource="segment"
|
||||
/>
|
||||
</Box>
|
||||
</Center>) : <Skeleton h={500} />}
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'} wrap="wrap">
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SD</Text>
|
||||
<ColorSwatch color="#4b6Ef5" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMP</Text>
|
||||
<ColorSwatch color="#14b885" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMA/SMK</Text>
|
||||
<ColorSwatch color="#E6A03B" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>D3</Text>
|
||||
<ColorSwatch color="#DB524D" size={30} />
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
||||
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>S1</Text>
|
||||
<ColorSwatch color="#1018A8FF" size={30} />
|
||||
</Flex>
|
||||
|
||||
@@ -103,7 +103,7 @@ function Page() {
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Button onClick={() => router.push('https://www.whatsapp.com/?lang=id')} bg={colors['blue-button']}>Lamar Sekarang</Button>
|
||||
<Button onClick={() => router.push(`https://wa.me/${v.notelp?.replace(/\D/g, '')}`)}>Lamar Sekarang</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import pasarDesaState from '@/app/admin/(dashboard)/_state/ekonomi/pasar-desa/pa
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Flex, Grid, GridCol, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconMapPinFilled, IconSearch, IconShoppingCartFilled, IconStarFilled } from '@tabler/icons-react';
|
||||
import { IconBrandWhatsapp, IconMapPinFilled, IconSearch, IconStarFilled } from '@tabler/icons-react';
|
||||
import { motion } from 'motion/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -25,14 +25,14 @@ function Page() {
|
||||
} = state.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
pasarDesaState.kategoriProduk.findMany.load()
|
||||
pasarDesaState.kategoriProduk.findManyAll.load()
|
||||
}, [])
|
||||
|
||||
// Filter data based on selected category
|
||||
const filteredData = selectedCategory
|
||||
? data?.filter(item =>
|
||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||
)
|
||||
? data?.filter(item =>
|
||||
item.KategoriToPasar?.some(kategori => kategori.kategoriId === selectedCategory)
|
||||
)
|
||||
: data;
|
||||
|
||||
useShallowEffect(() => {
|
||||
@@ -87,7 +87,7 @@ function Page() {
|
||||
<Box>
|
||||
<Select
|
||||
placeholder="Pilih Kategori"
|
||||
data={pasarDesaState.kategoriProduk.findMany.data?.map((v) => ({
|
||||
data={pasarDesaState.kategoriProduk.findManyAll.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama
|
||||
})) || []}
|
||||
@@ -105,7 +105,7 @@ function Page() {
|
||||
return (
|
||||
<Stack key={k}>
|
||||
<motion.div
|
||||
onClick={() => router.push('https://www.whatsapp.com/?lang=id')}
|
||||
onClick={() => router.push(`https://wa.me/${v.kontak?.replace(/\D/g, '')}`)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.8 }}
|
||||
>
|
||||
@@ -132,7 +132,7 @@ function Page() {
|
||||
<Text fz={'sm'} ml={2}>{v.alamatUsaha}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
<IconShoppingCartFilled size={20} color={colors['blue-button']} />
|
||||
<IconBrandWhatsapp size={20} color={colors['blue-button']} />
|
||||
</Flex>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
|
||||
@@ -56,7 +56,7 @@ function Page() {
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
placeholder='Cari Puskesmas'
|
||||
placeholder='Cari Keamanan Lingkungan'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftSection={<IconSearch size={20} />}
|
||||
|
||||
@@ -34,6 +34,52 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Group px={{ base: 'md', md: 100 }} justify={'space-between'} align='center'>
|
||||
<Box>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Kontak Darurat
|
||||
</Text>
|
||||
<Text fz={{ base: "h4", md: "h3" }} >
|
||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung.
|
||||
</Text>
|
||||
</Box>
|
||||
<TextInput
|
||||
placeholder='Cari kontak darurat, nama, atau nomor...'
|
||||
leftSection={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Group>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
||||
<Flex justify={'center'} gap={'lg'} align={'center'}>
|
||||
<Avatar radius={"xl"} size={'lg'} bg={colors['BG-trans']}>
|
||||
<IconPhoneCall size={30} color={colors["blue-button"]} />
|
||||
</Avatar>
|
||||
<Box>
|
||||
<Text ta={'center'} c={colors['blue-button']} py={10} fz={{ base: "md", md: "h4" }} fw={"bold"} >
|
||||
Nomor Darurat Utama
|
||||
</Text>
|
||||
<Text ta={'center'} fw={"bold"} fz={'h2'} c={colors["blue-button"]}>112</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Center>
|
||||
<Text fz={"h1"} c={colors["blue-button"]} fw={"bold"}>Tidak ada kontak darurat yang ditemukan</Text>
|
||||
</Center>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
@@ -76,57 +122,63 @@ function Page() {
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
|
||||
{/* Layanan Darurat */}
|
||||
{data.map((item) => (
|
||||
<Paper
|
||||
<a
|
||||
key={item.id}
|
||||
p="lg"
|
||||
radius="md"
|
||||
bg={colors['white-trans-1']}
|
||||
href={`tel:${item.kontakItems[0]?.kontakItem?.nomorTelepon || '112'}`}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<Group pb="md" align="center">
|
||||
<Avatar radius="xl" size="lg" bg={colors['BG-trans']}>
|
||||
{item.icon && (
|
||||
<IconMapper
|
||||
name={item.icon as IconKey}
|
||||
size={32}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
)}
|
||||
</Avatar>
|
||||
<Text fw="bold" fz={{ base: "lg", md: "xl" }} c={colors["blue-button"]}>
|
||||
{item.nama}
|
||||
</Text>
|
||||
</Group>
|
||||
<Paper
|
||||
|
||||
{/* Kontak Items */}
|
||||
{item.kontakItems?.map((kontak) => (
|
||||
<Paper
|
||||
key={kontak.id}
|
||||
p="lg"
|
||||
bg={colors['BG-trans']}
|
||||
radius="md"
|
||||
shadow="xs"
|
||||
mt="sm"
|
||||
>
|
||||
<Group align="center" justify="space-between">
|
||||
<Group align="center">
|
||||
{kontak.kontakItem?.icon && (
|
||||
<IconMapper
|
||||
name={kontak.kontakItem.icon as IconKey}
|
||||
size={24}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
)}
|
||||
p="lg"
|
||||
radius="md"
|
||||
bg={colors['white-trans-1']}
|
||||
>
|
||||
<Group pb="md" align="center">
|
||||
<Avatar radius="xl" size="lg" bg={colors['BG-trans']}>
|
||||
{item.icon && (
|
||||
<IconMapper
|
||||
name={item.icon as IconKey}
|
||||
size={32}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
)}
|
||||
</Avatar>
|
||||
<Text fw="bold" fz={{ base: "lg", md: "xl" }} c={colors["blue-button"]}>
|
||||
{item.nama}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{/* Kontak Items */}
|
||||
{item.kontakItems?.map((kontak) => (
|
||||
<Paper
|
||||
key={kontak.id}
|
||||
p="lg"
|
||||
bg={colors['BG-trans']}
|
||||
radius="md"
|
||||
shadow="xs"
|
||||
mt="sm"
|
||||
>
|
||||
<Group align="center" justify="space-between">
|
||||
<Group align="center">
|
||||
{kontak.kontakItem?.icon && (
|
||||
<IconMapper
|
||||
name={kontak.kontakItem.icon as IconKey}
|
||||
size={24}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
)}
|
||||
<Text fw="bold" fz={{ base: "sm", md: "md" }} c={colors["blue-button"]}>
|
||||
{kontak.kontakItem?.nama}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text fw="bold" fz={{ base: "sm", md: "md" }} c={colors["blue-button"]}>
|
||||
{kontak.kontakItem?.nama}
|
||||
{kontak.kontakItem?.nomorTelepon}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text fw="bold" fz={{ base: "sm", md: "md" }} c={colors["blue-button"]}>
|
||||
{kontak.kontakItem?.nomorTelepon}
|
||||
</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
))}
|
||||
</Paper>
|
||||
</Paper>
|
||||
))}
|
||||
</Paper>
|
||||
</a>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Center>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import pencegahanKriminalitasState from '@/app/admin/(dashboard)/_state/keamanan/pencegahan-kriminalitas';
|
||||
import colors from '@/con/colors';
|
||||
import { ActionIcon, Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowRight } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
@@ -63,7 +63,7 @@ function Page() {
|
||||
<Stack pt={30} gap="lg">
|
||||
{data.length > 0 ? (
|
||||
data.map((item) => (
|
||||
<ActionIcon key={item.id} variant='transparent' onClick={() => router.push(`/darmasaba/keamanan/pencegahan-kriminalitas/${item.id}`)}>
|
||||
<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"}>
|
||||
<Text fz="h3" c={colors['white-1']}>
|
||||
@@ -71,7 +71,7 @@ function Page() {
|
||||
</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</ActionIcon>
|
||||
</a>
|
||||
))
|
||||
) : (
|
||||
<Text color="dimmed">
|
||||
|
||||
@@ -20,10 +20,41 @@ function Page() {
|
||||
} = state;
|
||||
|
||||
useEffect(() => {
|
||||
if (!data && !loading) {
|
||||
load();
|
||||
}
|
||||
}, [data, loading]);
|
||||
load();
|
||||
}, []);
|
||||
|
||||
// kalau masih loading
|
||||
if (loading) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
// kalau data kosong
|
||||
if (!data) {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box pb={10} px={{ base: 20, md: 100 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Kantor Polisi Terdekat
|
||||
</Text>
|
||||
<Text pb={15} fz={'h4'} >
|
||||
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
|
||||
</Text>
|
||||
</Box>
|
||||
<Center py="xl">
|
||||
<Text fz="lg" fw="bold" c="red">
|
||||
Data Polsek tidak ada
|
||||
</Text>
|
||||
</Center>
|
||||
</Stack >
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
|
||||
@@ -8,10 +8,12 @@ import React, { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../../desa/layanan/_com/BackButto';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(polsekTerdekatState);
|
||||
const [search, setSearch] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
@@ -23,8 +25,8 @@ function Page() {
|
||||
} = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, search)
|
||||
}, [page, search])
|
||||
load(page, 3, debouncedSearch)
|
||||
}, [page, debouncedSearch])
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
|
||||
@@ -39,10 +39,9 @@ function Page() {
|
||||
|
||||
const nama = data?.name || 'Fasilitas Kesehatan';
|
||||
const prosedur = data?.prosedurpendaftaran.content || '';
|
||||
console.log("Prosedur:", data?.prosedurpendaftaran);
|
||||
const alamat = data?.informasiumum?.alamat || '-';
|
||||
const jam = data?.informasiumum?.jamOperasional || '-';
|
||||
const layananUnggulan = data?.layananunggulan || '';
|
||||
const layananUnggulan = data?.layananunggulan?.content || '';
|
||||
const tenaga = data?.dokterdantenagamedis || null;
|
||||
const fasilitasPendukungHtml = data?.fasilitaspendukung?.content || '';
|
||||
const tarif = (data?.tarifdanlayanan as TarifDanLayanan) || null;
|
||||
|
||||
@@ -53,7 +53,11 @@ function JadwalKegiatanPage() {
|
||||
{item.informasijadwalkegiatan.name}
|
||||
</Text>
|
||||
<Text fw={600} fz="sm" c={colors['blue-button']}>
|
||||
{item.informasijadwalkegiatan.tanggal}
|
||||
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
'use client'
|
||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||
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 DetailInfoWabahPenyakitUser() {
|
||||
const state = useProxy(infoWabahPenyakit);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={400} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = state.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Tombol Back */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Wrapper Detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w="100%"
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="lg">
|
||||
{/* Judul */}
|
||||
<Text fz="xl" fw="bold" c={colors['blue-button']} ta="center">
|
||||
{data.name || 'Kontak Darurat'}
|
||||
</Text>
|
||||
|
||||
{/* Gambar */}
|
||||
{data.image?.link && (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.name}
|
||||
radius="md"
|
||||
maw={400}
|
||||
mx="auto"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailInfoWabahPenyakitUser;
|
||||
@@ -2,10 +2,14 @@
|
||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Divider,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
@@ -13,27 +17,25 @@ import {
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Badge,
|
||||
HoverCard,
|
||||
Divider,
|
||||
Group,
|
||||
TextInput
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconSearch, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconInfoCircle, IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(infoWabahPenyakit);
|
||||
const router = useTransitionRouter();
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500)
|
||||
const { data, page, totalPages, loading, load } = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, search);
|
||||
}, [page, search]);
|
||||
load(page, 3, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -125,28 +127,9 @@ function Page() {
|
||||
<Text fz="sm" lh={1.5}>
|
||||
{v.deskripsiSingkat}
|
||||
</Text>
|
||||
<HoverCard shadow="md" position="bottom" radius="md" width={300}>
|
||||
<HoverCard.Target>
|
||||
<Text
|
||||
fz="sm"
|
||||
fw={500}
|
||||
c={colors['blue-button']}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
Lihat detail lengkap
|
||||
</Text>
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<Text
|
||||
fz="sm"
|
||||
lh={1.6}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: v.deskripsiLengkap,
|
||||
}}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
/>
|
||||
</HoverCard.Dropdown>
|
||||
</HoverCard>
|
||||
<Button variant="light" radius="md" size="md" onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${v.id}`)}>
|
||||
Selengkapnya
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
@@ -154,17 +137,17 @@ function Page() {
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
radius="xl"
|
||||
size="md"
|
||||
mt="lg"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
radius="xl"
|
||||
size="md"
|
||||
mt="lg"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Grid,
|
||||
GridCol,
|
||||
@@ -15,23 +15,24 @@ import {
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
Badge,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { IconSearch, IconPhone } from '@tabler/icons-react';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconBrandWhatsapp, IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||
import colors from '@/con/colors';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(kontakDarurat);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500)
|
||||
const { data, page, totalPages, loading, load } = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 6, search);
|
||||
}, [page, search]);
|
||||
load(page, 3, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -113,16 +114,16 @@ function Page() {
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed" ta="center" lineClamp={3}>
|
||||
<span style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
<span style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Text>
|
||||
<Badge
|
||||
color="blue"
|
||||
leftSection={<IconPhone size={14} />}
|
||||
variant="light"
|
||||
mt="sm"
|
||||
>
|
||||
Panggil Sekarang
|
||||
</Badge>
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<IconBrandWhatsapp size={18} />}
|
||||
component="a"
|
||||
href={`https://wa.me/${v.whatsapp.replace(/\D/g, '')}`}
|
||||
target="_blank"
|
||||
aria-label="Hubungi WhatsApp"
|
||||
>WhatsApp</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
@@ -130,22 +131,20 @@ function Page() {
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage, 6, search)}
|
||||
total={totalPages}
|
||||
radius="xl"
|
||||
size="md"
|
||||
styles={{
|
||||
control: {
|
||||
borderRadius: '999px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage, 3, search)}
|
||||
total={totalPages}
|
||||
size="lg"
|
||||
radius="xl"
|
||||
styles={{
|
||||
control: {
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
TextInput,
|
||||
Tooltip
|
||||
} from '@mantine/core'
|
||||
import { useShallowEffect } from '@mantine/hooks'
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'
|
||||
import { IconSearch } from '@tabler/icons-react'
|
||||
import { useState } from 'react'
|
||||
import { useProxy } from 'valtio/utils'
|
||||
@@ -26,12 +26,12 @@ import BackButton from '../../desa/layanan/_com/BackButto'
|
||||
function Page() {
|
||||
const state = useProxy(penangananDarurat)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500)
|
||||
const { data, page, totalPages, loading, load } = state.findMany
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 6, search)
|
||||
}, [page, search])
|
||||
load(page, 3, debouncedSearch)
|
||||
}, [page, debouncedSearch])
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -127,7 +127,7 @@ function Page() {
|
||||
c="dimmed"
|
||||
lineClamp={4}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -141,22 +141,21 @@ function Page() {
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage, 6, search)}
|
||||
total={totalPages}
|
||||
size="lg"
|
||||
radius="xl"
|
||||
styles={{
|
||||
control: {
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage, 3, search)}
|
||||
total={totalPages}
|
||||
size="lg"
|
||||
radius="xl"
|
||||
styles={{
|
||||
control: {
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,16 @@ export default function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py="xl" px={{ base: "md", md: 100 }}>
|
||||
<Text fz="lg" fw="bold" c={colors["blue-button"]}>
|
||||
Tidak ada posyandu yang ditemukan
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="xl">
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
@@ -111,10 +121,11 @@ export default function Page() {
|
||||
<IconCalendar size={18} stroke={1.5} />
|
||||
<Text fz="sm" c="dimmed">
|
||||
Jadwal:{" "}
|
||||
<span style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} />
|
||||
<span style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: v.jadwalPelayanan }} />
|
||||
</Text>
|
||||
</Flex>
|
||||
<Spoiler
|
||||
key={`spoiler-${v.id}`}
|
||||
maxHeight={70}
|
||||
showLabel="Lihat selengkapnya"
|
||||
hideLabel="Sembunyikan"
|
||||
@@ -124,7 +135,7 @@ export default function Page() {
|
||||
fz="sm"
|
||||
lh={1.5}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
</Spoiler>
|
||||
</Stack>
|
||||
|
||||
@@ -30,7 +30,7 @@ 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 { useShallowEffect } from "@mantine/hooks";
|
||||
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const manfaatProgram = [
|
||||
@@ -58,11 +58,12 @@ export default function Page() {
|
||||
const state = useProxy(programKesehatan);
|
||||
const router = useRouter();
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||
const { data, page, totalPages, loading, load } = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 3, search);
|
||||
}, [page, search]);
|
||||
load(page, 3, debouncedSearch);
|
||||
}, [page, debouncedSearch]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -125,14 +126,18 @@ export default function Page() {
|
||||
className="hover-scale"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.name}
|
||||
radius="xl"
|
||||
height={180}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Box h={180} w="100%">
|
||||
<Image
|
||||
src={v.image?.link}
|
||||
alt={v.name}
|
||||
radius="xl"
|
||||
w="100%"
|
||||
h="100%"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box px="lg" pb="lg">
|
||||
<Text
|
||||
fw="bold"
|
||||
@@ -147,7 +152,7 @@ export default function Page() {
|
||||
c="dimmed"
|
||||
lineClamp={3}
|
||||
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
|
||||
style={{wordBreak: "break-word", whiteSpace: "normal"}}
|
||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||
/>
|
||||
<Group justify="space-between" mt="md">
|
||||
<Group gap="xs">
|
||||
@@ -155,13 +160,13 @@ export default function Page() {
|
||||
<Text size="sm">
|
||||
{v.createdAt
|
||||
? new Date(v.createdAt).toLocaleDateString(
|
||||
"id-ID",
|
||||
{
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}
|
||||
)
|
||||
"id-ID",
|
||||
{
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}
|
||||
)
|
||||
: "Tanggal tidak tersedia"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -104,9 +104,7 @@ function Potensi() {
|
||||
{v.name}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }}>
|
||||
{v.deskripsi}
|
||||
</Text>
|
||||
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user