Compare commits

...

3 Commits

61 changed files with 2530 additions and 1658 deletions

View File

@@ -0,0 +1,57 @@
[
{
"id" : "cmdxyb9zi0010vniiaeyi55ui",
"name" : "Surat Keterangan Beda Biodata Diri",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Fotocopy dokumen bersangkutan yang terdapat perbedaan biodata diri misal : Sertifikat Tanah/Ijazah/Polis Asuransi dan lainnya.</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxycqz40014vniidftrixvf",
"name" : "Surat Keterangan Yatim Piatu",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau KIA atau Kartu Keluarga</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdwx3wph0003vnr74us2t7h7",
"name" : "Surat Keterangan Domisili Organisasi",
"deskripsi" : "<p>Persyaratan Dokumen:</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy Surat Keterangan Terdaftar (SKT) organisasi atau Pengukuhan Kelompok</p></li><li><p>Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap dengan Kop Organisasi</p></li><li><p>Tanggal berdiri/Tahun berdiri/Sejak kapan berdirinya organisasi</p></li></ul><p>Alur Pelayanan:</p>"
},
{
"id" : "cmdxxv3i80004vniidg1mrucc",
"name" : "Surat Keterangan Penghasilan",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP orang tua atau Fotocopy Kartu keluarga</p></li><li><p>Membuat Surat Pernyataan Penghasilan bermaterai (disertai jumlah penghasilan)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxxwp070008vnii9jbdcto7",
"name" : "Surat Keterangan Tidak Mampu",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP/KIA atau Kartu Keluarga</p></li><li><p>Fotocopy Kartu Indonesia Pintar/Kartu Perlindungan Sosial/Terdaftar dalam DTKS</p></li><li><p>Jika tidak memiliki Kartu tersebut diatas diwajibkan membuat Surat Pernyataan Tidak Mampu</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxxyfkl000cvnii1bxinnfi",
"name" : "Surat Keterangan Kelahiran",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy Surat lahir dari dokter/bidan (jika ada)</p></li><li><p>Fotocopy Kartu Keluarga</p></li><li><p>Fotocopy KTP 2 orang saksi</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy23pl000gvniihsg38aq4",
"name" : "Surat Keterangan Usaha",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy4mgt000kvniib1nemjem",
"name" : "Surat Keterangan Kematian",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Surat Kematian dari rumah sakit atau dokter (jika ada)</p></li><li><p>tanggal kematian</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy61a1000ovniif4ytb9hs",
"name" : "Surat Keterangan Tempat Usaha",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Foto Lokasi&nbsp;dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</p></li><li><p>Surat Perjanjian Sewa/Kontrak atau Kwintansi Pembayaran Sewa 3 bulan terakhir bagi yang mengontrak tempat usaha, apabila tempat usaha milik sendiri lampiri dengan dokumen kepemilikan tempat usaha (dapat berupa fotocopy sppt atau Fotocopy Sertipikat Hak Milik)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy754q000svniiiz8oqyo0",
"name" : "Surat Keterangan Belum Kawin",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Khusus bagi yang berstatus duda atau janda melampirkan fotocopy akta cerai atau dokumen pendukung lainnya</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy8pi2000wvnii48fc1sxd",
"name" : "Surat Keterangan Kelakuan Baik",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li></ul><p>Alur Pelayanan :</p>"
}
]

View File

@@ -0,0 +1,20 @@
[
{
"id": "cmdy0dwx10000vnnb6nmt06rv",
"name": "Telunjuk Sakti Desa Akta Kelahiran (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Kelahiran",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KELAHIRAN_(dengan%20contoh%20Formulir).pdf"
},
{
"id": "cmdy0ttpz0001vnnbrvr9jb3z",
"name": "Telunjuk Sakti Desa Akta Perkawinan (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Perkawinan",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20PERKAWINAN_(dengan%20contoh%20Formulir).pdf"
},
{
"id": "cmdy0vjic0002vnnbcp0e9lgq",
"name": "Telunjuk Sakti Desa Akta Kematian (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Kematian",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KEMATIAN_(dengan%20contoh%20Formulir).pdf"
}
]

View File

