Compare commits
2 Commits
nico/19-se
...
nico/24-se
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e4a7a1c0a | |||
| b5c044df6e |
@@ -672,17 +672,18 @@ model GalleryVideo {
|
||||
|
||||
// ========================================= LAYANAN DESA ========================================= //
|
||||
model PelayananSuratKeterangan {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
deskripsi String @db.Text
|
||||
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
||||
image2Id String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
deskripsi String @db.Text
|
||||
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
||||
image2Id String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
AjukanPermohonan AjukanPermohonan[]
|
||||
}
|
||||
|
||||
model PelayananTelunjukSaktiDesa {
|
||||
@@ -717,6 +718,20 @@ model PelayananPendudukNonPermanen {
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model AjukanPermohonan {
|
||||
id String @id @default(cuid())
|
||||
nama String
|
||||
nik String
|
||||
alamat String
|
||||
nomorKk String
|
||||
kategori PelayananSuratKeterangan @relation(fields: [kategoriId], references: [id])
|
||||
kategoriId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= PENGHARGAAN ========================================= //
|
||||
model Penghargaan {
|
||||
id String @id @default(cuid())
|
||||
@@ -1254,15 +1269,15 @@ model KontakDaruratToItem {
|
||||
|
||||
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
||||
model PencegahanKriminalitas {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String
|
||||
deskripsiSingkat String
|
||||
linkVideo String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String
|
||||
deskripsiSingkat String
|
||||
linkVideo String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= LAPORAN PUBLIK ========================================= //
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 47 KiB |
@@ -71,6 +71,22 @@ const pelayananPendudukNonPermanenForm = {
|
||||
deskripsi: "",
|
||||
};
|
||||
|
||||
const templateAjukanForm = z.object({
|
||||
nama: z.string().min(1).max(5000),
|
||||
nik: z.string().min(1).max(5000),
|
||||
alamat: z.string().min(1).max(5000),
|
||||
nomorKk: z.string().min(1).max(5000),
|
||||
kategoriId: z.string().min(1).max(5000),
|
||||
});
|
||||
|
||||
const defaultAjukanForm = {
|
||||
nama: "",
|
||||
nik: "",
|
||||
alamat: "",
|
||||
nomorKk: "",
|
||||
kategoriId: "",
|
||||
};
|
||||
|
||||
const suratKeterangan = proxy({
|
||||
create: {
|
||||
form: { ...suratKeteranganForm },
|
||||
@@ -146,6 +162,30 @@ const suratKeterangan = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
findManyAll: {
|
||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[] | null,
|
||||
loading: false,
|
||||
load: async () => {
|
||||
suratKeterangan.findManyAll.loading = true;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan["findManyAll"].get();
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
suratKeterangan.findManyAll.data = res.data.data || [];
|
||||
} else {
|
||||
suratKeterangan.findManyAll.data = [];
|
||||
console.error("Failed to load surat keterangan all:", res.data?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading surat keterangan all:", error);
|
||||
suratKeterangan.findManyAll.data = [];
|
||||
} finally {
|
||||
suratKeterangan.findManyAll.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||
include: {
|
||||
@@ -769,11 +809,250 @@ const pelayananPendudukNonPermanen = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
const ajukanPermohonan = proxy({
|
||||
create: {
|
||||
form: { ...defaultAjukanForm },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateAjukanForm.safeParse(
|
||||
ajukanPermohonan.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
ajukanPermohonan.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.ajukanpermohonan[
|
||||
"create"
|
||||
].post(ajukanPermohonan.create.form);
|
||||
if (res.status === 200) {
|
||||
ajukanPermohonan.findMany.load();
|
||||
return toast.success("Ajukan permohonan berhasil disimpan!");
|
||||
}
|
||||
return toast.error("Gagal menyimpan ajukan permohonan");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
ajukanPermohonan.create.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
ajukanPermohonan.create.form = { ...defaultAjukanForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Prisma.AjukanPermohonanGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
};
|
||||
}>[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
ajukanPermohonan.findMany.loading = true; // Use the full path to access the property
|
||||
ajukanPermohonan.findMany.page = page;
|
||||
ajukanPermohonan.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
const res = await ApiFetch.api.desa.ajukanpermohonan[
|
||||
"findMany"
|
||||
].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
ajukanPermohonan.findMany.data = res.data.data || [];
|
||||
ajukanPermohonan.findMany.total = res.data.total || 0;
|
||||
ajukanPermohonan.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load ajukan permohonan:", res.data?.message);
|
||||
ajukanPermohonan.findMany.data = [];
|
||||
ajukanPermohonan.findMany.total = 0;
|
||||
ajukanPermohonan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading ajukan permohonan:", error);
|
||||
ajukanPermohonan.findMany.data = [];
|
||||
ajukanPermohonan.findMany.total = 0;
|
||||
ajukanPermohonan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
ajukanPermohonan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.AjukanPermohonanGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
}
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/desa/ajukanpermohonan/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
ajukanPermohonan.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch ajukan permohonan:", res.statusText);
|
||||
ajukanPermohonan.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching ajukan permohonan:", error);
|
||||
ajukanPermohonan.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
ajukanPermohonan.delete.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/desa/ajukanpermohonan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Ajukan permohonan berhasil dihapus");
|
||||
await ajukanPermohonan.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result.message || "Gagal menghapus ajukan permohonan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus ajukan permohonan");
|
||||
} finally {
|
||||
ajukanPermohonan.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...defaultAjukanForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/desa/ajukanpermohonan/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
nik: data.nik,
|
||||
alamat: data.alamat,
|
||||
nomorKk: data.nomorKk,
|
||||
kategoriId: data.kategoriId,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching ajukan permohonan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateAjukanForm.safeParse(
|
||||
ajukanPermohonan.edit.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
ajukanPermohonan.edit.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/desa/ajukanpermohonan/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
nik: this.form.nik,
|
||||
alamat: this.form.alamat,
|
||||
nomorKk: this.form.nomorKk,
|
||||
kategoriId: this.form.kategoriId,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success(result.message || "Ajukan permohonan berhasil diupdate");
|
||||
await ajukanPermohonan.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal mengupdate ajukan permohonan"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating ajukan permohonan:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update ajukan permohonan"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
ajukanPermohonan.edit.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateLayananDesa = proxy({
|
||||
suratKeterangan,
|
||||
pelayananPerizinanBerusaha,
|
||||
pelayananTelunjukSaktiDesa,
|
||||
pelayananPendudukNonPermanen,
|
||||
ajukanPermohonan,
|
||||
});
|
||||
|
||||
export default stateLayananDesa;
|
||||
|
||||
@@ -354,14 +354,39 @@ const kategoriKegiatan = proxy({
|
||||
id: string;
|
||||
nama: string;
|
||||
}> | null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
kategoriKegiatan.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kategoriKegiatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kategoriKegiatan.findMany.page = page;
|
||||
kategoriKegiatan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res =
|
||||
await ApiFetch.api.lingkungan.kategorikegiatan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriKegiatan.findMany.data = res.data.data ?? [];
|
||||
kategoriKegiatan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kategoriKegiatan.findMany.data = [];
|
||||
kategoriKegiatan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori kegiatan paginated:", err);
|
||||
kategoriKegiatan.findMany.data = [];
|
||||
kategoriKegiatan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kategoriKegiatan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.KategoriKegiatanGetPayload<{
|
||||
|
||||
@@ -4,7 +4,7 @@ import colors from '@/con/colors';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react';
|
||||
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers, IconUsersPlus } from '@tabler/icons-react';
|
||||
|
||||
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
@@ -37,6 +37,13 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent",
|
||||
icon: <IconUsers size={18} stroke={1.8} />,
|
||||
tooltip: "Pendataan penduduk non-permanent"
|
||||
},
|
||||
{
|
||||
label: "Ajukan Permohonan",
|
||||
value: "ajukanpermohonan",
|
||||
href: "/admin/desa/layanan/ajukan_permohonan",
|
||||
icon: <IconUsersPlus size={18} stroke={1.8} />,
|
||||
tooltip: "Ajukan permohonan"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
@@ -18,7 +17,7 @@ import {
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
@@ -87,7 +86,6 @@ function ListBerita({ search }: { search: string }) {
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Kategori</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Gambar</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
@@ -96,7 +94,7 @@ function ListBerita({ search }: { search: string }) {
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '30%' }}>
|
||||
<Box w={200}>
|
||||
<Box w={150}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
@@ -107,19 +105,6 @@ function ListBerita({ search }: { search: string }) {
|
||||
{item.kategoriBerita?.name || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Box
|
||||
w={80}
|
||||
h={80}
|
||||
style={{ borderRadius: 8, overflow: 'hidden' }}
|
||||
>
|
||||
{item.image?.link ? (
|
||||
<Image loading='lazy' src={item.image.link} alt="gambar" fit="cover" />
|
||||
) : (
|
||||
<Box bg={colors['blue-button']} w="100%" h="100%" />
|
||||
)}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
'use client'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditAjukanPermohonan() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const stateAjukan = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nama: stateAjukan.edit.form.nama,
|
||||
nik: stateAjukan.edit.form.nik,
|
||||
alamat: stateAjukan.edit.form.alamat,
|
||||
nomorKk: stateAjukan.edit.form.nomorKk,
|
||||
kategoriId: stateAjukan.edit.form.kategoriId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
stateLayananDesa.suratKeterangan.findManyAll.load();
|
||||
const loadAjukan = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await stateAjukan.edit.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
nama: data.nama || '',
|
||||
nik: data.nik || '',
|
||||
alamat: data.alamat || '',
|
||||
nomorKk: data.nomorKk || '',
|
||||
kategoriId: data.kategoriId || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading ajukan:', error);
|
||||
toast.error('Gagal memuat data ajukan');
|
||||
}
|
||||
};
|
||||
|
||||
loadAjukan();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
stateAjukan.edit.form = {
|
||||
...stateAjukan.edit.form,
|
||||
...formData,
|
||||
};
|
||||
toast.success('Ajukan berhasil diperbarui!');
|
||||
router.push('/admin/desa/layanan/ajukan_permohonan');
|
||||
} catch (error) {
|
||||
console.error('Error updating ajukan:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui ajukan');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Back Button */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Ajukan Permohonan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama"
|
||||
placeholder="Masukkan nama"
|
||||
value={formData.nama}
|
||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
type="number"
|
||||
label="NIK"
|
||||
placeholder="Masukkan NIK"
|
||||
value={formData.nik}
|
||||
onChange={(e) => setFormData({ ...formData, nik: e.target.value })}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat"
|
||||
value={formData.alamat}
|
||||
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
type="number"
|
||||
label="Nomor KK"
|
||||
placeholder="Masukkan nomor KK"
|
||||
value={formData.nomorKk}
|
||||
onChange={(e) => setFormData({ ...formData, nomorKk: e.target.value })}
|
||||
required
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Kategori"
|
||||
placeholder="Pilih kategori"
|
||||
data={stateLayananDesa.suratKeterangan.findManyAll.data?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={formData.kategoriId || null}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
const selected = stateLayananDesa.suratKeterangan.findMany.data?.find(
|
||||
(item) => item.id === val
|
||||
);
|
||||
if (selected) {
|
||||
stateAjukan.edit.form.kategoriId = selected.id;
|
||||
}
|
||||
} else {
|
||||
stateAjukan.edit.form.kategoriId = '';
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditAjukanPermohonan;
|
||||
@@ -0,0 +1,172 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailAjukanPermohonan() {
|
||||
const ajukanPermohonanState = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
ajukanPermohonanState.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
ajukanPermohonanState.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push('/admin/desa/layanan/ajukan_permohonan');
|
||||
}
|
||||
};
|
||||
|
||||
if (!ajukanPermohonanState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = ajukanPermohonanState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Tombol Kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
Detail Surat Keterangan
|
||||
</Text>
|
||||
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Nama
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data?.nama || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
NIK
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data?.nik || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Alamat
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data?.alamat || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Nomor KK
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data?.nomorKk || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Kategori
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data?.kategori.name || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Group gap="sm">
|
||||
<Tooltip label="Hapus Surat" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
disabled={ajukanPermohonanState.delete.loading}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Surat" withArrow position="top">
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/desa/layanan/ajukan_permohonan/${data.id}/edit`
|
||||
)
|
||||
}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah Anda yakin ingin menghapus ajukan permohonan ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailAjukanPermohonan;
|
||||
@@ -0,0 +1,155 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||
|
||||
function AjukanPermohonan() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Pelayanan Ajukan Permohonan'
|
||||
placeholder='Cari nama atau deskripsi...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListAjukanPermohonan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListAjukanPermohonan({ search }: { search: string }) {
|
||||
const AjukanPermohonanState = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = AjukanPermohonanState.findMany;
|
||||
|
||||
useEffect(() => {
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
// Loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||
<Title order={4}>List Ajukan Permohonan</Title>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '30%' }}>Nama</TableTh>
|
||||
<TableTh style={{ width: '45%' }}>Alamat</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>NIK</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.length > 0 ? (
|
||||
data.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '30%' }}>
|
||||
<Box w={200}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.nama}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '45%' }}>
|
||||
<Box w={200}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.alamat}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '45%' }}>
|
||||
<Box w={200}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.nik}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/desa/layanan/ajukan_permohonan/${item.id}`)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={3}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data ajukan permohonan yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10, search);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default AjukanPermohonan;
|
||||
@@ -94,9 +94,11 @@ function ListPengumuman({ search }: { search: string }) {
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Box w={150}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" c="dimmed">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip, Pagination } from '@mantine/core';
|
||||
import { Box, Button, Center, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip, Pagination, Group } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -60,7 +60,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Stack>
|
||||
<Box style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 }}>
|
||||
<Group justify="space-between">
|
||||
<Title order={4}>List Kategori Potensi</Title>
|
||||
<Tooltip label="Tambah Kategori Potensi" withArrow>
|
||||
<Button
|
||||
@@ -72,7 +72,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
|
||||
|
||||
@@ -87,7 +87,9 @@ function ListAPBDes({ search }: { search: string }) {
|
||||
<Text fw={500} truncate="end">{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>Rp. {item.jumlah}</Text>
|
||||
<Box w={150}>
|
||||
<Text>Rp. {item.jumlah}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{item.file?.link ? (
|
||||
|
||||
@@ -93,7 +93,9 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500}>{item.name}</Text>
|
||||
<Box w={200}>
|
||||
<Text fw={500} lineClamp={1}>{item.name}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Tooltip label="Edit" withArrow>
|
||||
|
||||
@@ -98,9 +98,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '30%' }}>
|
||||
<Text fz="sm" c="dimmed">
|
||||
<Box w={200}>
|
||||
<Text fz="sm" c="dimmed" lineClamp={1}>
|
||||
{item.kategori?.name || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
||||
<Button
|
||||
|
||||
@@ -154,7 +154,7 @@ function Page() {
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={220}
|
||||
size={180}
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
@@ -185,7 +185,7 @@ function Page() {
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={220}
|
||||
size={180}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
@@ -216,7 +216,7 @@ function Page() {
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={220}
|
||||
size={180}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
|
||||
@@ -88,66 +88,74 @@ function ListResponden({ search }: ListRespondenProps) {
|
||||
<Title order={4} mb="sm">
|
||||
Daftar Responden
|
||||
</Title>
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
withRowBorders
|
||||
verticalSpacing="sm"
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '25%', textAlign: 'center' }}>Nama</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
withRowBorders
|
||||
verticalSpacing="sm"
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Text ta="center" c="dimmed">
|
||||
Tidak ditemukan data dengan kata kunci pencarian
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '25%', textAlign: 'center' }}>Nama</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center">{index + 1}</TableTd>
|
||||
<TableTd ta="center">{item.name}</TableTd>
|
||||
<TableTd ta="center">
|
||||
{item.tanggal
|
||||
? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Text ta="center" c="dimmed">
|
||||
Tidak ditemukan data dengan kata kunci pencarian
|
||||
</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center">{index + 1}</TableTd>
|
||||
<TableTd ta="center">{item.name}</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Box w={150}>
|
||||
{item.tanggal
|
||||
? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '-'}
|
||||
</TableTd>
|
||||
<TableTd ta="center">{item.jenisKelamin.name}</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImac size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
: '-'}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Box w={100}>
|
||||
{item.jenisKelamin.name}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImac size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
|
||||
@@ -96,7 +96,11 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||
<Tooltip label="Edit" withArrow position="top">
|
||||
<Button
|
||||
|
||||
@@ -78,9 +78,9 @@ function DetailPrestasiDesa() {
|
||||
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Box
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
<Box
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: detailState.findUnique.data?.deskripsi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
@@ -91,10 +91,11 @@ function DetailPrestasiDesa() {
|
||||
<Image
|
||||
src={detailState.findUnique.data.image.link}
|
||||
alt={detailState.findUnique.data.name || 'Gambar Prestasi'}
|
||||
w={300}
|
||||
w="100%"
|
||||
maw={300} // max width 300px
|
||||
fit="contain"
|
||||
style={{ borderRadius: '8px', border: '1px solid #e0e0e0' }}
|
||||
loading='lazy'
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||
|
||||
@@ -104,7 +104,9 @@ function ListPrestasi({ search }: { search: string }) {
|
||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text truncate="end" fz={"sm"}>{item.kategori?.name || 'Tidak ada kategori'}</Text>
|
||||
<Box w={150}>
|
||||
<Text truncate="end" fz={"sm"}>{item.kategori?.name || 'Tidak ada kategori'}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
||||
<Tooltip label="Kelola Prestasi" withArrow>
|
||||
|
||||
@@ -51,8 +51,10 @@ function DetailMediaSosial() {
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
withBorder
|
||||
w="100%"
|
||||
maw={500} // <= tambahkan ini, biar tidak lebih dari 500px
|
||||
mx="auto" // center di layar
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
@@ -70,9 +72,9 @@ function DetailMediaSosial() {
|
||||
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Box >
|
||||
<Text fz="lg" fw="bold">Link / Nomor Telepon</Text>
|
||||
<Text fz="md" c="dimmed">{data.iconUrl || '-'}</Text>
|
||||
<Text fz="md" c="dimmed" style={{ wordBreak: "break-all", whiteSpace: "pre-wrap" }}>{data.iconUrl || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
@@ -81,12 +83,14 @@ function DetailMediaSosial() {
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.name || 'Gambar Media Sosial'}
|
||||
w={120}
|
||||
h={120}
|
||||
w="100%"
|
||||
maw={120} // max width biar tidak keluar layar
|
||||
h="auto"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||
)}
|
||||
|
||||
@@ -224,7 +224,7 @@ function EditPejabatDesa() {
|
||||
alt="Preview"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '200px',
|
||||
maxHeight: '150px',
|
||||
objectFit: 'contain',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ddd',
|
||||
|
||||
@@ -72,7 +72,7 @@ function Page() {
|
||||
<Image
|
||||
pt={{ base: 0, md: 60 }}
|
||||
src={item.image?.link || "/perbekel.png"}
|
||||
w={{ base: 250, md: 350 }}
|
||||
w={{ base: 150, md: 350 }}
|
||||
alt="Foto Profil Pejabat"
|
||||
radius="md"
|
||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||
@@ -87,7 +87,7 @@ function Page() {
|
||||
className="glass3"
|
||||
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
||||
>
|
||||
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.2rem", md: "1.6rem" }}>
|
||||
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1rem", md: "1.6rem" }}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Paper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
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 { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -49,9 +49,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
||||
return (
|
||||
<Box py={15}>
|
||||
<Paper bg={colors['white-1']} withBorder p="lg" radius="md" shadow="sm">
|
||||
<Box mb="md" display="flex"
|
||||
style={{ justifyContent: 'space-between', alignItems: 'center' }}
|
||||
>
|
||||
<Group justify='space-between'>
|
||||
<Title order={4}>Daftar Program Inovasi</Title>
|
||||
<Tooltip label="Tambah Program Inovasi" withArrow>
|
||||
<Button
|
||||
@@ -64,7 +62,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
||||
Tambah Program
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped verticalSpacing="sm">
|
||||
<TableThead>
|
||||
|
||||
@@ -3,7 +3,17 @@
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -19,34 +29,34 @@ interface FormDataLingkunganDesa {
|
||||
}
|
||||
|
||||
type IconKey =
|
||||
'ekowisata' |
|
||||
'kompetisi' |
|
||||
'wisata' |
|
||||
'ekonomi' |
|
||||
'sampah' |
|
||||
'truck' |
|
||||
'scale' |
|
||||
'clipboard' |
|
||||
'trash' |
|
||||
'lingkunganSehat' |
|
||||
'sumberOksigen' |
|
||||
'ekonomiBerkelanjutan' |
|
||||
'mencegahBencana' |
|
||||
'rumah' |
|
||||
'pohon' |
|
||||
'air';
|
||||
|
||||
| 'ekowisata'
|
||||
| 'kompetisi'
|
||||
| 'wisata'
|
||||
| 'ekonomi'
|
||||
| 'sampah'
|
||||
| 'truck'
|
||||
| 'scale'
|
||||
| 'clipboard'
|
||||
| 'trash'
|
||||
| 'lingkunganSehat'
|
||||
| 'sumberOksigen'
|
||||
| 'ekonomiBerkelanjutan'
|
||||
| 'mencegahBencana'
|
||||
| 'rumah'
|
||||
| 'pohon'
|
||||
| 'air';
|
||||
|
||||
function EditDataLingkunganDesa() {
|
||||
const stateDataLingkunganDesa = useProxy(dataLingkunganDesaState)
|
||||
const params = useParams()
|
||||
const stateDataLingkunganDesa = useProxy(dataLingkunganDesaState);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
const [formData, setFormData] = useState<FormDataLingkunganDesa>({
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
jumlah: '',
|
||||
icon: '',
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadProgramKreatif = async () => {
|
||||
@@ -56,7 +66,6 @@ function EditDataLingkunganDesa() {
|
||||
try {
|
||||
const data = await stateDataLingkunganDesa.update.load(id);
|
||||
if (data) {
|
||||
// ⬇️ FIX PENTING: tambahkan ini
|
||||
stateDataLingkunganDesa.update.id = id;
|
||||
|
||||
stateDataLingkunganDesa.update.form = {
|
||||
@@ -74,16 +83,14 @@ function EditDataLingkunganDesa() {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading data lingkungan desa:", error);
|
||||
toast.error("Gagal memuat data data lingkungan desa");
|
||||
console.error('Error loading data lingkungan desa:', error);
|
||||
toast.error('Gagal memuat data lingkungan desa');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadProgramKreatif();
|
||||
}, [params?.id]);
|
||||
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
stateDataLingkunganDesa.update.form = {
|
||||
@@ -92,49 +99,68 @@ function EditDataLingkunganDesa() {
|
||||
deskripsi: formData.deskripsi.trim(),
|
||||
jumlah: formData.jumlah.trim(),
|
||||
icon: formData.icon.trim(),
|
||||
}
|
||||
};
|
||||
await stateDataLingkunganDesa.update.submit();
|
||||
router.push("/admin/lingkungan/data-lingkungan-desa");
|
||||
toast.success('Data lingkungan desa berhasil diperbarui!');
|
||||
router.push('/admin/lingkungan/data-lingkungan-desa');
|
||||
} catch (error) {
|
||||
console.error("Error updating data lingkungan desa:", error);
|
||||
toast.error("Gagal memuat data data lingkungan desa");
|
||||
console.error('Error updating data lingkungan desa:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui data lingkungan desa');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
};
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit Data Lingkungan Desa</Title>
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Data Lingkungan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Data Lingkungan Desa</Text>}
|
||||
placeholder="masukkan nama data lingkungan desa"
|
||||
onChange={(val) => {
|
||||
label={<Text fz="sm" fw="bold">Nama Data Lingkungan Desa</Text>}
|
||||
placeholder="Masukkan nama data lingkungan desa"
|
||||
onChange={(val) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
name: val.target.value
|
||||
name: val.target.value,
|
||||
})
|
||||
}}
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
value={formData.jumlah}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Jumlah Data Lingkungan Desa</Text>}
|
||||
placeholder="masukkan jumlah data lingkungan desa"
|
||||
onChange={(val) => {
|
||||
label={<Text fz="sm" fw="bold">Jumlah Data Lingkungan Desa</Text>}
|
||||
placeholder="Masukkan jumlah data lingkungan desa"
|
||||
onChange={(val) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
jumlah: val.target.value
|
||||
jumlah: val.target.value,
|
||||
})
|
||||
}}
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(htmlContent) => {
|
||||
@@ -143,16 +169,34 @@ function EditDataLingkunganDesa() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Ikon Data Lingkungan Desa</Text>
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Ikon Data Lingkungan Desa
|
||||
</Text>
|
||||
<SelectIconProgramEdit
|
||||
value={formData.icon as IconKey}
|
||||
onChange={(value) => {
|
||||
setFormData((prev) => ({ ...prev, icon: value }));
|
||||
stateDataLingkunganDesa.update.form.icon = value;
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard, IconDroplet, IconEdit, IconHome, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTree, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react';
|
||||
import {
|
||||
IconArrowBack,
|
||||
IconChartLine,
|
||||
IconChristmasTreeFilled,
|
||||
IconClipboard,
|
||||
IconDroplet,
|
||||
IconEdit,
|
||||
IconHome,
|
||||
IconHomeEco,
|
||||
IconLeaf,
|
||||
IconRecycle,
|
||||
IconScale,
|
||||
IconShieldFilled,
|
||||
IconTent,
|
||||
IconTrash,
|
||||
IconTree,
|
||||
IconTrendingUp,
|
||||
IconTrophy,
|
||||
IconTruck,
|
||||
} from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
||||
|
||||
function DetailDataLingkunganDesa() {
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const stateDataLingkungan = useProxy(dataLingkunganDesaState)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const stateDataLingkungan = useProxy(dataLingkunganDesaState);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
const iconMap: Record<string, React.FC<any>> = {
|
||||
ekowisata: IconLeaf,
|
||||
@@ -35,90 +52,117 @@ function DetailDataLingkunganDesa() {
|
||||
mencegahBencana: IconShieldFilled,
|
||||
rumah: IconHome,
|
||||
pohon: IconTree,
|
||||
air: IconDroplet
|
||||
air: IconDroplet,
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDataLingkungan.findUnique.load(params?.id as string)
|
||||
}, [params?.id])
|
||||
stateDataLingkungan.findUnique.load(params?.id as string);
|
||||
}, [params?.id]);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateDataLingkungan.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/lingkungan/data-lingkungan-desa")
|
||||
stateDataLingkungan.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push('/admin/lingkungan/data-lingkungan-desa');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!stateDataLingkungan.findUnique.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton h={500} />
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Data Lingkungan Desa</Text>
|
||||
const data = stateDataLingkungan.findUnique.data;
|
||||
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Back Button */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Main Card */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
{/* Title */}
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
Detail Data Lingkungan Desa
|
||||
</Text>
|
||||
|
||||
{/* Content Card */}
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Nama Data Lingkungan Desa</Text>
|
||||
<Text fz={"lg"}>{stateDataLingkungan.findUnique.data?.name}</Text>
|
||||
<Text fz="lg" fw="bold">Nama Data Lingkungan Desa</Text>
|
||||
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Jumlah Data Lingkungan Desa</Text>
|
||||
<Text fz={"lg"}>{stateDataLingkungan.findUnique.data?.jumlah}</Text>
|
||||
<Text fz="lg" fw="bold">Jumlah Data Lingkungan Desa</Text>
|
||||
<Text fz="md" c="dimmed">{data?.jumlah || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Ikon Data Lingkungan Desa</Text>
|
||||
{iconMap[stateDataLingkungan.findUnique.data?.icon] && (
|
||||
<Box title={stateDataLingkungan.findUnique.data?.icon}>
|
||||
{React.createElement(iconMap[stateDataLingkungan.findUnique.data?.icon], { size: 24 })}
|
||||
<Text fz="lg" fw="bold">Ikon Data Lingkungan Desa</Text>
|
||||
{iconMap[data?.icon] ? (
|
||||
<Box title={data?.icon}>
|
||||
{React.createElement(iconMap[data.icon], { size: 28, color: colors['blue-button'] })}
|
||||
</Box>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada ikon</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: stateDataLingkungan.findUnique.data?.deskripsi }}></Text>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Group gap="sm" mt="sm">
|
||||
<Tooltip label="Hapus Data Lingkungan Desa" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
if (stateDataLingkungan.findUnique.data) {
|
||||
setSelectedId(stateDataLingkungan.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={stateDataLingkungan.delete.loading || !stateDataLingkungan.findUnique.data}
|
||||
color={"red"}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconX size={20} />
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Data Lingkungan Desa" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateDataLingkungan.findUnique.data) {
|
||||
router.push(`/admin/lingkungan/data-lingkungan-desa/${stateDataLingkungan.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!stateDataLingkungan.findUnique.data}
|
||||
color={"green"}
|
||||
color="green"
|
||||
onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${data.id}/edit`)}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
@@ -130,7 +174,7 @@ function DetailDataLingkunganDesa() {
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus data lingkungan desa ini?"
|
||||
/>
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -8,60 +18,105 @@ import CreateEditor from '../../../_com/createEditor';
|
||||
import SelectIconProgram from '../../../_com/selectIcon';
|
||||
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
||||
|
||||
|
||||
function CreateDataLingkunganDesa() {
|
||||
const stateCreate = useProxy(dataLingkunganDesaState)
|
||||
const stateCreate = useProxy(dataLingkunganDesaState);
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
stateCreate.create.form = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
jumlah: "",
|
||||
icon: "",
|
||||
}
|
||||
}
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
jumlah: '',
|
||||
icon: '',
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await stateCreate.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/lingkungan/data-lingkungan-desa")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
router.push('/admin/lingkungan/data-lingkungan-desa');
|
||||
};
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Create Data Lingkungan Desa</Title>
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={22} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Tambah Data Lingkungan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Card Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Data Lingkungan Desa</Text>}
|
||||
placeholder="masukkan nama data lingkungan desa"
|
||||
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
||||
label={<Text fw="bold" fz="sm">Nama Data Lingkungan Desa</Text>}
|
||||
placeholder="Masukkan nama data lingkungan desa"
|
||||
value={stateCreate.create.form.name || ''}
|
||||
onChange={(val) => (stateCreate.create.form.name = val.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Ikon Data Lingkungan Desa</Text>
|
||||
<SelectIconProgram onChange={(value) => stateCreate.create.form.icon = value} />
|
||||
</Box>
|
||||
<TextInput
|
||||
type='number'
|
||||
onChange={(e) => stateCreate.create.form.jumlah = e.currentTarget.value}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Jmlah data lingkungan desa</Text>}
|
||||
placeholder='Masukkan jumlah data lingkungan desa'
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi data lingkungan desa</Text>
|
||||
<CreateEditor
|
||||
value={stateCreate.create.form.deskripsi}
|
||||
onChange={(htmlContent) => stateCreate.create.form.deskripsi = htmlContent}
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Ikon Data Lingkungan Desa
|
||||
</Text>
|
||||
<SelectIconProgram
|
||||
onChange={(value) => (stateCreate.create.form.icon = value)}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
|
||||
<TextInput
|
||||
label={<Text fw="bold" fz="sm">Jumlah Data Lingkungan Desa</Text>}
|
||||
placeholder="Masukkan jumlah data lingkungan desa"
|
||||
value={stateCreate.create.form.jumlah || ''}
|
||||
onChange={(e) => (stateCreate.create.form.jumlah = e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi Data Lingkungan Desa
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={stateCreate.create.form.deskripsi}
|
||||
onChange={(htmlContent) =>
|
||||
(stateCreate.create.form.deskripsi = htmlContent)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Group justify="right" mt="sm">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import {
|
||||
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDeviceImac, IconDroplet, IconHome, IconHomeEco, IconLeaf,
|
||||
Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack,
|
||||
Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text,
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled,
|
||||
IconDeviceImacCog, IconDroplet, IconHome, IconHomeEco, IconLeaf,
|
||||
IconPlus,
|
||||
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent,
|
||||
IconTrashFilled,
|
||||
IconTree,
|
||||
IconTrendingUp,
|
||||
IconTrophy,
|
||||
IconTruckFilled
|
||||
IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled
|
||||
} from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import dataLingkunganDesaState from '../../_state/lingkungan/data-lingkungan-desa';
|
||||
|
||||
|
||||
@@ -26,7 +28,7 @@ function DataLingkunganDesa() {
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Data Lingkungan Desa'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari data lingkungan...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -64,76 +66,98 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
||||
rumah: IconHome,
|
||||
pohon: IconTree,
|
||||
air: IconDroplet
|
||||
|
||||
};
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={650} />
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md" >
|
||||
<Paper withBorder shadow="md" radius="md" p="lg" bg={colors['white-1']}>
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Lingkungan Desa'
|
||||
href='/admin/lingkungan/data-lingkungan-desa/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Data Lingkungan Desa</Title>
|
||||
<Tooltip label="Tambah Data Lingkungan Desa" withArrow>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama Data Lingkungan Desa</TableTh>
|
||||
<TableTh>Jumlah Data Lingkungan Desa</TableTh>
|
||||
<TableTh>Jumlah</TableTh>
|
||||
<TableTh>Ikon</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data lingkungan desa yang tersedia</Text>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">Tidak ada data lingkungan desa yang tersedia</Text>
|
||||
</Center>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'} h={{ base: 'auto', md: 650 }}>
|
||||
<JudulList
|
||||
title='List Data Lingkungan Desa'
|
||||
href='/admin/lingkungan/data-lingkungan-desa/create'
|
||||
/>
|
||||
<Box style={{ overflowY: 'auto' }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<Paper withBorder shadow="md" radius="md" bg={colors['white-1']} p="lg">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Data Lingkungan Desa</Title>
|
||||
<Tooltip label="Tambah Data Lingkungan Desa" withArrow>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan/create')}>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Nama Data Lingkungan Desa</TableTh>
|
||||
<TableTh style={{ width: '35%' }}>Jumlah Data Lingkungan Desa</TableTh>
|
||||
<TableTh style={{ width: '10%' }}>Ikon</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Nama Data</TableTh>
|
||||
<TableTh style={{ width: '35%' }}>Jumlah</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Ikon</TableTh>
|
||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
||||
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>± {item.jumlah}</TableTd>
|
||||
<TableTd style={{ width: '10%' }}>
|
||||
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" c="dimmed">± {item.jumlah}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{iconMap[item.icon] && (
|
||||
<Box title={item.icon}>
|
||||
{React.createElement(iconMap[item.icon], { size: 24 })}
|
||||
{React.createElement(iconMap[item.icon], { size: 22 })}
|
||||
</Box>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||
<Button onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${item.id}`)}>
|
||||
<IconDeviceImac size={25} />
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -147,11 +171,13 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
|
||||
@@ -1,67 +1,116 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { IconBook, IconLeaf, IconSchool } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Tujuan Edukasi Lingkungan",
|
||||
value: "tujuanedukasilingkungan",
|
||||
href: "/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan"
|
||||
href: "/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan",
|
||||
tooltip: "Lihat tujuan edukasi lingkungan",
|
||||
icon: <IconLeaf size={18} stroke={1.8} />
|
||||
},
|
||||
{
|
||||
label: "Materi Edukasi Yang Diberikan",
|
||||
value: "materiedukasiyangdiberikan",
|
||||
href: "/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan"
|
||||
href: "/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan",
|
||||
tooltip: "Kelola materi edukasi yang diberikan",
|
||||
icon: <IconBook size={18} stroke={1.8} />
|
||||
},
|
||||
{
|
||||
label: "Contoh Kegiatan Di Desa Darmasaba",
|
||||
value: "contohkegiatan",
|
||||
href: "/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba"
|
||||
href: "/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba",
|
||||
tooltip: "Lihat contoh kegiatan desa Darmasaba",
|
||||
icon: <IconSchool size={18} stroke={1.8} />
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const currentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
if (tab) router.push(tab.href)
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
if (match) setActiveTab(match.value)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Jumlah Penduduk Usia Kerja yang Menganggur</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
<Stack gap="md">
|
||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||
Edukasi Lingkungan
|
||||
</Title>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
whiteSpace: "nowrap",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
display: "inline-block",
|
||||
maxWidth: "200px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}}>
|
||||
{tab.label}
|
||||
</span>
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel key={i} value={tab.value}>
|
||||
<Box p="md">
|
||||
{children}
|
||||
</Box>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabs;
|
||||
|
||||
@@ -9,77 +9,105 @@ import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
const EdukasiLingkunganTextEditor = dynamic(() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), {
|
||||
ssr: false,
|
||||
});
|
||||
const EdukasiLingkunganTextEditor = dynamic(
|
||||
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
function EditContohKegiatanDesaDarmasaba() {
|
||||
const router = useRouter()
|
||||
const contohEdukasiState = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan)
|
||||
const router = useRouter();
|
||||
const contohEdukasiState = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan);
|
||||
const [judul, setJudul] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!contohEdukasiState.findById.data) {
|
||||
contohEdukasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
||||
contohEdukasiState.findById.initialize();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contohEdukasiState.findById.data) {
|
||||
setJudul(contohEdukasiState.findById.data.judul ?? '')
|
||||
setContent(contohEdukasiState.findById.data.deskripsi ?? '')
|
||||
setJudul(contohEdukasiState.findById.data.judul ?? '');
|
||||
setContent(contohEdukasiState.findById.data.deskripsi ?? '');
|
||||
}
|
||||
}, [contohEdukasiState.findById.data])
|
||||
}, [contohEdukasiState.findById.data]);
|
||||
|
||||
const submit = () => {
|
||||
if (contohEdukasiState.findById.data) {
|
||||
contohEdukasiState.findById.data.judul = judul;
|
||||
contohEdukasiState.findById.data.deskripsi = content;
|
||||
contohEdukasiState.update.save(contohEdukasiState.findById.data)
|
||||
contohEdukasiState.update.save(contohEdukasiState.findById.data);
|
||||
}
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba')
|
||||
}
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Contoh Kegiatan Di Desa Darmasaba</Title>
|
||||
<Text fw={"bold"}>Judul</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
<Text fw={"bold"}>Deskripsi</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
loading={contohEdukasiState.update.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Contoh Kegiatan di Desa Darmasaba
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Paper */}
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Judul
|
||||
</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button
|
||||
onClick={submit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={contohEdukasiState.update.loading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
@@ -8,47 +7,72 @@ import { useProxy } from 'valtio/utils';
|
||||
import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkungan';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listContohEdukasi = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan)
|
||||
const router = useRouter();
|
||||
const listContohEdukasi = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan);
|
||||
|
||||
useShallowEffect(() => {
|
||||
listContohEdukasi.findById.load('edit')
|
||||
}, [])
|
||||
listContohEdukasi.findById.load('edit');
|
||||
}, []);
|
||||
|
||||
if (!listContohEdukasi.findById.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
||||
<Stack gap={"22"}>
|
||||
<Grid>
|
||||
<Box p="md">
|
||||
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Contoh Kegiatan Di Desa Darmasaba</Title>
|
||||
<Title order={3} fw={600}>
|
||||
Preview Contoh Kegiatan Di Desa Darmasaba
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba/edit')}>
|
||||
<IconEdit size={16} />
|
||||
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="green"
|
||||
radius="md"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba/edit'
|
||||
)
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.judul }} />
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.deskripsi }} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
<Stack gap="md">
|
||||
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||
<Box mb="md">
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={600}
|
||||
c="black"
|
||||
dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.judul }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
lineClamp={10}
|
||||
dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -9,77 +9,90 @@ import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
const EdukasiLingkunganTextEditor = dynamic(() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), {
|
||||
ssr: false,
|
||||
});
|
||||
const EdukasiLingkunganTextEditor = dynamic(
|
||||
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
function EditMateriEdukasiYangDiberikan() {
|
||||
const router = useRouter()
|
||||
const materiEdukasiState = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan)
|
||||
const router = useRouter();
|
||||
const materiEdukasiState = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan);
|
||||
const [judul, setJudul] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!materiEdukasiState.findById.data) {
|
||||
materiEdukasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
||||
materiEdukasiState.findById.initialize();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (materiEdukasiState.findById.data) {
|
||||
setJudul(materiEdukasiState.findById.data.judul ?? '')
|
||||
setContent(materiEdukasiState.findById.data.deskripsi ?? '')
|
||||
setJudul(materiEdukasiState.findById.data.judul ?? '');
|
||||
setContent(materiEdukasiState.findById.data.deskripsi ?? '');
|
||||
}
|
||||
}, [materiEdukasiState.findById.data])
|
||||
}, [materiEdukasiState.findById.data]);
|
||||
|
||||
const submit = () => {
|
||||
if (materiEdukasiState.findById.data) {
|
||||
materiEdukasiState.findById.data.judul = judul;
|
||||
materiEdukasiState.findById.data.deskripsi = content;
|
||||
materiEdukasiState.update.save(materiEdukasiState.findById.data)
|
||||
materiEdukasiState.update.save(materiEdukasiState.findById.data);
|
||||
}
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan')
|
||||
}
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Materi Edukasi Yang Diberikan</Title>
|
||||
<Text fw={"bold"}>Judul</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
<Text fw={"bold"}>Content</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
loading={materiEdukasiState.update.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Group mb="md">
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Materi Edukasi Yang Diberikan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Judul
|
||||
</Text>
|
||||
<EdukasiLingkunganTextEditor showSubmit={false} onChange={setJudul} initialContent={judul} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Konten
|
||||
</Text>
|
||||
<EdukasiLingkunganTextEditor showSubmit={false} onChange={setContent} initialContent={content} />
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button
|
||||
onClick={submit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={materiEdukasiState.update.loading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
@@ -10,44 +9,65 @@ import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkunga
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listMateriEdukasi = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan)
|
||||
|
||||
useShallowEffect(() => {
|
||||
listMateriEdukasi.findById.load('edit')
|
||||
}, [])
|
||||
|
||||
if (!listMateriEdukasi.findById.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
||||
<Stack gap={"22"}>
|
||||
<Grid>
|
||||
<Box p="md">
|
||||
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Materi Edukasi Yang Diberikan</Title>
|
||||
<Title order={3} fw={600}>Preview Materi Edukasi Yang Diberikan</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan/edit')}>
|
||||
<IconEdit size={16} />
|
||||
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="green"
|
||||
radius="md"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() =>
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan/edit')
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.judul }} />
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.deskripsi }} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<Stack gap="md">
|
||||
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||
<Box mb="md">
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={600}
|
||||
c="black"
|
||||
dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.judul }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
lineClamp={10}
|
||||
dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,77 +9,103 @@ import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
const EdukasiLingkunganTextEditor = dynamic(() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), {
|
||||
ssr: false,
|
||||
});
|
||||
const EdukasiLingkunganTextEditor = dynamic(
|
||||
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
function EditTujuanEdukasiLingkungan() {
|
||||
const router = useRouter()
|
||||
const tujuanEdukasiState = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi)
|
||||
const router = useRouter();
|
||||
const tujuanEdukasiState = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi);
|
||||
const [judul, setJudul] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!tujuanEdukasiState.findById.data) {
|
||||
tujuanEdukasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
||||
tujuanEdukasiState.findById.initialize();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (tujuanEdukasiState.findById.data) {
|
||||
setJudul(tujuanEdukasiState.findById.data.judul ?? '')
|
||||
setContent(tujuanEdukasiState.findById.data.deskripsi ?? '')
|
||||
setJudul(tujuanEdukasiState.findById.data.judul ?? '');
|
||||
setContent(tujuanEdukasiState.findById.data.deskripsi ?? '');
|
||||
}
|
||||
}, [tujuanEdukasiState.findById.data])
|
||||
}, [tujuanEdukasiState.findById.data]);
|
||||
|
||||
const submit = () => {
|
||||
if (tujuanEdukasiState.findById.data) {
|
||||
tujuanEdukasiState.findById.data.judul = judul;
|
||||
tujuanEdukasiState.findById.data.deskripsi = content;
|
||||
tujuanEdukasiState.update.save(tujuanEdukasiState.findById.data)
|
||||
tujuanEdukasiState.update.save(tujuanEdukasiState.findById.data);
|
||||
}
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan')
|
||||
}
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Tujuan Edukasi Lingkungan</Title>
|
||||
<Text fw={"bold"}>Judul</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
<Text fw={"bold"}>Content</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
loading={tujuanEdukasiState.update.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Tujuan Edukasi Lingkungan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Judul
|
||||
</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Konten
|
||||
</Text>
|
||||
<EdukasiLingkunganTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button
|
||||
onClick={submit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={tujuanEdukasiState.update.loading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
@@ -8,47 +7,68 @@ import { useProxy } from 'valtio/utils';
|
||||
import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkungan';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listTujuanEdukasi = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi)
|
||||
const router = useRouter();
|
||||
const listTujuanEdukasi = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi);
|
||||
|
||||
useShallowEffect(() => {
|
||||
listTujuanEdukasi.findById.load('edit')
|
||||
}, [])
|
||||
listTujuanEdukasi.findById.load('edit');
|
||||
}, []);
|
||||
|
||||
if (!listTujuanEdukasi.findById.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
||||
<Stack gap={"22"}>
|
||||
<Grid>
|
||||
<Box p="md">
|
||||
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Tujuan Edukasi Lingkungan</Title>
|
||||
<Title order={3} fw={600}>Preview Tujuan Edukasi Lingkungan</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan/edit')}>
|
||||
<IconEdit size={16} />
|
||||
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="green"
|
||||
radius="md"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() =>
|
||||
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan/edit')
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.judul }} />
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.deskripsi }} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
<Stack gap="md">
|
||||
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||
<Box mb="md">
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={600}
|
||||
c='black'
|
||||
dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.judul }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
lineClamp={10}
|
||||
dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -1,62 +1,121 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconClipboardList, IconTags } from '@tabler/icons-react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Kegiatan Desa",
|
||||
value: "kegiatanDesa",
|
||||
href: "/admin/lingkungan/gotong-royong/kegiatan-desa"
|
||||
},
|
||||
{
|
||||
label: "Kategori Kegiatan",
|
||||
value: "kategoriKegiatan",
|
||||
href: "/admin/lingkungan/gotong-royong/kategori-kegiatan"
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
const tabs = [
|
||||
{
|
||||
label: "Kegiatan Desa",
|
||||
value: "kegiatanDesa",
|
||||
href: "/admin/lingkungan/gotong-royong/kegiatan-desa",
|
||||
icon: <IconClipboardList size={18} stroke={1.8} />,
|
||||
tooltip: "Lihat dan kelola kegiatan desa",
|
||||
},
|
||||
{
|
||||
label: "Kategori Kegiatan",
|
||||
value: "kategoriKegiatan",
|
||||
href: "/admin/lingkungan/gotong-royong/kategori-kegiatan",
|
||||
icon: <IconTags size={18} stroke={1.8} />,
|
||||
tooltip: "Kelola kategori kegiatan desa",
|
||||
},
|
||||
];
|
||||
|
||||
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value);
|
||||
if (tab) {
|
||||
router.push(tab.href);
|
||||
}
|
||||
setActiveTab(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname);
|
||||
if (match) {
|
||||
setActiveTab(match.value);
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Gotong Royong</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* ✅ Title lebih tegas */}
|
||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||
Gotong Royong
|
||||
</Title>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{/* ✅ Panel dengan gaya kartu */}
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabs;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client'
|
||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -16,7 +16,7 @@ function EditKategoriKegiatan() {
|
||||
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nama: "",
|
||||
nama: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -27,15 +27,14 @@ function EditKategoriKegiatan() {
|
||||
const data = await stateKategori.edit.load(id);
|
||||
|
||||
if (data) {
|
||||
// pastikan id-nya masuk ke state edit
|
||||
stateKategori.edit.id = id;
|
||||
setFormData({
|
||||
nama: data.nama || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori kegiatan:", error);
|
||||
toast.error("Gagal memuat data kategori kegiatan");
|
||||
console.error('Error loading kategori kegiatan:', error);
|
||||
toast.error('Gagal memuat data kategori kegiatan');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,45 +48,63 @@ function EditKategoriKegiatan() {
|
||||
return;
|
||||
}
|
||||
|
||||
stateKategori.edit.form = {
|
||||
nama: formData.nama.trim(),
|
||||
};
|
||||
|
||||
// Safety check tambahan: pastikan ID tidak kosong
|
||||
if (!stateKategori.edit.id) {
|
||||
stateKategori.edit.id = id; // fallback
|
||||
}
|
||||
stateKategori.edit.form = { nama: formData.nama.trim() };
|
||||
if (!stateKategori.edit.id) stateKategori.edit.id = id;
|
||||
|
||||
const success = await stateKategori.edit.update();
|
||||
|
||||
if (success) {
|
||||
router.push("/admin/lingkungan/gotong-royong/kategori-kegiatan");
|
||||
toast.success('Kategori kegiatan berhasil diperbarui!');
|
||||
router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating kategori kegiatan:", error);
|
||||
// toast akan ditampilkan dari fungsi update
|
||||
console.error('Error updating kategori kegiatan:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Group mb="md" align="center">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button variant="subtle" p="xs" radius="md" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Kategori Kegiatan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Kategori kegiatan</Title>
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
value={formData.nama}
|
||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori kegiatan</Text>}
|
||||
placeholder='Masukkan nama kategori kegiatan'
|
||||
label={<Text fw="bold" fz="sm">Nama Kategori Kegiatan</Text>}
|
||||
placeholder="Masukkan nama kategori kegiatan"
|
||||
required
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
@@ -29,31 +29,53 @@ function CreateKategoriKegiatan() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md" align="center">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button variant="subtle" p="xs" radius="md" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Tambah Kategori Kegiatan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Kategori Kegiatan</Title>
|
||||
<TextInput
|
||||
value={stateKategori.create.form.nama}
|
||||
onChange={(val) => {
|
||||
stateKategori.create.form.nama = val.target.value;
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
value={stateKategori.create.form.nama}
|
||||
onChange={(val) => (stateKategori.create.form.nama = val.target.value)}
|
||||
label={<Text fw="bold" fz="sm">Nama Kategori Kegiatan</Text>}
|
||||
placeholder="Masukkan nama kategori kegiatan"
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Kegiatan</Text>}
|
||||
placeholder='Masukkan nama kategori kegiatan'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,40 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
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, IconX } from '@tabler/icons-react';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
|
||||
|
||||
|
||||
function KategoriKegiatan() {
|
||||
const [search, setSearch] = useState("")
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Kategori Kegiatan'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari kategori kegiatan...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -34,6 +50,14 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateKategori.findMany
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateKategori.delete.byId(selectedId)
|
||||
@@ -43,61 +67,110 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateKategori.findMany.load()
|
||||
}, [])
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const filteredData = (stateKategori.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.nama.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = data || []
|
||||
|
||||
if (!stateKategori.findMany.data) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Kategori Kegiatan'
|
||||
href='/admin/lingkungan/gotong-royong/kategori-kegiatan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.nama}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Kategori Kegiatan</Title>
|
||||
<Tooltip label="Tambah Kategori Baru" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '60%' }}>Nama Kategori</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Delete</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="green"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() => router.push(`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="red"
|
||||
leftSection={<IconTrash size={16} />}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}
|
||||
>
|
||||
Hapus
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={3}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">Tidak ada kategori kegiatan yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
|
||||
@@ -4,7 +4,7 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
@@ -12,7 +12,6 @@ import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
interface FormKegiatanDesa {
|
||||
judul: string;
|
||||
deskripsiSingkat: string;
|
||||
@@ -92,14 +91,11 @@ function EditGotongRoyong() {
|
||||
if (file) {
|
||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||
const uploaded = res.data?.data;
|
||||
if (!uploaded?.id) {
|
||||
return toast.error("Gagal upload gambar");
|
||||
}
|
||||
|
||||
// Update imageId in global state
|
||||
if (!uploaded?.id) return toast.error("Gagal upload gambar");
|
||||
kegiatanDesaState.edit.form.imageId = uploaded.id;
|
||||
}
|
||||
await kegiatanDesaState.edit.update()
|
||||
toast.success("Kegiatan desa berhasil diperbarui!")
|
||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa");
|
||||
} catch (error) {
|
||||
console.error("Error updating kegiatan desa:", error);
|
||||
@@ -108,27 +104,40 @@ function EditGotongRoyong() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Kegiatan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Edit Kegiatan Desa</Title>
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
value={formData.judul}
|
||||
label={<Text fz="sm" fw="bold">Judul Kegiatan Desa</Text>}
|
||||
placeholder="masukkan judul kegiatan desa"
|
||||
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
value={formData.deskripsiSingkat}
|
||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat Kegiatan Desa</Text>}
|
||||
placeholder="masukkan deskripsi singkat kegiatan desa"
|
||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<Select
|
||||
label="Kategori Kegiatan"
|
||||
@@ -141,7 +150,7 @@ function EditGotongRoyong() {
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Lengkap Kegiatan Desa</Text>
|
||||
<Text fw="bold" fz="sm">Deskripsi Lengkap Kegiatan Desa</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsiLengkap}
|
||||
onChange={(htmlContent) => {
|
||||
@@ -150,6 +159,7 @@ function EditGotongRoyong() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Tanggal Kegiatan Desa</Text>}
|
||||
placeholder="masukkan tanggal kegiatan desa"
|
||||
@@ -169,71 +179,70 @@ function EditGotongRoyong() {
|
||||
placeholder="masukkan partisipan kegiatan desa"
|
||||
onChange={(e) => {
|
||||
const val = Number(e.target.value);
|
||||
if (!isNaN(val)) {
|
||||
setFormData({ ...formData, partisipan: val });
|
||||
}
|
||||
if (!isNaN(val)) setFormData({ ...formData, partisipan: val });
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const selectedFile = files[0]; // Ambil file pertama
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||
}
|
||||
}}
|
||||
onReject={() => toast.error('File tidak valid.')}
|
||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||
accept={{ 'image/*': [] }}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
<Text fw="bold" fz="sm" mb={6}>Gambar Kegiatan Desa</Text>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const selectedFile = files[0];
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||
}
|
||||
}}
|
||||
onReject={() => toast.error('File tidak valid.')}
|
||||
maxSize={5 * 1024 ** 2}
|
||||
accept={{ 'image/*': [] }}
|
||||
radius="md"
|
||||
p="xl"
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={180}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={48} color="red" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
<Stack gap="xs" align="center">
|
||||
<Text size="md" fw={500}>Drag gambar atau klik untuk pilih file</Text>
|
||||
<Text size="sm" c="dimmed">Maksimal 5MB, format gambar wajib</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag gambar ke sini atau klik untuk pilih file
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Maksimal 5MB dan harus format gambar
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
|
||||
{/* Tampilkan preview kalau ada */}
|
||||
{previewImage && (
|
||||
<Box mt="sm">
|
||||
<Image
|
||||
src={previewImage}
|
||||
alt="Preview"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '200px',
|
||||
objectFit: 'contain',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ddd',
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
{previewImage && (
|
||||
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Image
|
||||
src={previewImage}
|
||||
alt="Preview"
|
||||
radius="md"
|
||||
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit} >
|
||||
Simpan
|
||||
</Button>
|
||||
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -1,121 +1,166 @@
|
||||
'use client'
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import colors from '@/con/colors';
|
||||
|
||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
|
||||
function DetailKegiatanDesa() {
|
||||
const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
kegiatanDesaState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
kegiatanDesaState.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
kegiatanDesaState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa")
|
||||
kegiatanDesaState.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!kegiatanDesaState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const data = kegiatanDesaState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Kegiatan Desa Inovasi</Text>
|
||||
{kegiatanDesaState.findUnique.data ? (
|
||||
<Paper key={kegiatanDesaState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Judul Kegiatan Desa Inovasi</Text>
|
||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.judul}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.tanggal
|
||||
? new Date(kegiatanDesaState.findUnique.data.tanggal).toLocaleDateString()
|
||||
: "-"}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Deskripsi Singkat</Text>
|
||||
<Text fz={"lg"} >{kegiatanDesaState.findUnique.data?.deskripsiSingkat}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kegiatanDesaState.findUnique.data?.deskripsiLengkap }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Kategori Kegiatan</Text>
|
||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.kategoriKegiatan?.nama}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Partisipan</Text>
|
||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.partisipan}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Lokasi</Text>
|
||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.lokasi}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||
<Image w={{ base: 150, md: 150, lg: 150 }} src={kegiatanDesaState.findUnique.data?.image?.link} alt="gambar" loading="lazy" />
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Box py={10}>
|
||||
{/* Tombol Kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Container Detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
Detail Kegiatan Desa
|
||||
</Text>
|
||||
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
{/* Judul */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Judul Kegiatan Desa Inovasi</Text>
|
||||
<Text fz="md" c="dimmed">{data.judul || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Tanggal */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Tanggal</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data.tanggal ? new Date(data.tanggal).toLocaleDateString() : '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Deskripsi Singkat */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||
<Text fz="md" c="dimmed">{data.deskripsiSingkat || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Deskripsi Lengkap */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} />
|
||||
</Box>
|
||||
|
||||
{/* Kategori */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Kategori Kegiatan</Text>
|
||||
<Text fz="md" c="dimmed">{data.kategoriKegiatan?.nama || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Partisipan */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Partisipan</Text>
|
||||
<Text fz="md" c="dimmed">{data.partisipan || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Lokasi */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Lokasi</Text>
|
||||
<Text fz="md" c="dimmed">{data.lokasi || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Gambar */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">Gambar</Text>
|
||||
{data.image?.link ? (
|
||||
<Image
|
||||
src={data.image.link}
|
||||
alt={data.judul || 'Gambar Kegiatan Desa'}
|
||||
w={150}
|
||||
h={150}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Tombol Hapus & Edit */}
|
||||
<Flex gap="sm" mt={10}>
|
||||
<Tooltip label="Hapus Kegiatan Desa" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
if (kegiatanDesaState.findUnique.data) {
|
||||
setSelectedId(kegiatanDesaState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={kegiatanDesaState.delete.loading || !kegiatanDesaState.findUnique.data}
|
||||
color={"red"}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Kegiatan Desa" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (kegiatanDesaState.findUnique.data) {
|
||||
router.push(`/admin/lingkungan/gotong-royong/kegiatan-desa/${kegiatanDesaState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!kegiatanDesaState.findUnique.data}
|
||||
color={"green"}
|
||||
color="green"
|
||||
onClick={() => router.push(`/admin/lingkungan/gotong-royong/kegiatan-desa/${data.id}/edit`)}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -130,4 +175,4 @@ function DetailKegiatanDesa() {
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailKegiatanDesa;
|
||||
export default DetailKegiatanDesa;
|
||||
|
||||
@@ -4,7 +4,19 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Image,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -12,10 +24,9 @@ import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function CreateKegiatanDesa() {
|
||||
const router = useRouter();
|
||||
const stateKegiatanDesa = useProxy(gotongRoyongState.kegiatanDesa)
|
||||
const stateKegiatanDesa = useProxy(gotongRoyongState.kegiatanDesa);
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
@@ -26,32 +37,33 @@ function CreateKegiatanDesa() {
|
||||
|
||||
const resetForm = () => {
|
||||
stateKegiatanDesa.create.form = {
|
||||
judul: "",
|
||||
deskripsiSingkat: "",
|
||||
deskripsiLengkap: "",
|
||||
judul: '',
|
||||
deskripsiSingkat: '',
|
||||
deskripsiLengkap: '',
|
||||
tanggal: new Date(),
|
||||
lokasi: "",
|
||||
lokasi: '',
|
||||
partisipan: 0,
|
||||
imageId: "",
|
||||
kategoriKegiatanId: "",
|
||||
imageId: '',
|
||||
kategoriKegiatanId: '',
|
||||
};
|
||||
setPreviewImage(null);
|
||||
setFile(null);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!file) {
|
||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||
return toast.warn('Silakan pilih file gambar terlebih dahulu');
|
||||
}
|
||||
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file,
|
||||
name: file.name,
|
||||
})
|
||||
});
|
||||
|
||||
const uploaded = res.data?.data;
|
||||
|
||||
if (!uploaded?.id) {
|
||||
return toast.error("Gagal mengupload file");
|
||||
return toast.error('Gagal mengunggah gambar, silakan coba lagi');
|
||||
}
|
||||
|
||||
stateKegiatanDesa.create.form.imageId = uploaded.id;
|
||||
@@ -59,153 +71,176 @@ function CreateKegiatanDesa() {
|
||||
await stateKegiatanDesa.create.create();
|
||||
|
||||
resetForm();
|
||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa")
|
||||
}
|
||||
router.push('/admin/lingkungan/gotong-royong/kegiatan-desa');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Tambah Kegiatan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Kegiatan Desa</Title>
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{/* Upload Gambar */}
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const selectedFile = files[0]; // Ambil file pertama
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||
}
|
||||
}}
|
||||
onReject={() => toast.error('File tidak valid.')}
|
||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||
accept={{ 'image/*': [] }}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Gambar Kegiatan
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const selectedFile = files[0];
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||
}
|
||||
}}
|
||||
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||
maxSize={5 * 1024 ** 2}
|
||||
accept={{ 'image/*': [] }}
|
||||
radius="md"
|
||||
p="xl"
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={180}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
</Group>
|
||||
<Text ta="center" mt="sm" size="sm" color="dimmed">
|
||||
Seret gambar atau klik untuk memilih file (maks 5MB)
|
||||
</Text>
|
||||
</Dropzone>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag gambar ke sini atau klik untuk pilih file
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Maksimal 5MB dan harus format gambar
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
|
||||
{/* Tampilkan preview kalau ada */}
|
||||
{previewImage && (
|
||||
<Box mt="sm">
|
||||
<Image
|
||||
src={previewImage}
|
||||
alt="Preview"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '200px',
|
||||
objectFit: 'contain',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ddd',
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
{previewImage && (
|
||||
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||
<Image
|
||||
src={previewImage}
|
||||
alt="Preview Gambar"
|
||||
radius="md"
|
||||
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Input Form */}
|
||||
<TextInput
|
||||
label="Judul Kegiatan"
|
||||
placeholder="Masukkan judul kegiatan"
|
||||
value={stateKegiatanDesa.create.form.judul}
|
||||
onChange={(val) => {
|
||||
stateKegiatanDesa.create.form.judul = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Kegiatan</Text>}
|
||||
placeholder='Masukkan judul kegiatan'
|
||||
onChange={(e) => (stateKegiatanDesa.create.form.judul = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Deskripsi Singkat"
|
||||
placeholder="Masukkan deskripsi singkat"
|
||||
value={stateKegiatanDesa.create.form.deskripsiSingkat}
|
||||
onChange={(val) => {
|
||||
stateKegiatanDesa.create.form.deskripsiSingkat = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
||||
placeholder='Masukkan deskripsi singkat'
|
||||
onChange={(e) => (stateKegiatanDesa.create.form.deskripsiSingkat = e.target.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.1} // bisa pakai 0.1 biar support desimal
|
||||
value={stateKegiatanDesa.create.form.partisipan}
|
||||
onChange={(val) => {
|
||||
const value = Number(val.target.value);
|
||||
|
||||
// Validasi manual juga boleh (jaga-jaga)
|
||||
if (value >= 0 && value <= 1000) {
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
if (value >= 0) {
|
||||
stateKegiatanDesa.create.form.partisipan = value;
|
||||
}
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Partisipan</Text>}
|
||||
placeholder='Masukkan partisipan'
|
||||
label="Partisipan"
|
||||
placeholder="Masukkan jumlah partisipan"
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
||||
label="Tanggal"
|
||||
type="date"
|
||||
placeholder="Contoh: 2022-01-01"
|
||||
value={stateKegiatanDesa.create.form.tanggal ? stateKegiatanDesa.create.form.tanggal.toISOString().split('T')[0] : ''}
|
||||
value={
|
||||
stateKegiatanDesa.create.form.tanggal
|
||||
? stateKegiatanDesa.create.form.tanggal.toISOString().split('T')[0]
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
const dateValue = e.currentTarget.value;
|
||||
stateKegiatanDesa.create.form.tanggal = dateValue ? new Date(dateValue) : new Date();
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi kegiatan"
|
||||
value={stateKegiatanDesa.create.form.lokasi}
|
||||
onChange={(val) => {
|
||||
stateKegiatanDesa.create.form.lokasi = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
||||
placeholder='Masukkan lokasi'
|
||||
onChange={(e) => (stateKegiatanDesa.create.form.lokasi = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Deskripsi Lengkap</Text>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi Lengkap
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={stateKegiatanDesa.create.form.deskripsiLengkap}
|
||||
onChange={(val) => {
|
||||
stateKegiatanDesa.create.form.deskripsiLengkap = val;
|
||||
}}
|
||||
onChange={(val) => (stateKegiatanDesa.create.form.deskripsiLengkap = val)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Select
|
||||
label="Kategori Kegiatan"
|
||||
placeholder="Pilih kategori kegiatan"
|
||||
value={stateKegiatanDesa.create.form.kategoriKegiatanId}
|
||||
onChange={(val) => {
|
||||
stateKegiatanDesa.create.form.kategoriKegiatanId = val ?? "";
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori Kegiatan</Text>}
|
||||
placeholder="Pilih kategori produk"
|
||||
onChange={(val) => (stateKegiatanDesa.create.form.kategoriKegiatanId = val ?? '')}
|
||||
data={
|
||||
gotongRoyongState.kategoriKegiatan.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.nama,
|
||||
})) || []
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
{/* Submit */}
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
|
||||
|
||||
function KegiatanDesa() {
|
||||
@@ -15,8 +31,8 @@ function KegiatanDesa() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Kegiatan Desa'
|
||||
placeholder='pencarian'
|
||||
title="Kegiatan Desa"
|
||||
placeholder="Cari judul, lokasi, atau kategori kegiatan..."
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -27,71 +43,123 @@ function KegiatanDesa() {
|
||||
}
|
||||
|
||||
function ListKegiatanDesa({ search }: { search: string }) {
|
||||
const listState = useProxy(gotongRoyongState.kegiatanDesa)
|
||||
const listState = useProxy(gotongRoyongState.kegiatanDesa);
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
listState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
item.lokasi.toLowerCase().includes(keyword) ||
|
||||
item.kategoriKegiatan?.nama?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = listState.findMany;
|
||||
|
||||
if (!listState.findMany.data) {
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Kegiatan Desa'
|
||||
href='/admin/lingkungan/gotong-royong/kegiatan-desa/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Judul Kegiatan Desa</TableTh>
|
||||
<TableTh>Kategori Kegiatan Desa</TableTh>
|
||||
<TableTh>Lokasi Kegiatan Desa</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Kegiatan Desa</Title>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() =>
|
||||
router.push('/admin/lingkungan/gotong-royong/kegiatan-desa/create')
|
||||
}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Kategori</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Lokasi</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
|
||||
</Box>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>{item.kategoriKegiatan?.nama}</TableTd>
|
||||
<TableTd>{item.lokasi}</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/lingkungan/gotong-royong/kegiatan-desa/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
<Text fz="sm" c="dimmed">
|
||||
{item.kategoriKegiatan?.nama || '-'}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm">{item.lokasi}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/lingkungan/gotong-royong/kegiatan-desa/${item.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">
|
||||
Tidak ada kegiatan desa yang cocok dengan pencarian
|
||||
</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default KegiatanDesa;
|
||||
|
||||
@@ -1,67 +1,116 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { IconBook, IconLeaf, IconSchool } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Filosofi Tri Hita",
|
||||
value: "filosofi-tri-hita",
|
||||
href: "/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana"
|
||||
href: "/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana",
|
||||
tooltip: "Lihat filosofi Tri Hita Karana",
|
||||
icon: <IconLeaf size={18} stroke={1.8} />
|
||||
},
|
||||
{
|
||||
label: "Nilai Konservasi Adat",
|
||||
value: "nilai-konservasi-adat",
|
||||
href: "/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat"
|
||||
href: "/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat",
|
||||
tooltip: "Kelola nilai konservasi adat",
|
||||
icon: <IconBook size={18} stroke={1.8} />
|
||||
},
|
||||
{
|
||||
label: "Bentuk Konservasi Berdasarkan Adat",
|
||||
value: "bentuk-konservasi-berdasarkan-adat",
|
||||
href: "/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat"
|
||||
href: "/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat",
|
||||
tooltip: "Lihat bentuk konservasi berdasarkan adat",
|
||||
icon: <IconSchool size={18} stroke={1.8} />
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const currentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
if (tab) router.push(tab.href)
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
if (match) setActiveTab(match.value)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Konservasi Adat Bali</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
<Stack gap="md">
|
||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||
Konservasi Adat Bali
|
||||
</Title>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
whiteSpace: "nowrap",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
display: "inline-block",
|
||||
maxWidth: "200px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}}>
|
||||
{tab.label}
|
||||
</span>
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel key={i} value={tab.value}>
|
||||
<Box p="md">
|
||||
{children}
|
||||
</Box>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabs;
|
||||
|
||||
@@ -9,77 +9,105 @@ import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), {
|
||||
ssr: false,
|
||||
});
|
||||
const KonservasiAdatBaliTextEditor = dynamic(
|
||||
() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
function EditBentukKonservasiBerdasarkanAdat() {
|
||||
const router = useRouter()
|
||||
const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat)
|
||||
const router = useRouter();
|
||||
const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat);
|
||||
const [judul, setJudul] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!bentukKonservasiState.findById.data) {
|
||||
bentukKonservasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
||||
bentukKonservasiState.findById.initialize();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (bentukKonservasiState.findById.data) {
|
||||
setJudul(bentukKonservasiState.findById.data.judul ?? '')
|
||||
setContent(bentukKonservasiState.findById.data.deskripsi ?? '')
|
||||
setJudul(bentukKonservasiState.findById.data.judul ?? '');
|
||||
setContent(bentukKonservasiState.findById.data.deskripsi ?? '');
|
||||
}
|
||||
}, [bentukKonservasiState.findById.data])
|
||||
}, [bentukKonservasiState.findById.data]);
|
||||
|
||||
const submit = () => {
|
||||
if (bentukKonservasiState.findById.data) {
|
||||
bentukKonservasiState.findById.data.judul = judul;
|
||||
bentukKonservasiState.findById.data.deskripsi = content;
|
||||
bentukKonservasiState.update.save(bentukKonservasiState.findById.data)
|
||||
bentukKonservasiState.update.save(bentukKonservasiState.findById.data);
|
||||
}
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat')
|
||||
}
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Bentuk Konservasi Berdasarkan Adat</Title>
|
||||
<Text fw={"bold"}>Judul</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
<Text fw={"bold"}>Content</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
loading={bentukKonservasiState.update.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Bentuk Konservasi Berdasarkan Adat
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Paper */}
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Judul
|
||||
</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button
|
||||
onClick={submit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={bentukKonservasiState.update.loading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,47 +8,80 @@ import { useProxy } from 'valtio/utils';
|
||||
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listBentukKonservasiBerdasarkanAdat = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat)
|
||||
const router = useRouter();
|
||||
const listBentukKonservasiBerdasarkanAdat = useProxy(
|
||||
stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat
|
||||
);
|
||||
|
||||
useShallowEffect(() => {
|
||||
listBentukKonservasiBerdasarkanAdat.findById.load('edit')
|
||||
}, [])
|
||||
listBentukKonservasiBerdasarkanAdat.findById.load('edit');
|
||||
}, []);
|
||||
|
||||
if (!listBentukKonservasiBerdasarkanAdat.findById.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
<Stack py={20} align="center">
|
||||
<Skeleton radius="md" height={600} width="100%" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
||||
<Stack gap={"22"}>
|
||||
<Grid>
|
||||
<Box p={{ base: 'md', md: 'xl' }}>
|
||||
<Paper withBorder radius="md" p={{ base: 'md', md: 'lg' }} bg={colors['white-1']}>
|
||||
{/* Header */}
|
||||
<Grid align="center" mb="lg">
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Bentuk Konservasi Berdasarkan Adat</Title>
|
||||
<Title order={3} fw={600} c="dark">
|
||||
Preview Bentuk Konservasi Berdasarkan Adat
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit')}>
|
||||
<IconEdit size={16} />
|
||||
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="green"
|
||||
radius="md"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit'
|
||||
)
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listBentukKonservasiBerdasarkanAdat.findById.data.judul }} />
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listBentukKonservasiBerdasarkanAdat.findById.data.deskripsi }} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
{/* Konten */}
|
||||
<Stack gap="md">
|
||||
<Paper radius="md" p={{ base: 'md', md: 'xl' }} bg={colors['BG-trans']} shadow="sm">
|
||||
<Box mb="md">
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={600}
|
||||
c="dark"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: listBentukKonservasiBerdasarkanAdat.findById.data.judul,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
lineClamp={10}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: listBentukKonservasiBerdasarkanAdat.findById.data.deskripsi,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -9,78 +9,108 @@ import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), {
|
||||
ssr: false,
|
||||
});
|
||||
const KonservasiAdatBaliTextEditor = dynamic(
|
||||
() =>
|
||||
import('../../_lib/konservasiAdatBaliTextEditor').then(
|
||||
(mod) => mod.KonservasiAdatBaliTextEditor
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
function EditFilosofiTriHitaKarana() {
|
||||
const router = useRouter()
|
||||
const filosofiTriHitaState = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita)
|
||||
const router = useRouter();
|
||||
const filosofiTriHitaState = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita);
|
||||
const [judul, setJudul] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!filosofiTriHitaState.findById.data) {
|
||||
filosofiTriHitaState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
||||
filosofiTriHitaState.findById.initialize();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (filosofiTriHitaState.findById.data) {
|
||||
setJudul(filosofiTriHitaState.findById.data.judul ?? '')
|
||||
setContent(filosofiTriHitaState.findById.data.deskripsi ?? '')
|
||||
setJudul(filosofiTriHitaState.findById.data.judul ?? '');
|
||||
setContent(filosofiTriHitaState.findById.data.deskripsi ?? '');
|
||||
}
|
||||
}, [filosofiTriHitaState.findById.data])
|
||||
}, [filosofiTriHitaState.findById.data]);
|
||||
|
||||
const submit = () => {
|
||||
if (filosofiTriHitaState.findById.data) {
|
||||
filosofiTriHitaState.findById.data.judul = judul;
|
||||
filosofiTriHitaState.findById.data.deskripsi = content;
|
||||
filosofiTriHitaState.update.save(filosofiTriHitaState.findById.data)
|
||||
filosofiTriHitaState.update.save(filosofiTriHitaState.findById.data);
|
||||
}
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana')
|
||||
}
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Filosofi Tri Hita Karana</Title>
|
||||
<Text fw={"bold"}>Judul</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
<Text fw={"bold"}>Content</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
loading={filosofiTriHitaState.update.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Filosofi Tri Hita Karana
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Paper */}
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Judul
|
||||
</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button
|
||||
onClick={submit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={filosofiTriHitaState.update.loading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
@@ -7,49 +6,73 @@ import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
||||
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listFilosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita)
|
||||
const router = useRouter();
|
||||
const listFilosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita);
|
||||
|
||||
useShallowEffect(() => {
|
||||
listFilosofi.findById.load('edit')
|
||||
}, [])
|
||||
listFilosofi.findById.load('edit');
|
||||
}, []);
|
||||
|
||||
if (!listFilosofi.findById.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
||||
<Stack gap={"22"}>
|
||||
<Grid>
|
||||
<Box p="md">
|
||||
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Filosofi Tri Hita Karana</Title>
|
||||
<Title order={3} fw={600}>
|
||||
Preview Filosofi Tri Hita Karana
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana/edit')}>
|
||||
<IconEdit size={16} />
|
||||
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="green"
|
||||
radius="md"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana/edit'
|
||||
)
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.judul }} />
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.deskripsi }} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
<Stack gap="md">
|
||||
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||
<Box mb="md" px={{ base: 0, md: 20 }}>
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={600}
|
||||
c="black"
|
||||
dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.judul }}
|
||||
/>
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 20 }}>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
lineClamp={10}
|
||||
dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -9,77 +9,105 @@ import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), {
|
||||
ssr: false,
|
||||
});
|
||||
const KonservasiAdatBaliTextEditor = dynamic(
|
||||
() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
function EditNilaiKonservasiAdat() {
|
||||
const router = useRouter()
|
||||
const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat)
|
||||
const router = useRouter();
|
||||
const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat);
|
||||
const [judul, setJudul] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!nilaiKonservasiState.findById.data) {
|
||||
nilaiKonservasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
||||
nilaiKonservasiState.findById.initialize();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (nilaiKonservasiState.findById.data) {
|
||||
setJudul(nilaiKonservasiState.findById.data.judul ?? '')
|
||||
setContent(nilaiKonservasiState.findById.data.deskripsi ?? '')
|
||||
setJudul(nilaiKonservasiState.findById.data.judul ?? '');
|
||||
setContent(nilaiKonservasiState.findById.data.deskripsi ?? '');
|
||||
}
|
||||
}, [nilaiKonservasiState.findById.data])
|
||||
}, [nilaiKonservasiState.findById.data]);
|
||||
|
||||
const submit = () => {
|
||||
if (nilaiKonservasiState.findById.data) {
|
||||
nilaiKonservasiState.findById.data.judul = judul;
|
||||
nilaiKonservasiState.findById.data.deskripsi = content;
|
||||
nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data)
|
||||
nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data);
|
||||
}
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat')
|
||||
}
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Nilai Konservasi Adat</Title>
|
||||
<Text fw={"bold"}>Judul</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
<Text fw={"bold"}>Content</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
loading={nilaiKonservasiState.update.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Nilai Konservasi Adat
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Paper */}
|
||||
<Paper
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Judul
|
||||
</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setJudul}
|
||||
initialContent={judul}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<KonservasiAdatBaliTextEditor
|
||||
showSubmit={false}
|
||||
onChange={setContent}
|
||||
initialContent={content}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button
|
||||
onClick={submit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
loading={nilaiKonservasiState.update.loading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
@@ -8,47 +7,70 @@ import { useProxy } from 'valtio/utils';
|
||||
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const listNilaiKonservasiAdat = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat)
|
||||
const router = useRouter();
|
||||
const listNilaiKonservasiAdat = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat);
|
||||
|
||||
useShallowEffect(() => {
|
||||
listNilaiKonservasiAdat.findById.load('edit')
|
||||
}, [])
|
||||
listNilaiKonservasiAdat.findById.load('edit');
|
||||
}, []);
|
||||
|
||||
if (!listNilaiKonservasiAdat.findById.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
<Stack py={20}>
|
||||
<Skeleton radius="md" height={600} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
||||
<Stack gap={"22"}>
|
||||
<Grid>
|
||||
<Box p="md">
|
||||
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Nilai Konservasi Adat</Title>
|
||||
<Title order={3} fw={600}>
|
||||
Preview Nilai Konservasi Adat
|
||||
</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit')}>
|
||||
<IconEdit size={16} />
|
||||
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
color="green"
|
||||
radius="md"
|
||||
leftSection={<IconEdit size={16} />}
|
||||
onClick={() =>
|
||||
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit')
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.judul }} />
|
||||
</Box>
|
||||
<Box px={{ base: 0, md: 30 }}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.deskripsi }} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
<Stack gap="md">
|
||||
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||
<Box mb="md">
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw={600}
|
||||
c="black"
|
||||
dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.judul }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
ta="justify"
|
||||
c="dimmed"
|
||||
lineClamp={10}
|
||||
dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.deskripsi }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
'use client'
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { IconTrash, IconRecycle } from '@tabler/icons-react';
|
||||
import colors from '@/con/colors';
|
||||
|
||||
function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
@@ -45,49 +46,75 @@ function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.R
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Title order={3} mb="sm">Pengelolaan Sampah Bank Sampah</Title>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
<Stack gap="lg">
|
||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||
Pengelolaan Sampah Bank Sampah
|
||||
</Title>
|
||||
|
||||
<Tabs
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
radius="md"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList>
|
||||
{tabs.map((tab) => (
|
||||
<Tooltip
|
||||
key={tab.value}
|
||||
label={tab.tooltip}
|
||||
position="top"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 300 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
height: 'auto',
|
||||
minHeight: 44,
|
||||
}}
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsPanel
|
||||
value={activeTab || ''}
|
||||
pt="lg"
|
||||
style={{
|
||||
minHeight: '60vh',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsPanel>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
minHeight: "60vh",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsPengelolaanSampahBankSampah;
|
||||
export default LayoutTabsPengelolaanSampahBankSampah;
|
||||
|
||||
@@ -4,14 +4,23 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
|
||||
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
interface FormProgramPenghijauan {
|
||||
name: string;
|
||||
deskripsi: string;
|
||||
@@ -19,19 +28,32 @@ interface FormProgramPenghijauan {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah' | 'truck' | 'scale' | 'clipboard' | 'trash' | 'lingkunganSehat' | 'sumberOksigen' | 'ekonomiBerkelanjutan' | 'mencegahBencana';
|
||||
|
||||
type IconKey =
|
||||
| 'ekowisata'
|
||||
| 'kompetisi'
|
||||
| 'wisata'
|
||||
| 'ekonomi'
|
||||
| 'sampah'
|
||||
| 'truck'
|
||||
| 'scale'
|
||||
| 'clipboard'
|
||||
| 'trash'
|
||||
| 'lingkunganSehat'
|
||||
| 'sumberOksigen'
|
||||
| 'ekonomiBerkelanjutan'
|
||||
| 'mencegahBencana';
|
||||
|
||||
function EditProgramPenghijauan() {
|
||||
const stateProgramPenghijauan = useProxy(programPenghijauanState)
|
||||
const params = useParams()
|
||||
const stateProgramPenghijauan = useProxy(programPenghijauanState);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
const [formData, setFormData] = useState<FormProgramPenghijauan>({
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
judul: '',
|
||||
icon: '',
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadProgramPenghijauan = async () => {
|
||||
@@ -41,7 +63,6 @@ function EditProgramPenghijauan() {
|
||||
try {
|
||||
const data = await stateProgramPenghijauan.update.load(id);
|
||||
if (data) {
|
||||
// ⬇️ FIX PENTING: tambahkan ini
|
||||
stateProgramPenghijauan.update.id = id;
|
||||
|
||||
stateProgramPenghijauan.update.form = {
|
||||
@@ -59,16 +80,14 @@ function EditProgramPenghijauan() {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading program penghijauan:", error);
|
||||
toast.error("Gagal memuat data program penghijauan");
|
||||
console.error('Error loading program penghijauan:', error);
|
||||
toast.error('Gagal memuat data program penghijauan');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadProgramPenghijauan();
|
||||
}, [params?.id]);
|
||||
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
stateProgramPenghijauan.update.form = {
|
||||
@@ -77,49 +96,74 @@ function EditProgramPenghijauan() {
|
||||
deskripsi: formData.deskripsi.trim(),
|
||||
judul: formData.judul.trim(),
|
||||
icon: formData.icon.trim(),
|
||||
}
|
||||
};
|
||||
await stateProgramPenghijauan.update.submit();
|
||||
router.push("/admin/lingkungan/program-penghijauan");
|
||||
router.push('/admin/lingkungan/program-penghijauan');
|
||||
} catch (error) {
|
||||
console.error("Error updating program penghijauan:", error);
|
||||
toast.error("Gagal memuat data program penghijauan");
|
||||
console.error('Error updating program penghijauan:', error);
|
||||
toast.error('Gagal memperbarui program penghijauan');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
};
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit Program Penghijauan</Title>
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header dengan back button */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Program Penghijauan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Card utama */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Program Penghijauan</Text>}
|
||||
placeholder="masukkan nama program penghijauan"
|
||||
onChange={(val) => {
|
||||
label="Nama Program Penghijauan"
|
||||
placeholder="Masukkan nama program penghijauan"
|
||||
onChange={(val) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
name: val.target.value
|
||||
name: val.target.value,
|
||||
})
|
||||
}}
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
value={formData.judul}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Judul Deskripsi Program Penghijauan</Text>}
|
||||
placeholder="masukkan judul deskripsi program penghijauan"
|
||||
onChange={(val) => {
|
||||
label="Judul Deskripsi Program Penghijauan"
|
||||
placeholder="Masukkan judul deskripsi program penghijauan"
|
||||
onChange={(val) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
judul: val.target.value
|
||||
judul: val.target.value,
|
||||
})
|
||||
}}
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(htmlContent) => {
|
||||
@@ -128,16 +172,35 @@ function EditProgramPenghijauan() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Ikon Program Penghijauan</Text>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Ikon Program Penghijauan
|
||||
</Text>
|
||||
<SelectIconProgramEdit
|
||||
value={formData.icon as IconKey}
|
||||
onChange={(value) => {
|
||||
setFormData((prev) => ({ ...prev, icon: value }));
|
||||
stateProgramPenghijauan.update.form.icon = value;
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||
|
||||
{/* Tombol simpan */}
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard, IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react';
|
||||
import {
|
||||
IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard,
|
||||
IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale,
|
||||
IconShieldFilled, IconTent, IconTrash, IconTrendingUp,
|
||||
IconTrophy, IconTruck
|
||||
} from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan';
|
||||
|
||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function DetailProgramPenghijauan() {
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const stateProgramPenghijauan = useProxy(programPenghijauanState)
|
||||
@@ -50,72 +53,97 @@ function DetailProgramPenghijauan() {
|
||||
|
||||
if (!stateProgramPenghijauan.findUnique.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton h={500} />
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Program Penghijauan</Text>
|
||||
const data = stateProgramPenghijauan.findUnique.data
|
||||
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
return (
|
||||
<Box py={10}>
|
||||
{/* Tombol kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Konten detail */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: "100%", md: "60%" }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
Detail Program Penghijauan
|
||||
</Text>
|
||||
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Nama Program Penghijauan</Text>
|
||||
<Text fz={"lg"}>{stateProgramPenghijauan.findUnique.data?.name}</Text>
|
||||
<Text fz="lg" fw="bold">Nama Program</Text>
|
||||
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Ikon Program Penghijauan</Text>
|
||||
{iconMap[stateProgramPenghijauan.findUnique.data?.icon] && (
|
||||
<Box title={stateProgramPenghijauan.findUnique.data?.icon}>
|
||||
{React.createElement(iconMap[stateProgramPenghijauan.findUnique.data?.icon], { size: 24 })}
|
||||
<Text fz="lg" fw="bold">Ikon Program</Text>
|
||||
{iconMap[data?.icon] ? (
|
||||
<Box title={data?.icon}>
|
||||
{React.createElement(iconMap[data.icon], { size: 28, color: colors['blue-button'] })}
|
||||
</Box>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada ikon</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Judul Deskripsi Program Penghijauan</Text>
|
||||
<Text fz={"lg"}>{stateProgramPenghijauan.findUnique.data?.judul}</Text>
|
||||
<Text fz="lg" fw="bold">Judul Deskripsi</Text>
|
||||
<Text fz="md" c="dimmed">{data?.judul || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Program Penghijauan</Text>
|
||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: stateProgramPenghijauan.findUnique.data?.deskripsi }}></Text>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
|
||||
{/* Tombol aksi */}
|
||||
<Group gap="sm" mt="md">
|
||||
<Tooltip label="Hapus Program" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
if (stateProgramPenghijauan.findUnique.data) {
|
||||
setSelectedId(stateProgramPenghijauan.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={stateProgramPenghijauan.delete.loading || !stateProgramPenghijauan.findUnique.data}
|
||||
color={"red"}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconX size={20} />
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Program" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateProgramPenghijauan.findUnique.data) {
|
||||
router.push(`/admin/lingkungan/program-penghijauan/${stateProgramPenghijauan.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!stateProgramPenghijauan.findUnique.data}
|
||||
color={"green"}
|
||||
color="green"
|
||||
onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${data.id}/edit`)}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
@@ -126,7 +154,7 @@ function DetailProgramPenghijauan() {
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus program penghijauan ini?"
|
||||
text="Apakah Anda yakin ingin menghapus program penghijauan ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -8,59 +18,104 @@ import CreateEditor from '../../../_com/createEditor';
|
||||
import SelectIconProgram from '../../../_com/selectIcon';
|
||||
import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan';
|
||||
|
||||
|
||||
function CreateProgramPenghijauan() {
|
||||
const stateCreate = useProxy(programPenghijauanState)
|
||||
const stateCreate = useProxy(programPenghijauanState);
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
stateCreate.create.form = {
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
judul: "",
|
||||
icon: "",
|
||||
}
|
||||
}
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
judul: '',
|
||||
icon: '',
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await stateCreate.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/lingkungan/program-penghijauan")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
router.push('/admin/lingkungan/program-penghijauan');
|
||||
};
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Create Program Penghijauan</Title>
|
||||
return (
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Tombol back + title */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Tambah Program Penghijauan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Program Penghijauan</Text>}
|
||||
placeholder="masukkan nama program penghijauan"
|
||||
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
||||
label={<Text fz="sm" fw="bold">Nama Program Penghijauan</Text>}
|
||||
placeholder="Masukkan nama program penghijauan"
|
||||
value={stateCreate.create.form.name || ''}
|
||||
onChange={(e) => (stateCreate.create.form.name = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Ikon Program Penghijauan</Text>
|
||||
<SelectIconProgram onChange={(value) => stateCreate.create.form.icon = value} />
|
||||
</Box>
|
||||
<TextInput
|
||||
onChange={(e) => stateCreate.create.form.judul = e.currentTarget.value}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Deskripsi Program Penghijauan</Text>}
|
||||
placeholder='Masukkan judul deskripsi program penghijauan'
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Program Penghijauan</Text>
|
||||
<CreateEditor
|
||||
value={stateCreate.create.form.deskripsi}
|
||||
onChange={(htmlContent) => stateCreate.create.form.deskripsi = htmlContent}
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Ikon Program Penghijauan
|
||||
</Text>
|
||||
<SelectIconProgram
|
||||
onChange={(value) => (stateCreate.create.form.icon = value)}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Judul Deskripsi Program Penghijauan</Text>}
|
||||
placeholder="Masukkan judul deskripsi program penghijauan"
|
||||
value={stateCreate.create.form.judul || ''}
|
||||
onChange={(e) => (stateCreate.create.form.judul = e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold" mb={6}>
|
||||
Deskripsi Program Penghijauan
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={stateCreate.create.form.deskripsi}
|
||||
onChange={(htmlContent) =>
|
||||
(stateCreate.create.form.deskripsi = htmlContent)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group justify="right" mt="sm">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -2,30 +2,55 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import {
|
||||
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDeviceImac, IconHomeEco, IconLeaf,
|
||||
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconChartLine,
|
||||
IconChristmasTreeFilled,
|
||||
IconClipboardTextFilled,
|
||||
IconDeviceImac,
|
||||
IconHomeEco,
|
||||
IconLeaf,
|
||||
IconRecycle,
|
||||
IconScale,
|
||||
IconSearch,
|
||||
IconShieldFilled,
|
||||
IconTent,
|
||||
IconTrashFilled,
|
||||
IconTrendingUp,
|
||||
IconTrophy,
|
||||
IconTruckFilled
|
||||
IconTruckFilled,
|
||||
IconPlus,
|
||||
} from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import programPenghijauanState from '../../_state/lingkungan/program-penghijauan';
|
||||
|
||||
|
||||
function ProgramPenghijauan() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Program Penghijauan'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari program penghijauan...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -41,18 +66,10 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
load(page, 10)
|
||||
}, [page])
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
const filteredData = (data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
item.icon.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = data || []
|
||||
|
||||
const iconMap: Record<string, React.FC<any>> = {
|
||||
ekowisata: IconLeaf,
|
||||
@@ -73,93 +90,104 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={650} />
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper p="md" >
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Program Penghijauan'
|
||||
href='/admin/lingkungan/program-penghijauan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama Program Penghijauan</TableTh>
|
||||
<TableTh>Judul Deskripsi Program Penghijauan</TableTh>
|
||||
<TableTh>Ikon</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
<Text ta="center">Tidak ada data program penghijauan yang tersedia</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'} h={{ base: 'auto', md: 650 }}>
|
||||
<JudulList
|
||||
title='List Program Penghijauan'
|
||||
href='/admin/lingkungan/program-penghijauan/create'
|
||||
/>
|
||||
<Box style={{ overflowY: 'auto' }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
{/* Header Section */}
|
||||
<Box mb="md" display="flex" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Title order={4}>Daftar Program Penghijauan</Title>
|
||||
<Tooltip label="Tambah Program Penghijauan" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/lingkungan/program-penghijauan/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{/* Table Section */}
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Nama Program Penghijauan</TableTh>
|
||||
<TableTh style={{ width: '35%' }}>Judul Deskripsi Program Penghijauan</TableTh>
|
||||
<TableTh style={{ width: '10%' }}>Ikon</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Nama Program</TableTh>
|
||||
<TableTh style={{ width: '35%' }}>Judul / Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Ikon</TableTh>
|
||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
||||
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>
|
||||
<Text lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.judul }}/>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '10%' }}>
|
||||
{iconMap[item.icon] && (
|
||||
<Box title={item.icon}>
|
||||
{React.createElement(iconMap[item.icon], { size: 24 })}
|
||||
</Box>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||
<Button onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${item.id}`)}>
|
||||
<IconDeviceImac size={25} />
|
||||
</Button>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.judul }} />
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
{iconMap[item.icon] && (
|
||||
<Box title={item.icon} mx="auto">
|
||||
{React.createElement(iconMap[item.icon], { size: 22 })}
|
||||
</Box>
|
||||
)}
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImac size={16} />}
|
||||
onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={5}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">Tidak ada data program penghijauan yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProgramPenghijauan;
|
||||
|
||||
@@ -72,7 +72,8 @@ function Page() {
|
||||
<Image
|
||||
pt={{ base: 0, md: 60 }}
|
||||
src={item.image?.link || "/perbekel.png"}
|
||||
w={{ base: 250, md: 350 }}
|
||||
w="100%"
|
||||
maw={300}
|
||||
alt="Foto Profil PPID"
|
||||
radius="md"
|
||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||
|
||||
@@ -117,14 +117,14 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
||||
).map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Box w={150}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.namaLengkap}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Box w={150}>
|
||||
<Badge variant="light" color="blue">
|
||||
{item.posisi?.nama || 'Belum diatur'}
|
||||
</Badge>
|
||||
|
||||
@@ -68,10 +68,10 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Posisi Organisasi PPID</Title>
|
||||
<Tooltip label="Tambah Posisi Organisasi" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
@@ -82,51 +82,54 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '25%' }}>Nama Posisi</TableTh>
|
||||
<TableTh style={{ width: '45%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Hierarki</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Nama Posisi</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Hierarki</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '45%' }}>
|
||||
<Text lineClamp={2} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Box w={200}>
|
||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text>{item.hierarki || '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<Group gap="xs">
|
||||
<Tooltip label="Edit" withArrow>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip label="Hapus" withArrow>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Tooltip label="Edit" withArrow>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Tooltip label="Hapus" withArrow>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
|
||||
@@ -11,6 +11,7 @@ import KategoriPotensi from "./potensi/kategori-potensi";
|
||||
import KategoriBerita from "./berita/kategori-berita";
|
||||
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
||||
import MantanPerbekel from "./profile/profile-mantan-perbekel";
|
||||
import AjukanPermohonan from "./layanan/ajukan_permohonan";
|
||||
|
||||
|
||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
@@ -26,6 +27,7 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
.use(KategoriPotensi)
|
||||
.use(KategoriBerita)
|
||||
.use(KategoriPengumuman)
|
||||
.use(AjukanPermohonan)
|
||||
|
||||
|
||||
export default Desa;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
nama: string;
|
||||
nik: string;
|
||||
alamat: string;
|
||||
nomorKk: string;
|
||||
kategoriId: string;
|
||||
}
|
||||
|
||||
export default async function createAjukanPermohonan(context: Context){
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.ajukanPermohonan.create({
|
||||
data: {
|
||||
nama: body.nama,
|
||||
nik: body.nik,
|
||||
alamat: body.alamat,
|
||||
nomorKk: body.nomorKk,
|
||||
kategoriId: body.kategoriId
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
message: 'Ajukan permohonan berhasil dibuat',
|
||||
data: {
|
||||
...body
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function deleteAjukanPermohonan(context: Context) {
|
||||
const id = context.params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
status: 400,
|
||||
message: "ID tidak diberikan",
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.ajukanPermohonan.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
message: "Ajukan permohonan berhasil dihapus",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function findManyAjukanPermohonan(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ nama: { contains: search, mode: 'insensitive' } },
|
||||
{ nik: { contains: search, mode: 'insensitive' } },
|
||||
{ alamat: { contains: search, mode: 'insensitive' } },
|
||||
{ nomorKk: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.ajukanPermohonan.findMany({
|
||||
where,
|
||||
include: {
|
||||
kategori: true
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'asc' },
|
||||
}),
|
||||
prisma.ajukanPermohonan.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil data dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default findManyAjukanPermohonan;
|
||||
@@ -0,0 +1,66 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function findUniqueAjukanPermohonan(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split("/");
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== "string") {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await prisma.ajukanPermohonan.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
kategori: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Ajukan permohonan tidak ditemukan",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Success fetch ajukan permohonan by ID",
|
||||
data,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message:
|
||||
"Gagal mengambil ajukan permohonan: " +
|
||||
(e instanceof Error ? e.message : "Unknown error"),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import createAjukanPermohonan from "./create";
|
||||
import findManyAjukanPermohonan from "./findMany";
|
||||
import findUniqueAjukanPermohonan from "./findUnique";
|
||||
import updateAjukanPermohonan from "./updt";
|
||||
import deleteAjukanPermohonan from "./del";
|
||||
|
||||
|
||||
const AjukanPermohonan = new Elysia({
|
||||
prefix: "ajukanpermohonan",
|
||||
tags: ["Desa/Layanan/AjukanPermohonan"],
|
||||
})
|
||||
.get("/findMany", findManyAjukanPermohonan)
|
||||
.post("/create", createAjukanPermohonan, {
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
nik: t.String(),
|
||||
alamat: t.String(),
|
||||
nomorKk: t.String(),
|
||||
kategoriId: t.String(),
|
||||
})
|
||||
})
|
||||
.get("/:id", findUniqueAjukanPermohonan)
|
||||
.put("/:id", updateAjukanPermohonan, {
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
nik: t.String(),
|
||||
alamat: t.String(),
|
||||
nomorKk: t.String(),
|
||||
kategoriId: t.String(),
|
||||
})
|
||||
})
|
||||
.delete("/del/:id", deleteAjukanPermohonan)
|
||||
|
||||
|
||||
export default AjukanPermohonan;
|
||||
@@ -0,0 +1,48 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
id: string;
|
||||
nama: string;
|
||||
nik: string;
|
||||
alamat: string;
|
||||
nomorKk: string;
|
||||
kategoriId: string;
|
||||
};
|
||||
|
||||
export default async function updateAjukanPermohonan(context: Context) {
|
||||
const id = context.params?.id;
|
||||
const body = context.body as FormUpdate;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID tidak diberikan",
|
||||
};
|
||||
}
|
||||
|
||||
const existing = await prisma.ajukanPermohonan.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
kategori: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Ajukan permohonan tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
const updated = await prisma.ajukanPermohonan.update({
|
||||
where: { id },
|
||||
data: body,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success update ajukan permohonan",
|
||||
data: updated,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function pelayananSuratKeteranganFindManyAll() {
|
||||
try {
|
||||
const data = await prisma.pelayananSuratKeterangan.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: {
|
||||
image: true,
|
||||
image2: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil semua data pelayanan surat keterangan",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findManyAll:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data pelayanan surat keterangan",
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import pelayananSuratKeteranganFindUnique from "./findUnique";
|
||||
import pelayananSuratKeteranganCreate from "./create";
|
||||
import pelayananSuratKeteranganUpdate from "./updt";
|
||||
import pelayananSuratKeteranganDelete from "./del";
|
||||
|
||||
import pelayananSuratKeteranganFindManyAll from "./findManyAll";
|
||||
import { t } from "elysia";
|
||||
|
||||
const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan", tags: ["Desa/Layanan/Pelayanan Surat Keterangan"] })
|
||||
.get("/find-many", pelayananSuratKeteranganFindMany)
|
||||
.get("/findManyAll", pelayananSuratKeteranganFindManyAll)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await pelayananSuratKeteranganFindUnique(new Request(context.request));
|
||||
return response;
|
||||
|
||||
@@ -1,15 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function kategoriKegiatanFindMany() {
|
||||
const data = await prisma.kategoriKegiatan.findMany();
|
||||
return {
|
||||
success: true,
|
||||
data: data.map((item: any) => {
|
||||
return {
|
||||
id: item.id,
|
||||
nama: item.nama,
|
||||
}
|
||||
}),
|
||||
export default async function kategoriKegiatanFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || "";
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [{ nama: { contains: search, mode: "insensitive" } }];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.kategoriKegiatan.findMany({
|
||||
where: where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.kategoriKegiatan.count({
|
||||
where: where,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: data.map((item: any) => {
|
||||
return {
|
||||
id: item.id,
|
||||
nama: item.nama,
|
||||
};
|
||||
}),
|
||||
message: "Success fetch administrasi online with pagination",
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch administrasi online with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async function pengelolaanSampahFindMany(context: Context) {
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
}),
|
||||
prisma.pengelolaanSampah.count({
|
||||
where,
|
||||
|
||||
@@ -1,105 +1,83 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Image, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import BackButton from '../darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||
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 React from 'react';
|
||||
|
||||
|
||||
const data1 = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'Peran Pecalang dalam Keamanan Desa',
|
||||
image: '/api/img/pecalang.png',
|
||||
pengertian: 'Pecalang adalah petugas keamanan adat di Bali yang memiliki peran penting dalam menjaga ketertiban dan budaya lokal. Tugas mereka meliputi:',
|
||||
deskripsi: <List>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengamankan upacara adat dan kegiatan keagamaan.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengatur lalu lintas saat ada perayaan atau kegiatan besar.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli untuk mencegah gangguan keamanan di lingkungan desa.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berkoordinasi dengan aparat desa dan kepolisian dalam penanganan situasi darurat.</ListItem>
|
||||
</List>
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Patwal (Patroli Pengawal) Desa',
|
||||
image: '/api/img/patwal-1.png',
|
||||
pengertian: 'Selain Pecalang, Desa Darmasaba juga memiliki Patwal yang bertugas menjaga keamanan sehari-hari. Peran mereka antara lain:',
|
||||
deskripsi: <List>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli secara rutin untuk memastikan lingkungan tetap aman.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Menjaga ketertiban lalu lintas di area desa.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Melakukan tindakan preventif terhadap potensi gangguan keamanan.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Siap siaga dalam keadaan darurat untuk membantu warga.</ListItem>
|
||||
</List>
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'Layanan Keamanan yang Tersedia',
|
||||
image: '/api/img/pospecalang.png',
|
||||
pengertian: 'Jika terjadi keadaan darurat atau membutuhkan bantuan keamanan, warga dapat menghubungi:',
|
||||
deskripsi: <List>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Pos Pecalang Desa: [Masukkan Nomor Kontak].</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Patwal Desa Darmasaba: [Masukkan Nomor Kontak].</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Polsek Terdekat: 110 (Layanan Kepolisian).</ListItem>
|
||||
</List>
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
judul: 'Program Keamanan Desa',
|
||||
image: '/api/img/rond.png',
|
||||
pengertian: 'Untuk meningkatkan keamanan, Desa Darmasaba menjalankan berbagai program, seperti:',
|
||||
deskripsi: <List>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Ronda Malam Warga: Kegiatan jaga malam secara bergilir oleh warga bersama Pecalang dan Patwal.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Sosialisasi Keamanan: Edukasi bagi warga tentang cara menjaga keamanan lingkungan.</ListItem>
|
||||
<ListItem fz={{ base: 'h4', md: 'lg' }}> Pengawasan Kamera CCTV: Memantau titik- titik strategis untuk mencegah tindak kejahatan.</ListItem>
|
||||
</List>
|
||||
}
|
||||
]
|
||||
import Link from 'next/link';
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Keamanan Lingkungan (Pecalang / Patwal)
|
||||
</Text>
|
||||
<Text px={{ base: 20, md: 150 }} ta={"center"} fz={{ base: "h4", md: "h3" }} >
|
||||
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal). Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'}>
|
||||
<SimpleGrid
|
||||
pb={10}
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 3,
|
||||
}}>
|
||||
{data1.map((v, k) => {
|
||||
return (
|
||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
||||
<Stack gap={'xs'}>
|
||||
<Center px={10} py={20}>
|
||||
<Image src={v.image} alt='' />
|
||||
</Center>
|
||||
<Box px={'lg'}>
|
||||
<Box pb={20}>
|
||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Text pb={10} fz={"h4"} ta={'justify'}>
|
||||
{v.pengertian}
|
||||
</Text>
|
||||
<Box px={10}>
|
||||
{v.deskripsi}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<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
|
||||
</Text>
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</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>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Grid, GridCol, ScrollArea, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { Box, Group, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -23,95 +23,52 @@ function LayoutTabsBerita({
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
// Get active tab from URL path
|
||||
const activeTab = pathname.split('/').pop() || 'semua';
|
||||
|
||||
// Get initial search value from URL
|
||||
const initialSearch = searchParams.get('search') || '';
|
||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// Update active tab state when pathname changes
|
||||
const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
useEffect(() => {
|
||||
setActiveTabState(activeTab);
|
||||
}, [activeTab]);
|
||||
|
||||
// Clean up timeouts on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (searchTimeout !== null) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
};
|
||||
}, [searchTimeout]);
|
||||
|
||||
// Handle search input change with debounce
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setSearchValue(value);
|
||||
|
||||
// Clear previous timeout
|
||||
if (searchTimeout !== null) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||
|
||||
// Set new timeout
|
||||
const newTimeout = window.setTimeout(() => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (value) {
|
||||
params.set('search', value);
|
||||
} else {
|
||||
params.delete('search');
|
||||
}
|
||||
if (value) params.set('search', value);
|
||||
else params.delete('search');
|
||||
|
||||
// Only update URL if the search value has actually changed
|
||||
if (params.toString() !== searchParams.toString()) {
|
||||
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||
}
|
||||
}, 500); // 500ms debounce delay
|
||||
}, 500);
|
||||
|
||||
setSearchTimeout(newTimeout);
|
||||
};
|
||||
const tabs = [
|
||||
{
|
||||
label: "Semua",
|
||||
value: "semua",
|
||||
href: "/darmasaba/desa/berita/semua"
|
||||
},
|
||||
{
|
||||
label: "Budaya",
|
||||
value: "budaya",
|
||||
href: "/darmasaba/desa/berita/budaya"
|
||||
},
|
||||
{
|
||||
label: "Pemerintahan",
|
||||
value: "pemerintahan",
|
||||
href: "/darmasaba/desa/berita/pemerintahan"
|
||||
},
|
||||
{
|
||||
label: "Ekonomi",
|
||||
value: "ekonomi",
|
||||
href: "/darmasaba/desa/berita/ekonomi"
|
||||
},
|
||||
{
|
||||
label: "Pembangunan",
|
||||
value: "pembangunan",
|
||||
href: "/darmasaba/desa/berita/pembangunan"
|
||||
},
|
||||
{
|
||||
label: "Sosial",
|
||||
value: "sosial",
|
||||
href: "/darmasaba/desa/berita/sosial"
|
||||
},
|
||||
{
|
||||
label: "Teknologi",
|
||||
value: "teknologi",
|
||||
href: "/darmasaba/desa/berita/teknologi"
|
||||
},
|
||||
|
||||
const tabs = [
|
||||
{ label: "Semua", value: "semua", href: "/darmasaba/desa/berita/semua" },
|
||||
{ label: "Budaya", value: "budaya", href: "/darmasaba/desa/berita/budaya" },
|
||||
{ label: "Pemerintahan", value: "pemerintahan", href: "/darmasaba/desa/berita/pemerintahan" },
|
||||
{ label: "Ekonomi", value: "ekonomi", href: "/darmasaba/desa/berita/ekonomi" },
|
||||
{ label: "Pembangunan", value: "pembangunan", href: "/darmasaba/desa/berita/pembangunan" },
|
||||
{ label: "Sosial", value: "sosial", href: "/darmasaba/desa/berita/sosial" },
|
||||
{ label: "Teknologi", value: "teknologi", href: "/darmasaba/desa/berita/teknologi" },
|
||||
];
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
if (!value) return;
|
||||
const tab = tabs.find(t => t.value === value);
|
||||
@@ -127,16 +84,29 @@ function LayoutTabsBerita({
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="0" >
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Group justify='space-between' align="center">
|
||||
<Stack gap="0">
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" >
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text>
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
<Box>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={placeholder}
|
||||
leftSection={searchIcon}
|
||||
w="100%"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
@@ -145,33 +115,25 @@ function LayoutTabsBerita({
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList>
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTab
|
||||
key={index}
|
||||
value={tab.value}
|
||||
onClick={() => router.push(tab.href)}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={placeholder}
|
||||
leftSection={searchIcon}
|
||||
w="100%"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
{/* SCROLLABLE TABS */}
|
||||
<Box style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}>
|
||||
<TabsList style={{ display: 'flex', flexWrap: 'nowrap', gap: '0.5rem' }}>
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTab
|
||||
key={index}
|
||||
value={tab.value}
|
||||
onClick={() => router.push(tab.href)}
|
||||
style={{
|
||||
flex: '0 0 auto', // Prevent shrinking
|
||||
minWidth: 100, // optional: makes them touch-friendly
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{children}
|
||||
@@ -180,4 +142,4 @@ function LayoutTabsBerita({
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsBerita;
|
||||
export default LayoutTabsBerita;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text } from '@mantine/core';
|
||||
import BackButton from '../../layanan/_com/BackButto';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { SearchBarProps } from './searchBar';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Stack, Tabs, TabsList, TabsTab, Text } from '@mantine/core';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import BackButton from '../../layanan/_com/BackButto';
|
||||
import type { SearchBarProps } from './searchBar';
|
||||
|
||||
// Define tabs outside the component to ensure consistency between server and client
|
||||
const TABS = [
|
||||
@@ -35,7 +35,7 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
|
||||
// Set default active tab to empty string to prevent hydration mismatch
|
||||
const [activeTab, setActiveTab] = useState('');
|
||||
|
||||
@@ -47,7 +47,7 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
||||
// Update active tab based on current route - only on client side
|
||||
useEffect(() => {
|
||||
if (!isClient) return;
|
||||
|
||||
|
||||
const currentTab = TABS.find(tab => pathname.includes(tab.value));
|
||||
if (currentTab) {
|
||||
setActiveTab(currentTab.value);
|
||||
@@ -75,42 +75,38 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack align="center" gap="0">
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Galeri Kegiatan Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
<Box>
|
||||
<SearchBar />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Tabs
|
||||
<Tabs
|
||||
value={isClient ? activeTab : undefined}
|
||||
defaultValue={TABS[0].value}
|
||||
onChange={handleTabChange}
|
||||
color={colors['blue-button']}
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
keepMounted={false}
|
||||
>
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
<TabsList>
|
||||
{TABS.map((tab) => (
|
||||
<TabsTab
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
component="button"
|
||||
type="button"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
<SearchBar />
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<TabsList>
|
||||
{TABS.map((tab) => (
|
||||
<TabsTab
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
component="button"
|
||||
type="button"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</Box>
|
||||
|
||||
<Container size={'xl'}>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { TextInput } from '@mantine/core';
|
||||
import { Group, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
@@ -65,13 +65,15 @@ export function SearchBar({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
<Group justify='center'>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={placeholder}
|
||||
leftSection={searchIcon}
|
||||
w="100%"
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -2,19 +2,24 @@
|
||||
|
||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Pagination, Paper, SimpleGrid, Spoiler, Stack, Text } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Pagination,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Spoiler,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
export default function VideoContent() {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
// ✅ expanded state per index
|
||||
const [expandedMap, setExpandedMap] = useState<Record<number, boolean>>({});
|
||||
const videoState = useSnapshot(stateGallery.video);
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
} = videoState.findMany;
|
||||
const { data, page, totalPages, loading } = videoState.findMany;
|
||||
|
||||
// Handle search and pagination changes
|
||||
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
||||
@@ -27,24 +32,18 @@ export default function VideoContent() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlSearch = urlParams.get('search') || '';
|
||||
const urlPage = parseInt(urlParams.get('page') || '1');
|
||||
|
||||
loadData(urlPage, urlSearch);
|
||||
};
|
||||
|
||||
// Handle search updates from the search bar
|
||||
const handleSearchUpdate = (e: Event) => {
|
||||
const { search } = (e as CustomEvent).detail;
|
||||
loadData(1, search);
|
||||
};
|
||||
|
||||
// Initial load
|
||||
handleRouteChange();
|
||||
|
||||
// Set up event listeners
|
||||
window.addEventListener('popstate', handleRouteChange);
|
||||
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
||||
|
||||
// Cleanup
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('popstate', handleRouteChange);
|
||||
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
||||
@@ -57,6 +56,13 @@ export default function VideoContent() {
|
||||
loadData(newPage, search);
|
||||
};
|
||||
|
||||
const toggleExpanded = (index: number, value: boolean) => {
|
||||
setExpandedMap((prev) => ({
|
||||
...prev,
|
||||
[index]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const dataVideo = data || [];
|
||||
|
||||
if (loading && !data) {
|
||||
@@ -72,7 +78,13 @@ export default function VideoContent() {
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataVideo.map((v, k) => (
|
||||
<Box key={k}>
|
||||
<Paper mb={50} p="md" radius={26} bg={colors['white-trans-1']} w={{ base: '100%', md: '100%' }}>
|
||||
<Paper
|
||||
mb={50}
|
||||
p="md"
|
||||
radius={26}
|
||||
bg={colors['white-trans-1']}
|
||||
w={{ base: '100%', md: '100%' }}
|
||||
>
|
||||
<Box>
|
||||
<Center>
|
||||
<Box
|
||||
@@ -109,8 +121,8 @@ export default function VideoContent() {
|
||||
Hide details
|
||||
</Text>
|
||||
}
|
||||
expanded={expanded}
|
||||
onExpandedChange={setExpanded}
|
||||
expanded={expandedMap[k] || false}
|
||||
onExpandedChange={(val) => toggleExpanded(k, val)}
|
||||
>
|
||||
<Text
|
||||
ta="justify"
|
||||
@@ -137,15 +149,15 @@ export default function VideoContent() {
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ Fix: HAPUS SPASI BERLEBIH DI URL
|
||||
// ✅ Fix: convert YouTube URL ke embed
|
||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||
try {
|
||||
const url = new URL(youtubeUrl);
|
||||
const videoId = url.searchParams.get('v');
|
||||
if (!videoId) return youtubeUrl;
|
||||
return `https://www.youtube.com/embed/${videoId}`; // ✅ tanpa spasi!
|
||||
return `https://www.youtube.com/embed/${videoId}`;
|
||||
} catch (err) {
|
||||
console.error('Error converting YouTube URL to embed:', err);
|
||||
return youtubeUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
'use client'
|
||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Container, Group, Image, Modal, Paper, Select, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../_com/BackButto';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
|
||||
interface LayananData {
|
||||
id: string;
|
||||
@@ -31,7 +32,12 @@ function Page() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [data, setData] = useState<LayananData | null>(null);
|
||||
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const stateCreate = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
state.suratKeterangan.findManyAll.load()
|
||||
const loadData = async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
@@ -48,6 +54,22 @@ function Page() {
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
const resetForm = () => {
|
||||
stateCreate.create.form = {
|
||||
nama: '',
|
||||
nik: '',
|
||||
alamat: '',
|
||||
nomorKk: '',
|
||||
kategoriId: '',
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await stateCreate.create.create();
|
||||
resetForm();
|
||||
close();
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Center h="100vh" bg={colors.Bg}>
|
||||
@@ -105,12 +127,76 @@ function Page() {
|
||||
size="lg"
|
||||
variant="gradient"
|
||||
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
||||
onClick={open}
|
||||
>
|
||||
Ajukan Permohonan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
radius={0}
|
||||
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||
>
|
||||
<Paper p="md" withBorder>
|
||||
<Stack gap="xs">
|
||||
<Title order={3}>Ajukan Permohonan</Title>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Nama</Text>}
|
||||
placeholder="masukkan nama"
|
||||
onChange={(val) => (stateCreate.create.form.nama = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
label={<Text fz="sm" fw="bold">NIK</Text>}
|
||||
placeholder="masukkan NIK"
|
||||
onChange={(val) => (stateCreate.create.form.nik = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
||||
placeholder="masukkan alamat"
|
||||
onChange={(val) => (stateCreate.create.form.alamat = val.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
label={<Text fz="sm" fw="bold">Nomor KK</Text>}
|
||||
placeholder="masukkan Nomor KK"
|
||||
onChange={(val) => (stateCreate.create.form.nomorKk = val.target.value)}
|
||||
/>
|
||||
<Select
|
||||
label="Kategori"
|
||||
placeholder="Pilih kategori"
|
||||
data={stateLayananDesa.suratKeterangan.findManyAll.data?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={stateCreate.create.form.kategoriId || null}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
const selected = stateLayananDesa.suratKeterangan.findMany.data?.find(
|
||||
(item) => item.id === val
|
||||
);
|
||||
if (selected) {
|
||||
stateCreate.create.form.kategoriId = selected.id;
|
||||
}
|
||||
} else {
|
||||
stateCreate.create.form.kategoriId = '';
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
required
|
||||
/>
|
||||
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||
Simpan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Modal>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function Page() {
|
||||
<Container w={{ base: "100%", md: "50%" }} >
|
||||
<Stack align="center" gap={0}>
|
||||
{/* Bagian Layanan */}
|
||||
<Text fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Text fz={{ base: "1.8rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Layanan Desa Darmasaba
|
||||
</Text>
|
||||
<Text
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Flex, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -31,7 +31,7 @@ function Page() {
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack gap="xs" >
|
||||
<Flex justify={"space-between"} align={"center"}>
|
||||
<Group justify={"space-between"} align={"center"}>
|
||||
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
||||
{detail.data?.judul}
|
||||
</Text>
|
||||
@@ -40,7 +40,7 @@ function Page() {
|
||||
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
</Flex>
|
||||
</Group>
|
||||
<Paper bg={colors["white-1"]} p="md">
|
||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
||||
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
|
||||
|
||||
@@ -1,67 +1,76 @@
|
||||
'use client'
|
||||
import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi';
|
||||
import mitraKolaborasi from '@/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Grid, GridCol, Group, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(kolaborasiInovasiState)
|
||||
const kolabState = useProxy(kolaborasiInovasiState)
|
||||
const mitraState = useProxy(mitraKolaborasi)
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedYear, setSelectedYear] = useState<string | null>(null);
|
||||
|
||||
// Get unique years from the data
|
||||
// Get unique years from kolaborasiInovasi data
|
||||
const years = Array.from(
|
||||
new Set(
|
||||
state.findMany.data?.map(item =>
|
||||
kolabState.findMany.data?.map(item =>
|
||||
new Date(item.createdAt).getFullYear().toString()
|
||||
) || []
|
||||
)
|
||||
)
|
||||
.sort((a, b) => b.localeCompare(a)) // Sort descending (newest first)
|
||||
.map(year => ({
|
||||
value: year,
|
||||
label: year,
|
||||
}));
|
||||
.sort((a, b) => b.localeCompare(a))
|
||||
.map(year => ({ value: year, label: year }));
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = state.findMany
|
||||
const { data, page, totalPages, loading, load } = kolabState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search, selectedYear || '')
|
||||
}, [page, search, selectedYear])
|
||||
mitraState.findMany.load(page, 10);
|
||||
load(page, 10, search, selectedYear || '');
|
||||
}, [page, search, selectedYear]);
|
||||
|
||||
const mitraData = mitraState.findMany.data || [];
|
||||
const mitraLoading = mitraState.findMany.loading;
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={650} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
|
||||
{/* Header Kolaborasi Inovasi */}
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Grid align='center'>
|
||||
<GridCol span={{ base: 12, md: 9 }}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||
Kolaborasi Inovasi
|
||||
</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3 }}>
|
||||
<TextInput
|
||||
radius={"lg"}
|
||||
radius="lg"
|
||||
placeholder='Cari Kolaborasi Inovasi'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
@@ -71,40 +80,43 @@ function Page() {
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* Filter Tahun */}
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Group justify='flex-end'>
|
||||
<Stack gap="lg" justify="center">
|
||||
<Group justify="flex-end">
|
||||
<Select
|
||||
value={selectedYear}
|
||||
onChange={setSelectedYear}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Tahun</Text>}
|
||||
label={<Text fw="bold" fz="sm">Tahun</Text>}
|
||||
placeholder='Semua Tahun'
|
||||
clearable
|
||||
data={[
|
||||
{ value: '', label: 'Semua Tahun' },
|
||||
...years
|
||||
]}
|
||||
data={[{ value: '', label: 'Semua Tahun' }, ...years]}
|
||||
/>
|
||||
</Group>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing={'lg'}>
|
||||
{data.map((v, k) => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.name}</Text>
|
||||
<Box pr={'lg'} pb={20}>
|
||||
{v.slug}
|
||||
</Box>
|
||||
<Box pr={'lg'}>
|
||||
{v.kolaborator}
|
||||
</Box>
|
||||
|
||||
{/* List Kolaborasi Inovasi */}
|
||||
{loading || !data ? (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={650} />
|
||||
</Stack>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||
{data.map((v, k) => (
|
||||
<Paper p="xl" key={k}>
|
||||
<Text fz="h3" fw="bold" c={colors['blue-button']}>{v.name}</Text>
|
||||
<Box pr="lg" pb={20}>{v.slug}</Box>
|
||||
<Box pr="lg">{v.kolaborator}</Box>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</SimpleGrid>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
@@ -113,17 +125,44 @@ function Page() {
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Mitra Kolaborasi Section */}
|
||||
<Box py={40} px={{ base: "md", md: 100 }} bg={colors['white-trans-1']}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
<Stack gap="lg" justify="center">
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||
Mitra Kolaborasi
|
||||
</Text>
|
||||
<Text ta={'center'} fz={'h4'}>Kami berkolaborasi dengan berbagai mitra dari berbagai sektor untuk mewujudkan visi Smart Village Darmasaba.</Text>
|
||||
<Text ta="center" fz="h4">
|
||||
Kami berkolaborasi dengan berbagai mitra dari berbagai sektor untuk mewujudkan visi Smart Village Darmasaba.
|
||||
</Text>
|
||||
</Box>
|
||||
<Center>
|
||||
<Image src={'/api/img/logoukm-kolaborasiinvoasi.png'} alt='' w={{ base: 500, md: 650 }} loading="lazy"/>
|
||||
</Center>
|
||||
|
||||
{mitraLoading ? (
|
||||
<Center py={20}><Skeleton height={100} width="80%" /></Center>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 2, md: 4 }} spacing="xl">
|
||||
{mitraData.map((m) => (
|
||||
<Paper key={m.id} p="md" shadow="sm" radius="md">
|
||||
<Center mb="sm">
|
||||
{m.image?.link ? (
|
||||
<Image
|
||||
src={`${process.env.NEXT_PUBLIC_BASE_URL || ''}${m.image.link}`}
|
||||
alt={m.name}
|
||||
w={150}
|
||||
h={100}
|
||||
fit="cover"
|
||||
radius="md"
|
||||
/>
|
||||
) : (
|
||||
<Box w={100} h={100} bg={colors['blue-button']} style={{ borderRadius: '50%' }} />
|
||||
)}
|
||||
</Center>
|
||||
<Text ta="center" fw="bold">{m.name}</Text>
|
||||
</Paper>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Paper, Stack, Text, Skeleton } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import programKreatifState from '@/app/admin/(dashboard)/_state/inovasi/program-kreatif';
|
||||
|
||||
import { IconMapper, IconKey } from '@/app/admin/(dashboard)/_com/iconMap';
|
||||
|
||||
function Page() {
|
||||
const stateProgramKreatif = useProxy(programKreatifState);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateProgramKreatif.findUnique.load(params?.id as string);
|
||||
}, [params?.id]);
|
||||
|
||||
if (!stateProgramKreatif.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = stateProgramKreatif.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box px={{ base: 'md', md: 100 }} py="md">
|
||||
{/* Tombol Kembali */}
|
||||
<Box mb="md">
|
||||
<Text
|
||||
c={colors['blue-button']}
|
||||
fw="bold"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
← Kembali
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Konten Utama */}
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '60%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
mx="auto"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
{data?.name || 'Program Kreatif Desa'}
|
||||
</Text>
|
||||
|
||||
{/* Ikon */}
|
||||
{data?.icon && (
|
||||
<Box>
|
||||
<IconMapper
|
||||
name={data.icon as IconKey}
|
||||
size={40}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Deskripsi Singkat */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Deskripsi Singkat
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">{data?.slug || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Deskripsi Detail */}
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Deskripsi
|
||||
</Text>
|
||||
<Text
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -1,51 +1,91 @@
|
||||
'use client'
|
||||
import programKreatifState from '@/app/admin/(dashboard)/_state/inovasi/program-kreatif';
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, SimpleGrid, Paper, Center, Button } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import React, { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { IconBuildingCircus, IconChartLine, IconLeaf, IconRecycle, IconTrophy } from '@tabler/icons-react';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { IconKey, IconMapper } from '@/app/admin/(dashboard)/_com/iconMap';
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
icon: <IconLeaf size={50} color={colors['blue-button']} />,
|
||||
judul: 'Ekowisata dan Desa Hijau',
|
||||
deskripsi: 'Inisiatif ramah lingkungan untuk desa berkelanjutan'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: <IconTrophy size={50} color={colors['blue-button']} />,
|
||||
judul: 'Kompetisi dan Festival Desa',
|
||||
deskripsi: 'Ajang kompetisi inovasi dan festival tahunan desa'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: <IconBuildingCircus size={50} color={colors['blue-button']} />,
|
||||
judul: 'Wisata Kreatif dan Budaya',
|
||||
deskripsi: 'Promosi destinasi wisata berbasis budaya dan alam'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: <IconChartLine size={50} color={colors['blue-button']} />,
|
||||
judul: 'Ekonomi Kreatif',
|
||||
deskripsi: 'Mendukung pelaku UMKM dengan platform digital untuk mempromosikan produk lokal ke pasar global'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
icon: <IconRecycle size={50} color={colors['blue-button']} />,
|
||||
judul: 'Smart Waste Management',
|
||||
deskripsi: 'Inisiatif pengelolaan sampah berbasis teknologi untuk menciptakan lingkungan yang bersih dan berkelanjutan.'
|
||||
},
|
||||
]
|
||||
// const data = [
|
||||
// {
|
||||
// id: 1,
|
||||
// icon: <IconLeaf size={50} color={colors['blue-button']} />,
|
||||
// judul: 'Ekowisata dan Desa Hijau',
|
||||
// deskripsi: 'Inisiatif ramah lingkungan untuk desa berkelanjutan'
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// icon: <IconTrophy size={50} color={colors['blue-button']} />,
|
||||
// judul: 'Kompetisi dan Festival Desa',
|
||||
// deskripsi: 'Ajang kompetisi inovasi dan festival tahunan desa'
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// icon: <IconBuildingCircus size={50} color={colors['blue-button']} />,
|
||||
// judul: 'Wisata Kreatif dan Budaya',
|
||||
// deskripsi: 'Promosi destinasi wisata berbasis budaya dan alam'
|
||||
// },
|
||||
// {
|
||||
// id: 4,
|
||||
// icon: <IconChartLine size={50} color={colors['blue-button']} />,
|
||||
// judul: 'Ekonomi Kreatif',
|
||||
// deskripsi: 'Mendukung pelaku UMKM dengan platform digital untuk mempromosikan produk lokal ke pasar global'
|
||||
// },
|
||||
// {
|
||||
// id: 5,
|
||||
// icon: <IconRecycle size={50} color={colors['blue-button']} />,
|
||||
// judul: 'Smart Waste Management',
|
||||
// deskripsi: 'Inisiatif pengelolaan sampah berbasis teknologi untuk menciptakan lingkungan yang bersih dan berkelanjutan.'
|
||||
// },
|
||||
// ]
|
||||
function Page() {
|
||||
const listState = useProxy(programKreatifState);
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
||||
const router = useTransitionRouter()
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
page,
|
||||
totalPages,
|
||||
load
|
||||
} = listState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, debouncedSearch);
|
||||
}, [debouncedSearch])
|
||||
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={650} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Box px={{ base: 'md', md: 100 }} >
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Program Kreatif Desa
|
||||
</Text>
|
||||
<Group justify="space-between" mb="md" align='center'>
|
||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||
Program Kreatif Desa
|
||||
</Text>
|
||||
<TextInput
|
||||
placeholder="Cari program kreatif..."
|
||||
leftSection={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
@@ -55,17 +95,25 @@ function Page() {
|
||||
md: 3
|
||||
}}
|
||||
>
|
||||
{data.map((v, k) => {
|
||||
{filteredData.map((v, k) => {
|
||||
return (
|
||||
<Stack key={k} >
|
||||
<Paper p={'xl'} >
|
||||
<Center pb={20}>
|
||||
{v.icon}
|
||||
{v.icon ? (
|
||||
<IconMapper
|
||||
name={v.icon as IconKey}
|
||||
size={32}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada ikon</Text>
|
||||
)}
|
||||
</Center>
|
||||
<Text ta={'center'} fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.judul}</Text>
|
||||
<Text py={10} ta={'center'} fz={'lg'} c={'black'}>{v.deskripsi}</Text>
|
||||
<Text ta={'center'} fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.name}</Text>
|
||||
<Text py={10} ta={'center'} fz={'lg'} c={'black'}>{v.slug}</Text>
|
||||
<Center>
|
||||
<Button bg={colors['blue-button']}>Selengkapnya</Button>
|
||||
<Button onClick={() => router.push(`/darmasaba/inovasi/program-kreatif-desa/${v.id}`)} bg={colors['blue-button']}>Selengkapnya</Button>
|
||||
</Center>
|
||||
</Paper>
|
||||
</Stack>
|
||||
@@ -73,6 +121,20 @@ function Page() {
|
||||
})}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Text, ThemeIcon } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import {
|
||||
IconArrowLeft, IconChartLine, IconChristmasTreeFilled, IconClipboard,
|
||||
IconHomeEco, IconLeaf, IconRecycle, IconScale,
|
||||
IconShieldFilled, IconTent, IconTrash,
|
||||
IconTrendingUp, IconTrophy, IconTruck
|
||||
} from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function Page() {
|
||||
const stateProgramPenghijauan = useProxy(programPenghijauanState);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
const iconMap: Record<string, React.FC<any>> = {
|
||||
ekowisata: IconLeaf,
|
||||
kompetisi: IconTrophy,
|
||||
wisata: IconTent,
|
||||
ekonomi: IconChartLine,
|
||||
sampah: IconRecycle,
|
||||
truck: IconTruck,
|
||||
scale: IconScale,
|
||||
clipboard: IconClipboard,
|
||||
trash: IconTrash,
|
||||
lingkunganSehat: IconHomeEco,
|
||||
sumberOksigen: IconChristmasTreeFilled,
|
||||
ekonomiBerkelanjutan: IconTrendingUp,
|
||||
mencegahBencana: IconShieldFilled,
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateProgramPenghijauan.findUnique.load(params?.id as string);
|
||||
}, [params?.id]);
|
||||
|
||||
if (!stateProgramPenghijauan.findUnique.data) {
|
||||
return (
|
||||
<Stack py="xl" align="center">
|
||||
<Skeleton height={300} radius="lg" w="100%" />
|
||||
<Text c="dimmed" fz="sm">Sedang memuat detail program...</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = stateProgramPenghijauan.findUnique.data;
|
||||
const IconComponent = data?.icon ? iconMap[data.icon] : null;
|
||||
|
||||
return (
|
||||
<Box py="md" px={{ base: 'md', md: 100 }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowLeft size={20} />}
|
||||
mb="lg"
|
||||
radius="xl"
|
||||
>
|
||||
Kembali ke daftar
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '75%' }}
|
||||
mx="auto"
|
||||
p="xl"
|
||||
radius="xl"
|
||||
shadow="md"
|
||||
withBorder
|
||||
>
|
||||
<Stack gap="md" align="center" ta="center">
|
||||
{IconComponent && (
|
||||
<ThemeIcon
|
||||
size={72}
|
||||
radius="xl"
|
||||
variant="light"
|
||||
color="blue"
|
||||
mb="sm"
|
||||
style={{ boxShadow: '0 0 15px rgba(0, 123, 255, 0.3)' }}
|
||||
>
|
||||
<IconComponent size={40} />
|
||||
</ThemeIcon>
|
||||
)}
|
||||
|
||||
<Text fz="xl" fw={700} c="blue">
|
||||
{data?.name || 'Nama program tidak tersedia'}
|
||||
</Text>
|
||||
|
||||
<Text fz="lg" fw={600}>
|
||||
{data?.judul || 'Judul belum tersedia'}
|
||||
</Text>
|
||||
|
||||
<Box w="100%" mt="sm">
|
||||
{data?.deskripsi ? (
|
||||
<Text
|
||||
fz="md"
|
||||
lh={1.7}
|
||||
ta="justify"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
|
||||
/>
|
||||
) : (
|
||||
<Text c="dimmed" fz="sm" ta="center">
|
||||
Tidak ada deskripsi yang tersedia untuk program ini.
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -8,11 +8,13 @@ import { IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconHo
|
||||
import React, { 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(programPenghijauanState);
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
||||
const router = useTransitionRouter()
|
||||
const { data, load, page, totalPages, loading } = state.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
@@ -96,6 +98,7 @@ function Page() {
|
||||
el.style.transform = 'translateY(0)';
|
||||
el.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
||||
}}
|
||||
onClick={() => router.push(`/darmasaba/lingkungan/program-penghijauan/${v.id}`)}
|
||||
>
|
||||
<Stack align="center" gap="sm">
|
||||
<Center>
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client';
|
||||
|
||||
import daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Divider,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
export default function DetailInformasiPublikUser() {
|
||||
const state = useProxy(daftarInformasiPublik);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (params?.id) state.findUnique.load(params.id as string);
|
||||
}, [params?.id]);
|
||||
|
||||
const data = state.findUnique.data;
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
<Text fz="lg" fw="bold">
|
||||
Informasi tidak ditemukan
|
||||
</Text>
|
||||
<Button variant="light" onClick={() => router.push('/informasi-publik')}>
|
||||
Kembali ke Daftar
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py="lg" px={{ base: 'md', md: 100 }} bg={colors.Bg}>
|
||||
{/* Tombol Kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={18} color={colors['blue-button']} />}
|
||||
mb="md"
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
radius="lg"
|
||||
p={{ base: 'md', md: 'xl' }}
|
||||
mx="auto"
|
||||
maw={800}
|
||||
bg="white"
|
||||
shadow="xs"
|
||||
>
|
||||
<Stack gap="xl">
|
||||
<Text
|
||||
fz={{ base: 'xl', md: '2xl' }}
|
||||
fw="bold"
|
||||
ta="center"
|
||||
c={colors['blue-button']}
|
||||
>
|
||||
Detail Informasi Publik
|
||||
</Text>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
|
||||
Jenis Informasi
|
||||
</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
{data.jenisInformasi || '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
|
||||
Tanggal Publikasi
|
||||
</Text>
|
||||
<Text fz={{ base: 'sm', md: 'md' }} c="dimmed">
|
||||
{data.tanggal
|
||||
? new Date(data.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '-'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={{ base: 'md', md: 'lg' }} fw="bold" mb={4}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<Box
|
||||
className="prose max-w-none leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -3,10 +3,13 @@
|
||||
import daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
@@ -17,21 +20,20 @@ import {
|
||||
TableTr,
|
||||
Text,
|
||||
TextInput,
|
||||
Paper,
|
||||
Badge,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconSearch, IconFileInfo, IconMail, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconBrandWhatsapp, IconDeviceImacCog, IconFileInfo, IconMail, IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
|
||||
function Page() {
|
||||
const listData = useProxy(daftarInformasiPublik)
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||
|
||||
const router = useTransitionRouter()
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
@@ -63,7 +65,7 @@ function Page() {
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Center>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 70, md: 100 }} alt="Logo Desa Darmasaba" loading="lazy"/>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 70, md: 100 }} alt="Logo Desa Darmasaba" loading="lazy" />
|
||||
</Center>
|
||||
<Text ta="center" fz={{ base: "1.8rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold" lh={1.4}>
|
||||
Daftar Informasi Publik Desa Darmasaba
|
||||
@@ -99,38 +101,63 @@ function Page() {
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
<Table withRowBorders withColumnBorders withTableBorder highlightOnHover verticalSpacing="md">
|
||||
<TableThead bg={colors['blue-button']}>
|
||||
<TableTr c={colors['white-1']}>
|
||||
<TableTh fz="sm" ta="center" w="5%">No</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="25%">Jenis Informasi</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="40%">Deskripsi</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="20%">Tanggal Publikasi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody bg={colors['white-1']}>
|
||||
{data.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center">{(page - 1) * 5 + index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Badge variant="light" size="lg" color="blue">
|
||||
{item.jenisInformasi}
|
||||
</Badge>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" c="dark" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd ta="center">
|
||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}) : '-'}
|
||||
</TableTd>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table withRowBorders withColumnBorders withTableBorder highlightOnHover verticalSpacing="md">
|
||||
<TableThead bg={colors['blue-button']}>
|
||||
<TableTr c={colors['white-1']}>
|
||||
<TableTh fz="sm" ta="center" w="5%">No</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="25%">Jenis Informasi</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="40%">Deskripsi</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="20%">Tanggal Publikasi</TableTh>
|
||||
<TableTh fz="sm" ta="center" w="15%">Aksi</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody bg={colors['white-1']}>
|
||||
{data.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd ta="center">{(page - 1) * 5 + index + 1}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Badge variant="light" size="lg" color="blue">
|
||||
{item.jenisInformasi}
|
||||
</Badge>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={150}>
|
||||
<Text lineClamp={1} fz="sm" c="dark" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd ta="center">
|
||||
<Box w={150}>
|
||||
{item.tanggal ? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}) : '-'}
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ textAlign: 'center' }}>
|
||||
<Box w={150}>
|
||||
<Tooltip label="Lihat Detail" withArrow>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/darmasaba/ppid/daftar-informasi-publik-desa-darmasaba/${item.id}`)}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Center>
|
||||
|
||||
@@ -4,7 +4,7 @@ import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/ind
|
||||
import colors from "@/con/colors";
|
||||
import { BarChart, PieChart } from '@mantine/charts';
|
||||
import { Box, Button, Center, Container, Flex, Group, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||
import { useDisclosure, useMediaQuery, useShallowEffect } from "@mantine/hooks";
|
||||
import { useState } from "react";
|
||||
import { useProxy } from "valtio/utils";
|
||||
|
||||
@@ -24,7 +24,8 @@ function Kepuasan() {
|
||||
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
||||
const [opened, { open, close }] = useDisclosure(false)
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
const resetForm = () => {
|
||||
state.create.form = {
|
||||
@@ -140,12 +141,12 @@ function Kepuasan() {
|
||||
|
||||
if ((loading && !data) || !data) {
|
||||
return (
|
||||
<Stack py={10} px="xl">
|
||||
<Skeleton height={300} mb="md" />
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={300} />
|
||||
<Stack py={10} px="sm">
|
||||
<Skeleton height={200} mb="md" />
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="md">
|
||||
<Skeleton height={200} />
|
||||
<Skeleton height={200} />
|
||||
<Skeleton height={200} />
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
@@ -412,50 +413,41 @@ function Kepuasan() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Group justify={"center"}>
|
||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||
<Stack p="sm">
|
||||
<Container w={{ base: "100%", md: "80%" }} p={isMobile ? "md" : "xl"}>
|
||||
<Stack gap="xs">
|
||||
<Text ta="center" fz={{ base: "2rem", md: "3rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
<Group justify="center">
|
||||
<Button radius="lg" bg={colors["blue-button"]} onClick={open}>
|
||||
Ajukan Responden
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Container>
|
||||
<Box px={"xl"}>
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Flex justify={"space-between"} align={"center"}>
|
||||
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||
<Box px={isMobile ? "sm" : "xl"}>
|
||||
<Paper p="lg" bg={colors.Bg}>
|
||||
<Paper p={isMobile ? "sm" : "lg"}>
|
||||
<Stack gap="xs">
|
||||
<Flex direction={isMobile ? "column" : "row"} justify="space-between" align={isMobile ? "start" : "center"}>
|
||||
<Text fw="bold" mb={isMobile ? "sm" : 0}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString('id-ID')}
|
||||
<Text fz="sm" fw="bold" c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta="end" fz="h1" fw="bold" c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString("id-ID")}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<BarChart
|
||||
h={300}
|
||||
h={isMobile ? 200 : 300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
tickLine="y"
|
||||
xAxisLabel="Bulan"
|
||||
yAxisLabel="Jumlah Responden"
|
||||
series={[{ name: "count", color: colors["blue-button"] }]}
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Box py={"xl"}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 1,
|
||||
lg: 1,
|
||||
xl: 3
|
||||
}}
|
||||
>
|
||||
<Box py="xl">
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, xl: 3 }} spacing="lg">
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
<Stack>
|
||||
@@ -465,28 +457,17 @@ function Kepuasan() {
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={200}
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Stack gap="sm" mt="md">
|
||||
{donutDataJenisKelamin.map((entry) => (
|
||||
<Flex key={entry.name} gap="md" align="center">
|
||||
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Paper p="md" radius="md">
|
||||
<Stack>
|
||||
<Center>
|
||||
<PieChart
|
||||
size={isMobile ? 150 : 200}
|
||||
withLabels
|
||||
data={donutDataJenisKelamin}
|
||||
withTooltip
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -501,35 +482,18 @@ function Kepuasan() {
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={200}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataRating.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
<Paper p="md" radius="md">
|
||||
<Stack>
|
||||
<Center>
|
||||
<PieChart
|
||||
size={isMobile ? 150 : 200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
withLabelsLine
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -544,35 +508,18 @@ function Kepuasan() {
|
||||
Belum ada data untuk ditampilkan dalam grafik
|
||||
</Text>
|
||||
) : (
|
||||
<Paper p="md" radius="md" withBorder>
|
||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Box style={{ position: 'relative', width: '100%' }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
withTooltip
|
||||
tooltipAnimationDuration={200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={190}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
<Box mt="md" style={{ width: '100%' }}>
|
||||
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||
{donutDataKelompokUmur.map((entry) => (
|
||||
<Flex key={entry.name} gap="sm" align="center" style={{ overflow: 'hidden' }}>
|
||||
<Box bg={entry.color} w={16} h={16} style={{ flexShrink: 0 }} />
|
||||
<Text size="xs" lineClamp={1} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{entry.name}: {entry.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
<Paper p="md" radius="md">
|
||||
<Stack>
|
||||
<Center>
|
||||
<PieChart
|
||||
size={isMobile ? 150 : 200}
|
||||
withLabels
|
||||
labelsPosition="outside"
|
||||
withLabelsLine
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -32,9 +32,9 @@ function DesaAntiKorupsi() {
|
||||
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||
<Center>
|
||||
<Text fz={{ base: "2.4rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
||||
</Center>
|
||||
<Text ta={"center"} fz={{ base: "1.2rem", md: "1.4rem" }}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
|
||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
|
||||
<Center py={20}>
|
||||
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desa-anti-korupsi/detail"}>Selengkapnya</Button>
|
||||
</Center>
|
||||
@@ -49,6 +49,7 @@ function DesaAntiKorupsi() {
|
||||
cols={{ base: 1, sm: 2, md: 3 }}
|
||||
spacing="lg"
|
||||
mt="lg"
|
||||
mb="xl"
|
||||
>
|
||||
{data.map((v, k) => (
|
||||
<Paper
|
||||
|
||||
@@ -160,11 +160,11 @@ function Kepuasan() {
|
||||
</Center>
|
||||
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||
<Center mt={10}>
|
||||
<Button
|
||||
radius={"lg"}
|
||||
onClick={open}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
<Button
|
||||
radius={"lg"}
|
||||
onClick={open}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#26667F", to: "#124170" }}
|
||||
>Ajukan Responden</Button>
|
||||
</Center>
|
||||
</Container>
|
||||
@@ -182,7 +182,7 @@ function Kepuasan() {
|
||||
</Box>
|
||||
</Flex>
|
||||
<BarChart
|
||||
h={300}
|
||||
h={window.innerWidth < 480 ? 200 : 300}
|
||||
data={barChartData}
|
||||
dataKey="month"
|
||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||
@@ -196,12 +196,9 @@ function Kepuasan() {
|
||||
</Paper>
|
||||
<Box py={"xl"}>
|
||||
<SimpleGrid
|
||||
cols={{
|
||||
base: 1,
|
||||
md: 1,
|
||||
lg: 1,
|
||||
xl: 3
|
||||
}}
|
||||
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||
spacing="md"
|
||||
verticalSpacing="md"
|
||||
>
|
||||
{/* Chart Jenis Kelamin */}
|
||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||
@@ -220,7 +217,7 @@ function Kepuasan() {
|
||||
withLabels
|
||||
withTooltip
|
||||
labelsType="percent"
|
||||
size={200}
|
||||
size={250} // Fixed size in pixels
|
||||
data={donutDataJenisKelamin}
|
||||
/>
|
||||
</Center>
|
||||
@@ -259,7 +256,7 @@ function Kepuasan() {
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={200}
|
||||
size={250}
|
||||
data={donutDataRating}
|
||||
/>
|
||||
</Center>
|
||||
@@ -302,7 +299,7 @@ function Kepuasan() {
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
withLabelsLine
|
||||
size={190}
|
||||
size={250}
|
||||
data={donutDataKelompokUmur}
|
||||
/>
|
||||
</Center>
|
||||
@@ -419,7 +416,7 @@ function Kepuasan() {
|
||||
}
|
||||
return (
|
||||
<Stack p={"sm"}>
|
||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
||||
<Container size="lg" px="md">
|
||||
<Center>
|
||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||
</Center>
|
||||
@@ -432,9 +429,15 @@ function Kepuasan() {
|
||||
<Paper p={"lg"} bg={colors.Bg}>
|
||||
<Paper p={"lg"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Flex justify={"space-between"} align={"center"}>
|
||||
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||
<Box>
|
||||
<Flex
|
||||
direction={{ base: "column", sm: "row" }}
|
||||
justify="space-between"
|
||||
align={{ base: "flex-start", sm: "center" }}
|
||||
>
|
||||
<Text fw="bold" ta={{ base: "center", sm: "left" }}>
|
||||
Pelayanan Terhadap Publik Desa Darmasaba
|
||||
</Text>
|
||||
<Box mt={{ base: "sm", sm: 0 }}>
|
||||
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
||||
{state.findMany.total.toLocaleString('id-ID')}
|
||||
|
||||
@@ -100,11 +100,11 @@ function Potensi() {
|
||||
style={{ zIndex: 1 }}
|
||||
>
|
||||
<Tooltip label={v.name} position="top-start">
|
||||
<Text fw={700} c="white" size="2.2rem" truncate>
|
||||
<Text fw={700} c="white" fz={{ base: "1.2rem", md: "1.4rem" }} truncate>
|
||||
{v.name}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Text lineClamp={2} c="gray.2" size="sm">
|
||||
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }}>
|
||||
{v.deskripsi}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user