Fix Ui Admin & User to Mobile && QC Menu Landing Page, PPID, Desa
This commit is contained in:
@@ -672,17 +672,18 @@ model GalleryVideo {
|
|||||||
|
|
||||||
// ========================================= LAYANAN DESA ========================================= //
|
// ========================================= LAYANAN DESA ========================================= //
|
||||||
model PelayananSuratKeterangan {
|
model PelayananSuratKeterangan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
||||||
image2Id String?
|
image2Id String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
|
AjukanPermohonan AjukanPermohonan[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model PelayananTelunjukSaktiDesa {
|
model PelayananTelunjukSaktiDesa {
|
||||||
@@ -717,6 +718,20 @@ model PelayananPendudukNonPermanen {
|
|||||||
isActive Boolean @default(true)
|
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 ========================================= //
|
// ========================================= PENGHARGAAN ========================================= //
|
||||||
model Penghargaan {
|
model Penghargaan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
@@ -1254,15 +1269,15 @@ model KontakDaruratToItem {
|
|||||||
|
|
||||||
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
||||||
model PencegahanKriminalitas {
|
model PencegahanKriminalitas {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
judul String
|
judul String
|
||||||
deskripsi String
|
deskripsi String
|
||||||
deskripsiSingkat String
|
deskripsiSingkat String
|
||||||
linkVideo String
|
linkVideo String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= LAPORAN PUBLIK ========================================= //
|
// ========================================= 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: "",
|
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({
|
const suratKeterangan = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: { ...suratKeteranganForm },
|
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: {
|
findUnique: {
|
||||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||||
include: {
|
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({
|
const stateLayananDesa = proxy({
|
||||||
suratKeterangan,
|
suratKeterangan,
|
||||||
pelayananPerizinanBerusaha,
|
pelayananPerizinanBerusaha,
|
||||||
pelayananTelunjukSaktiDesa,
|
pelayananTelunjukSaktiDesa,
|
||||||
pelayananPendudukNonPermanen,
|
pelayananPendudukNonPermanen,
|
||||||
|
ajukanPermohonan,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stateLayananDesa;
|
export default stateLayananDesa;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import colors from '@/con/colors';
|
|||||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
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 }) {
|
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -37,6 +37,13 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
|||||||
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent",
|
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent",
|
||||||
icon: <IconUsers size={18} stroke={1.8} />,
|
icon: <IconUsers size={18} stroke={1.8} />,
|
||||||
tooltip: "Pendataan penduduk non-permanent"
|
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,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -18,7 +17,7 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
@@ -87,7 +86,6 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Kategori</TableTh>
|
<TableTh style={{ width: '20%' }}>Kategori</TableTh>
|
||||||
<TableTh style={{ width: '25%' }}>Gambar</TableTh>
|
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -96,7 +94,7 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '30%' }}>
|
<TableTd style={{ width: '30%' }}>
|
||||||
<Box w={200}>
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.judul}
|
{item.judul}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -107,19 +105,6 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
{item.kategoriBerita?.name || '-'}
|
{item.kategoriBerita?.name || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</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%' }}>
|
<TableTd style={{ width: '15%' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
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) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.judul}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.judul}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text fz="sm" c="dimmed">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -60,7 +60,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
|
|||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Box style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 }}>
|
<Group justify="space-between">
|
||||||
<Title order={4}>List Kategori Potensi</Title>
|
<Title order={4}>List Kategori Potensi</Title>
|
||||||
<Tooltip label="Tambah Kategori Potensi" withArrow>
|
<Tooltip label="Tambah Kategori Potensi" withArrow>
|
||||||
<Button
|
<Button
|
||||||
@@ -72,7 +72,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
|
|||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
|
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ function ListAPBDes({ search }: { search: string }) {
|
|||||||
<Text fw={500} truncate="end">{item.name}</Text>
|
<Text fw={500} truncate="end">{item.name}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text>Rp. {item.jumlah}</Text>
|
<Box w={150}>
|
||||||
|
<Text>Rp. {item.jumlah}</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
{item.file?.link ? (
|
{item.file?.link ? (
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500}>{item.name}</Text>
|
<Box w={200}>
|
||||||
|
<Text fw={500} lineClamp={1}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Tooltip label="Edit" withArrow>
|
<Tooltip label="Edit" withArrow>
|
||||||
|
|||||||
@@ -98,9 +98,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
|||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '30%' }}>
|
<TableTd style={{ width: '30%' }}>
|
||||||
<Text fz="sm" c="dimmed">
|
<Box w={200}>
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={1}>
|
||||||
{item.kategori?.name || '-'}
|
{item.kategori?.name || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ function Page() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={220}
|
size={180}
|
||||||
data={donutDataJenisKelamin}
|
data={donutDataJenisKelamin}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -185,7 +185,7 @@ function Page() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={220}
|
size={180}
|
||||||
data={donutDataRating}
|
data={donutDataRating}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -216,7 +216,7 @@ function Page() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={220}
|
size={180}
|
||||||
data={donutDataKelompokUmur}
|
data={donutDataKelompokUmur}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|||||||
@@ -88,66 +88,74 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
<Title order={4} mb="sm">
|
<Title order={4} mb="sm">
|
||||||
Daftar Responden
|
Daftar Responden
|
||||||
</Title>
|
</Title>
|
||||||
<Table
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
striped
|
<Table
|
||||||
highlightOnHover
|
striped
|
||||||
withRowBorders
|
highlightOnHover
|
||||||
verticalSpacing="sm"
|
withRowBorders
|
||||||
>
|
verticalSpacing="sm"
|
||||||
<TableThead>
|
>
|
||||||
<TableTr>
|
<TableThead>
|
||||||
<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 ? (
|
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={5}>
|
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||||
<Text ta="center" c="dimmed">
|
<TableTh style={{ width: '25%', textAlign: 'center' }}>Nama</TableTh>
|
||||||
Tidak ditemukan data dengan kata kunci pencarian
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
||||||
</Text>
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
||||||
</TableTd>
|
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
) : (
|
</TableThead>
|
||||||
filteredData.map((item, index) => (
|
<TableTbody>
|
||||||
<TableTr key={item.id}>
|
{filteredData.length === 0 ? (
|
||||||
<TableTd ta="center">{index + 1}</TableTd>
|
<TableTr>
|
||||||
<TableTd ta="center">{item.name}</TableTd>
|
<TableTd colSpan={5}>
|
||||||
<TableTd ta="center">
|
<Text ta="center" c="dimmed">
|
||||||
{item.tanggal
|
Tidak ditemukan data dengan kata kunci pencarian
|
||||||
? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
</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',
|
day: '2-digit',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})
|
})
|
||||||
: '-'}
|
: '-'}
|
||||||
</TableTd>
|
</Box>
|
||||||
<TableTd ta="center">{item.jenisKelamin.name}</TableTd>
|
</TableTd>
|
||||||
<TableTd ta="center">
|
<TableTd ta="center">
|
||||||
<Button
|
<Box w={100}>
|
||||||
size="xs"
|
{item.jenisKelamin.name}
|
||||||
radius="md"
|
</Box>
|
||||||
variant="light"
|
</TableTd>
|
||||||
color="blue"
|
<TableTd ta="center">
|
||||||
leftSection={<IconDeviceImac size={16} />}
|
<Button
|
||||||
onClick={() =>
|
size="xs"
|
||||||
router.push(
|
radius="md"
|
||||||
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
|
variant="light"
|
||||||
)
|
color="blue"
|
||||||
}
|
leftSection={<IconDeviceImac size={16} />}
|
||||||
>
|
onClick={() =>
|
||||||
Detail
|
router.push(
|
||||||
</Button>
|
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
|
||||||
</TableTd>
|
)
|
||||||
</TableTr>
|
}
|
||||||
))
|
>
|
||||||
)}
|
Detail
|
||||||
</TableTbody>
|
</Button>
|
||||||
</Table>
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|||||||
@@ -96,7 +96,11 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
|||||||
) : (
|
) : (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<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' }}>
|
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||||
<Tooltip label="Edit" withArrow position="top">
|
<Tooltip label="Edit" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -91,10 +91,11 @@ function DetailPrestasiDesa() {
|
|||||||
<Image
|
<Image
|
||||||
src={detailState.findUnique.data.image.link}
|
src={detailState.findUnique.data.image.link}
|
||||||
alt={detailState.findUnique.data.name || 'Gambar Prestasi'}
|
alt={detailState.findUnique.data.name || 'Gambar Prestasi'}
|
||||||
w={300}
|
w="100%"
|
||||||
|
maw={300} // max width 300px
|
||||||
fit="contain"
|
fit="contain"
|
||||||
style={{ borderRadius: '8px', border: '1px solid #e0e0e0' }}
|
style={{ borderRadius: '8px', border: '1px solid #e0e0e0' }}
|
||||||
loading='lazy'
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
<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 }} />
|
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<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>
|
||||||
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
||||||
<Tooltip label="Kelola Prestasi" withArrow>
|
<Tooltip label="Kelola Prestasi" withArrow>
|
||||||
|
|||||||
@@ -51,8 +51,10 @@ function DetailMediaSosial() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w="100%"
|
||||||
|
maw={500} // <= tambahkan ini, biar tidak lebih dari 500px
|
||||||
|
mx="auto" // center di layar
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -70,9 +72,9 @@ function DetailMediaSosial() {
|
|||||||
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box >
|
||||||
<Text fz="lg" fw="bold">Link / Nomor Telepon</Text>
|
<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>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
@@ -81,12 +83,14 @@ function DetailMediaSosial() {
|
|||||||
<Image
|
<Image
|
||||||
src={data.image.link}
|
src={data.image.link}
|
||||||
alt={data.name || 'Gambar Media Sosial'}
|
alt={data.name || 'Gambar Media Sosial'}
|
||||||
w={120}
|
w="100%"
|
||||||
h={120}
|
maw={120} // max width biar tidak keluar layar
|
||||||
|
h="auto"
|
||||||
radius="md"
|
radius="md"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ function EditPejabatDesa() {
|
|||||||
alt="Preview"
|
alt="Preview"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '200px',
|
maxHeight: '150px',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function Page() {
|
|||||||
<Image
|
<Image
|
||||||
pt={{ base: 0, md: 60 }}
|
pt={{ base: 0, md: 60 }}
|
||||||
src={item.image?.link || "/perbekel.png"}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
w={{ base: 250, md: 350 }}
|
w={{ base: 150, md: 350 }}
|
||||||
alt="Foto Profil Pejabat"
|
alt="Foto Profil Pejabat"
|
||||||
radius="md"
|
radius="md"
|
||||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||||
@@ -87,7 +87,7 @@ function Page() {
|
|||||||
className="glass3"
|
className="glass3"
|
||||||
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
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}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -49,9 +49,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
|||||||
return (
|
return (
|
||||||
<Box py={15}>
|
<Box py={15}>
|
||||||
<Paper bg={colors['white-1']} withBorder p="lg" radius="md" shadow="sm">
|
<Paper bg={colors['white-1']} withBorder p="lg" radius="md" shadow="sm">
|
||||||
<Box mb="md" display="flex"
|
<Group justify='space-between'>
|
||||||
style={{ justifyContent: 'space-between', alignItems: 'center' }}
|
|
||||||
>
|
|
||||||
<Title order={4}>Daftar Program Inovasi</Title>
|
<Title order={4}>Daftar Program Inovasi</Title>
|
||||||
<Tooltip label="Tambah Program Inovasi" withArrow>
|
<Tooltip label="Tambah Program Inovasi" withArrow>
|
||||||
<Button
|
<Button
|
||||||
@@ -64,7 +62,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
|||||||
Tambah Program
|
Tambah Program
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover striped verticalSpacing="sm">
|
<Table highlightOnHover striped verticalSpacing="sm">
|
||||||
<TableThead>
|
<TableThead>
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ function Page() {
|
|||||||
<Image
|
<Image
|
||||||
pt={{ base: 0, md: 60 }}
|
pt={{ base: 0, md: 60 }}
|
||||||
src={item.image?.link || "/perbekel.png"}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
w={{ base: 250, md: 350 }}
|
w="100%"
|
||||||
|
maw={300}
|
||||||
alt="Foto Profil PPID"
|
alt="Foto Profil PPID"
|
||||||
radius="md"
|
radius="md"
|
||||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
).map((item) => (
|
).map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.namaLengkap}
|
{item.namaLengkap}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Box w={150}>
|
||||||
<Badge variant="light" color="blue">
|
<Badge variant="light" color="blue">
|
||||||
{item.posisi?.nama || 'Belum diatur'}
|
{item.posisi?.nama || 'Belum diatur'}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -82,51 +82,54 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Nama Posisi</TableTh>
|
<TableTh style={{ width: '20%' }}>Nama Posisi</TableTh>
|
||||||
<TableTh style={{ width: '45%' }}>Deskripsi</TableTh>
|
<TableTh style={{ width: '20%' }}>Deskripsi</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Hierarki</TableTh>
|
<TableTh style={{ width: '20%' }}>Hierarki</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '45%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text lineClamp={2} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
<Box w={200}>
|
||||||
|
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text>{item.hierarki || '-'}</Text>
|
<Text>{item.hierarki || '-'}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Group gap="xs">
|
<Tooltip label="Edit" withArrow>
|
||||||
<Tooltip label="Edit" withArrow>
|
<Button
|
||||||
<Button
|
variant="light"
|
||||||
variant="light"
|
color="green"
|
||||||
color="green"
|
size="sm"
|
||||||
size="sm"
|
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
>
|
||||||
>
|
<IconEdit size={18} />
|
||||||
<IconEdit size={18} />
|
</Button>
|
||||||
</Button>
|
</Tooltip>
|
||||||
</Tooltip>
|
</TableTd>
|
||||||
<Tooltip label="Hapus" withArrow>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Button
|
<Tooltip label="Hapus" withArrow>
|
||||||
variant="light"
|
<Button
|
||||||
color="red"
|
variant="light"
|
||||||
size="sm"
|
color="red"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
setSelectedId(item.id);
|
onClick={() => {
|
||||||
setModalHapus(true);
|
setSelectedId(item.id);
|
||||||
}}
|
setModalHapus(true);
|
||||||
>
|
}}
|
||||||
<IconTrash size={18} />
|
>
|
||||||
</Button>
|
<IconTrash size={18} />
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Group>
|
</Tooltip>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import KategoriPotensi from "./potensi/kategori-potensi";
|
|||||||
import KategoriBerita from "./berita/kategori-berita";
|
import KategoriBerita from "./berita/kategori-berita";
|
||||||
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
||||||
import MantanPerbekel from "./profile/profile-mantan-perbekel";
|
import MantanPerbekel from "./profile/profile-mantan-perbekel";
|
||||||
|
import AjukanPermohonan from "./layanan/ajukan_permohonan";
|
||||||
|
|
||||||
|
|
||||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
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(KategoriPotensi)
|
||||||
.use(KategoriBerita)
|
.use(KategoriBerita)
|
||||||
.use(KategoriPengumuman)
|
.use(KategoriPengumuman)
|
||||||
|
.use(AjukanPermohonan)
|
||||||
|
|
||||||
|
|
||||||
export default Desa;
|
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 pelayananSuratKeteranganCreate from "./create";
|
||||||
import pelayananSuratKeteranganUpdate from "./updt";
|
import pelayananSuratKeteranganUpdate from "./updt";
|
||||||
import pelayananSuratKeteranganDelete from "./del";
|
import pelayananSuratKeteranganDelete from "./del";
|
||||||
|
import pelayananSuratKeteranganFindManyAll from "./findManyAll";
|
||||||
import { t } from "elysia";
|
import { t } from "elysia";
|
||||||
|
|
||||||
const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan", tags: ["Desa/Layanan/Pelayanan Surat Keterangan"] })
|
const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan", tags: ["Desa/Layanan/Pelayanan Surat Keterangan"] })
|
||||||
.get("/find-many", pelayananSuratKeteranganFindMany)
|
.get("/find-many", pelayananSuratKeteranganFindMany)
|
||||||
|
.get("/findManyAll", pelayananSuratKeteranganFindManyAll)
|
||||||
.get("/:id", async (context) => {
|
.get("/:id", async (context) => {
|
||||||
const response = await pelayananSuratKeteranganFindUnique(new Request(context.request));
|
const response = await pelayananSuratKeteranganFindUnique(new Request(context.request));
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { IconSearch } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -23,95 +23,52 @@ function LayoutTabsBerita({
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Get active tab from URL path
|
|
||||||
const activeTab = pathname.split('/').pop() || 'semua';
|
const activeTab = pathname.split('/').pop() || 'semua';
|
||||||
|
|
||||||
// Get initial search value from URL
|
|
||||||
const initialSearch = searchParams.get('search') || '';
|
const initialSearch = searchParams.get('search') || '';
|
||||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||||
|
|
||||||
// Update active tab state when pathname changes
|
|
||||||
const [activeTabState, setActiveTabState] = useState(activeTab);
|
const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveTabState(activeTab);
|
setActiveTabState(activeTab);
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
// Clean up timeouts on unmount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (searchTimeout !== null) {
|
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [searchTimeout]);
|
}, [searchTimeout]);
|
||||||
|
|
||||||
// Handle search input change with debounce
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
setSearchValue(value);
|
setSearchValue(value);
|
||||||
|
|
||||||
// Clear previous timeout
|
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||||
if (searchTimeout !== null) {
|
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set new timeout
|
|
||||||
const newTimeout = window.setTimeout(() => {
|
const newTimeout = window.setTimeout(() => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
if (value) {
|
if (value) params.set('search', value);
|
||||||
params.set('search', value);
|
else params.delete('search');
|
||||||
} else {
|
|
||||||
params.delete('search');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only update URL if the search value has actually changed
|
|
||||||
if (params.toString() !== searchParams.toString()) {
|
if (params.toString() !== searchParams.toString()) {
|
||||||
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||||
}
|
}
|
||||||
}, 500); // 500ms debounce delay
|
}, 500);
|
||||||
|
|
||||||
setSearchTimeout(newTimeout);
|
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) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
const tab = tabs.find(t => t.value === value);
|
const tab = tabs.find(t => t.value === value);
|
||||||
@@ -127,16 +84,29 @@ function LayoutTabsBerita({
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md">
|
|
||||||
<Stack align="center" gap="0" >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Group justify='space-between' align="center">
|
||||||
Portal Berita Darmasaba
|
<Stack gap="0">
|
||||||
</Text>
|
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" >
|
||||||
<Text ta="center" px="md">
|
Portal Berita Darmasaba
|
||||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
</Text>
|
||||||
</Text>
|
<Text>
|
||||||
</Stack>
|
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||||
</Container>
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Box>
|
||||||
|
<TextInput
|
||||||
|
radius="lg"
|
||||||
|
placeholder={placeholder}
|
||||||
|
leftSection={searchIcon}
|
||||||
|
w="100%"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors['blue-button']}
|
||||||
@@ -145,33 +115,25 @@ function LayoutTabsBerita({
|
|||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||||
<Grid>
|
{/* SCROLLABLE TABS */}
|
||||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
<Box style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<TabsList style={{ display: 'flex', flexWrap: 'nowrap', gap: '0.5rem' }}>
|
||||||
<TabsList>
|
{tabs.map((tab, index) => (
|
||||||
{tabs.map((tab, index) => (
|
<TabsTab
|
||||||
<TabsTab
|
key={index}
|
||||||
key={index}
|
value={tab.value}
|
||||||
value={tab.value}
|
onClick={() => router.push(tab.href)}
|
||||||
onClick={() => router.push(tab.href)}
|
style={{
|
||||||
>
|
flex: '0 0 auto', // Prevent shrinking
|
||||||
{tab.label}
|
minWidth: 100, // optional: makes them touch-friendly
|
||||||
</TabsTab>
|
textAlign: 'center'
|
||||||
))}
|
}}
|
||||||
</TabsList>
|
>
|
||||||
</ScrollArea>
|
{tab.label}
|
||||||
</GridCol>
|
</TabsTab>
|
||||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
))}
|
||||||
<TextInput
|
</TabsList>
|
||||||
radius="lg"
|
</Box>
|
||||||
placeholder={placeholder}
|
|
||||||
leftSection={searchIcon}
|
|
||||||
w="100%"
|
|
||||||
value={searchValue}
|
|
||||||
onChange={handleSearchChange}
|
|
||||||
/>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
'use client';
|
'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 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
|
// Define tabs outside the component to ensure consistency between server and client
|
||||||
const TABS = [
|
const TABS = [
|
||||||
@@ -75,13 +75,16 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md">
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack align="center" gap="0">
|
<Stack align="center" gap="0">
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||||
Galeri Kegiatan Desa Darmasaba
|
Galeri Kegiatan Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
<Box>
|
||||||
|
<SearchBar />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={isClient ? activeTab : undefined}
|
value={isClient ? activeTab : undefined}
|
||||||
@@ -92,25 +95,18 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
|||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||||
<Grid>
|
<TabsList>
|
||||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
{TABS.map((tab) => (
|
||||||
<TabsList>
|
<TabsTab
|
||||||
{TABS.map((tab) => (
|
key={tab.value}
|
||||||
<TabsTab
|
value={tab.value}
|
||||||
key={tab.value}
|
component="button"
|
||||||
value={tab.value}
|
type="button"
|
||||||
component="button"
|
>
|
||||||
type="button"
|
{tab.label}
|
||||||
>
|
</TabsTab>
|
||||||
{tab.label}
|
))}
|
||||||
</TabsTab>
|
</TabsList>
|
||||||
))}
|
|
||||||
</TabsList>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
|
||||||
<SearchBar />
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Container size={'xl'}>
|
<Container size={'xl'}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { TextInput } from '@mantine/core';
|
import { Group, TextInput } from '@mantine/core';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
import { usePathname, useSearchParams } from 'next/navigation';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
@@ -65,13 +65,15 @@ export function SearchBar({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<Group justify='center'>
|
||||||
|
<TextInput
|
||||||
radius="lg"
|
radius="lg"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
leftSection={searchIcon}
|
leftSection={searchIcon}
|
||||||
w="100%"
|
w={{ base: '100%', md: '50%' }}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
/>
|
/>
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,24 @@
|
|||||||
|
|
||||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
import colors from '@/con/colors';
|
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 { useCallback, useEffect, useState } from 'react';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
export default function VideoContent() {
|
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 videoState = useSnapshot(stateGallery.video);
|
||||||
const {
|
const { data, page, totalPages, loading } = videoState.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
} = videoState.findMany;
|
|
||||||
|
|
||||||
// Handle search and pagination changes
|
// Handle search and pagination changes
|
||||||
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
||||||
@@ -27,24 +32,18 @@ export default function VideoContent() {
|
|||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const urlSearch = urlParams.get('search') || '';
|
const urlSearch = urlParams.get('search') || '';
|
||||||
const urlPage = parseInt(urlParams.get('page') || '1');
|
const urlPage = parseInt(urlParams.get('page') || '1');
|
||||||
|
|
||||||
loadData(urlPage, urlSearch);
|
loadData(urlPage, urlSearch);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle search updates from the search bar
|
|
||||||
const handleSearchUpdate = (e: Event) => {
|
const handleSearchUpdate = (e: Event) => {
|
||||||
const { search } = (e as CustomEvent).detail;
|
const { search } = (e as CustomEvent).detail;
|
||||||
loadData(1, search);
|
loadData(1, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial load
|
|
||||||
handleRouteChange();
|
handleRouteChange();
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
window.addEventListener('popstate', handleRouteChange);
|
window.addEventListener('popstate', handleRouteChange);
|
||||||
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('popstate', handleRouteChange);
|
window.removeEventListener('popstate', handleRouteChange);
|
||||||
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
||||||
@@ -57,6 +56,13 @@ export default function VideoContent() {
|
|||||||
loadData(newPage, search);
|
loadData(newPage, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleExpanded = (index: number, value: boolean) => {
|
||||||
|
setExpandedMap((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[index]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const dataVideo = data || [];
|
const dataVideo = data || [];
|
||||||
|
|
||||||
if (loading && !data) {
|
if (loading && !data) {
|
||||||
@@ -72,7 +78,13 @@ export default function VideoContent() {
|
|||||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||||
{dataVideo.map((v, k) => (
|
{dataVideo.map((v, k) => (
|
||||||
<Box key={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>
|
<Box>
|
||||||
<Center>
|
<Center>
|
||||||
<Box
|
<Box
|
||||||
@@ -109,8 +121,8 @@ export default function VideoContent() {
|
|||||||
Hide details
|
Hide details
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
expanded={expanded}
|
expanded={expandedMap[k] || false}
|
||||||
onExpandedChange={setExpanded}
|
onExpandedChange={(val) => toggleExpanded(k, val)}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
ta="justify"
|
ta="justify"
|
||||||
@@ -137,13 +149,13 @@ export default function VideoContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Fix: HAPUS SPASI BERLEBIH DI URL
|
// ✅ Fix: convert YouTube URL ke embed
|
||||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||||
try {
|
try {
|
||||||
const url = new URL(youtubeUrl);
|
const url = new URL(youtubeUrl);
|
||||||
const videoId = url.searchParams.get('v');
|
const videoId = url.searchParams.get('v');
|
||||||
if (!videoId) return youtubeUrl;
|
if (!videoId) return youtubeUrl;
|
||||||
return `https://www.youtube.com/embed/${videoId}`; // ✅ tanpa spasi!
|
return `https://www.youtube.com/embed/${videoId}`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error converting YouTube URL to embed:', err);
|
console.error('Error converting YouTube URL to embed:', err);
|
||||||
return youtubeUrl;
|
return youtubeUrl;
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
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 { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../_com/BackButto';
|
import BackButton from '../_com/BackButto';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
|
||||||
interface LayananData {
|
interface LayananData {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -31,7 +32,12 @@ function Page() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [data, setData] = useState<LayananData | null>(null);
|
const [data, setData] = useState<LayananData | null>(null);
|
||||||
|
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const stateCreate = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
state.suratKeterangan.findManyAll.load()
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
try {
|
try {
|
||||||
@@ -48,6 +54,22 @@ function Page() {
|
|||||||
loadData();
|
loadData();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stateCreate.create.form = {
|
||||||
|
nama: '',
|
||||||
|
nik: '',
|
||||||
|
alamat: '',
|
||||||
|
nomorKk: '',
|
||||||
|
kategoriId: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await stateCreate.create.create();
|
||||||
|
resetForm();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Center h="100vh" bg={colors.Bg}>
|
<Center h="100vh" bg={colors.Bg}>
|
||||||
@@ -105,12 +127,76 @@ function Page() {
|
|||||||
size="lg"
|
size="lg"
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
||||||
|
onClick={open}
|
||||||
>
|
>
|
||||||
Ajukan Permohonan
|
Ajukan Permohonan
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</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>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function Page() {
|
|||||||
<Container w={{ base: "100%", md: "50%" }} >
|
<Container w={{ base: "100%", md: "50%" }} >
|
||||||
<Stack align="center" gap={0}>
|
<Stack align="center" gap={0}>
|
||||||
{/* Bagian Layanan */}
|
{/* 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
|
Layanan Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -31,7 +31,7 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md">
|
<Container size="lg" px="md">
|
||||||
<Stack gap="xs" >
|
<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" >
|
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
||||||
{detail.data?.judul}
|
{detail.data?.judul}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -40,7 +40,7 @@ function Page() {
|
|||||||
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Group>
|
</Group>
|
||||||
</Flex>
|
</Group>
|
||||||
<Paper bg={colors["white-1"]} p="md">
|
<Paper bg={colors["white-1"]} p="md">
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
||||||
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
|
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
|
||||||
|
|||||||
@@ -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 daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Image,
|
Image,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
@@ -17,21 +20,20 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Paper,
|
Tooltip
|
||||||
Badge,
|
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch, IconFileInfo, IconMail, IconBrandWhatsapp } from '@tabler/icons-react';
|
import { IconBrandWhatsapp, IconDeviceImacCog, IconFileInfo, IconMail, IconSearch } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const listData = useProxy(daftarInformasiPublik)
|
const listData = useProxy(daftarInformasiPublik)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||||
|
const router = useTransitionRouter()
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
@@ -63,7 +65,7 @@ function Page() {
|
|||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
<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>
|
</Center>
|
||||||
<Text ta="center" fz={{ base: "1.8rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold" lh={1.4}>
|
<Text ta="center" fz={{ base: "1.8rem", md: "2.5rem" }} c={colors["blue-button"]} fw="bold" lh={1.4}>
|
||||||
Daftar Informasi Publik Desa Darmasaba
|
Daftar Informasi Publik Desa Darmasaba
|
||||||
@@ -99,38 +101,63 @@ function Page() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<Table withRowBorders withColumnBorders withTableBorder highlightOnHover verticalSpacing="md">
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<TableThead bg={colors['blue-button']}>
|
<Table withRowBorders withColumnBorders withTableBorder highlightOnHover verticalSpacing="md">
|
||||||
<TableTr c={colors['white-1']}>
|
<TableThead bg={colors['blue-button']}>
|
||||||
<TableTh fz="sm" ta="center" w="5%">No</TableTh>
|
<TableTr c={colors['white-1']}>
|
||||||
<TableTh fz="sm" ta="center" w="25%">Jenis Informasi</TableTh>
|
<TableTh fz="sm" ta="center" w="5%">No</TableTh>
|
||||||
<TableTh fz="sm" ta="center" w="40%">Deskripsi</TableTh>
|
<TableTh fz="sm" ta="center" w="25%">Jenis Informasi</TableTh>
|
||||||
<TableTh fz="sm" ta="center" w="20%">Tanggal Publikasi</TableTh>
|
<TableTh fz="sm" ta="center" w="40%">Deskripsi</TableTh>
|
||||||
</TableTr>
|
<TableTh fz="sm" ta="center" w="20%">Tanggal Publikasi</TableTh>
|
||||||
</TableThead>
|
<TableTh fz="sm" ta="center" w="15%">Aksi</TableTh>
|
||||||
<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>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody bg={colors['white-1']}>
|
||||||
</Table>
|
{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>
|
<Center>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/ind
|
|||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import { BarChart, PieChart } from '@mantine/charts';
|
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 { 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 { useState } from "react";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@ function Kepuasan() {
|
|||||||
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||||
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
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 = () => {
|
const resetForm = () => {
|
||||||
state.create.form = {
|
state.create.form = {
|
||||||
@@ -140,12 +141,12 @@ function Kepuasan() {
|
|||||||
|
|
||||||
if ((loading && !data) || !data) {
|
if ((loading && !data) || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10} px="xl">
|
<Stack py={10} px="sm">
|
||||||
<Skeleton height={300} mb="md" />
|
<Skeleton height={200} mb="md" />
|
||||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="md">
|
||||||
<Skeleton height={300} />
|
<Skeleton height={200} />
|
||||||
<Skeleton height={300} />
|
<Skeleton height={200} />
|
||||||
<Skeleton height={300} />
|
<Skeleton height={200} />
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -412,50 +413,41 @@ function Kepuasan() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack p={"sm"}>
|
<Stack p="sm">
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
<Container w={{ base: "100%", md: "80%" }} p={isMobile ? "md" : "xl"}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="xs">
|
||||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
<Text ta="center" fz={{ base: "2rem", md: "3rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||||
<Group justify={"center"}>
|
<Group justify="center">
|
||||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
<Button radius="lg" bg={colors["blue-button"]} onClick={open}>
|
||||||
|
Ajukan Responden
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={"xl"}>
|
<Box px={isMobile ? "sm" : "xl"}>
|
||||||
<Paper p={"lg"} bg={colors.Bg}>
|
<Paper p="lg" bg={colors.Bg}>
|
||||||
<Paper p={"lg"}>
|
<Paper p={isMobile ? "sm" : "lg"}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="xs">
|
||||||
<Flex justify={"space-between"} align={"center"}>
|
<Flex direction={isMobile ? "column" : "row"} justify="space-between" align={isMobile ? "start" : "center"}>
|
||||||
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
<Text fw="bold" mb={isMobile ? "sm" : 0}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
<Text fz="sm" fw="bold" c={colors["blue-button"]}>Total Responden</Text>
|
||||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
<Text ta="end" fz="h1" fw="bold" c={colors["blue-button"]}>
|
||||||
{state.findMany.total.toLocaleString('id-ID')}
|
{state.findMany.total.toLocaleString("id-ID")}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<BarChart
|
<BarChart
|
||||||
h={300}
|
h={isMobile ? 200 : 300}
|
||||||
data={barChartData}
|
data={barChartData}
|
||||||
dataKey="month"
|
dataKey="month"
|
||||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
series={[{ name: "count", color: colors["blue-button"] }]}
|
||||||
tickLine="y"
|
|
||||||
xAxisLabel="Bulan"
|
|
||||||
yAxisLabel="Jumlah Responden"
|
|
||||||
withTooltip
|
withTooltip
|
||||||
tooltipAnimationDuration={200}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Box py={"xl"}>
|
<Box py="xl">
|
||||||
<SimpleGrid
|
<SimpleGrid cols={{ base: 1, sm: 2, xl: 3 }} spacing="lg">
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 1,
|
|
||||||
lg: 1,
|
|
||||||
xl: 3
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Chart Jenis Kelamin */}
|
{/* Chart Jenis Kelamin */}
|
||||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -465,28 +457,17 @@ function Kepuasan() {
|
|||||||
Belum ada data untuk ditampilkan dalam grafik
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Paper p="md" radius="md" withBorder>
|
<Paper p="md" radius="md">
|
||||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
<Stack>
|
||||||
<Box style={{ position: 'relative', width: '100%' }}>
|
<Center>
|
||||||
<Center>
|
<PieChart
|
||||||
<PieChart
|
size={isMobile ? 150 : 200}
|
||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
data={donutDataJenisKelamin}
|
||||||
labelsType="percent"
|
withTooltip
|
||||||
size={200}
|
/>
|
||||||
data={donutDataJenisKelamin}
|
</Center>
|
||||||
/>
|
</Stack>
|
||||||
</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>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -501,35 +482,18 @@ function Kepuasan() {
|
|||||||
Belum ada data untuk ditampilkan dalam grafik
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Paper p="md" radius="md" withBorder>
|
<Paper p="md" radius="md">
|
||||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
<Stack>
|
||||||
<Box style={{ position: 'relative', width: '100%' }}>
|
<Center>
|
||||||
<Center>
|
<PieChart
|
||||||
<PieChart
|
size={isMobile ? 150 : 200}
|
||||||
withTooltip
|
withLabels
|
||||||
tooltipAnimationDuration={200}
|
labelsPosition="outside"
|
||||||
withLabels
|
withLabelsLine
|
||||||
labelsPosition="outside"
|
data={donutDataRating}
|
||||||
labelsType="percent"
|
/>
|
||||||
withLabelsLine
|
</Center>
|
||||||
size={200}
|
</Stack>
|
||||||
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>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -544,35 +508,18 @@ function Kepuasan() {
|
|||||||
Belum ada data untuk ditampilkan dalam grafik
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Paper p="md" radius="md" withBorder>
|
<Paper p="md" radius="md">
|
||||||
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
<Stack>
|
||||||
<Box style={{ position: 'relative', width: '100%' }}>
|
<Center>
|
||||||
<Center>
|
<PieChart
|
||||||
<PieChart
|
size={isMobile ? 150 : 200}
|
||||||
withTooltip
|
withLabels
|
||||||
tooltipAnimationDuration={200}
|
labelsPosition="outside"
|
||||||
withLabels
|
withLabelsLine
|
||||||
labelsPosition="outside"
|
data={donutDataKelompokUmur}
|
||||||
labelsType="percent"
|
/>
|
||||||
withLabelsLine
|
</Center>
|
||||||
size={190}
|
</Stack>
|
||||||
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>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ function DesaAntiKorupsi() {
|
|||||||
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
||||||
<Center>
|
<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>
|
</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}>
|
<Center py={20}>
|
||||||
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desa-anti-korupsi/detail"}>Selengkapnya</Button>
|
<Button radius={"lg"} fz={"h4"} bg={colors["blue-button"]} component={Link} href={"/darmasaba/desa-anti-korupsi/detail"}>Selengkapnya</Button>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -49,6 +49,7 @@ function DesaAntiKorupsi() {
|
|||||||
cols={{ base: 1, sm: 2, md: 3 }}
|
cols={{ base: 1, sm: 2, md: 3 }}
|
||||||
spacing="lg"
|
spacing="lg"
|
||||||
mt="lg"
|
mt="lg"
|
||||||
|
mb="xl"
|
||||||
>
|
>
|
||||||
{data.map((v, k) => (
|
{data.map((v, k) => (
|
||||||
<Paper
|
<Paper
|
||||||
|
|||||||
@@ -161,10 +161,10 @@ function Kepuasan() {
|
|||||||
<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>
|
<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}>
|
<Center mt={10}>
|
||||||
<Button
|
<Button
|
||||||
radius={"lg"}
|
radius={"lg"}
|
||||||
onClick={open}
|
onClick={open}
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{ from: "#26667F", to: "#124170" }}
|
gradient={{ from: "#26667F", to: "#124170" }}
|
||||||
>Ajukan Responden</Button>
|
>Ajukan Responden</Button>
|
||||||
</Center>
|
</Center>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -182,7 +182,7 @@ function Kepuasan() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<BarChart
|
<BarChart
|
||||||
h={300}
|
h={window.innerWidth < 480 ? 200 : 300}
|
||||||
data={barChartData}
|
data={barChartData}
|
||||||
dataKey="month"
|
dataKey="month"
|
||||||
series={[{ name: 'count', color: colors['blue-button'] }]}
|
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||||
@@ -196,12 +196,9 @@ function Kepuasan() {
|
|||||||
</Paper>
|
</Paper>
|
||||||
<Box py={"xl"}>
|
<Box py={"xl"}>
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
cols={{
|
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||||
base: 1,
|
spacing="md"
|
||||||
md: 1,
|
verticalSpacing="md"
|
||||||
lg: 1,
|
|
||||||
xl: 3
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Chart Jenis Kelamin */}
|
{/* Chart Jenis Kelamin */}
|
||||||
<Paper bg={colors['white-1']} p="md" radius="md">
|
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||||
@@ -220,7 +217,7 @@ function Kepuasan() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={200}
|
size={250} // Fixed size in pixels
|
||||||
data={donutDataJenisKelamin}
|
data={donutDataJenisKelamin}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -259,7 +256,7 @@ function Kepuasan() {
|
|||||||
labelsPosition="outside"
|
labelsPosition="outside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
size={200}
|
size={250}
|
||||||
data={donutDataRating}
|
data={donutDataRating}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -302,7 +299,7 @@ function Kepuasan() {
|
|||||||
labelsPosition="outside"
|
labelsPosition="outside"
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
size={190}
|
size={250}
|
||||||
data={donutDataKelompokUmur}
|
data={donutDataKelompokUmur}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -419,7 +416,7 @@ function Kepuasan() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack p={"sm"}>
|
<Stack p={"sm"}>
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
<Container size="lg" px="md">
|
||||||
<Center>
|
<Center>
|
||||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -432,9 +429,15 @@ function Kepuasan() {
|
|||||||
<Paper p={"lg"} bg={colors.Bg}>
|
<Paper p={"lg"} bg={colors.Bg}>
|
||||||
<Paper p={"lg"}>
|
<Paper p={"lg"}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Flex justify={"space-between"} align={"center"}>
|
<Flex
|
||||||
<Text fw={"bold"}>Pelayanan Terhadap Publik Desa Darmasaba</Text>
|
direction={{ base: "column", sm: "row" }}
|
||||||
<Box>
|
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 fz={"sm"} fw={"bold"} c={colors["blue-button"]}>Total Responden</Text>
|
||||||
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
<Text ta={"end"} fz={"h1"} fw={"bold"} c={colors["blue-button"]}>
|
||||||
{state.findMany.total.toLocaleString('id-ID')}
|
{state.findMany.total.toLocaleString('id-ID')}
|
||||||
|
|||||||
@@ -100,11 +100,11 @@ function Potensi() {
|
|||||||
style={{ zIndex: 1 }}
|
style={{ zIndex: 1 }}
|
||||||
>
|
>
|
||||||
<Tooltip label={v.name} position="top-start">
|
<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}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Text lineClamp={2} c="gray.2" size="sm">
|
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }}>
|
||||||
{v.deskripsi}
|
{v.deskripsi}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
Reference in New Issue
Block a user