@@ -50,59 +50,58 @@ model AppMenuChild {
// ========================================= FILE STORAGE ========================================= //
model FileStorage {
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
link String
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
Pelapor Pelapor[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
link String
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
Pelapor Pelapor[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2")
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
DataPerpustakaan DataPerpustakaan[]
PegawaiPPID PegawaiPPID[]
}
//========================================= MENU LANDING PAGE ========================================= //
@@ -612,17 +611,28 @@ model KategoriBerita {
// ========================================= POTENSI DESA ========================================= //
model PotensiDesa {
id String @id @default(cuid())
name String
deskripsi String
kategori String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
name String
deskripsi String
kategori KategoriPotensi? @relation(fields: [kategoriId], references: [id])
kategoriId String?
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model KategoriPotensi {
id String @id @default(cuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PotensiDesa PotensiDesa[]
}
// ========================================= PENGUMUMAN ========================================= //

View File

@@ -4,6 +4,7 @@ import programInovasi from "./data/landing-page/profile/programInovasi.json";
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
import apbdes from "./data/landing-page/apbdes/apbdes.json";
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
import categoryPengumuman from "./data/category-pengumuman.json";
import kategoriBerita from "./data/kategori-berita.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
@@ -98,6 +99,23 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("media sosial success ...");
// =========== LAYANAN DESA ===========
for (const p of pelayananSuratKeterangan) {
await prisma.pelayananSuratKeterangan.upsert({
where: { id: p.id },
update: {
name: p.name,
deskripsi: p.deskripsi,
},
create: {
id: p.id,
name: p.name,
deskripsi: p.deskripsi,
},
});
}
console.log("media sosial success ...");
// =========== LAYANAN ===========

View File

@@ -336,15 +336,38 @@ const pelayananTelunjukSaktiDesa = proxy({
},
},
findMany: {
data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
omit: { isActive: true };
}>[],
async load() {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get();
if (res.status === 200) {
pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property
pelayananTelunjukSaktiDesa.findMany.page = page;
try {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pelayananTelunjukSaktiDesa.findMany.data = res.data.data || [];
pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load telunjuk sakti desa:", res.data?.message);
pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading telunjuk sakti desa:", error);
pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
} finally {
pelayananTelunjukSaktiDesa.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,219 +6,470 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
kategori: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),
})
name: z.string().min(1).max(5000),
deskripsi: z.string().min(1).max(5000),
kategoriId: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),
});
const defaultForm = {
name: "",
deskripsi: "",
kategori: "",
imageId: "",
content: "",
}
name: "",
deskripsi: "",
kategoriId: "",
imageId: "",
content: "",
};
const potensiDesa = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(potensiDesa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
potensiDesa.create.loading = true;
const res = await ApiFetch.api.desa.potensi["create"].post(
potensiDesa.create.form
);
if (res.status === 200) {
potensiDesa.findMany.load();
return toast.success("Potensi berhasil disimpan!");
}
return toast.error("Gagal menyimpan potensi");
} catch (error) {
console.log((error as Error).message);
} finally {
potensiDesa.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
potensiDesa.findMany.loading = true; // Use the full path to access the property
potensiDesa.findMany.page = page;
try {
const res = await ApiFetch.api.desa.potensi[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
potensiDesa.findMany.data = res.data.data || [];
potensiDesa.findMany.total = res.data.total || 0;
potensiDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load potensi desa:", res.data?.message);
potensiDesa.findMany.data = [];
potensiDesa.findMany.total = 0;
potensiDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading potensi desa:", error);
potensiDesa.findMany.data = [];
potensiDesa.findMany.total = 0;
potensiDesa.findMany.totalPages = 1;
} finally {
potensiDesa.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PotensiDesaGetPayload<{
include: {
image: true;
kategori: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/potensi/${id}`);
if (res.ok) {
const data = await res.json();
potensiDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch potensi:", res.statusText);
potensiDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching potensi:", error);
potensiDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
potensiDesa.delete.loading = true;
const response = await fetch(`/api/desa/potensi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Potensi berhasil dihapus");
await potensiDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus potensi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus potensi");
} finally {
potensiDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/potensi/${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 = {
name: data.name,
deskripsi: data.deskripsi,
kategoriId: data.kategoriId,
imageId: data.imageId || "",
content: data.content,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading potensi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(potensiDesa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
potensiDesa.edit.loading = true;
const response = await fetch(`/api/desa/potensi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategoriId: this.form.kategoriId,
imageId: this.form.imageId,
content: this.form.content,
}),
});
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("Berhasil update potensi");
await potensiDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update potensi");
}
} catch (error) {
console.error("Error updating potensi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update potensi"
);
return false;
} finally {
potensiDesa.edit.loading = false;
}
},
reset() {
potensiDesa.edit.id = "";
potensiDesa.edit.form = { ...defaultForm };
},
},
});
const templateKategoriPotensi = z.object({
nama: z.string().min(1, "Nama harus diisi"),
});
const defaultKategoriPotensi = {
nama: "",
};
const kategoriPotensi = proxy({
create: {
form: { ...defaultKategoriPotensi },
loading: false,
async create() {
const cek = templateKategoriPotensi.safeParse(
kategoriPotensi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriPotensi.create.loading = true;
const res = await ApiFetch.api.desa.kategoripotensi["create"].post(
kategoriPotensi.create.form
);
if (res.status === 200) {
kategoriPotensi.findMany.load();
return toast.success("Data Kategori Potensi Berhasil Dibuat");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log(error);
return toast.error("failed create");
} finally {
kategoriPotensi.create.loading = false;
}
},
},
findMany: {
data: [] as Prisma.KategoriPotensiGetPayload<{
omit: {
isActive: true;
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get();
if (res.status === 200) {
kategoriPotensi.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriPotensiGetPayload<{
omit: {
isActive: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/desa/kategoripotensi/${id}`);
if (res.ok) {
const data = await res.json();
kategoriPotensi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriPotensi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriPotensi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async delete(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriPotensi.delete.loading = true;
const response = await fetch(`/api/desa/kategoripotensi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Data Kategori Potensi berhasil dihapus"
);
await kategoriPotensi.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus Data Kategori Potensi"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Data Kategori Potensi");
} finally {
kategoriPotensi.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultKategoriPotensi },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/kategoripotensi/${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,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori potensi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateKategoriPotensi.safeParse(
kategoriPotensi.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriPotensi.update.loading = true;
const response = await fetch(`/api/desa/kategoripotensi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
}),
});
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("Berhasil update data kategori potensi");
await kategoriPotensi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal update data kategori potensi"
);
}
} catch (error) {
console.error("Error updating data kategori potensi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update data kategori potensi"
);
return false;
} finally {
kategoriPotensi.update.loading = false;
}
},
reset() {
kategoriPotensi.update.id = "";
kategoriPotensi.update.form = { ...defaultKategoriPotensi };
},
},
});
const potensiDesaState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(potensiDesaState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
potensiDesaState.create.loading = true;
const res = await ApiFetch.api.desa.potensi["create"].post(potensiDesaState.create.form);
if (res.status === 200) {
potensiDesaState.findMany.load();
return toast.success("Potensi berhasil disimpan!");
}
return toast.error("Gagal menyimpan potensi");
} catch (error) {
console.log((error as Error).message);
} finally {
potensiDesaState.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PotensiDesaGetPayload<{
include: {
image: true;
}
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.potensi["find-many"].get();
if (res.status === 200) {
potensiDesaState.findMany.data = res.data?.data ?? [];
}
}
},
findUnique: {
data: null as
| Prisma.PotensiDesaGetPayload<{
include: {
image: true;
}
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/potensi/${id}`);
if (res.ok) {
const data = await res.json();
potensiDesaState.findUnique.data = data.data ?? null;
} else {
console.error('Failed to fetch potensi:', res.statusText);
potensiDesaState.findUnique.data = null;
}
} catch (error) {
console.error('Error fetching potensi:', error);
potensiDesaState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
potensiDesaState.delete.loading = true;
const response = await fetch(`/api/desa/potensi/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Potensi berhasil dihapus");
await potensiDesaState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus potensi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus potensi");
} finally {
potensiDesaState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
potensiDesa,
kategoriPotensi,
});
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/potensi/${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 = {
name: data.name,
deskripsi: data.deskripsi,
kategori: data.kategori,
imageId: data.imageId || "",
content: data.content,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading potensi:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(potensiDesaState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
potensiDesaState.edit.loading = true;
const response = await fetch(`/api/desa/potensi/${this.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategori: this.form.kategori,
imageId: this.form.imageId,
content: this.form.content,
}),
});
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("Berhasil update potensi");
await potensiDesaState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update potensi");
}
} catch (error) {
console.error("Error updating potensi:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update potensi");
return false;
} finally {
potensiDesaState.edit.loading = false;
}
},
reset() {
potensiDesaState.edit.id = "";
potensiDesaState.edit.form = { ...defaultForm };
}
}
})
export default potensiDesaState
export default potensiDesaState;

View File

@@ -111,7 +111,7 @@ function ListSuratKeterangan({ search }: { search: string }) {
</TableTd>
<TableTd>
<Box w={300}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>

View File

@@ -1,6 +1,5 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
@@ -76,6 +75,14 @@ function EditPelayananTelunjukSakti() {
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
placeholder="masukkan nama surat keterangan"
/>
<TextInput
value={formData.deskripsi}
onChange={(val) => {
setFormData({ ...formData, deskripsi: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Tautan Link</Text>}
placeholder="masukkan tautan link"
/>
<TextInput
value={formData.link}
onChange={(val) => {
@@ -84,15 +91,6 @@ function EditPelayananTelunjukSakti() {
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
placeholder="masukkan link"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData({ ...formData, deskripsi: htmlContent });
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -58,11 +58,24 @@ function DetailPelayananTelunjukSakti() {
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Link</Text>
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.link}</Text>
<Text
component="a"
href={telunjukSaktiState.findUnique.data?.link}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
{telunjukSaktiState.findUnique.data?.link}
</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: telunjukSaktiState.findUnique.data?.deskripsi }}></Text>
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.deskripsi}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button

View File

@@ -1,5 +1,4 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
@@ -43,6 +42,14 @@ function CreatePelayananTelunjukDesa() {
label={<Text fz={"sm"} fw={"bold"}>Nama Pelayanan Telunjuk Sakti Desa</Text>}
placeholder="masukkan nama pelayanan telunjuk sakti desa"
/>
<TextInput
value={stateTelunjukDesa.create.form.deskripsi}
onChange={(val) => {
stateTelunjukDesa.create.form.deskripsi = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Tautan Link</Text>}
placeholder="masukkan tautan link"
/>
<TextInput
value={stateTelunjukDesa.create.form.link}
onChange={(val) => {
@@ -51,15 +58,6 @@ function CreatePelayananTelunjukDesa() {
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
placeholder="masukkan link"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<CreateEditor
value={stateTelunjukDesa.create.form.deskripsi}
onChange={(htmlContent) => {
stateTelunjukDesa.create.form.deskripsi = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -1,89 +1,153 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import stateLayananDesa from '../../../_state/desa/layananDesa';
function PelayananTelunjukSakti() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
}
function ListPelayananTelunjukSakti({ search }: { search: string }) {
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
useShallowEffect(() => {
telunjukSaktiState.findMany.load()
const {
data,
page,
totalPages,
loading,
load,
} = telunjukSaktiState.findMany;
useEffect(() => {
load(page, 10)
}, [])
const filteredData = (telunjukSaktiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [data, search]);
if (!telunjukSaktiState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={300} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Pelayanan Telunjuk Sakti Desa'
href='/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={3}>
<Text fz={"sm"} color="gray.5">
Tidak ada data
</Text>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Pelayanan Telunjuk Sakti Desa'
href='/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd><Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /></TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
<TableTd>
<Box w={100}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.name }} />
</Box>
</TableTd>
<TableTd>
<Box w={100}>
<a href={item.link} target="_blank" rel="noopener noreferrer">
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} truncate="end" fz={"sm"} />
</a>
</Box>
</TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -0,0 +1,63 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabsPotensi({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "List Potensi",
value: "list_potensi",
href: "/admin/desa/potensi/list-potensi"
},
{
label: "Kategori Potensi",
value: "kategori_potensi",
href: "/admin/desa/potensi/kategori-potensi"
},
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Title order={3}>Potensi</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabsPotensi;

View File

@@ -1,128 +0,0 @@
'use client';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '../../../_state/desa/potensi';
import { useRouter } from 'next/navigation';
import CreateEditor from '../../../_com/createEditor';
function CreatePotensi() {
const potensiState = useProxy(potensiDesaState);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
const handleSubmit = async () => {
if (!file) return toast.warn('Pilih file gambar terlebih dahulu');
const res = await ApiFetch.api.fileStorage.create.post({
file,
name: file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error('Gagal upload gambar');
}
potensiState.create.form.imageId = uploaded.id;
await potensiState.create.create();
resetForm();
router.push('/admin/desa/potensi');
};
const resetForm = () => {
potensiState.create.form = {
name: '',
deskripsi: '',
kategori: '',
imageId: '',
content: '',
};
setPreviewImage(null);
setFile(null);
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Create Potensi</Title>
<TextInput
value={potensiState.create.form.name}
onChange={(val) => (potensiState.create.form.name = val.target.value)}
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={potensiState.create.form.deskripsi}
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<TextInput
value={potensiState.create.form.kategori}
onChange={(val) => (potensiState.create.form.kategori = val.target.value)}
label={<Text fz="sm" fw="bold">Kategori</Text>}
placeholder="masukkan kategori"
/>
<FileInput
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg="gray">
<IconImageInPicture />
</Center>
)}
<Box>
<Text fz="sm" fw="bold">Konten</Text>
<CreateEditor
value={potensiState.create.form.content}
onChange={(htmlContent) => {
potensiState.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan Potensi
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePotensi;

View File

@@ -0,0 +1,80 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } 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 EditKategoriPotensi() {
const editState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
nama: editState.update.form.nama || '',
});
useEffect(() => {
const loadKategori = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
if (data) {
setFormData({
nama: data.nama || '',
});
}
} catch (error) {
console.error("Error loading kategori potensi:", error);
toast.error("Gagal memuat data kategori potensi");
}
};
loadKategori();
}, [params?.id]);
const handleSubmit = async () => {
try {
editState.update.form = {
...editState.update.form,
nama: formData.nama,
};
await editState.update.update();
toast.success('Kategori Potensi berhasil diperbarui!');
router.push('/admin/desa/potensi/kategori-potensi');
} catch (error) {
console.error('Error updating kategori potensi:', error);
toast.error('Terjadi kesalahan saat memperbarui kategori potensi');
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={30} />
</Button>
</Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit Kategori Potensi</Title>
<TextInput
value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Potensi</Text>}
placeholder="masukkan nama kategori potensi"
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditKategoriPotensi;

View File

@@ -0,0 +1,55 @@
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateKategoriPotensi() {
const createState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const resetForm = () => {
createState.create.form = {
nama: "",
};
};
const handleSubmit = async () => {
await createState.create.create();
resetForm();
router.push("/admin/desa/potensi/kategori-potensi")
};
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Kategori Potensi</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Potensi</Text>}
placeholder='Masukkan nama kategori Potensi'
value={createState.create.form.nama}
onChange={(val) => {
createState.create.form.nama = val.target.value;
}}
/>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateKategoriPotensi;

View File

@@ -0,0 +1,128 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconEdit, IconSearch, IconTrash } 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 JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import potensiDesaState from '../../../_state/desa/potensi';
function KategoriPotensi() {
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Kategori Potensi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListKategoriPotensi search={search} />
</Box>
);
}
function ListKategoriPotensi({ search }: { search: string }) {
const listDataState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
useEffect(() => {
listDataState.findMany.load()
}, [])
const handleDelete = () => {
if (selectedId) {
listDataState.delete.delete(selectedId)
setModalHapus(false)
setSelectedId(null)
listDataState.findMany.load()
}
}
const filteredData = (listDataState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
if (!listDataState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<JudulList
title='List Kategori Potensi'
href='/admin/desa/potensi/kategori-potensi/create'
/>
<Box style={{ overflowX: 'auto' }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Hapus</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
</Box>
</TableTd>
<TableTd>{item.nama}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/desa/potensi/kategori-potensi/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={listDataState.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus kategori Potensi ini?'
/>
</Box>
)
}
export default KategoriPotensi;

View File

@@ -0,0 +1,14 @@
'use client'
import React from 'react';
import LayoutTabsPotensi from './_lib/layoutTabs';
function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsPotensi>
{children}
</LayoutTabsPotensi>
)
}
export default Layout;

View File

@@ -1,21 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";
import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from "@mantine/core";
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
import { useParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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 EditPotensi() {
const potensiState = useProxy(potensiDesaState)
const potensiState = useProxy(potensiDesaState.potensiDesa)
const router = useRouter()
const params = useParams()
@@ -24,23 +25,24 @@ function EditPotensi() {
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
kategori: '',
kategoriId: '',
content: '',
imageId: ''
});
useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load()
const loadPotensi = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await potensiDesaState.edit.load(id); // ambil data dari API
const data = await potensiState.edit.load(id); // ambil data dari API
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
kategori: data.kategori || '',
kategoriId: data.kategoriId || '',
content: data.content || '',
imageId: data.imageId || '',
});
@@ -65,7 +67,7 @@ function EditPotensi() {
...potensiState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
kategori: formData.kategori,
kategoriId: formData.kategoriId,
content: formData.content,
};
@@ -82,7 +84,7 @@ function EditPotensi() {
await potensiState.edit.update();
toast.success("Potensi berhasil diperbarui!");
router.push("/admin/desa/potensi");
router.push("/admin/desa/potensi/list-potensi");
} catch (error) {
console.error("Error updating potensi:", error);
toast.error("Terjadi kesalahan saat memperbarui potensi");
@@ -119,40 +121,82 @@ function EditPotensi() {
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<TextInput
value={formData.kategori}
onChange={(e) => {
const val = e.target.value;
setFormData((prev) => ({ ...prev, kategori: val }));
potensiState.edit.form.kategori = val;
}}
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
placeholder="masukkan kategori"
<Select
value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
placeholder='Pilih kategori'
data={
potensiDesaState.kategoriPotensi.findMany.data?.map((v) => ({
value: v.id,
label: v.nama
})) || []
}
clearable
searchable
required
error={!formData.kategoriId ? "Pilih kategori" : undefined}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
"data:image/png;base64," + Buffer.from(buf).toString("base64")
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
</Box>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.content}
value={formData.content}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, content: htmlContent }));
potensiState.edit.form.content = htmlContent;

View File

@@ -5,18 +5,19 @@ import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '../../../../_state/desa/potensi';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
export default function DetailPotensi() {
const router = useRouter()
const params = useParams()
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const potensiState = useProxy(potensiDesaState)
const potensiState = useProxy(potensiDesaState.potensiDesa)
useShallowEffect(() => {
potensiState.findUnique.load(params?.id as string)
@@ -60,7 +61,7 @@ export default function DetailPotensi() {
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
<Text fz={"lg"}>{potensiState.findUnique.data.kategori}</Text>
<Text fz={"lg"}>{potensiState.findUnique.data.kategori?.nama}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
@@ -91,7 +92,7 @@ export default function DetailPotensi() {
<Button
onClick={() => {
if (potensiState.findUnique.data) {
router.push(`/admin/desa/potensi/edit/${potensiState.findUnique.data.id}`)
router.push(`/admin/desa/potensi/list-potensi/${potensiState.findUnique.data.id}/edit`)
}
}}
disabled={!potensiState.findUnique.data}

View File

@@ -0,0 +1,176 @@
'use client';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreatePotensi() {
const potensiState = useProxy(potensiDesaState.potensiDesa);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load()
}, [])
const handleSubmit = async () => {
if (!file) return toast.warn('Pilih file gambar terlebih dahulu');
const res = await ApiFetch.api.fileStorage.create.post({
file,
name: file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error('Gagal upload gambar');
}
potensiState.create.form.imageId = uploaded.id;
await potensiState.create.create();
resetForm();
router.push('/admin/desa/potensi/list-potensi');
};
const resetForm = () => {
potensiState.create.form = {
name: '',
deskripsi: '',
kategoriId: '',
imageId: '',
content: '',
};
setPreviewImage(null);
setFile(null);
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Create Potensi</Title>
<TextInput
value={potensiState.create.form.name}
onChange={(val) => (potensiState.create.form.name = val.target.value)}
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={potensiState.create.form.deskripsi}
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<Select
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
placeholder='Pilih kategori'
value={potensiState.create.form.kategoriId || ""}
onChange={(val) => {
potensiState.create.form.kategoriId = val ?? "";
}}
data={potensiDesaState.kategoriPotensi.findMany.data?.map((item) => ({
value: item.id,
label: item.nama,
}))}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
</Box>
<Box>
<Text fz="sm" fw="bold">Konten</Text>
<CreateEditor
value={potensiState.create.form.content}
onChange={(htmlContent) => {
potensiState.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan Potensi
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePotensi;

View File

@@ -0,0 +1,158 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { 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 JudulList from '../../../_com/judulList';
import potensiDesaState from '../../../_state/desa/potensi';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = potensiState.potensiDesa.findMany;
useEffect(() => {
potensiState.kategoriPotensi.findMany.load()
load(page, 10)
}, [])
const filteredData = (potensiState.potensiDesa.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori?.nama.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/list-potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={4}>Tidak Ada Data</TableTd>
</TableTr>
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/list-potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>{item.kategori?.nama}</TableTd>
<TableTd>
<Box w={300}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/potensi/list-potensi/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}
export default Potensi;

View File

@@ -1,100 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import potensiDesaState from '../../_state/desa/potensi';
import { useState } from 'react';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
useShallowEffect(() => {
potensiState.findMany.load()
}, [])
const filteredData = (potensiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori.toLowerCase().includes(keyword)
);
});
if (!potensiState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>{item.kategori}</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/potensi/detail/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
export default Potensi;

View File

@@ -98,7 +98,7 @@ export const navBar = [
{
id: "Desa_2",
name: "Potensi",
path: "/admin/desa/potensi"
path: "/admin/desa/potensi/list-potensi"
},
{
id: "Desa_3",

View File

@@ -7,6 +7,7 @@ import GalleryFoto from "./gallery/foto";
import GalleryVideo from "./gallery/video";
import LayananDesa from "./layanan";
import Penghargaan from "./penghargaan";
import KategoriPotensi from "./potensi/kategori-potensi";
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
@@ -18,5 +19,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
.use(GalleryVideo)
.use(LayananDesa)
.use(Penghargaan)
.use(KategoriPotensi)
export default Desa;

View File

@@ -1,21 +1,43 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pelayananTelunjukSaktiDesaFindMany() {
export default async function pelayananTelunjukSaktiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const data = await prisma.pelayananTelunjukSaktiDesa.findMany({
where: { isActive: true },
});
return {
success: true,
message: "Success fetch pelayanan telunjuk sakti desa",
data,
};
const [data, total] = await Promise.all([
prisma.pelayananTelunjukSaktiDesa.findMany({
where: { isActive: true },
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pelayananTelunjukSaktiDesa.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch pelayanan telunjuk sakti desa with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch pelayanan telunjuk sakti desa",
};
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch pelayanan telunjuk sakti desa with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}

View File

@@ -6,7 +6,7 @@ type FormCreate = Prisma.PotensiDesaGetPayload<{
select: {
name: true;
deskripsi: true;
kategori: true;
kategoriId: true;
imageId: true;
content: true;
}
@@ -18,7 +18,7 @@ export default async function potensiDesaCreate(context: Context) {
data: {
name: body.name,
deskripsi: body.deskripsi,
kategori: body.kategori,
kategoriId: body.kategoriId,
imageId: body.imageId,
content: body.content,
},

View File

@@ -17,6 +17,7 @@ const potensiDesaDelete = async (context: Context) => {
where: { id },
include: {
image: true,
kategori: true
},
});

View File

@@ -1,26 +1,47 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function potensiDesaFindMany() {
export default async function potensiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const data = await prisma.potensiDesa.findMany({
where: { isActive: true },
include: {
image: true,
},
});
return {
success: true,
message: "Success fetch potensi desa",
data,
};
const [data, total] = await Promise.all([
prisma.potensiDesa.findMany({
where: { isActive: true },
skip,
take: limit,
include: {
image: true,
kategori: true
},
orderBy: { createdAt: 'desc' },
}),
prisma.potensiDesa.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch potensi desa with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch potensi desa",
};
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch potensi desa with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}
export default potensiDesaFindMany
}

View File

@@ -25,6 +25,7 @@ export default async function findUnique(
where: { id },
include: {
image: true,
kategori: true
},
});

View File

@@ -14,7 +14,7 @@ const PotensiDesa = new Elysia({
body: t.Object({
name: t.String(),
deskripsi: t.String(),
kategori: t.String(),
kategoriId: t.String(),
imageId: t.String(),
content: t.String(),
}),
@@ -35,7 +35,7 @@ const PotensiDesa = new Elysia({
body: t.Object({
name: t.String(),
deskripsi: t.String(),
kategori: t.String(),
kategoriId: t.String(),
imageId: t.String(),
content: t.String(),
}),

View File

@@ -0,0 +1,27 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormCreate = {
nama: string;
}
export default async function kategoriPotensiCreate(context: Context) {
const body = (await context.body) as FormCreate;
try {
const result = await prisma.kategoriPotensi.create({
data: {
nama: body.nama
},
});
return {
success: true,
message: "Berhasil membuat kategori potensi",
data: result,
};
} catch (error) {
console.error("Error creating kategori potensi:", error);
throw new Error("Gagal membuat kategori potensi: " + (error as Error).message);
}
}

View File

@@ -0,0 +1,16 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriPotensiDelete(context: Context) {
const id = context.params.id as string;
await prisma.kategoriPotensi.delete({
where: { id },
});
return {
status: 200,
success: true,
message: "Success delete kategori potensi",
};
}

View File

@@ -0,0 +1,11 @@
import prisma from "@/lib/prisma";
export default async function kategoriPotensiFindMany() {
const data = await prisma.kategoriPotensi.findMany();
return {
success: true,
message: "Success get all kategori potensi",
data,
};
}

View File

@@ -0,0 +1,46 @@
import prisma from "@/lib/prisma";
export default async function kategoriBukuFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return {
success: false,
message: "ID is required",
}
}
try {
if (typeof id !== 'string') {
return {
success: false,
message: "ID is required",
}
}
const data = await prisma.kategoriPotensi.findUnique({
where: { id },
});
if (!data) {
return {
success: false,
message: "Data not found",
}
}
return {
success: true,
message: "Success get kategori potensi",
data,
}
} catch (error) {
console.error("Find by ID error:", error);
return {
success: false,
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
}
}
}

View File

@@ -0,0 +1,33 @@
import Elysia, { t } from "elysia";
import kategoriPotensiCreate from "./create";
import kategoriPotensiDelete from "./del";
import kategoriPotensiFindMany from "./findMany";
import kategoriPotensiFindUnique from "./findUnique";
import kategoriPotensiUpdate from "./updt";
const KategoriPotensi = new Elysia({
prefix: "/kategoripotensi",
tags: ["Desa / Potensi"],
})
.post("/create", kategoriPotensiCreate, {
body: t.Object({
nama: t.String(),
}),
})
.get("/findMany", kategoriPotensiFindMany)
.get("/:id", async (context) => {
const response = await kategoriPotensiFindUnique(
new Request(context.request)
);
return response;
})
.put("/:id", kategoriPotensiUpdate, {
body: t.Object({
nama: t.String(),
}),
})
.delete("/del/:id", kategoriPotensiDelete);
export default KategoriPotensi;

View File

@@ -0,0 +1,28 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
nama: string;
}
export default async function kategoriPotensiUpdate(context: Context) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.kategoriPotensi.update({
where: { id },
data: {
nama: body.nama,
},
});
return {
success: true,
message: "Berhasil mengupdate kategori potensi",
data: result,
};
} catch (error) {
console.error("Error updating kategori potensi:", error);
throw new Error("Gagal mengupdate kategori potensi: " + (error as Error).message);
}
}

View File

@@ -9,7 +9,7 @@ type FormUpdate = Prisma.PotensiDesaGetPayload<{
id: true;
name: true;
deskripsi: true;
kategori: true;
kategoriId: true;
imageId: true;
content: true;
};
@@ -23,7 +23,7 @@ export default async function potensiDesaUpdate(context: Context) {
const {
name,
deskripsi,
kategori,
kategoriId,
imageId,
content,
} = body;
@@ -39,6 +39,7 @@ export default async function potensiDesaUpdate(context: Context) {
where: { id },
include: {
image: true,
kategori: true
}
});
@@ -69,7 +70,7 @@ export default async function potensiDesaUpdate(context: Context) {
data: {
name,
deskripsi,
kategori,
kategoriId,
imageId,
content,
},

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Beda Biodata Diri
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy dokumen bersangkutan yang terdapat perbedaan biodata diri misal : Sertifikat Tanah/Ijazah/Polis Asuransi dan lainnya.</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Belum Kawin
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Khusus bagi yang berstatus duda atau janda melampirkan fotocopy akta cerai atau dokumen pendukung lainnya</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Box, Button, Center, Container, Image, List, ListItem, Stack, Text } from '@mantine/core';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Domisili Organisasi
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0} >
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Surat Keterangan Terdaftar (SKT) organisasi atau Pengukuhan Kelompok</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap dengan Kop Organisasi</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Tanggal berdiri/Tahun berdiri/Sejak kapan berdirinya organisasi</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Group, Center } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kelahiran
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Surat lahir dari dokter/bidan (jika ada)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP 2 orang saksi</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-kelahiran-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,41 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kelakuan Baik (Pengantar SKCK)
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kematian
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Surat Kematian dari rumah sakit atau dokter (jika ada)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Tanggal dan Waktu Kematian</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Penghasilan
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{base:"h4", md: "h2"}} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{base:"sm", md: "h3"}}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{base:"sm", md: "h3"}}>Fotocopy KTP orang tua atau Fotocopy Kartu keluarga</ListItem>
<ListItem fz={{base:"sm", md: "h3"}}>Membuat Surat Pernyataan Penghasilan bermaterai (disertai jumlah penghasilan)</ListItem>
</List>
<Box py={20}>
<Text fz={{base:"h4", md: "h2"}} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Tempat Usaha
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Surat Perjanjian Sewa/Kontrak atau Kwintansi Pembayaran Sewa 3 bulan terakhir bagi yang mengontrak tempat usaha, apabila tempat usaha milik sendiri lampiri dengan dokumen kepemilikan tempat usaha (dapat berupa fotocopy sppt atau Fotocopy Sertipikat Hak Milik)</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-tempat-usaha-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,44 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Tidak Mampu
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy KTP/KIA atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Kartu Indonesia Pintar/Kartu Perlindungan Sosial/Terdaftar dalam DTKS</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Jika tidak memiliki Kartu tersebut diatas diwajibkan membuat Surat Pernyataan Tidak Mampu
</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-tidak-mampu-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Usaha
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack >
);
}
export default Page;

View File

@@ -1,41 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Yatim Piatu
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Ktp, KIA atau Kartu Keluarga</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,106 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import { useParams } from 'next/navigation';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../_com/BackButto';
interface LayananData {
id: string;
name: string;
deskripsi: string;
imageId: string;
image2Id: string;
image?: {
id: string;
link: string;
};
image2?: {
id: string;
link: string;
};
}
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(stateLayananDesa);
const [loading, setLoading] = useState(true);
const [data, setData] = useState<LayananData | null>(null);
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.suratKeterangan.findUnique.load(id);
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
setData(result);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, [id]);
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text
fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }}
ta="center"
fw="bold"
>
{data.name}
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
fz={{ base: "h4", md: "h2" }}
pb={20}
/>
{data.image2?.link && (
<Center>
<Image src={data.image2.link} alt={data.name} />
</Center>
)}
<Group justify='center' mt="md">
<Button radius="lg" fz="h4" bg={colors['blue-button']}>
Ajukan Permohonan
</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,10 +0,0 @@
import { Stack } from "@mantine/core";
export default async function Page({ params }: { params: Promise<{ sub: string }> }) {
const { sub } = await params
return (
<Stack>
{sub}
</Stack>
)
}

View File

@@ -0,0 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { ActionIcon, Box, Divider, Flex, Skeleton, Text } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPendudukNonPermanent() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananPendudukNonPermanen.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = state.pelayananPendudukNonPermanen.findById.data
return (
<Box>
{loading ? (
<Skeleton h={500} />
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{data?.name}</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"} dangerouslySetInnerHTML={{__html: data?.deskripsi || ''}} />
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
)}
</Box>
);
}
export default PelayananPendudukNonPermanent;

View File

@@ -0,0 +1,106 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Button, Center, Group, Skeleton, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPerizinanBerusaha() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.pelayananPerizinanBerusaha.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
const data = state.pelayananPerizinanBerusaha.findById.data;
if (!data) {
return (
<Center>
<Text>Data tidak tersedia</Text>
</Center>
);
}
return (
<Box>
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text
py={10}
ta={"justify"}
fz={{ base: "sm", md: 'h3' }}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '' }}
/>
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
<Box p={"xl"} w={{ base: "100%", md: "100%" }}>
<Stepper active={active} onStepClick={setActive} orientation="vertical"
styles={{
separator: {
marginLeft: 25
},
step: {
padding: '12px 0'
}
}}>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
Pendaftaran akun pada portal OSS
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI">
Memilih KBLI dengan jenis usaha yang akan didaftarkan
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
Proses verifikasi dan persetujuan oleh instansi terkait
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
</StepperStep>
<StepperCompleted>
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
</StepperCompleted>
</Stepper>
<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>Back</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>
Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
resmi OSS <a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.
</Text>
</Box>
</Box>
)}
</Box>
);
}
export default PelayananPerizinanBerusaha;

View File

@@ -0,0 +1,97 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Group, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useRouter } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananSuratKeterangan({ search }: { search: string }) {
const [loading, setLoading] = useState(false);
const router = useRouter()
const state = useProxy(stateLayananDesa)
const filteredData = useMemo(() => {
if (!state.suratKeterangan.findMany.data) return [];
return state.suratKeterangan.findMany.data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
}, [state.suratKeterangan.findMany.data, search]);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.suratKeterangan.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
return (
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
filteredData.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(`/darmasaba/desa/layanan/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
</Box>
);
}
export default PelayananSuratKeterangan;

View File

@@ -0,0 +1,67 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Flex, Skeleton, Text, Title } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
interface ServiceItem {
name: string;
deskripsi: string;
link: string;
}
function PelayananTelunjukSaktiDesa() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananTelunjukSaktiDesa.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{
name: string;
id: string;
deskripsi: string;
link: string;
items: ServiceItem[];
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}>
return (
<Box>
<Title fz="h2" py={10} mb="md">Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. Layanan Telunjuk Sakti Desa meliputi :</Title>
{loading ? (
<Skeleton h={500} />
) : (
data.map((v, k) => {
return (
<Box key={k}>
<Box py={10}>
<Flex gap={"3"} align={"center"}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{v.name}
</Text>
<Text span fz={{ base: "h4", md: "h3" }}>
<a href={v.link} target="_blank" rel="noopener noreferrer" style={{ color: '#228be6' }}>{v.deskripsi}</a>
</Text>
</Flex>
</Box>
</Box>
)
})
)}
</Box>
);
}
export default PelayananTelunjukSaktiDesa;

View File

@@ -1,85 +1,19 @@
'use client'
import colors from "@/con/colors";
import { ActionIcon, BackgroundImage, Box, Button, Container, Divider, Flex, Group, List, ListItem, SimpleGrid, Stack, Stepper, StepperCompleted, StepperStep, Text, TextInput } from "@mantine/core";
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp, IconSearch } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
import { Box, Container, Stack, Text, TextInput } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import BackButton from "./_com/BackButto";
import PelayananPerizinanBerusaha from "./_com/pelayananPerizinanBerusaha";
import PelayananSuratKeterangan from "./_com/pelayananSuratKeterangan";
import PelayananTelunjukSaktiDesa from "./_com/pelayananTelunjukSaktiDesa";
import PelayananPendudukNonPermanent from "./_com/pelayananPendudukNonPermanent";
const data = [
{
id: 1,
images: "/api/img/test.png",
name: "Surat Keterangan Domisili Organisasi",
link: "/darmasaba/desa/layanan/surat-keterangan-domisili"
},
{
id: 2,
images: "/api/img/test-3.jpeg",
name: "Surat Keterangan Penghasilan",
link: "/darmasaba/desa/layanan/surat-keterangan-penghasilan"
},
{
id: 3,
images: "/api/img/domisili.jpeg",
name: "Surat Keterangan Tidak Mampu",
link: "/darmasaba/desa/layanan/surat-keterangan-tidak-mampu"
},
{
id: 4,
images: "/api/img/kelahiran.jpeg",
name: "Surat Keterangan Kelahiran",
link: "/darmasaba/desa/layanan/surat-keterangan-kelahiran"
},
{
id: 5,
images: "/api/img/keterangan-usaha.jpeg",
name: "Surat Keterangan Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-usaha"
},
{
id: 6,
images: "/api/img/kematian.jpeg",
name: "Surat Keterangan Kematian",
link: "/darmasaba/desa/layanan/surat-keterangan-kematian"
},
{
id: 7,
images: "/api/img/tempatusaha.jpeg",
name: "Surat Keterangan Tempat Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-tempat-usaha"
},
{
id: 8,
images: "/api/img/belumkawin.jpeg",
name: "Surat Keterangan Belum Kawin",
link: "/darmasaba/desa/layanan/surat-keterangan-belum-kawin"
},
{
id: 9,
images: "/api/img/berkelakuan-baik.jpeg",
name: "Surat Keterangan Kelakuan Baik",
link: "/darmasaba/desa/layanan/surat-keterangan-kelakuan-baik"
},
{
id: 10,
images: "/api/img/biodata.jpeg",
name: "Surat Keterangan Beda Biodata Diri",
link: "/darmasaba/desa/layanan/surat-keterangan-beda-biodata-diri"
},
{
id: 11,
images: "/api/img/yatim.jpeg",
name: "Surat Keterangan Yatim Piatu",
link: "/darmasaba/desa/layanan/surat-keterangan-yatim-piatu"
}
]
export default function Page() {
const router = useRouter()
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
const [search, setSearch] = useState("")
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -100,149 +34,20 @@ export default function Page() {
w={{ base: "70%", md: "50%" }}
placeholder="Cari Layanan"
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Stack>
</Container>
<Box px={{ base: "md", md: 100 }}>
{/* Bagian Pelayanan Surat Keterangan */}
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
</Box>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
{data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.images}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(v.link)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})}
</SimpleGrid>
<PelayananSuratKeterangan search={search} />
{/* Bagian Pelayanan Perizinan Berusaha */}
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS)
merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha,
Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas
Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349
Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</Text>
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
<Box p={"xl"} w={{ base: "100%", md: "100%" }} >
<Stepper active={active} onStepClick={setActive} orientation="vertical"
styles={{
separator: {
marginLeft: 25
},
step: {
padding: '12px 0'
}
}}>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
Pendaftaran akun pada portal OSS
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI ">
Memilih KBLI dengan jenis usaha yang akan didaftarkan
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
Proses verifikasi dan persetujuan oleh instansi terkait
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
</StepperStep>
<StepperCompleted >
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
</StepperCompleted>
</Stepper>
<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>Back</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
resmi OSS <a href={"https://oss.go.id/"}>(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.</Text>
</Box>
<PelayananPerizinanBerusaha/>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<Box py={15}>
<Text fz={{base: "h4", md: "h3"}} fw={"bold"}>Pelayanan Telunjuk Sakti Desa</Text>
</Box>
<Text fz={{ base: "sm", md: 'h3' }} py={10}>Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. layanan telunjuk sakti Desa meliputi :</Text>
<List type="ordered" pb={20}>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Kelahiran (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KELAHIRAN_(dengan%20contoh%20Formulir).pdf">Akta Kelahiran</a>)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Perkawinan (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20PERKAWINAN_(dengan%20contoh%20Formulir).pdf">Akta Perkawinan</a>)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Kematian (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KEMATIAN_(dengan%20contoh%20Formulir).pdf">Akata Kematian</a>)</ListItem>
</List>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<Box py={15}>
<Text fz={{base: "h4", md: "h3"}} fw={"bold"}>Pelayanan Penduduk Non-Permanent</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"}>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</Text>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
<PelayananTelunjukSaktiDesa/>
{/* Bagian Pelayanan Penduduk Non Permanent */}
<PelayananPendudukNonPermanent/>
</Box>
</Stack>
)

View File

@@ -0,0 +1,77 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(potensiDesaState.potensiDesa)
const [loading, setLoading] = useState(true)
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.findUnique.load(id);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [id])
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!state.findUnique.data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
{state.findUnique.data?.name}
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Informasi dan Pelayanan Administrasi Digital
</Text>
</Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
{state.findUnique.data?.deskripsi || ''}
</Text>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,105 +1,36 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Flex, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useTransitionRouter } from 'next-view-transitions';
import BackButton from '../layanan/_com/BackButto';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
const datamap = [
{
id: 1,
images: "/api/img/tps.png",
name: "TPS3R Pudak Mesari",
kategori: "Lingkungan",
link: "/darmasaba/desa/potensi/tps3r-pudak-mesari",
},
{
id: 2,
images: "/api/img/ack.png",
name: "Bumdes Pudak Mesari",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/bumdes-pudak-mesari",
},
{
id: 3,
images: "/api/img/taman-beji.jpg",
name: "Taman Beji Cengana",
kategori: "Wisata",
link: "/darmasaba/desa/potensi/taman-beji-cengana",
},
{
id: 4,
images: "/api/img/waterpark.png",
name: "Gumuh Sari Water Park",
kategori: "Wisata",
link: "/darmasaba/desa/potensi/gumuh-sari-water-park",
},
{
id: 5,
images: "/api/img/pertanian.jpg",
name: "Pertanian",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/pertanian"
},
{
id: 6,
images: "/api/img/warungumkm.jpg",
name: "Kawasan Kuliner",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/kawasan-kuliner"
},
{
id: 7,
images: "/api/img/ikm.png",
name: "IKM Berbasis Pengolahan Pangan",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/ikm-berbasis-pengolahan-pangan"
},
{
id: 8,
images: "/api/img/genteng.jpeg",
name: "Genteng",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/genteng"
},
{
id: 9,
images: "/api/img/peternakanlele.jpg",
name: "Peternakan Ikan Lele",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/peternakan-ikan-lele"
},
{
id: 10,
images: "/api/img/jogging.jpg",
name: "Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa",
kategori: "Lingkungan",
link: "/darmasaba/desa/potensi/jogging-track-tegeh-aban-karang-gadon-dan-munduk-uma-desa"
},
{
id: 11,
images: "/api/img/damtanahputih.jpeg",
name: "Dam Tanah Putih",
kategori: "Wisata",
link: "/darmasaba/desa/potensi/dam-tanah-putih"
},
{
id: 12,
images: "/api/img/umkm.jpeg",
name: "UMKM",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/umkm"
},
{
id: 13,
images: "/api/img/potong-daging.png",
name: "Pemotongan Daging",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/pemotongan-daging"
},
]
function Page() {
const router = useRouter()
const router = useTransitionRouter()
const [loading, setLoading] = useState(false)
const state = useProxy(potensiDesaState)
useEffect(()=> {
state.kategoriPotensi.findMany.load()
const loadData = async () => {
try {
setLoading(true)
await state.potensiDesa.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = state.potensiDesa.findMany.data
// const kategoriData = state.kategoriPotensi.findMany.data
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} >
<Box px={{ base: "md", md: 100 }}>
@@ -118,13 +49,17 @@ function Page() {
</Text>
</Box>
<Paper radius={"md"} px={"xl"} py={5} bg={colors["blue-button"]} >
<Flex justify={"space-evenly"} align={"center"} gap={"xl"}>
<Flex justify={"space-between"} align={"center"} gap={"xl"}>
<Box>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>13</Text>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata' ).length || 0}
</Text>
<Text ta={"center"} fz={"sm"} c={"white"}>Potensi</Text>
</Box>
<Box>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>5</Text>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata' ).length || 0}
</Text>
<Text ta={"center"} fz={"sm"} c={"white"}>Destinasi Wisata</Text>
</Box>
</Flex>
@@ -140,11 +75,16 @@ function Page() {
sm: 3
}}
>
{datamap.map((v, k) => {
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
data?.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.images}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
@@ -162,7 +102,7 @@ function Page() {
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Group>
<Paper radius={"lg"} py={7} px={10}>
<Text>{v.kategori}</Text>
<Text>{v.kategori?.nama}</Text>
</Paper>
</Group>
<Box p={"lg"}>
@@ -176,14 +116,15 @@ function Page() {
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(v.link)}>
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})}
})
)}
</SimpleGrid>
</Box>

View File

@@ -176,7 +176,7 @@ function LandingPage() {
</Grid.Col>
<Grid.Col span={{
base: 6,
base: 3,
lg: 2,
md: 3,
}}>
@@ -206,7 +206,7 @@ function LandingPage() {
</Grid.Col>
<Grid.Col span={{
base: 12,
lg: 8,
lg: 12,
md: 12,
}}>
<Paper

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
"use client";
import stateLayananDesa from "@/app/admin/(dashboard)/_state/desa/layananDesa";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";
import { Carousel } from "@mantine/carousel";
import {
Box,
@@ -10,6 +10,7 @@ import {
Divider,
Group,
Paper,
Skeleton,
Stack,
Text,
useMantineTheme
@@ -19,170 +20,114 @@ import Autoplay from "embla-carousel-autoplay";
import _ from "lodash";
import { useTransitionRouter } from "next-view-transitions";
import Link from "next/link";
import { useRef } from "react";
import useSWR from "swr";
import { useEffect, useRef, useState } from "react";
import { useProxy } from "valtio/utils";
const data = [
{
id: 1,
images: "/api/img/test.png",
name: "Surat Keterangan Domisili Organisasi",
link: "/darmasaba/desa/layanan/surat-keterangan-domisili"
},
{
id: 2,
images: "/api/img/test-3.jpeg",
name: "Surat Keterangan Penghasilan",
link: "/darmasaba/desa/layanan/surat-keterangan-penghasilan"
},
{
id: 3,
images: "/api/img/domisili.jpeg",
name: "Surat Keterangan Tidak Mampu",
link: "/darmasaba/desa/layanan/surat-keterangan-tidak-mampu"
},
{
id: 4,
images: "/api/img/kelahiran.jpeg",
name: "Surat Keterangan Kelahiran",
link: "/darmasaba/desa/layanan/surat-keterangan-kelahiran"
},
{
id: 5,
images: "/api/img/keterangan-usaha.jpeg",
name: "Surat Keterangan Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-usaha"
},
{
id: 6,
images: "/api/img/kematian.jpeg",
name: "Surat Keterangan Kematian",
link: "/darmasaba/desa/layanan/surat-keterangan-kematian"
},
{
id: 7,
images: "/api/img/tempatusaha.jpeg",
name: "Surat Keterangan Tempat Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-tempat-usaha"
},
{
id: 8,
images: "/api/img/belumkawin.jpeg",
name: "Surat Keterangan Belum Kawin",
link: "/darmasaba/desa/layanan/surat-keterangan-belum-kawin"
},
{
id: 9,
images: "/api/img/berkelakuan-baik.jpeg",
name: "Surat Keterangan Kelakuan Baik",
link: "/darmasaba/desa/layanan/surat-keterangan-kelakuan-baik"
},
{
id: 10,
images: "/api/img/biodata.jpeg",
name: "Surat Keterangan Beda Biodata Diri",
link: "/darmasaba/desa/layanan/surat-keterangan-beda-biodata-diri"
},
{
id: 11,
images: "/api/img/yatim.jpeg",
name: "Surat Keterangan Yatim Piatu",
link: "/darmasaba/desa/layanan/surat-keterangan-yatim-piatu"
}
]
const textHeading = {
title: "Layanan",
des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!",
title: "Layanan",
des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!",
};
function Layanan() {
const { data, isLoading } = useSWR(
"/",
(url) => ApiFetch.api.layanan.get().then(({ data }) => data?.data),
{
fallbackData: [],
}
);
const router = useTransitionRouter()
return (
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
<Container w={{ base: "100%", md: "50%" }} p={"xl"}>
<Stack align="center" gap={"0"}>
<Text fz={"3.4rem"} fw={"bold"}>
{textHeading.title}
</Text>
<Text
style={{
textAlign: "center",
}}
>
{textHeading.des}
</Text>
<Box p={"md"}>
<Button component={Link} href={"/darmasaba/desa/layanan"} variant="filled" bg={colors["blue-button"]} radius={100}>
Detail
</Button>
</Box>
</Stack>
</Container>
<Slider />
<Divider />
</Stack>
);
return (
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
<Container w={{ base: "100%", md: "50%" }} p={"xl"}>
<Stack align="center" gap={"0"}>
<Text fz={"3.4rem"} fw={"bold"}>
{textHeading.title}
</Text>
<Text
style={{
textAlign: "center",
}}
>
{textHeading.des}
</Text>
<Box p={"md"}>
<Button component={Link} href={"/darmasaba/desa/layanan"} variant="filled" bg={colors["blue-button"]} radius={100}>
Detail
</Button>
</Box>
</Stack>
</Container>
<Slider />
<Divider />
</Stack>
);
}
const height = 720;
function Slider() {
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const autoplay = useRef(Autoplay({ delay: 2000 }));
const router = useTransitionRouter()
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false);
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const autoplay = useRef(Autoplay({ delay: 2000 }));
const router = useTransitionRouter()
const slides = data.map((item) => (
<Carousel.Slide key={item.id} >
<Paper h={"100%"} pos={"relative"} style={{
backgroundImage: `url(${item.images}) `,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}>
<Box
style={{
borderRadius: 16,
zIndex: 0,
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify="space-between" h={"100%"} gap={0} p={"lg"} pos={"relative"} >
<Box p={"lg"}>
<Text
useEffect(()=> {
const loadData = async () => {
try {
setLoading(true);
await state.suratKeterangan.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}
>
{_.startCase(item.name)}
</Text>
</Box>
<Group justify="center">
<Button component={Link} href={item.link} px={46} radius={"100"} size="md" bg={colors["blue-button"]}>
Detail
</Button>
</Group>
</Stack>
</Paper>
</Carousel.Slide>
));
const data = (state.suratKeterangan.findMany.data || []).slice(0, 8);
return (
const slides = data.map((item) => (
<Carousel.Slide key={item.id} >
<Paper h={"100%"} pos={"relative"} style={{
backgroundImage: `url(${item.image?.link})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}>
<Box
style={{
borderRadius: 16,
zIndex: 0,
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify="space-between" h={"100%"} gap={0} p={"lg"} pos={"relative"} >
<Box p={"lg"}>
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}
>
{_.startCase(item.name)}
</Text>
</Box>
<Group justify="center">
<Button onClick={()=> router.push(`/darmasaba/desa/layanan/${item.id}`)} px={46} radius={"100"} size="md" bg={colors["blue-button"]}>
Detail
</Button>
</Group>
</Stack>
</Paper>
</Carousel.Slide>
));
return (
<Box>
{loading ? (
<Skeleton height={height} />
) : (
<Carousel
plugins={[autoplay.current]}
onMouseEnter={autoplay.current.stop}
@@ -196,7 +141,10 @@ function Slider() {
>
{slides}
</Carousel>
);
)}
</Box>
);
}
export default Layanan;

View File

@@ -1,8 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client";
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
import colors from "@/con/colors";
import images from "@/con/images";
import ApiFetch from "@/lib/api-fetch";
import {
BackgroundImage,
Box,
@@ -11,45 +11,13 @@ import {
Group,
SimpleGrid,
Stack,
Text,
Title,
Text
} from "@mantine/core";
import _ from "lodash";
import { motion } from "motion/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import useSWR from "swr";
const datamap = [
{
id: 1,
images: "/api/img/tps.png",
name: "TPS3R Pudak Mesari",
deskripsi: "TPS 3R Pudak Mesari Darmasaba layak mendapat penghargaan demikian apresiasi dari Delterra Sosial Indonesia nie Semeton Darmasaba!, Hal tersebut dikarenakan walaupun baru berdiri namun TPS 3R kebanggaan Desa Darmasaba tersebut sudah berjalan dengan sangat baik. ",
link: "/darmasaba/desa/potensi/tps3r-pudak-mesari"
},
{
id: 2,
images: "/api/img/ack.png",
name: "Bumdes Pudak Mesari",
deskripsi: "Bumdes Pudak Mesari sangat membantu warga desa Darmasaba dalam mengelola dan membangun sebuah desa yang lebih baik lagi",
link: "/darmasaba/desa/potensi/bumdes-pudak-mesari"
},
{
id: 3,
images: "/api/img/taman-beji.jpg",
name: "Taman Beji Cengana",
deskripsi: "Tirta Klebutan di Pura Taman Beji Cengana di Desa Adat Darmasaba, Badung, selain dipercaya nunas Taksu serta pembersihan diri. Tersemat juga asal usul cerita ditemukannya Tirta Klebutan yang tepat berada di pinggir Tukad Cengana tersebut.",
link: "/darmasaba/desa/potensi/taman-beji-cengana"
},
{
id: 4,
images: "/api/img/waterpark.png",
name: "Gumuh Sari Water Park",
deskripsi: "Gumuh Sari Rekreasi atau waterpark, tempat wisata yang asyik dan seru untuk kamu sekeluarga! Tempat liburan di Bali memang seakan nggak ada habisnya. Selalu ada aja destinasi wisata seru yang bisa jadi wishlist. Ada banyak banget tempat wisata yang kamu kunjungi di Bali, mulai dari wisata alam, wisata modern, sampai wisata air.",
link: "/darmasaba/desa/potensi/gumuh-sari-water-park"
},
]
import { useTransitionRouter } from "next-view-transitions";
import { useEffect, useState } from "react";
import { useProxy } from "valtio/utils";
@@ -59,11 +27,25 @@ const textHeading = {
};
function Potensi() {
const router = useRouter()
const { data, isLoading } = useSWR("/", (url) =>
ApiFetch.api.potensi.get().then(({ data }) => data?.data)
);
if (isLoading) return <Text>loading ...</Text>;
const router = useTransitionRouter()
const [loading, setLoading] = useState(false)
const state = useProxy(potensiDesaState.potensiDesa)
useEffect(()=> {
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
const data = (state.findMany.data || []).slice(0, 4);
return (
<Stack p={"sm"} gap={"4rem"}>
<Box
@@ -86,10 +68,10 @@ function Potensi() {
key={k}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.8 }}
onClick={() => router.push(datamap[k].link)}
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}
>
<BackgroundImage
src={datamap[k].images}
src={v.image?.link}
h={320}
key={k}
radius={16}
@@ -116,7 +98,7 @@ function Potensi() {
}}
>
<Text fw={"bold"} c={"gray.1"} size={"2.4rem"}>
{datamap[k].name}
{v.name}
</Text>
<Text
lineClamp={2}
@@ -125,7 +107,7 @@ function Potensi() {
}}
c={colors["white-1"]}
>
{datamap[k].deskripsi}
{v.deskripsi}
</Text>
</Stack>
</BackgroundImage>
@@ -134,7 +116,7 @@ function Potensi() {
</SimpleGrid>
<Stack align="center">
<Group>
<Button component={Link} href={"/darmasaba/desa/potensi"} color={colors["blue-button"]} variant="outline" radius={100} size="md">
<Button onClick={()=> router.push("/darmasaba/desa/potensi")} color={colors["blue-button"]} variant="outline" radius={100} size="md">
Selengkapnya
</Button>
</Group>