Compare commits
5 Commits
nico/19-se
...
nico/25-se
| Author | SHA1 | Date | |
|---|---|---|---|
| 33fc472472 | |||
| d8fa56d923 | |||
| cac146471a | |||
| 3e4a7a1c0a | |||
| b5c044df6e |
@@ -672,17 +672,18 @@ model GalleryVideo {
|
|||||||
|
|
||||||
// ========================================= LAYANAN DESA ========================================= //
|
// ========================================= LAYANAN DESA ========================================= //
|
||||||
model PelayananSuratKeterangan {
|
model PelayananSuratKeterangan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
|
||||||
image2Id String?
|
image2Id String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
|
AjukanPermohonan AjukanPermohonan[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model PelayananTelunjukSaktiDesa {
|
model PelayananTelunjukSaktiDesa {
|
||||||
@@ -717,6 +718,20 @@ model PelayananPendudukNonPermanen {
|
|||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model AjukanPermohonan {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String
|
||||||
|
nik String
|
||||||
|
alamat String
|
||||||
|
nomorKk String
|
||||||
|
kategori PelayananSuratKeterangan @relation(fields: [kategoriId], references: [id])
|
||||||
|
kategoriId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================= PENGHARGAAN ========================================= //
|
// ========================================= PENGHARGAAN ========================================= //
|
||||||
model Penghargaan {
|
model Penghargaan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
@@ -835,8 +850,8 @@ model JadwalKegiatan {
|
|||||||
syaratKetentuanJadwalKegiatanId String
|
syaratKetentuanJadwalKegiatanId String
|
||||||
dokumenjadwalkegiatan DokumenJadwalKegiatan @relation(fields: [dokumenJadwalKegiatanId], references: [id])
|
dokumenjadwalkegiatan DokumenJadwalKegiatan @relation(fields: [dokumenJadwalKegiatanId], references: [id])
|
||||||
dokumenJadwalKegiatanId String
|
dokumenJadwalKegiatanId String
|
||||||
pendaftaranjadwalkegiatan PendaftaranJadwalKegiatan @relation(fields: [pendaftaranJadwalKegiatanId], references: [id])
|
pendaftaranjadwalkegiatan PendaftaranJadwalKegiatan? @relation(fields: [pendaftaranJadwalKegiatanId], references: [id])
|
||||||
pendaftaranJadwalKegiatanId String
|
pendaftaranJadwalKegiatanId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
@@ -1254,15 +1269,15 @@ model KontakDaruratToItem {
|
|||||||
|
|
||||||
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
||||||
model PencegahanKriminalitas {
|
model PencegahanKriminalitas {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
judul String
|
judul String
|
||||||
deskripsi String
|
deskripsi String
|
||||||
deskripsiSingkat String
|
deskripsiSingkat String
|
||||||
linkVideo String
|
linkVideo String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= LAPORAN PUBLIK ========================================= //
|
// ========================================= LAPORAN PUBLIK ========================================= //
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 47 KiB |
@@ -71,6 +71,22 @@ const pelayananPendudukNonPermanenForm = {
|
|||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const templateAjukanForm = z.object({
|
||||||
|
nama: z.string().min(1).max(5000),
|
||||||
|
nik: z.string().min(1).max(5000),
|
||||||
|
alamat: z.string().min(1).max(5000),
|
||||||
|
nomorKk: z.string().min(1).max(5000),
|
||||||
|
kategoriId: z.string().min(1).max(5000),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultAjukanForm = {
|
||||||
|
nama: "",
|
||||||
|
nik: "",
|
||||||
|
alamat: "",
|
||||||
|
nomorKk: "",
|
||||||
|
kategoriId: "",
|
||||||
|
};
|
||||||
|
|
||||||
const suratKeterangan = proxy({
|
const suratKeterangan = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: { ...suratKeteranganForm },
|
form: { ...suratKeteranganForm },
|
||||||
@@ -146,6 +162,30 @@ const suratKeterangan = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findManyAll: {
|
||||||
|
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}>[] | null,
|
||||||
|
loading: false,
|
||||||
|
load: async () => {
|
||||||
|
suratKeterangan.findManyAll.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan["findManyAll"].get();
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
suratKeterangan.findManyAll.data = res.data.data || [];
|
||||||
|
} else {
|
||||||
|
suratKeterangan.findManyAll.data = [];
|
||||||
|
console.error("Failed to load surat keterangan all:", res.data?.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading surat keterangan all:", error);
|
||||||
|
suratKeterangan.findManyAll.data = [];
|
||||||
|
} finally {
|
||||||
|
suratKeterangan.findManyAll.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
@@ -769,11 +809,250 @@ const pelayananPendudukNonPermanen = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ajukanPermohonan = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultAjukanForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateAjukanForm.safeParse(
|
||||||
|
ajukanPermohonan.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ajukanPermohonan.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.ajukanpermohonan[
|
||||||
|
"create"
|
||||||
|
].post(ajukanPermohonan.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
ajukanPermohonan.findMany.load();
|
||||||
|
return toast.success("Ajukan permohonan berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan ajukan permohonan");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
ajukanPermohonan.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
ajukanPermohonan.create.form = { ...defaultAjukanForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as Prisma.AjukanPermohonanGetPayload<{
|
||||||
|
include: {
|
||||||
|
kategori: true;
|
||||||
|
};
|
||||||
|
}>[] | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
ajukanPermohonan.findMany.loading = true; // Use the full path to access the property
|
||||||
|
ajukanPermohonan.findMany.page = page;
|
||||||
|
ajukanPermohonan.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
const res = await ApiFetch.api.desa.ajukanpermohonan[
|
||||||
|
"findMany"
|
||||||
|
].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
ajukanPermohonan.findMany.data = res.data.data || [];
|
||||||
|
ajukanPermohonan.findMany.total = res.data.total || 0;
|
||||||
|
ajukanPermohonan.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load ajukan permohonan:", res.data?.message);
|
||||||
|
ajukanPermohonan.findMany.data = [];
|
||||||
|
ajukanPermohonan.findMany.total = 0;
|
||||||
|
ajukanPermohonan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading ajukan permohonan:", error);
|
||||||
|
ajukanPermohonan.findMany.data = [];
|
||||||
|
ajukanPermohonan.findMany.total = 0;
|
||||||
|
ajukanPermohonan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
ajukanPermohonan.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.AjukanPermohonanGetPayload<{
|
||||||
|
include: {
|
||||||
|
kategori: true;
|
||||||
|
}
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/ajukanpermohonan/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
ajukanPermohonan.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch ajukan permohonan:", res.statusText);
|
||||||
|
ajukanPermohonan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching ajukan permohonan:", error);
|
||||||
|
ajukanPermohonan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
ajukanPermohonan.delete.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/ajukanpermohonan/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Ajukan permohonan berhasil dihapus");
|
||||||
|
await ajukanPermohonan.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus ajukan permohonan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus ajukan permohonan");
|
||||||
|
} finally {
|
||||||
|
ajukanPermohonan.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultAjukanForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/ajukanpermohonan/${id}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
nama: data.nama,
|
||||||
|
nik: data.nik,
|
||||||
|
alamat: data.alamat,
|
||||||
|
nomorKk: data.nomorKk,
|
||||||
|
kategoriId: data.kategoriId,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching ajukan permohonan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateAjukanForm.safeParse(
|
||||||
|
ajukanPermohonan.edit.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ajukanPermohonan.edit.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/ajukanpermohonan/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nama: this.form.nama,
|
||||||
|
nik: this.form.nik,
|
||||||
|
alamat: this.form.alamat,
|
||||||
|
nomorKk: this.form.nomorKk,
|
||||||
|
kategoriId: this.form.kategoriId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Ajukan permohonan berhasil diupdate");
|
||||||
|
await ajukanPermohonan.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
result.message || "Gagal mengupdate ajukan permohonan"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating ajukan permohonan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update ajukan permohonan"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
ajukanPermohonan.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const stateLayananDesa = proxy({
|
const stateLayananDesa = proxy({
|
||||||
suratKeterangan,
|
suratKeterangan,
|
||||||
pelayananPerizinanBerusaha,
|
pelayananPerizinanBerusaha,
|
||||||
pelayananTelunjukSaktiDesa,
|
pelayananTelunjukSaktiDesa,
|
||||||
pelayananPendudukNonPermanen,
|
pelayananPendudukNonPermanen,
|
||||||
|
ajukanPermohonan,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stateLayananDesa;
|
export default stateLayananDesa;
|
||||||
|
|||||||
@@ -26,14 +26,6 @@ const templateForm = z.object({
|
|||||||
dokumenJadwalKegiatan: z.object({
|
dokumenJadwalKegiatan: z.object({
|
||||||
content: z.string().min(1, "Content minimal 1 karakter"),
|
content: z.string().min(1, "Content minimal 1 karakter"),
|
||||||
}),
|
}),
|
||||||
pendaftaranJadwalKegiatan: z.object({
|
|
||||||
name: z.string().min(1, "Name minimal 1 karakter"),
|
|
||||||
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
|
|
||||||
namaOrangtua: z.string().min(1, "Nama Orangtua minimal 1 karakter"),
|
|
||||||
nomor: z.string().min(1, "Nomor minimal 1 karakter"),
|
|
||||||
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
|
|
||||||
catatan: z.string().min(1, "Catatan minimal 1 karakter"),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
@@ -55,15 +47,7 @@ const defaultForm = {
|
|||||||
},
|
},
|
||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: "",
|
content: "",
|
||||||
},
|
}
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: "",
|
|
||||||
tanggal: "",
|
|
||||||
namaOrangtua: "",
|
|
||||||
nomor: "",
|
|
||||||
alamat: "",
|
|
||||||
catatan: "",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const jadwalkegiatanState = proxy({
|
const jadwalkegiatanState = proxy({
|
||||||
@@ -116,7 +100,6 @@ const jadwalkegiatanState = proxy({
|
|||||||
deskripsijadwalkegiatan: true;
|
deskripsijadwalkegiatan: true;
|
||||||
layananjadwalkegiatan: true;
|
layananjadwalkegiatan: true;
|
||||||
dokumenjadwalkegiatan: true;
|
dokumenjadwalkegiatan: true;
|
||||||
pendaftaranjadwalkegiatan: true;
|
|
||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
@@ -161,7 +144,6 @@ const jadwalkegiatanState = proxy({
|
|||||||
layananjadwalkegiatan: true;
|
layananjadwalkegiatan: true;
|
||||||
syaratketentuanjadwalkegiatan: true;
|
syaratketentuanjadwalkegiatan: true;
|
||||||
dokumenjadwalkegiatan: true;
|
dokumenjadwalkegiatan: true;
|
||||||
pendaftaranjadwalkegiatan: true;
|
|
||||||
};
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -209,15 +191,7 @@ const jadwalkegiatanState = proxy({
|
|||||||
},
|
},
|
||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: data.dokumenjadwalkegiatan.content,
|
content: data.dokumenjadwalkegiatan.content,
|
||||||
},
|
}
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: data.pendaftaranjadwalkegiatan.name,
|
|
||||||
tanggal: data.pendaftaranjadwalkegiatan.tanggal,
|
|
||||||
namaOrangtua: data.pendaftaranjadwalkegiatan.namaOrangtua,
|
|
||||||
nomor: data.pendaftaranjadwalkegiatan.nomor,
|
|
||||||
alamat: data.pendaftaranjadwalkegiatan.alamat,
|
|
||||||
catatan: data.pendaftaranjadwalkegiatan.catatan,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
@@ -259,20 +233,6 @@ const jadwalkegiatanState = proxy({
|
|||||||
content:
|
content:
|
||||||
jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
|
jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
|
||||||
},
|
},
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.name,
|
|
||||||
tanggal:
|
|
||||||
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.tanggal,
|
|
||||||
namaOrangtua:
|
|
||||||
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan
|
|
||||||
.namaOrangtua,
|
|
||||||
nomor:
|
|
||||||
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.nomor,
|
|
||||||
alamat:
|
|
||||||
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.alamat,
|
|
||||||
catatan:
|
|
||||||
jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.catatan,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
|
|||||||
@@ -354,14 +354,39 @@ const kategoriKegiatan = proxy({
|
|||||||
id: string;
|
id: string;
|
||||||
nama: string;
|
nama: string;
|
||||||
}> | null,
|
}> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
kategoriKegiatan.findMany.data = res.data?.data ?? [];
|
kategoriKegiatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
}
|
kategoriKegiatan.findMany.page = page;
|
||||||
},
|
kategoriKegiatan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res =
|
||||||
|
await ApiFetch.api.lingkungan.kategorikegiatan[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kategoriKegiatan.findMany.data = res.data.data ?? [];
|
||||||
|
kategoriKegiatan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kategoriKegiatan.findMany.data = [];
|
||||||
|
kategoriKegiatan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kategori kegiatan paginated:", err);
|
||||||
|
kategoriKegiatan.findMany.data = [];
|
||||||
|
kategoriKegiatan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kategoriKegiatan.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.KategoriKegiatanGetPayload<{
|
data: null as Prisma.KategoriKegiatanGetPayload<{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import colors from '@/con/colors';
|
|||||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react';
|
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers, IconUsersPlus } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -37,6 +37,13 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
|||||||
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent",
|
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent",
|
||||||
icon: <IconUsers size={18} stroke={1.8} />,
|
icon: <IconUsers size={18} stroke={1.8} />,
|
||||||
tooltip: "Pendataan penduduk non-permanent"
|
tooltip: "Pendataan penduduk non-permanent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Ajukan Permohonan",
|
||||||
|
value: "ajukanpermohonan",
|
||||||
|
href: "/admin/desa/layanan/ajukan_permohonan",
|
||||||
|
icon: <IconUsersPlus size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Ajukan permohonan"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -18,7 +17,7 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
@@ -87,7 +86,6 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Kategori</TableTh>
|
<TableTh style={{ width: '20%' }}>Kategori</TableTh>
|
||||||
<TableTh style={{ width: '25%' }}>Gambar</TableTh>
|
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -96,7 +94,7 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '30%' }}>
|
<TableTd style={{ width: '30%' }}>
|
||||||
<Box w={200}>
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.judul}
|
{item.judul}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -107,19 +105,6 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
{item.kategoriBerita?.name || '-'}
|
{item.kategoriBerita?.name || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '25%' }}>
|
|
||||||
<Box
|
|
||||||
w={80}
|
|
||||||
h={80}
|
|
||||||
style={{ borderRadius: 8, overflow: 'hidden' }}
|
|
||||||
>
|
|
||||||
{item.image?.link ? (
|
|
||||||
<Image loading='lazy' src={item.image.link} alt="gambar" fit="cover" />
|
|
||||||
) : (
|
|
||||||
<Box bg={colors['blue-button']} w="100%" h="100%" />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '15%' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditAjukanPermohonan() {
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const stateAjukan = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
nama: stateAjukan.edit.form.nama,
|
||||||
|
nik: stateAjukan.edit.form.nik,
|
||||||
|
alamat: stateAjukan.edit.form.alamat,
|
||||||
|
nomorKk: stateAjukan.edit.form.nomorKk,
|
||||||
|
kategoriId: stateAjukan.edit.form.kategoriId,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
stateLayananDesa.suratKeterangan.findManyAll.load();
|
||||||
|
const loadAjukan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await stateAjukan.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
nama: data.nama || '',
|
||||||
|
nik: data.nik || '',
|
||||||
|
alamat: data.alamat || '',
|
||||||
|
nomorKk: data.nomorKk || '',
|
||||||
|
kategoriId: data.kategoriId || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading ajukan:', error);
|
||||||
|
toast.error('Gagal memuat data ajukan');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAjukan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
stateAjukan.edit.form = {
|
||||||
|
...stateAjukan.edit.form,
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
toast.success('Ajukan berhasil diperbarui!');
|
||||||
|
router.push('/admin/desa/layanan/ajukan_permohonan');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating ajukan:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui ajukan');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
|
{/* Back Button */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Ajukan Permohonan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama"
|
||||||
|
placeholder="Masukkan nama"
|
||||||
|
value={formData.nama}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label="NIK"
|
||||||
|
placeholder="Masukkan NIK"
|
||||||
|
value={formData.nik}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nik: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label="Nomor KK"
|
||||||
|
placeholder="Masukkan nomor KK"
|
||||||
|
value={formData.nomorKk}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nomorKk: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Kategori"
|
||||||
|
placeholder="Pilih kategori"
|
||||||
|
data={stateLayananDesa.suratKeterangan.findManyAll.data?.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}))}
|
||||||
|
value={formData.kategoriId || null}
|
||||||
|
onChange={(val: string | null) => {
|
||||||
|
if (val) {
|
||||||
|
const selected = stateLayananDesa.suratKeterangan.findMany.data?.find(
|
||||||
|
(item) => item.id === val
|
||||||
|
);
|
||||||
|
if (selected) {
|
||||||
|
stateAjukan.edit.form.kategoriId = selected.id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stateAjukan.edit.form.kategoriId = '';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditAjukanPermohonan;
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
'use client'
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function DetailAjukanPermohonan() {
|
||||||
|
const ajukanPermohonanState = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const params = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
ajukanPermohonanState.findUnique.load(params?.id as string);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
ajukanPermohonanState.delete.byId(selectedId);
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
router.push('/admin/desa/layanan/ajukan_permohonan');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ajukanPermohonanState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ajukanPermohonanState.findUnique.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
{/* Tombol Kembali */}
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: '100%', md: '60%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Surat Keterangan
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">
|
||||||
|
Nama
|
||||||
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data?.nama || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">
|
||||||
|
NIK
|
||||||
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data?.nik || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">
|
||||||
|
Alamat
|
||||||
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data?.alamat || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">
|
||||||
|
Nomor KK
|
||||||
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data?.nomorKk || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">
|
||||||
|
Kategori
|
||||||
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data?.kategori.name || '-'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Surat" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
disabled={ajukanPermohonanState.delete.loading}
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Surat" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/desa/layanan/ajukan_permohonan/${data.id}/edit`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah Anda yakin ingin menghapus ajukan permohonan ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailAjukanPermohonan;
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||||
|
|
||||||
|
function AjukanPermohonan() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Pelayanan Ajukan Permohonan'
|
||||||
|
placeholder='Cari nama atau deskripsi...'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListAjukanPermohonan search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListAjukanPermohonan({ search }: { search: string }) {
|
||||||
|
const AjukanPermohonanState = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = AjukanPermohonanState.findMany;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load(page, 10, search);
|
||||||
|
}, [page, search]);
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={600} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
|
<Title order={4}>List Ajukan Permohonan</Title>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '30%' }}>Nama</TableTh>
|
||||||
|
<TableTh style={{ width: '45%' }}>Alamat</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>NIK</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{data.length > 0 ? (
|
||||||
|
data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd style={{ width: '30%' }}>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ width: '45%' }}>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.alamat}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ width: '45%' }}>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.nik}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ width: '15%' }}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/desa/layanan/ajukan_permohonan/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={3}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data ajukan permohonan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AjukanPermohonan;
|
||||||
@@ -94,9 +94,11 @@ function ListPengumuman({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.judul}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.judul}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text fz="sm" c="dimmed">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip, Pagination } from '@mantine/core';
|
import { Box, Button, Center, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip, Pagination, Group } from '@mantine/core';
|
||||||
import { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react';
|
import { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -60,7 +60,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
|
|||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Box style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 }}>
|
<Group justify="space-between">
|
||||||
<Title order={4}>List Kategori Potensi</Title>
|
<Title order={4}>List Kategori Potensi</Title>
|
||||||
<Tooltip label="Tambah Kategori Potensi" withArrow>
|
<Tooltip label="Tambah Kategori Potensi" withArrow>
|
||||||
<Button
|
<Button
|
||||||
@@ -72,7 +72,7 @@ function ListKategoriPotensi({ search }: { search: string }) {
|
|||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
|
<Table highlightOnHover striped withRowBorders style={{ minWidth: '700px' }}>
|
||||||
|
|||||||
@@ -114,12 +114,22 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.name}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{item.dokterdantenagamedis?.name || '-'}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{item.tarifdanlayanan?.layanan || '-'}
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.dokterdantenagamedis?.name || '-'}</TableTd>
|
|
||||||
<TableTd>{item.tarifdanlayanan?.layanan || '-'}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
|
|||||||
@@ -149,16 +149,30 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.nama}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
<Box w={150}>
|
||||||
day: '2-digit',
|
{item.nama}
|
||||||
month: 'long',
|
</Box>
|
||||||
year: 'numeric',
|
</TableTd>
|
||||||
})}
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{item.jenisKelamin}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{item.penyakit}
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.jenisKelamin}</TableTd>
|
|
||||||
<TableTd>{item.penyakit}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -212,24 +226,26 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
|
|||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
|
||||||
{mounted && diseaseChartData.length > 0 ? (
|
{mounted && diseaseChartData.length > 0 ? (
|
||||||
<BarChart
|
<Center>
|
||||||
width={isMobile ? 450 : isTablet ? 500 : 550}
|
<BarChart
|
||||||
height={350}
|
width={isMobile ? 320 : isTablet ? 600 : 800} // kecilin biar muat
|
||||||
data={diseaseChartData}
|
height={350}
|
||||||
>
|
data={diseaseChartData}
|
||||||
<XAxis
|
>
|
||||||
dataKey="name"
|
<XAxis
|
||||||
tick={{ fontSize: 12 }}
|
dataKey="name"
|
||||||
interval={0}
|
tick={{ fontSize: 12 }}
|
||||||
angle={-45}
|
interval={0}
|
||||||
textAnchor="end"
|
angle={-45}
|
||||||
height={70}
|
textAnchor="end"
|
||||||
/>
|
height={70}
|
||||||
<YAxis />
|
/>
|
||||||
<ChartTooltip />
|
<YAxis />
|
||||||
<Legend />
|
<ChartTooltip />
|
||||||
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
|
<Legend />
|
||||||
</BarChart>
|
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
|
||||||
|
</BarChart>
|
||||||
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,14 +41,6 @@ interface JadwalKegiatanFormBase {
|
|||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: string;
|
|
||||||
tanggal: string;
|
|
||||||
namaOrangtua: string;
|
|
||||||
nomor: string;
|
|
||||||
alamat: string;
|
|
||||||
catatan: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditJadwalKegiatan() {
|
function EditJadwalKegiatan() {
|
||||||
@@ -76,14 +68,6 @@ function EditJadwalKegiatan() {
|
|||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: stateJadwalKegiatan.edit.form.dokumenJadwalKegiatan?.content || '',
|
content: stateJadwalKegiatan.edit.form.dokumenJadwalKegiatan?.content || '',
|
||||||
},
|
},
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: stateJadwalKegiatan.edit.form.pendaftaranJadwalKegiatan?.name || '',
|
|
||||||
tanggal: stateJadwalKegiatan.edit.form.pendaftaranJadwalKegiatan?.tanggal || '',
|
|
||||||
namaOrangtua: stateJadwalKegiatan.edit.form.pendaftaranJadwalKegiatan?.namaOrangtua || '',
|
|
||||||
nomor: stateJadwalKegiatan.edit.form.pendaftaranJadwalKegiatan?.nomor || '',
|
|
||||||
alamat: stateJadwalKegiatan.edit.form.pendaftaranJadwalKegiatan?.alamat || '',
|
|
||||||
catatan: stateJadwalKegiatan.edit.form.pendaftaranJadwalKegiatan?.catatan || '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -115,14 +99,6 @@ function EditJadwalKegiatan() {
|
|||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: form.dokumenJadwalKegiatan?.content || '',
|
content: form.dokumenJadwalKegiatan?.content || '',
|
||||||
},
|
},
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: form.pendaftaranJadwalKegiatan?.name || '',
|
|
||||||
tanggal: form.pendaftaranJadwalKegiatan?.tanggal || '',
|
|
||||||
namaOrangtua: form.pendaftaranJadwalKegiatan?.namaOrangtua || '',
|
|
||||||
nomor: form.pendaftaranJadwalKegiatan?.nomor || '',
|
|
||||||
alamat: form.pendaftaranJadwalKegiatan?.alamat || '',
|
|
||||||
catatan: form.pendaftaranJadwalKegiatan?.catatan || '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -142,8 +118,7 @@ function EditJadwalKegiatan() {
|
|||||||
deskripsiJadwalKegiatan: { ...formData.deskripsiJadwalKegiatan },
|
deskripsiJadwalKegiatan: { ...formData.deskripsiJadwalKegiatan },
|
||||||
layananJadwalKegiatan: { ...formData.layananJadwalKegiatan },
|
layananJadwalKegiatan: { ...formData.layananJadwalKegiatan },
|
||||||
syaratKetentuanJadwalKegiatan: { ...formData.syaratKetentuanJadwalKegiatan },
|
syaratKetentuanJadwalKegiatan: { ...formData.syaratKetentuanJadwalKegiatan },
|
||||||
dokumenJadwalKegiatan: { ...formData.dokumenJadwalKegiatan },
|
dokumenJadwalKegiatan: { ...formData.dokumenJadwalKegiatan }
|
||||||
pendaftaranJadwalKegiatan: { ...formData.pendaftaranJadwalKegiatan },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const success = await stateJadwalKegiatan.edit.submit();
|
const success = await stateJadwalKegiatan.edit.submit();
|
||||||
@@ -252,7 +227,7 @@ function EditJadwalKegiatan() {
|
|||||||
|
|
||||||
{/* Dokumen */}
|
{/* Dokumen */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold">Dokumen Jadwal Kegiatan</Text>
|
<Text fz="md" fw="bold">Dokumen Yang Perlu Dibawa</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.dokumenJadwalKegiatan.content}
|
value={formData.dokumenJadwalKegiatan.content}
|
||||||
onChange={(val) => setFormData((prev) => ({
|
onChange={(val) => setFormData((prev) => ({
|
||||||
@@ -262,41 +237,6 @@ function EditJadwalKegiatan() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Pendaftaran */}
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold">Pendaftaran Jadwal Kegiatan</Text>
|
|
||||||
<TextInput label="Nama" value={formData.pendaftaranJadwalKegiatan.name}
|
|
||||||
onChange={(e) => setFormData((prev) => ({
|
|
||||||
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, name: e.target.value }
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<TextInput type="date" label="Tanggal" value={formData.pendaftaranJadwalKegiatan.tanggal}
|
|
||||||
onChange={(e) => setFormData((prev) => ({
|
|
||||||
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, tanggal: e.target.value }
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<TextInput label="Nama Orangtua" value={formData.pendaftaranJadwalKegiatan.namaOrangtua}
|
|
||||||
onChange={(e) => setFormData((prev) => ({
|
|
||||||
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, namaOrangtua: e.target.value }
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<TextInput label="Nomor" value={formData.pendaftaranJadwalKegiatan.nomor}
|
|
||||||
onChange={(e) => setFormData((prev) => ({
|
|
||||||
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, nomor: e.target.value }
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<TextInput label="Alamat" value={formData.pendaftaranJadwalKegiatan.alamat}
|
|
||||||
onChange={(e) => setFormData((prev) => ({
|
|
||||||
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, alamat: e.target.value }
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<TextInput label="Catatan" value={formData.pendaftaranJadwalKegiatan.catatan}
|
|
||||||
onChange={(e) => setFormData((prev) => ({
|
|
||||||
...prev, pendaftaranJadwalKegiatan: { ...prev.pendaftaranJadwalKegiatan, catatan: e.target.value }
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -109,18 +109,7 @@ function DetailJadwalKegiatan() {
|
|||||||
<Text fz="lg" fw="bold">Dokumen</Text>
|
<Text fz="lg" fw="bold">Dokumen</Text>
|
||||||
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.dokumenjadwalkegiatan.content }} />
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.dokumenjadwalkegiatan.content }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Prosedur Pendaftaran */}
|
|
||||||
<Box>
|
|
||||||
<Text fz="lg" fw="bold">Prosedur Pendaftaran</Text>
|
|
||||||
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.name}</Text>
|
|
||||||
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.tanggal}</Text>
|
|
||||||
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.namaOrangtua}</Text>
|
|
||||||
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.nomor}</Text>
|
|
||||||
<Text fz="md" c="dimmed">{data.pendaftaranjadwalkegiatan.alamat}</Text>
|
|
||||||
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.pendaftaranjadwalkegiatan.catatan }} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Aksi */}
|
{/* Aksi */}
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<Tooltip label="Hapus Data" withArrow position="top">
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
|
|||||||
@@ -42,15 +42,7 @@ function CreateJadwalKegiatan() {
|
|||||||
},
|
},
|
||||||
dokumenJadwalKegiatan: {
|
dokumenJadwalKegiatan: {
|
||||||
content: '',
|
content: '',
|
||||||
},
|
}
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: '',
|
|
||||||
tanggal: '',
|
|
||||||
namaOrangtua: '',
|
|
||||||
nomor: '',
|
|
||||||
alamat: '',
|
|
||||||
catatan: '',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,7 +165,7 @@ function CreateJadwalKegiatan() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="md" fw="bold" mb="sm">Dokumen</Text>
|
<Text fz="md" fw="bold" mb="sm">Dokumen Yang Perlu Dibawa</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateJadwalKegiatan.create.form.dokumenJadwalKegiatan.content}
|
value={stateJadwalKegiatan.create.form.dokumenJadwalKegiatan.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -181,65 +173,6 @@ function CreateJadwalKegiatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Text fz="md" fw="bold" mb="sm">Pendaftaran Jadwal Kegiatan</Text>
|
|
||||||
<TextInput
|
|
||||||
label="Nama"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan nama"
|
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.name}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.name = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
type="date"
|
|
||||||
required
|
|
||||||
label="Tanggal"
|
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.tanggal}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.tanggal = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Nama Orangtua"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan nama orangtua"
|
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.namaOrangtua}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.namaOrangtua = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Nomor"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan nomor"
|
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.nomor}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.nomor = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Alamat"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan alamat"
|
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.alamat}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.alamat = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Catatan"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan catatan"
|
|
||||||
value={stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.catatan}
|
|
||||||
onChange={(e) => {
|
|
||||||
stateJadwalKegiatan.create.form.pendaftaranJadwalKegiatan.catatan = e.target.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Save Button */}
|
{/* Save Button */}
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -111,11 +111,14 @@ function ListJadwalKegiatan({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.informasijadwalkegiatan.name}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.informasijadwalkegiatan.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString(
|
{new Date(item.informasijadwalkegiatan.tanggal).toLocaleDateString(
|
||||||
'id-ID',
|
'id-ID',
|
||||||
{
|
{
|
||||||
@@ -124,12 +127,19 @@ function ListJadwalKegiatan({ search }: { search: string }) {
|
|||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.informasijadwalkegiatan.waktu}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Box w={150}>
|
||||||
{item.informasijadwalkegiatan.lokasi}
|
{item.informasijadwalkegiatan.waktu}
|
||||||
</Text>
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
<Text truncate fz="sm" c="dimmed">
|
||||||
|
{item.informasijadwalkegiatan.lokasi}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -124,22 +124,32 @@ function ListKelahiran({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.nama}
|
{item.nama}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})}
|
})}
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.jenisKelamin}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{item.jenisKelamin}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Text truncate fz="sm" c="dimmed">
|
||||||
{item.alamat}
|
{item.alamat}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -13,152 +13,150 @@ import colors from '@/con/colors';
|
|||||||
|
|
||||||
|
|
||||||
function DetailKematian() {
|
function DetailKematian() {
|
||||||
const state = useProxy(persentaseKelahiranKematian.kematian);
|
const state = useProxy(persentaseKelahiranKematian.kematian);
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findUnique.load(params?.id as string);
|
state.findUnique.load(params?.id as string);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
state.delete.byId(selectedId);
|
state.delete.byId(selectedId);
|
||||||
setModalHapus(false);
|
setModalHapus(false);
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran");
|
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (!state.findUnique.data) {
|
if (!state.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={500} radius="md" />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const data = state.findUnique.data;
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
{/* Tombol kembali */}
|
{/* Tombol kembali */}
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
mb={15}
|
mb={15}
|
||||||
>
|
>
|
||||||
Kembali
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "50%" }}
|
w={{ base: "100%", md: "50%" }}
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
Detail Data Kematian
|
Detail Data Kematian
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
|
||||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Nama</Text>
|
<Text fz="lg" fw="bold">Nama</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.nama || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.nama || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Tanggal</Text>
|
<Text fz="lg" fw="bold">Tanggal</Text>
|
||||||
<Text fz="md" c="dimmed">
|
<Text fz="md" c="dimmed">
|
||||||
{data?.tanggal instanceof Date
|
{new Date(data.tanggal).toLocaleDateString("id-ID", {
|
||||||
? data.tanggal.toLocaleDateString('id-ID', {
|
day: "2-digit",
|
||||||
day: '2-digit',
|
month: "long",
|
||||||
month: 'long',
|
year: "numeric",
|
||||||
year: 'numeric'
|
})}
|
||||||
})
|
</Text>
|
||||||
: data?.tanggal || '-'}
|
</Box>
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Jenis Kelamin</Text>
|
<Text fz="lg" fw="bold">Jenis Kelamin</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.jenisKelamin || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.jenisKelamin || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Alamat</Text>
|
<Text fz="lg" fw="bold">Alamat</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.alamat || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.alamat || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Penyebab</Text>
|
<Text fz="lg" fw="bold">Penyebab</Text>
|
||||||
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.penyebab || '-' }} />
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.penyebab || '-' }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<Tooltip label="Hapus Data" withArrow position="top">
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedId(data.id);
|
setSelectedId(data.id);
|
||||||
setModalHapus(true);
|
setModalHapus(true);
|
||||||
}}
|
}}
|
||||||
variant="light"
|
variant="light"
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
>
|
>
|
||||||
<IconTrash size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
<Tooltip label="Edit Data" withArrow position="top">
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
onClick={() => router.push(
|
onClick={() => router.push(
|
||||||
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${data.id}/edit`
|
`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kematian/${data.id}/edit`
|
||||||
)}
|
)}
|
||||||
variant="light"
|
variant="light"
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah Anda yakin ingin menghapus data ini?"
|
text="Apakah Anda yakin ingin menghapus data ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -122,19 +122,33 @@ function ListKematian({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.nama}
|
{item.nama}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})}
|
})}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
{item.jenisKelamin}
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
<Text truncate fz="sm" c="dimmed">
|
||||||
|
{item.alamat}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.jenisKelamin}</TableTd>
|
|
||||||
<TableTd>{item.alamat}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Image,
|
Group,
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -17,16 +17,15 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
Group,
|
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
|
||||||
import HeaderSearch from '../../_com/header';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import infoWabahPenyakit from '../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import infoWabahPenyakit from '../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
|
||||||
function InfoWabahPenyakit() {
|
function InfoWabahPenyakit() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -96,7 +95,6 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Deskripsi Singkat</TableTh>
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -105,17 +103,18 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={200}>
|
||||||
{item.name}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Box w={200}>
|
||||||
{item.deskripsiSingkat}
|
<Text truncate fz="sm" c="dimmed">
|
||||||
</Text>
|
{item.deskripsiSingkat}
|
||||||
</TableTd>
|
</Text>
|
||||||
<TableTd>
|
</Box>
|
||||||
<Image w={100} src={item.image?.link} alt="image" radius="md" loading="lazy"/>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Image,
|
Group,
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
@@ -71,7 +71,7 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
{/* Judul + Tombol Tambah */}
|
{/* Judul + Tombol Tambah */}
|
||||||
<Stack mb="md" gap="sm">
|
<Stack mb="md" gap="sm">
|
||||||
<Box display="flex" style={{ justifyContent: "space-between", alignItems: "center" }}>
|
<Group justify="space-between">
|
||||||
<Title order={4}>Daftar Kontak Darurat</Title>
|
<Title order={4}>Daftar Kontak Darurat</Title>
|
||||||
<Tooltip label="Tambah Kontak Darurat" withArrow>
|
<Tooltip label="Tambah Kontak Darurat" withArrow>
|
||||||
<Button
|
<Button
|
||||||
@@ -83,7 +83,7 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Tabel */}
|
{/* Tabel */}
|
||||||
@@ -93,7 +93,6 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -102,15 +101,16 @@ function ListKontakDarurat({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.name}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Box w={200}>
|
||||||
</TableTd>
|
<Text truncate fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
<TableTd>
|
</Box>
|
||||||
<Image w={100} src={item.image?.link} alt="image" radius="md" loading="lazy"/>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -18,15 +17,15 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
|
||||||
import HeaderSearch from '../../_com/header';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import penangananDarurat from '../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import penangananDarurat from '../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
|
||||||
function PenangananDarurat() {
|
function PenangananDarurat() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -91,7 +90,6 @@ function ListPenangananDarurat({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Gambar</TableTh>
|
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -100,21 +98,22 @@ function ListPenangananDarurat({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.name}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text
|
<Box w={200}>
|
||||||
fz="sm"
|
<Text
|
||||||
c="dimmed"
|
fz="sm"
|
||||||
truncate
|
c="dimmed"
|
||||||
lineClamp={1}
|
truncate
|
||||||
|
lineClamp={1}
|
||||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
/>
|
/>
|
||||||
</TableTd>
|
</Box>
|
||||||
<TableTd>
|
|
||||||
<Image w={100} src={item.image?.link} alt="image" loading="lazy"/>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -107,21 +107,28 @@ function ListPosyandu({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd style={{ width: '25%' }}>
|
||||||
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
|
<Box w={150}>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Text truncate fz="sm" c="dimmed">
|
||||||
{item.nomor || '-'}
|
{item.nomor || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '30%' }}>
|
<TableTd style={{ width: '30%' }}>
|
||||||
|
<Box w={150}>
|
||||||
<Text
|
<Text
|
||||||
|
lineClamp={1}
|
||||||
truncate
|
truncate
|
||||||
fz="sm"
|
fz="sm"
|
||||||
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '15%' }}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -18,15 +17,15 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
|
||||||
import HeaderSearch from '../../_com/header';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import programKesehatan from '../../_state/kesehatan/program-kesehatan/programKesehatan';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import programKesehatan from '../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
|
|
||||||
function ProgramKesehatan() {
|
function ProgramKesehatan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -90,7 +89,7 @@ function ListProgramKesehatan({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Deskripsi Singkat</TableTh>
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -105,11 +104,13 @@ function ListProgramKesehatan({ search }: { search: string }) {
|
|||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Box w={200}>
|
||||||
<Text fz="sm" truncate="end" lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
|
<Text fz="sm" truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image?.link} alt="image" radius="md" loading="lazy"/>
|
<Box w={200}>
|
||||||
|
<Text fz="sm" truncate="end" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -78,8 +78,9 @@ function DetailPuskesmas() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Jam Operasional</Text>
|
<Text fz="lg" fw="bold">Jam Operasional</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.jam?.workDays || '-'}</Text>
|
<Text fz="md" c="dimmed">Senin - Jumat</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.jam?.weekDays || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.jam?.workDays || '-'} - {data?.jam?.weekDays || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">Sabtu - Minggu / Hari Libur</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.jam?.holiday || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.jam?.holiday || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -94,9 +95,13 @@ function DetailPuskesmas() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Kontak</Text>
|
<Text fz="lg" fw="bold">Kontak</Text>
|
||||||
|
<Text fz="md" c="dimmed">Kontak Puskesmas</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.kontak?.kontakPuskesmas || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.kontak?.kontakPuskesmas || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">Email</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.kontak?.email || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.kontak?.email || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">Facebook</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.kontak?.facebook || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.kontak?.facebook || '-'}</Text>
|
||||||
|
<Text fz="md" c="dimmed">Kontak UGD</Text>
|
||||||
<Text fz="md" c="dimmed">{data?.kontak?.kontakUGD || '-'}</Text>
|
<Text fz="md" c="dimmed">{data?.kontak?.kontakUGD || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -18,7 +17,7 @@ import {
|
|||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
@@ -90,7 +89,7 @@ function ListPuskesmas({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Puskesmas</TableTh>
|
<TableTh>Nama Puskesmas</TableTh>
|
||||||
<TableTh>Alamat</TableTh>
|
<TableTh>Alamat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Kontak</TableTh>
|
||||||
<TableTh>Aksi</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
@@ -99,13 +98,25 @@ function ListPuskesmas({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={150}>
|
||||||
{item.name}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={item.image.link} alt="image" radius="md" loading="lazy"/>
|
<Box w={150}>
|
||||||
|
<Text truncate fz="sm" c="dimmed" lineClamp={1}>
|
||||||
|
{item.alamat}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={150}>
|
||||||
|
<Text truncate fz="sm" c="dimmed" lineClamp={1}>
|
||||||
|
{item.kontak.kontakPuskesmas}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ function ListAPBDes({ search }: { search: string }) {
|
|||||||
<Text fw={500} truncate="end">{item.name}</Text>
|
<Text fw={500} truncate="end">{item.name}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text>Rp. {item.jumlah}</Text>
|
<Box w={150}>
|
||||||
|
<Text>Rp. {item.jumlah}</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
{item.file?.link ? (
|
{item.file?.link ? (
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fw={500}>{item.name}</Text>
|
<Box w={200}>
|
||||||
|
<Text fw={500} lineClamp={1}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Tooltip label="Edit" withArrow>
|
<Tooltip label="Edit" withArrow>
|
||||||
|
|||||||
@@ -98,9 +98,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
|||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '30%' }}>
|
<TableTd style={{ width: '30%' }}>
|
||||||
<Text fz="sm" c="dimmed">
|
<Box w={200}>
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={1}>
|
||||||
{item.kategori?.name || '-'}
|
{item.kategori?.name || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
<TableTd style={{ width: '20%', textAlign: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ function Page() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={220}
|
size={180}
|
||||||
data={donutDataJenisKelamin}
|
data={donutDataJenisKelamin}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -185,7 +185,7 @@ function Page() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={220}
|
size={180}
|
||||||
data={donutDataRating}
|
data={donutDataRating}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -216,7 +216,7 @@ function Page() {
|
|||||||
withLabels
|
withLabels
|
||||||
withTooltip
|
withTooltip
|
||||||
labelsType="percent"
|
labelsType="percent"
|
||||||
size={220}
|
size={180}
|
||||||
data={donutDataKelompokUmur}
|
data={donutDataKelompokUmur}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|||||||
@@ -88,66 +88,74 @@ function ListResponden({ search }: ListRespondenProps) {
|
|||||||
<Title order={4} mb="sm">
|
<Title order={4} mb="sm">
|
||||||
Daftar Responden
|
Daftar Responden
|
||||||
</Title>
|
</Title>
|
||||||
<Table
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
striped
|
<Table
|
||||||
highlightOnHover
|
striped
|
||||||
withRowBorders
|
highlightOnHover
|
||||||
verticalSpacing="sm"
|
withRowBorders
|
||||||
>
|
verticalSpacing="sm"
|
||||||
<TableThead>
|
>
|
||||||
<TableTr>
|
<TableThead>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
|
||||||
<TableTh style={{ width: '25%', textAlign: 'center' }}>Nama</TableTh>
|
|
||||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
|
||||||
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
|
||||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.length === 0 ? (
|
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={5}>
|
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||||
<Text ta="center" c="dimmed">
|
<TableTh style={{ width: '25%', textAlign: 'center' }}>Nama</TableTh>
|
||||||
Tidak ditemukan data dengan kata kunci pencarian
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
||||||
</Text>
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
||||||
</TableTd>
|
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
) : (
|
</TableThead>
|
||||||
filteredData.map((item, index) => (
|
<TableTbody>
|
||||||
<TableTr key={item.id}>
|
{filteredData.length === 0 ? (
|
||||||
<TableTd ta="center">{index + 1}</TableTd>
|
<TableTr>
|
||||||
<TableTd ta="center">{item.name}</TableTd>
|
<TableTd colSpan={5}>
|
||||||
<TableTd ta="center">
|
<Text ta="center" c="dimmed">
|
||||||
{item.tanggal
|
Tidak ditemukan data dengan kata kunci pencarian
|
||||||
? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
) : (
|
||||||
|
filteredData.map((item, index) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd ta="center">{index + 1}</TableTd>
|
||||||
|
<TableTd ta="center">{item.name}</TableTd>
|
||||||
|
<TableTd ta="center">
|
||||||
|
<Box w={150}>
|
||||||
|
{item.tanggal
|
||||||
|
? new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})
|
})
|
||||||
: '-'}
|
: '-'}
|
||||||
</TableTd>
|
</Box>
|
||||||
<TableTd ta="center">{item.jenisKelamin.name}</TableTd>
|
</TableTd>
|
||||||
<TableTd ta="center">
|
<TableTd ta="center">
|
||||||
<Button
|
<Box w={100}>
|
||||||
size="xs"
|
{item.jenisKelamin.name}
|
||||||
radius="md"
|
</Box>
|
||||||
variant="light"
|
</TableTd>
|
||||||
color="blue"
|
<TableTd ta="center">
|
||||||
leftSection={<IconDeviceImac size={16} />}
|
<Button
|
||||||
onClick={() =>
|
size="xs"
|
||||||
router.push(
|
radius="md"
|
||||||
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
|
variant="light"
|
||||||
)
|
color="blue"
|
||||||
}
|
leftSection={<IconDeviceImac size={16} />}
|
||||||
>
|
onClick={() =>
|
||||||
Detail
|
router.push(
|
||||||
</Button>
|
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
|
||||||
</TableTd>
|
)
|
||||||
</TableTr>
|
}
|
||||||
))
|
>
|
||||||
)}
|
Detail
|
||||||
</TableTbody>
|
</Button>
|
||||||
</Table>
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|||||||
@@ -96,7 +96,11 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
|||||||
) : (
|
) : (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTd>
|
||||||
|
<Box w={200}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
<TableTd style={{ textAlign: 'center', width: '120px' }}>
|
||||||
<Tooltip label="Edit" withArrow position="top">
|
<Tooltip label="Edit" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ function DetailPrestasiDesa() {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Box
|
<Box
|
||||||
fz="md"
|
fz="md"
|
||||||
c="dimmed"
|
c="dimmed"
|
||||||
dangerouslySetInnerHTML={{ __html: detailState.findUnique.data?.deskripsi || '-' }}
|
dangerouslySetInnerHTML={{ __html: detailState.findUnique.data?.deskripsi || '-' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -91,10 +91,11 @@ function DetailPrestasiDesa() {
|
|||||||
<Image
|
<Image
|
||||||
src={detailState.findUnique.data.image.link}
|
src={detailState.findUnique.data.image.link}
|
||||||
alt={detailState.findUnique.data.name || 'Gambar Prestasi'}
|
alt={detailState.findUnique.data.name || 'Gambar Prestasi'}
|
||||||
w={300}
|
w="100%"
|
||||||
|
maw={300} // max width 300px
|
||||||
fit="contain"
|
fit="contain"
|
||||||
style={{ borderRadius: '8px', border: '1px solid #e0e0e0' }}
|
style={{ borderRadius: '8px', border: '1px solid #e0e0e0' }}
|
||||||
loading='lazy'
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
|
|||||||
@@ -104,7 +104,9 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd style={{ width: '25%' }}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.kategori?.name || 'Tidak ada kategori'}</Text>
|
<Box w={150}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{item.kategori?.name || 'Tidak ada kategori'}</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
<TableTd style={{ width: '25%', textAlign: 'center' }}>
|
||||||
<Tooltip label="Kelola Prestasi" withArrow>
|
<Tooltip label="Kelola Prestasi" withArrow>
|
||||||
|
|||||||
@@ -51,8 +51,10 @@ function DetailMediaSosial() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
w={{ base: "100%", md: "60%" }}
|
w="100%"
|
||||||
|
maw={500} // <= tambahkan ini, biar tidak lebih dari 500px
|
||||||
|
mx="auto" // center di layar
|
||||||
bg={colors['white-1']}
|
bg={colors['white-1']}
|
||||||
p="lg"
|
p="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -70,9 +72,9 @@ function DetailMediaSosial() {
|
|||||||
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box >
|
||||||
<Text fz="lg" fw="bold">Link / Nomor Telepon</Text>
|
<Text fz="lg" fw="bold">Link / Nomor Telepon</Text>
|
||||||
<Text fz="md" c="dimmed">{data.iconUrl || '-'}</Text>
|
<Text fz="md" c="dimmed" style={{ wordBreak: "break-all", whiteSpace: "pre-wrap" }}>{data.iconUrl || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
@@ -81,12 +83,14 @@ function DetailMediaSosial() {
|
|||||||
<Image
|
<Image
|
||||||
src={data.image.link}
|
src={data.image.link}
|
||||||
alt={data.name || 'Gambar Media Sosial'}
|
alt={data.name || 'Gambar Media Sosial'}
|
||||||
w={120}
|
w="100%"
|
||||||
h={120}
|
maw={120} // max width biar tidak keluar layar
|
||||||
|
h="auto"
|
||||||
radius="md"
|
radius="md"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ function EditPejabatDesa() {
|
|||||||
alt="Preview"
|
alt="Preview"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '200px',
|
maxHeight: '150px',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function Page() {
|
|||||||
<Image
|
<Image
|
||||||
pt={{ base: 0, md: 60 }}
|
pt={{ base: 0, md: 60 }}
|
||||||
src={item.image?.link || "/perbekel.png"}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
w={{ base: 250, md: 350 }}
|
w={{ base: 150, md: 350 }}
|
||||||
alt="Foto Profil Pejabat"
|
alt="Foto Profil Pejabat"
|
||||||
radius="md"
|
radius="md"
|
||||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||||
@@ -87,7 +87,7 @@ function Page() {
|
|||||||
className="glass3"
|
className="glass3"
|
||||||
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
||||||
>
|
>
|
||||||
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.2rem", md: "1.6rem" }}>
|
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1rem", md: "1.6rem" }}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -49,9 +49,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
|||||||
return (
|
return (
|
||||||
<Box py={15}>
|
<Box py={15}>
|
||||||
<Paper bg={colors['white-1']} withBorder p="lg" radius="md" shadow="sm">
|
<Paper bg={colors['white-1']} withBorder p="lg" radius="md" shadow="sm">
|
||||||
<Box mb="md" display="flex"
|
<Group justify='space-between'>
|
||||||
style={{ justifyContent: 'space-between', alignItems: 'center' }}
|
|
||||||
>
|
|
||||||
<Title order={4}>Daftar Program Inovasi</Title>
|
<Title order={4}>Daftar Program Inovasi</Title>
|
||||||
<Tooltip label="Tambah Program Inovasi" withArrow>
|
<Tooltip label="Tambah Program Inovasi" withArrow>
|
||||||
<Button
|
<Button
|
||||||
@@ -64,7 +62,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
|||||||
Tambah Program
|
Tambah Program
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover striped verticalSpacing="sm">
|
<Table highlightOnHover striped verticalSpacing="sm">
|
||||||
<TableThead>
|
<TableThead>
|
||||||
|
|||||||
@@ -3,7 +3,17 @@
|
|||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
|
import dataLingkunganDesaState from '@/app/admin/(dashboard)/_state/lingkungan/data-lingkungan-desa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -19,34 +29,34 @@ interface FormDataLingkunganDesa {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IconKey =
|
type IconKey =
|
||||||
'ekowisata' |
|
| 'ekowisata'
|
||||||
'kompetisi' |
|
| 'kompetisi'
|
||||||
'wisata' |
|
| 'wisata'
|
||||||
'ekonomi' |
|
| 'ekonomi'
|
||||||
'sampah' |
|
| 'sampah'
|
||||||
'truck' |
|
| 'truck'
|
||||||
'scale' |
|
| 'scale'
|
||||||
'clipboard' |
|
| 'clipboard'
|
||||||
'trash' |
|
| 'trash'
|
||||||
'lingkunganSehat' |
|
| 'lingkunganSehat'
|
||||||
'sumberOksigen' |
|
| 'sumberOksigen'
|
||||||
'ekonomiBerkelanjutan' |
|
| 'ekonomiBerkelanjutan'
|
||||||
'mencegahBencana' |
|
| 'mencegahBencana'
|
||||||
'rumah' |
|
| 'rumah'
|
||||||
'pohon' |
|
| 'pohon'
|
||||||
'air';
|
| 'air';
|
||||||
|
|
||||||
|
|
||||||
function EditDataLingkunganDesa() {
|
function EditDataLingkunganDesa() {
|
||||||
const stateDataLingkunganDesa = useProxy(dataLingkunganDesaState)
|
const stateDataLingkunganDesa = useProxy(dataLingkunganDesaState);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormDataLingkunganDesa>({
|
const [formData, setFormData] = useState<FormDataLingkunganDesa>({
|
||||||
name: '',
|
name: '',
|
||||||
deskripsi: '',
|
deskripsi: '',
|
||||||
jumlah: '',
|
jumlah: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProgramKreatif = async () => {
|
const loadProgramKreatif = async () => {
|
||||||
@@ -56,7 +66,6 @@ function EditDataLingkunganDesa() {
|
|||||||
try {
|
try {
|
||||||
const data = await stateDataLingkunganDesa.update.load(id);
|
const data = await stateDataLingkunganDesa.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
// ⬇️ FIX PENTING: tambahkan ini
|
|
||||||
stateDataLingkunganDesa.update.id = id;
|
stateDataLingkunganDesa.update.id = id;
|
||||||
|
|
||||||
stateDataLingkunganDesa.update.form = {
|
stateDataLingkunganDesa.update.form = {
|
||||||
@@ -74,16 +83,14 @@ function EditDataLingkunganDesa() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading data lingkungan desa:", error);
|
console.error('Error loading data lingkungan desa:', error);
|
||||||
toast.error("Gagal memuat data data lingkungan desa");
|
toast.error('Gagal memuat data lingkungan desa');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loadProgramKreatif();
|
loadProgramKreatif();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateDataLingkunganDesa.update.form = {
|
stateDataLingkunganDesa.update.form = {
|
||||||
@@ -92,49 +99,68 @@ function EditDataLingkunganDesa() {
|
|||||||
deskripsi: formData.deskripsi.trim(),
|
deskripsi: formData.deskripsi.trim(),
|
||||||
jumlah: formData.jumlah.trim(),
|
jumlah: formData.jumlah.trim(),
|
||||||
icon: formData.icon.trim(),
|
icon: formData.icon.trim(),
|
||||||
}
|
};
|
||||||
await stateDataLingkunganDesa.update.submit();
|
await stateDataLingkunganDesa.update.submit();
|
||||||
router.push("/admin/lingkungan/data-lingkungan-desa");
|
toast.success('Data lingkungan desa berhasil diperbarui!');
|
||||||
|
router.push('/admin/lingkungan/data-lingkungan-desa');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating data lingkungan desa:", error);
|
console.error('Error updating data lingkungan desa:', error);
|
||||||
toast.error("Gagal memuat data data lingkungan desa");
|
toast.error('Terjadi kesalahan saat memperbarui data lingkungan desa');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Title order={3}>Edit Data Lingkungan Desa</Title>
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Data Lingkungan Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '60%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Data Lingkungan Desa</Text>}
|
label={<Text fz="sm" fw="bold">Nama Data Lingkungan Desa</Text>}
|
||||||
placeholder="masukkan nama data lingkungan desa"
|
placeholder="Masukkan nama data lingkungan desa"
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
name: val.target.value
|
name: val.target.value,
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.jumlah}
|
value={formData.jumlah}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Jumlah Data Lingkungan Desa</Text>}
|
label={<Text fz="sm" fw="bold">Jumlah Data Lingkungan Desa</Text>}
|
||||||
placeholder="masukkan jumlah data lingkungan desa"
|
placeholder="Masukkan jumlah data lingkungan desa"
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
jumlah: val.target.value
|
jumlah: val.target.value,
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) => {
|
||||||
@@ -143,16 +169,34 @@ function EditDataLingkunganDesa() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Ikon Data Lingkungan Desa</Text>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
|
Ikon Data Lingkungan Desa
|
||||||
|
</Text>
|
||||||
<SelectIconProgramEdit
|
<SelectIconProgramEdit
|
||||||
value={formData.icon as IconKey}
|
value={formData.icon as IconKey}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFormData((prev) => ({ ...prev, icon: value }));
|
setFormData((prev) => ({ ...prev, icon: value }));
|
||||||
stateDataLingkunganDesa.update.form.icon = value;
|
stateDataLingkunganDesa.update.form.icon = value;
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,23 +1,40 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard, IconDroplet, IconEdit, IconHome, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTree, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react';
|
import {
|
||||||
|
IconArrowBack,
|
||||||
|
IconChartLine,
|
||||||
|
IconChristmasTreeFilled,
|
||||||
|
IconClipboard,
|
||||||
|
IconDroplet,
|
||||||
|
IconEdit,
|
||||||
|
IconHome,
|
||||||
|
IconHomeEco,
|
||||||
|
IconLeaf,
|
||||||
|
IconRecycle,
|
||||||
|
IconScale,
|
||||||
|
IconShieldFilled,
|
||||||
|
IconTent,
|
||||||
|
IconTrash,
|
||||||
|
IconTree,
|
||||||
|
IconTrendingUp,
|
||||||
|
IconTrophy,
|
||||||
|
IconTruck,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailDataLingkunganDesa() {
|
function DetailDataLingkunganDesa() {
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const stateDataLingkungan = useProxy(dataLingkunganDesaState)
|
const stateDataLingkungan = useProxy(dataLingkunganDesaState);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
const iconMap: Record<string, React.FC<any>> = {
|
const iconMap: Record<string, React.FC<any>> = {
|
||||||
ekowisata: IconLeaf,
|
ekowisata: IconLeaf,
|
||||||
@@ -35,90 +52,117 @@ function DetailDataLingkunganDesa() {
|
|||||||
mencegahBencana: IconShieldFilled,
|
mencegahBencana: IconShieldFilled,
|
||||||
rumah: IconHome,
|
rumah: IconHome,
|
||||||
pohon: IconTree,
|
pohon: IconTree,
|
||||||
air: IconDroplet
|
air: IconDroplet,
|
||||||
};
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateDataLingkungan.findUnique.load(params?.id as string)
|
stateDataLingkungan.findUnique.load(params?.id as string);
|
||||||
}, [params?.id])
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateDataLingkungan.delete.byId(selectedId)
|
stateDataLingkungan.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/lingkungan/data-lingkungan-desa")
|
router.push('/admin/lingkungan/data-lingkungan-desa');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!stateDataLingkungan.findUnique.data) {
|
if (!stateDataLingkungan.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const data = stateDataLingkungan.findUnique.data;
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Data Lingkungan Desa</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box py={10}>
|
||||||
|
{/* Back Button */}
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Main Card */}
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: '100%', md: '60%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Title */}
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Data Lingkungan Desa
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Content Card */}
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Data Lingkungan Desa</Text>
|
<Text fz="lg" fw="bold">Nama Data Lingkungan Desa</Text>
|
||||||
<Text fz={"lg"}>{stateDataLingkungan.findUnique.data?.name}</Text>
|
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Jumlah Data Lingkungan Desa</Text>
|
<Text fz="lg" fw="bold">Jumlah Data Lingkungan Desa</Text>
|
||||||
<Text fz={"lg"}>{stateDataLingkungan.findUnique.data?.jumlah}</Text>
|
<Text fz="md" c="dimmed">{data?.jumlah || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Ikon Data Lingkungan Desa</Text>
|
<Text fz="lg" fw="bold">Ikon Data Lingkungan Desa</Text>
|
||||||
{iconMap[stateDataLingkungan.findUnique.data?.icon] && (
|
{iconMap[data?.icon] ? (
|
||||||
<Box title={stateDataLingkungan.findUnique.data?.icon}>
|
<Box title={data?.icon}>
|
||||||
{React.createElement(iconMap[stateDataLingkungan.findUnique.data?.icon], { size: 24 })}
|
{React.createElement(iconMap[data.icon], { size: 28, color: colors['blue-button'] })}
|
||||||
</Box>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada ikon</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: stateDataLingkungan.findUnique.data?.deskripsi }}></Text>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
{/* Action Buttons */}
|
||||||
|
<Group gap="sm" mt="sm">
|
||||||
|
<Tooltip label="Hapus Data Lingkungan Desa" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (stateDataLingkungan.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(stateDataLingkungan.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={stateDataLingkungan.delete.loading || !stateDataLingkungan.findUnique.data}
|
variant="light"
|
||||||
color={"red"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data Lingkungan Desa" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (stateDataLingkungan.findUnique.data) {
|
onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${data.id}/edit`)}
|
||||||
router.push(`/admin/lingkungan/data-lingkungan-desa/${stateDataLingkungan.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!stateDataLingkungan.findUnique.data}
|
|
||||||
color={"green"}
|
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -130,7 +174,7 @@ function DetailDataLingkunganDesa() {
|
|||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus data lingkungan desa ini?"
|
text="Apakah anda yakin ingin menghapus data lingkungan desa ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -8,60 +18,105 @@ import CreateEditor from '../../../_com/createEditor';
|
|||||||
import SelectIconProgram from '../../../_com/selectIcon';
|
import SelectIconProgram from '../../../_com/selectIcon';
|
||||||
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
import dataLingkunganDesaState from '../../../_state/lingkungan/data-lingkungan-desa';
|
||||||
|
|
||||||
|
|
||||||
function CreateDataLingkunganDesa() {
|
function CreateDataLingkunganDesa() {
|
||||||
const stateCreate = useProxy(dataLingkunganDesaState)
|
const stateCreate = useProxy(dataLingkunganDesaState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateCreate.create.form = {
|
stateCreate.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
jumlah: "",
|
jumlah: '',
|
||||||
icon: "",
|
icon: '',
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await stateCreate.create.create();
|
await stateCreate.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/lingkungan/data-lingkungan-desa")
|
router.push('/admin/lingkungan/data-lingkungan-desa');
|
||||||
}
|
};
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Title order={3}>Create Data Lingkungan Desa</Title>
|
{/* Header */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={22} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Data Lingkungan Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Card Form */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Data Lingkungan Desa</Text>}
|
label={<Text fw="bold" fz="sm">Nama Data Lingkungan Desa</Text>}
|
||||||
placeholder="masukkan nama data lingkungan desa"
|
placeholder="Masukkan nama data lingkungan desa"
|
||||||
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
value={stateCreate.create.form.name || ''}
|
||||||
|
onChange={(val) => (stateCreate.create.form.name = val.target.value)}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Ikon Data Lingkungan Desa</Text>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
<SelectIconProgram onChange={(value) => stateCreate.create.form.icon = value} />
|
Ikon Data Lingkungan Desa
|
||||||
</Box>
|
</Text>
|
||||||
<TextInput
|
<SelectIconProgram
|
||||||
type='number'
|
onChange={(value) => (stateCreate.create.form.icon = value)}
|
||||||
onChange={(e) => stateCreate.create.form.jumlah = e.currentTarget.value}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Jmlah data lingkungan desa</Text>}
|
|
||||||
placeholder='Masukkan jumlah data lingkungan desa'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi data lingkungan desa</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateCreate.create.form.deskripsi}
|
|
||||||
onChange={(htmlContent) => stateCreate.create.form.deskripsi = htmlContent}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<TextInput
|
||||||
|
label={<Text fw="bold" fz="sm">Jumlah Data Lingkungan Desa</Text>}
|
||||||
|
placeholder="Masukkan jumlah data lingkungan desa"
|
||||||
|
value={stateCreate.create.form.jumlah || ''}
|
||||||
|
onChange={(e) => (stateCreate.create.form.jumlah = e.currentTarget.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi Data Lingkungan Desa
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateCreate.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) =>
|
||||||
|
(stateCreate.create.form.deskripsi = htmlContent)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Group justify="right" mt="sm">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,21 +2,23 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
|
||||||
import {
|
import {
|
||||||
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDeviceImac, IconDroplet, IconHome, IconHomeEco, IconLeaf,
|
Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack,
|
||||||
|
Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
|
import {
|
||||||
|
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled,
|
||||||
|
IconDeviceImacCog, IconDroplet, IconHome, IconHomeEco, IconLeaf,
|
||||||
|
IconPlus,
|
||||||
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent,
|
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent,
|
||||||
IconTrashFilled,
|
IconTrashFilled, IconTree, IconTrendingUp, IconTrophy, IconTruckFilled
|
||||||
IconTree,
|
|
||||||
IconTrendingUp,
|
|
||||||
IconTrophy,
|
|
||||||
IconTruckFilled
|
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import dataLingkunganDesaState from '../../_state/lingkungan/data-lingkungan-desa';
|
import dataLingkunganDesaState from '../../_state/lingkungan/data-lingkungan-desa';
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ function DataLingkunganDesa() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Data Lingkungan Desa'
|
title='Data Lingkungan Desa'
|
||||||
placeholder='pencarian'
|
placeholder='Cari data lingkungan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -64,76 +66,98 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
|||||||
rumah: IconHome,
|
rumah: IconHome,
|
||||||
pohon: IconTree,
|
pohon: IconTree,
|
||||||
air: IconDroplet
|
air: IconDroplet
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={650} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper p="md" >
|
<Paper withBorder shadow="md" radius="md" p="lg" bg={colors['white-1']}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Data Lingkungan Desa'
|
<Title order={4}>Daftar Data Lingkungan Desa</Title>
|
||||||
href='/admin/lingkungan/data-lingkungan-desa/create'
|
<Tooltip label="Tambah Data Lingkungan Desa" withArrow>
|
||||||
/>
|
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan-desa/create')}>
|
||||||
<Table striped withTableBorder withRowBorders>
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>No</TableTh>
|
<TableTh>No</TableTh>
|
||||||
<TableTh>Nama Data Lingkungan Desa</TableTh>
|
<TableTh>Nama Data Lingkungan Desa</TableTh>
|
||||||
<TableTh>Jumlah Data Lingkungan Desa</TableTh>
|
<TableTh>Jumlah</TableTh>
|
||||||
<TableTh>Ikon</TableTh>
|
<TableTh>Ikon</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
</Table>
|
</Table>
|
||||||
<Text ta="center">Tidak ada data lingkungan desa yang tersedia</Text>
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data lingkungan desa yang tersedia</Text>
|
||||||
|
</Center>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box >
|
</Box >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'} h={{ base: 'auto', md: 650 }}>
|
<Paper withBorder shadow="md" radius="md" bg={colors['white-1']} p="lg">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Data Lingkungan Desa'
|
<Title order={4}>Daftar Data Lingkungan Desa</Title>
|
||||||
href='/admin/lingkungan/data-lingkungan-desa/create'
|
<Tooltip label="Tambah Data Lingkungan Desa" withArrow>
|
||||||
/>
|
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/lingkungan/data-lingkungan/create')}>
|
||||||
<Box style={{ overflowY: 'auto' }}>
|
Tambah Baru
|
||||||
<Table striped withTableBorder withRowBorders>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Nama Data Lingkungan Desa</TableTh>
|
<TableTh style={{ width: '25%' }}>Nama Data</TableTh>
|
||||||
<TableTh style={{ width: '35%' }}>Jumlah Data Lingkungan Desa</TableTh>
|
<TableTh style={{ width: '35%' }}>Jumlah</TableTh>
|
||||||
<TableTh style={{ width: '10%' }}>Ikon</TableTh>
|
<TableTh style={{ width: '15%' }}>Ikon</TableTh>
|
||||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item, index) => (
|
{filteredData.map((item, index) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
<TableTd>
|
||||||
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>± {item.jumlah}</TableTd>
|
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
|
||||||
<TableTd style={{ width: '10%' }}>
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" c="dimmed">± {item.jumlah}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
{iconMap[item.icon] && (
|
{iconMap[item.icon] && (
|
||||||
<Box title={item.icon}>
|
<Box title={item.icon}>
|
||||||
{React.createElement(iconMap[item.icon], { size: 24 })}
|
{React.createElement(iconMap[item.icon], { size: 22 })}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
<TableTd style={{ textAlign: 'center' }}>
|
||||||
<Button onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImac size={25} />
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
|
onClick={() => router.push(`/admin/lingkungan/data-lingkungan-desa/${item.id}`)}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -147,11 +171,13 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
|||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,67 +1,116 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconBook, IconLeaf, IconSchool } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "Tujuan Edukasi Lingkungan",
|
label: "Tujuan Edukasi Lingkungan",
|
||||||
value: "tujuanedukasilingkungan",
|
value: "tujuanedukasilingkungan",
|
||||||
href: "/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan"
|
href: "/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan",
|
||||||
|
tooltip: "Lihat tujuan edukasi lingkungan",
|
||||||
|
icon: <IconLeaf size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Materi Edukasi Yang Diberikan",
|
label: "Materi Edukasi Yang Diberikan",
|
||||||
value: "materiedukasiyangdiberikan",
|
value: "materiedukasiyangdiberikan",
|
||||||
href: "/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan"
|
href: "/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan",
|
||||||
|
tooltip: "Kelola materi edukasi yang diberikan",
|
||||||
|
icon: <IconBook size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Contoh Kegiatan Di Desa Darmasaba",
|
label: "Contoh Kegiatan Di Desa Darmasaba",
|
||||||
value: "contohkegiatan",
|
value: "contohkegiatan",
|
||||||
href: "/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba"
|
href: "/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba",
|
||||||
|
tooltip: "Lihat contoh kegiatan desa Darmasaba",
|
||||||
|
icon: <IconSchool size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find(t => t.value === value)
|
||||||
if (tab) {
|
if (tab) router.push(tab.href)
|
||||||
router.push(tab.href)
|
|
||||||
}
|
|
||||||
setActiveTab(value)
|
setActiveTab(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname)
|
||||||
if (match) {
|
if (match) setActiveTab(match.value)
|
||||||
setActiveTab(match.value)
|
|
||||||
}
|
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="md">
|
||||||
<Title order={3}>Jumlah Penduduk Usia Kerja yang Menganggur</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Edukasi Lingkungan
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
<Tabs
|
||||||
))}
|
color={colors['blue-button']}
|
||||||
</TabsList>
|
variant="pills"
|
||||||
{tabs.map((e, i) => (
|
value={activeTab}
|
||||||
<TabsPanel key={i} value={e.value}>
|
onChange={handleTabChange}
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
radius="lg"
|
||||||
<></>
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
display: "inline-block",
|
||||||
|
maxWidth: "200px",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}}>
|
||||||
|
{tab.label}
|
||||||
|
</span>
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel key={i} value={tab.value}>
|
||||||
|
<Box p="md">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
|
|||||||
@@ -9,77 +9,105 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
const EdukasiLingkunganTextEditor = dynamic(() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), {
|
const EdukasiLingkunganTextEditor = dynamic(
|
||||||
ssr: false,
|
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor),
|
||||||
});
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
function EditContohKegiatanDesaDarmasaba() {
|
function EditContohKegiatanDesaDarmasaba() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const contohEdukasiState = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan)
|
const contohEdukasiState = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan);
|
||||||
const [judul, setJudul] = useState('');
|
const [judul, setJudul] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
if (!contohEdukasiState.findById.data) {
|
if (!contohEdukasiState.findById.data) {
|
||||||
contohEdukasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
contohEdukasiState.findById.initialize();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contohEdukasiState.findById.data) {
|
if (contohEdukasiState.findById.data) {
|
||||||
setJudul(contohEdukasiState.findById.data.judul ?? '')
|
setJudul(contohEdukasiState.findById.data.judul ?? '');
|
||||||
setContent(contohEdukasiState.findById.data.deskripsi ?? '')
|
setContent(contohEdukasiState.findById.data.deskripsi ?? '');
|
||||||
}
|
}
|
||||||
}, [contohEdukasiState.findById.data])
|
}, [contohEdukasiState.findById.data]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (contohEdukasiState.findById.data) {
|
if (contohEdukasiState.findById.data) {
|
||||||
contohEdukasiState.findById.data.judul = judul;
|
contohEdukasiState.findById.data.judul = judul;
|
||||||
contohEdukasiState.findById.data.deskripsi = content;
|
contohEdukasiState.findById.data.deskripsi = content;
|
||||||
contohEdukasiState.update.save(contohEdukasiState.findById.data)
|
contohEdukasiState.update.save(contohEdukasiState.findById.data);
|
||||||
}
|
}
|
||||||
router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba')
|
router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack gap={'xs'}>
|
{/* Header */}
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant={'subtle'}
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
>
|
p="xs"
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
radius="md"
|
||||||
</Button>
|
>
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Box>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Stack gap={'xs'}>
|
Edit Contoh Kegiatan di Desa Darmasaba
|
||||||
<Title order={3}>Edit Contoh Kegiatan Di Desa Darmasaba</Title>
|
</Title>
|
||||||
<Text fw={"bold"}>Judul</Text>
|
</Group>
|
||||||
<EdukasiLingkunganTextEditor
|
|
||||||
showSubmit={false}
|
{/* Form Paper */}
|
||||||
onChange={setJudul}
|
<Paper
|
||||||
initialContent={judul}
|
bg={colors['white-1']}
|
||||||
/>
|
p="lg"
|
||||||
<Text fw={"bold"}>Deskripsi</Text>
|
radius="md"
|
||||||
<EdukasiLingkunganTextEditor
|
shadow="sm"
|
||||||
showSubmit={false}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={setContent}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
initialContent={content}
|
>
|
||||||
/>
|
<Stack gap="md">
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fw="bold" mb={6}>
|
||||||
bg={colors['blue-button']}
|
Judul
|
||||||
onClick={submit}
|
</Text>
|
||||||
loading={contohEdukasiState.update.loading}
|
<EdukasiLingkunganTextEditor
|
||||||
>
|
showSubmit={false}
|
||||||
Submit
|
onChange={setJudul}
|
||||||
</Button>
|
initialContent={judul}
|
||||||
</Group>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text fw="bold" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<EdukasiLingkunganTextEditor
|
||||||
|
showSubmit={false}
|
||||||
|
onChange={setContent}
|
||||||
|
initialContent={content}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={submit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
loading={contohEdukasiState.update.loading}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
@@ -8,47 +7,72 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkungan';
|
import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkungan';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listContohEdukasi = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan)
|
const listContohEdukasi = useProxy(stateEdukasiLingkungan.stateContohEdukasiLingkungan);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listContohEdukasi.findById.load('edit')
|
listContohEdukasi.findById.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!listContohEdukasi.findById.data) {
|
if (!listContohEdukasi.findById.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={20}>
|
||||||
<Skeleton radius={10} h={800} />
|
<Skeleton radius="md" height={600} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
<Box p="md">
|
||||||
<Stack gap={"22"}>
|
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||||
<Grid>
|
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={2}>Preview Contoh Kegiatan Di Desa Darmasaba</Title>
|
<Title order={3} fw={600}>
|
||||||
|
Preview Contoh Kegiatan Di Desa Darmasaba
|
||||||
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba/edit')}>
|
<Button
|
||||||
<IconEdit size={16} />
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/lingkungan/edukasi-lingkungan/contoh-kegiatan-desa-darmasaba/edit'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="md">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
<Box mb="md">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.judul }} />
|
<Text
|
||||||
</Box>
|
fz={{ base: 'xl', md: '2xl' }}
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
fw={600}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.deskripsi }} />
|
c="black"
|
||||||
</Box>
|
dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.judul }}
|
||||||
</Paper>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text
|
||||||
</Paper>
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
)
|
ta="justify"
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={10}
|
||||||
|
dangerouslySetInnerHTML={{ __html: listContohEdukasi.findById.data.deskripsi }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -9,77 +9,90 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
const EdukasiLingkunganTextEditor = dynamic(() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), {
|
const EdukasiLingkunganTextEditor = dynamic(
|
||||||
ssr: false,
|
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor),
|
||||||
});
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
function EditMateriEdukasiYangDiberikan() {
|
function EditMateriEdukasiYangDiberikan() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const materiEdukasiState = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan)
|
const materiEdukasiState = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan);
|
||||||
const [judul, setJudul] = useState('');
|
const [judul, setJudul] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
if (!materiEdukasiState.findById.data) {
|
if (!materiEdukasiState.findById.data) {
|
||||||
materiEdukasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
materiEdukasiState.findById.initialize();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (materiEdukasiState.findById.data) {
|
if (materiEdukasiState.findById.data) {
|
||||||
setJudul(materiEdukasiState.findById.data.judul ?? '')
|
setJudul(materiEdukasiState.findById.data.judul ?? '');
|
||||||
setContent(materiEdukasiState.findById.data.deskripsi ?? '')
|
setContent(materiEdukasiState.findById.data.deskripsi ?? '');
|
||||||
}
|
}
|
||||||
}, [materiEdukasiState.findById.data])
|
}, [materiEdukasiState.findById.data]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (materiEdukasiState.findById.data) {
|
if (materiEdukasiState.findById.data) {
|
||||||
materiEdukasiState.findById.data.judul = judul;
|
materiEdukasiState.findById.data.judul = judul;
|
||||||
materiEdukasiState.findById.data.deskripsi = content;
|
materiEdukasiState.findById.data.deskripsi = content;
|
||||||
materiEdukasiState.update.save(materiEdukasiState.findById.data)
|
materiEdukasiState.update.save(materiEdukasiState.findById.data);
|
||||||
}
|
}
|
||||||
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan')
|
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack gap={'xs'}>
|
<Group mb="md">
|
||||||
<Box>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<Button
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
variant={'subtle'}
|
</Button>
|
||||||
onClick={() => router.back()}
|
<Title order={4} ml="sm" c="dark">
|
||||||
>
|
Edit Materi Edukasi Yang Diberikan
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
</Title>
|
||||||
</Button>
|
</Group>
|
||||||
</Box>
|
|
||||||
<Box>
|
<Paper
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
bg={colors['white-1']}
|
||||||
<Stack gap={'xs'}>
|
p="lg"
|
||||||
<Title order={3}>Edit Materi Edukasi Yang Diberikan</Title>
|
radius="md"
|
||||||
<Text fw={"bold"}>Judul</Text>
|
shadow="sm"
|
||||||
<EdukasiLingkunganTextEditor
|
w={{ base: '100%', md: '50%' }}
|
||||||
showSubmit={false}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
onChange={setJudul}
|
>
|
||||||
initialContent={judul}
|
<Stack gap="md">
|
||||||
/>
|
<Box>
|
||||||
<Text fw={"bold"}>Content</Text>
|
<Text fw="bold" mb={6}>
|
||||||
<EdukasiLingkunganTextEditor
|
Judul
|
||||||
showSubmit={false}
|
</Text>
|
||||||
onChange={setContent}
|
<EdukasiLingkunganTextEditor showSubmit={false} onChange={setJudul} initialContent={judul} />
|
||||||
initialContent={content}
|
</Box>
|
||||||
/>
|
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fw="bold" mb={6}>
|
||||||
bg={colors['blue-button']}
|
Konten
|
||||||
onClick={submit}
|
</Text>
|
||||||
loading={materiEdukasiState.update.loading}
|
<EdukasiLingkunganTextEditor showSubmit={false} onChange={setContent} initialContent={content} />
|
||||||
>
|
</Box>
|
||||||
Submit
|
|
||||||
</Button>
|
<Group justify="right" mt="md">
|
||||||
</Group>
|
<Button
|
||||||
</Stack>
|
onClick={submit}
|
||||||
</Paper>
|
radius="md"
|
||||||
</Box>
|
size="md"
|
||||||
</Stack>
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
loading={materiEdukasiState.update.loading}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
@@ -10,44 +9,65 @@ import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkunga
|
|||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const listMateriEdukasi = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan)
|
const listMateriEdukasi = useProxy(stateEdukasiLingkungan.stateMateriEdukasiLingkungan)
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listMateriEdukasi.findById.load('edit')
|
listMateriEdukasi.findById.load('edit')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!listMateriEdukasi.findById.data) {
|
if (!listMateriEdukasi.findById.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={20}>
|
||||||
<Skeleton radius={10} h={800} />
|
<Skeleton radius="md" height={600} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
<Box p="md">
|
||||||
<Stack gap={"22"}>
|
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||||
<Grid>
|
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={2}>Preview Materi Edukasi Yang Diberikan</Title>
|
<Title order={3} fw={600}>Preview Materi Edukasi Yang Diberikan</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan/edit')}>
|
<Button
|
||||||
<IconEdit size={16} />
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/admin/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan/edit')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="md">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
<Box mb="md">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.judul }} />
|
<Text
|
||||||
</Box>
|
fz={{ base: 'xl', md: '2xl' }}
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
fw={600}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.deskripsi }} />
|
c="black"
|
||||||
</Box>
|
dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.judul }}
|
||||||
</Paper>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text
|
||||||
</Paper>
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
|
ta="justify"
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={10}
|
||||||
|
dangerouslySetInnerHTML={{ __html: listMateriEdukasi.findById.data.deskripsi }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,77 +9,103 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
const EdukasiLingkunganTextEditor = dynamic(() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor), {
|
const EdukasiLingkunganTextEditor = dynamic(
|
||||||
ssr: false,
|
() => import('../../_lib/edukasiLingkunganTextEditor').then(mod => mod.EdukasiLingkunganTextEditor),
|
||||||
});
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
function EditTujuanEdukasiLingkungan() {
|
function EditTujuanEdukasiLingkungan() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const tujuanEdukasiState = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi)
|
const tujuanEdukasiState = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi);
|
||||||
const [judul, setJudul] = useState('');
|
const [judul, setJudul] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
if (!tujuanEdukasiState.findById.data) {
|
if (!tujuanEdukasiState.findById.data) {
|
||||||
tujuanEdukasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
tujuanEdukasiState.findById.initialize();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tujuanEdukasiState.findById.data) {
|
if (tujuanEdukasiState.findById.data) {
|
||||||
setJudul(tujuanEdukasiState.findById.data.judul ?? '')
|
setJudul(tujuanEdukasiState.findById.data.judul ?? '');
|
||||||
setContent(tujuanEdukasiState.findById.data.deskripsi ?? '')
|
setContent(tujuanEdukasiState.findById.data.deskripsi ?? '');
|
||||||
}
|
}
|
||||||
}, [tujuanEdukasiState.findById.data])
|
}, [tujuanEdukasiState.findById.data]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (tujuanEdukasiState.findById.data) {
|
if (tujuanEdukasiState.findById.data) {
|
||||||
tujuanEdukasiState.findById.data.judul = judul;
|
tujuanEdukasiState.findById.data.judul = judul;
|
||||||
tujuanEdukasiState.findById.data.deskripsi = content;
|
tujuanEdukasiState.findById.data.deskripsi = content;
|
||||||
tujuanEdukasiState.update.save(tujuanEdukasiState.findById.data)
|
tujuanEdukasiState.update.save(tujuanEdukasiState.findById.data);
|
||||||
}
|
}
|
||||||
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan')
|
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack gap={'xs'}>
|
<Group mb="md">
|
||||||
<Box>
|
<Button
|
||||||
<Button
|
variant="subtle"
|
||||||
variant={'subtle'}
|
onClick={() => router.back()}
|
||||||
onClick={() => router.back()}
|
p="xs"
|
||||||
>
|
radius="md"
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
>
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
<Box>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
Edit Tujuan Edukasi Lingkungan
|
||||||
<Stack gap={'xs'}>
|
</Title>
|
||||||
<Title order={3}>Edit Tujuan Edukasi Lingkungan</Title>
|
</Group>
|
||||||
<Text fw={"bold"}>Judul</Text>
|
|
||||||
<EdukasiLingkunganTextEditor
|
<Paper
|
||||||
showSubmit={false}
|
bg={colors['white-1']}
|
||||||
onChange={setJudul}
|
p="lg"
|
||||||
initialContent={judul}
|
radius="md"
|
||||||
/>
|
shadow="sm"
|
||||||
<Text fw={"bold"}>Content</Text>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<EdukasiLingkunganTextEditor
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
showSubmit={false}
|
>
|
||||||
onChange={setContent}
|
<Stack gap="md">
|
||||||
initialContent={content}
|
<Box>
|
||||||
/>
|
<Text fw="bold" mb={6}>
|
||||||
<Group>
|
Judul
|
||||||
<Button
|
</Text>
|
||||||
bg={colors['blue-button']}
|
<EdukasiLingkunganTextEditor
|
||||||
onClick={submit}
|
showSubmit={false}
|
||||||
loading={tujuanEdukasiState.update.loading}
|
onChange={setJudul}
|
||||||
>
|
initialContent={judul}
|
||||||
Submit
|
/>
|
||||||
</Button>
|
</Box>
|
||||||
</Group>
|
|
||||||
</Stack>
|
<Box>
|
||||||
</Paper>
|
<Text fw="bold" mb={6}>
|
||||||
</Box>
|
Konten
|
||||||
</Stack>
|
</Text>
|
||||||
|
<EdukasiLingkunganTextEditor
|
||||||
|
showSubmit={false}
|
||||||
|
onChange={setContent}
|
||||||
|
initialContent={content}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={submit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
loading={tujuanEdukasiState.update.loading}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
@@ -8,47 +7,68 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkungan';
|
import stateEdukasiLingkungan from '../../../_state/lingkungan/edukasi-lingkungan';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listTujuanEdukasi = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi)
|
const listTujuanEdukasi = useProxy(stateEdukasiLingkungan.stateTujuanEdukasi);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listTujuanEdukasi.findById.load('edit')
|
listTujuanEdukasi.findById.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!listTujuanEdukasi.findById.data) {
|
if (!listTujuanEdukasi.findById.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={20}>
|
||||||
<Skeleton radius={10} h={800} />
|
<Skeleton radius="md" height={600} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
<Box p="md">
|
||||||
<Stack gap={"22"}>
|
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||||
<Grid>
|
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={2}>Preview Tujuan Edukasi Lingkungan</Title>
|
<Title order={3} fw={600}>Preview Tujuan Edukasi Lingkungan</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan/edit')}>
|
<Button
|
||||||
<IconEdit size={16} />
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan/edit')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="md">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
<Box mb="md">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.judul }} />
|
<Text
|
||||||
</Box>
|
fz={{ base: 'xl', md: '2xl' }}
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
fw={600}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.deskripsi }} />
|
c='black'
|
||||||
</Box>
|
dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.judul }}
|
||||||
</Paper>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text
|
||||||
</Paper>
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
)
|
ta="justify"
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={10}
|
||||||
|
dangerouslySetInnerHTML={{ __html: listTujuanEdukasi.findById.data.deskripsi }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,62 +1,121 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { IconClipboardList, IconTags } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
label: "Kegiatan Desa",
|
|
||||||
value: "kegiatanDesa",
|
|
||||||
href: "/admin/lingkungan/gotong-royong/kegiatan-desa"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Kategori Kegiatan",
|
|
||||||
value: "kategoriKegiatan",
|
|
||||||
href: "/admin/lingkungan/gotong-royong/kategori-kegiatan"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const tabs = [
|
||||||
const tab = tabs.find(t => t.value === value)
|
{
|
||||||
if (tab) {
|
label: "Kegiatan Desa",
|
||||||
router.push(tab.href)
|
value: "kegiatanDesa",
|
||||||
}
|
href: "/admin/lingkungan/gotong-royong/kegiatan-desa",
|
||||||
setActiveTab(value)
|
icon: <IconClipboardList size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Lihat dan kelola kegiatan desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Kategori Kegiatan",
|
||||||
|
value: "kategoriKegiatan",
|
||||||
|
href: "/admin/lingkungan/gotong-royong/kategori-kegiatan",
|
||||||
|
icon: <IconTags size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola kategori kegiatan desa",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
|
const handleTabChange = (value: string | null) => {
|
||||||
|
const tab = tabs.find(t => t.value === value);
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.href);
|
||||||
}
|
}
|
||||||
|
setActiveTab(value);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="lg">
|
||||||
<Title order={3}>Gotong Royong</Title>
|
{/* ✅ Title lebih tegas */}
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
Gotong Royong
|
||||||
{tabs.map((e, i) => (
|
</Title>
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
|
||||||
))}
|
<Tabs
|
||||||
</TabsList>
|
color={colors['blue-button']}
|
||||||
{tabs.map((e, i) => (
|
variant="pills"
|
||||||
<TabsPanel key={i} value={e.value}>
|
value={activeTab}
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
onChange={handleTabChange}
|
||||||
<></>
|
radius="lg"
|
||||||
</TabsPanel>
|
keepMounted={false}
|
||||||
))}
|
>
|
||||||
</Tabs>
|
{/* ✅ Scroll horizontal */}
|
||||||
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip
|
||||||
|
key={i}
|
||||||
|
label={tab.tooltip}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
|
>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{/* ✅ Panel dengan gaya kartu */}
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Stack>
|
</TabsPanel>
|
||||||
);
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -16,7 +16,7 @@ function EditKategoriKegiatan() {
|
|||||||
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan);
|
const stateKategori = useProxy(gotongRoyongState.kategoriKegiatan);
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
nama: "",
|
nama: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -27,15 +27,14 @@ function EditKategoriKegiatan() {
|
|||||||
const data = await stateKategori.edit.load(id);
|
const data = await stateKategori.edit.load(id);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// pastikan id-nya masuk ke state edit
|
|
||||||
stateKategori.edit.id = id;
|
stateKategori.edit.id = id;
|
||||||
setFormData({
|
setFormData({
|
||||||
nama: data.nama || '',
|
nama: data.nama || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading kategori kegiatan:", error);
|
console.error('Error loading kategori kegiatan:', error);
|
||||||
toast.error("Gagal memuat data kategori kegiatan");
|
toast.error('Gagal memuat data kategori kegiatan');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,45 +48,63 @@ function EditKategoriKegiatan() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stateKategori.edit.form = {
|
stateKategori.edit.form = { nama: formData.nama.trim() };
|
||||||
nama: formData.nama.trim(),
|
if (!stateKategori.edit.id) stateKategori.edit.id = id;
|
||||||
};
|
|
||||||
|
|
||||||
// Safety check tambahan: pastikan ID tidak kosong
|
|
||||||
if (!stateKategori.edit.id) {
|
|
||||||
stateKategori.edit.id = id; // fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = await stateKategori.edit.update();
|
const success = await stateKategori.edit.update();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
router.push("/admin/lingkungan/gotong-royong/kategori-kegiatan");
|
toast.success('Kategori kegiatan berhasil diperbarui!');
|
||||||
|
router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating kategori kegiatan:", error);
|
console.error('Error updating kategori kegiatan:', error);
|
||||||
// toast akan ditampilkan dari fungsi update
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md" align="center">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" p="xs" radius="md" onClick={() => router.back()}>
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Kategori Kegiatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Kategori kegiatan</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.nama}
|
value={formData.nama}
|
||||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori kegiatan</Text>}
|
label={<Text fw="bold" fz="sm">Nama Kategori Kegiatan</Text>}
|
||||||
placeholder='Masukkan nama kategori kegiatan'
|
placeholder="Masukkan nama kategori kegiatan"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@@ -29,31 +29,53 @@ function CreateKategoriKegiatan() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box>
|
{/* Header */}
|
||||||
<Box mb={10}>
|
<Group mb="md" align="center">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" p="xs" radius="md" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Kategori Kegiatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
{/* Form */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Create Kategori Kegiatan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<TextInput
|
bg={colors['white-1']}
|
||||||
value={stateKategori.create.form.nama}
|
p="lg"
|
||||||
onChange={(val) => {
|
radius="md"
|
||||||
stateKategori.create.form.nama = val.target.value;
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
value={stateKategori.create.form.nama}
|
||||||
|
onChange={(val) => (stateKategori.create.form.nama = val.target.value)}
|
||||||
|
label={<Text fw="bold" fz="sm">Nama Kategori Kegiatan</Text>}
|
||||||
|
placeholder="Masukkan nama kategori kegiatan"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Kegiatan</Text>}
|
>
|
||||||
placeholder='Masukkan nama kategori kegiatan'
|
Simpan
|
||||||
/>
|
</Button>
|
||||||
<Group>
|
</Group>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
</Stack>
|
||||||
</Group>
|
</Paper>
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
|
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
|
||||||
|
|
||||||
|
|
||||||
function KategoriKegiatan() {
|
function KategoriKegiatan() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Kategori Kegiatan'
|
title='Kategori Kegiatan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari kategori kegiatan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -34,6 +50,14 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateKategori.findMany
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateKategori.delete.byId(selectedId)
|
stateKategori.delete.byId(selectedId)
|
||||||
@@ -43,61 +67,110 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateKategori.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (stateKategori.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateKategori.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Kategori Kegiatan'
|
<Title order={4}>Daftar Kategori Kegiatan</Title>
|
||||||
href='/admin/lingkungan/gotong-royong/kategori-kegiatan/create'
|
<Tooltip label="Tambah Kategori Baru" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama Kategori</TableTh>
|
onClick={() => router.push('/admin/lingkungan/gotong-royong/kategori-kegiatan/create')}
|
||||||
<TableTh>Edit</TableTh>
|
>
|
||||||
<TableTh>Delete</TableTh>
|
Tambah Baru
|
||||||
</TableTr>
|
</Button>
|
||||||
</TableThead>
|
</Tooltip>
|
||||||
<TableTbody>
|
</Group>
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<TableTd>{item.nama}</TableTd>
|
<Table highlightOnHover>
|
||||||
<TableTd>
|
<TableThead>
|
||||||
<Button color="green" onClick={() => router.push(`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`)}>
|
<TableTr>
|
||||||
<IconEdit size={20} />
|
<TableTh style={{ width: '60%' }}>Nama Kategori</TableTh>
|
||||||
</Button>
|
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||||
</TableTd>
|
<TableTh style={{ width: '20%' }}>Delete</TableTh>
|
||||||
<TableTd>
|
|
||||||
<Button color="red" onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() => router.push(`/admin/lingkungan/gotong-royong/kategori-kegiatan/${item.id}`)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
leftSection={<IconTrash size={16} />}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Hapus
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={3}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada kategori kegiatan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -12,7 +12,6 @@ import { useEffect, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
interface FormKegiatanDesa {
|
interface FormKegiatanDesa {
|
||||||
judul: string;
|
judul: string;
|
||||||
deskripsiSingkat: string;
|
deskripsiSingkat: string;
|
||||||
@@ -92,14 +91,11 @@ function EditGotongRoyong() {
|
|||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) return toast.error("Gagal upload gambar");
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
kegiatanDesaState.edit.form.imageId = uploaded.id;
|
kegiatanDesaState.edit.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
await kegiatanDesaState.edit.update()
|
await kegiatanDesaState.edit.update()
|
||||||
|
toast.success("Kegiatan desa berhasil diperbarui!")
|
||||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa");
|
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating kegiatan desa:", error);
|
console.error("Error updating kegiatan desa:", error);
|
||||||
@@ -108,27 +104,40 @@ function EditGotongRoyong() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Kegiatan Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper
|
||||||
<Stack gap="xs">
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={3}>Edit Kegiatan Desa</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.judul}
|
value={formData.judul}
|
||||||
label={<Text fz="sm" fw="bold">Judul Kegiatan Desa</Text>}
|
label={<Text fz="sm" fw="bold">Judul Kegiatan Desa</Text>}
|
||||||
placeholder="masukkan judul kegiatan desa"
|
placeholder="masukkan judul kegiatan desa"
|
||||||
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.deskripsiSingkat}
|
value={formData.deskripsiSingkat}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat Kegiatan Desa</Text>}
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat Kegiatan Desa</Text>}
|
||||||
placeholder="masukkan deskripsi singkat kegiatan desa"
|
placeholder="masukkan deskripsi singkat kegiatan desa"
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label="Kategori Kegiatan"
|
label="Kategori Kegiatan"
|
||||||
@@ -141,7 +150,7 @@ function EditGotongRoyong() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Lengkap Kegiatan Desa</Text>
|
<Text fw="bold" fz="sm">Deskripsi Lengkap Kegiatan Desa</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsiLengkap}
|
value={formData.deskripsiLengkap}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) => {
|
||||||
@@ -150,6 +159,7 @@ function EditGotongRoyong() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Tanggal Kegiatan Desa</Text>}
|
label={<Text fz="sm" fw="bold">Tanggal Kegiatan Desa</Text>}
|
||||||
placeholder="masukkan tanggal kegiatan desa"
|
placeholder="masukkan tanggal kegiatan desa"
|
||||||
@@ -169,71 +179,70 @@ function EditGotongRoyong() {
|
|||||||
placeholder="masukkan partisipan kegiatan desa"
|
placeholder="masukkan partisipan kegiatan desa"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const val = Number(e.target.value);
|
const val = Number(e.target.value);
|
||||||
if (!isNaN(val)) {
|
if (!isNaN(val)) setFormData({ ...formData, partisipan: val });
|
||||||
setFormData({ ...formData, partisipan: val });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>Gambar Kegiatan Desa</Text>
|
||||||
<Box>
|
<Dropzone
|
||||||
<Dropzone
|
onDrop={(files) => {
|
||||||
onDrop={(files) => {
|
const selectedFile = files[0];
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
if (selectedFile) {
|
||||||
if (selectedFile) {
|
setFile(selectedFile);
|
||||||
setFile(selectedFile);
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
}
|
||||||
}
|
}}
|
||||||
}}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
maxSize={5 * 1024 ** 2}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
accept={{ 'image/*': [] }}
|
||||||
accept={{ 'image/*': [] }}
|
radius="md"
|
||||||
>
|
p="xl"
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
>
|
||||||
<Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
<Dropzone.Accept>
|
||||||
</Dropzone.Accept>
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
<Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<Dropzone.Reject>
|
||||||
</Dropzone.Reject>
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
<Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<Dropzone.Idle>
|
||||||
</Dropzone.Idle>
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>Drag gambar atau klik untuk pilih file</Text>
|
||||||
|
<Text size="sm" c="dimmed">Maksimal 5MB, format gambar wajib</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||||
</div>
|
loading="lazy"
|
||||||
</Group>
|
/>
|
||||||
</Dropzone>
|
</Box>
|
||||||
|
)}
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit} >
|
|
||||||
Simpan
|
<Group justify="right">
|
||||||
</Button>
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,121 +1,166 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
function DetailKegiatanDesa() {
|
function DetailKegiatanDesa() {
|
||||||
const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa)
|
const kegiatanDesaState = useProxy(gotongRoyongState.kegiatanDesa);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
kegiatanDesaState.findUnique.load(params?.id as string)
|
kegiatanDesaState.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
kegiatanDesaState.delete.byId(selectedId)
|
kegiatanDesaState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa")
|
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!kegiatanDesaState.findUnique.data) {
|
if (!kegiatanDesaState.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = kegiatanDesaState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Kembali */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Kegiatan Desa Inovasi</Text>
|
Kembali
|
||||||
{kegiatanDesaState.findUnique.data ? (
|
</Button>
|
||||||
<Paper key={kegiatanDesaState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
{/* Container Detail */}
|
||||||
<Box>
|
<Paper
|
||||||
<Text fw={"bold"} fz={"lg"}>Judul Kegiatan Desa Inovasi</Text>
|
withBorder
|
||||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.judul}</Text>
|
w={{ base: "100%", md: "60%" }}
|
||||||
</Box>
|
bg={colors['white-1']}
|
||||||
<Box>
|
p="lg"
|
||||||
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
|
radius="md"
|
||||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.tanggal
|
shadow="sm"
|
||||||
? new Date(kegiatanDesaState.findUnique.data.tanggal).toLocaleDateString()
|
>
|
||||||
: "-"}</Text>
|
<Stack gap="md">
|
||||||
</Box>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Box>
|
Detail Kegiatan Desa
|
||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi Singkat</Text>
|
</Text>
|
||||||
<Text fz={"lg"} >{kegiatanDesaState.findUnique.data?.deskripsiSingkat}</Text>
|
|
||||||
</Box>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Box>
|
<Stack gap="sm">
|
||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
{/* Judul */}
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kegiatanDesaState.findUnique.data?.deskripsiLengkap }} />
|
<Box>
|
||||||
</Box>
|
<Text fz="lg" fw="bold">Judul Kegiatan Desa Inovasi</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed">{data.judul || '-'}</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Kategori Kegiatan</Text>
|
</Box>
|
||||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.kategoriKegiatan?.nama}</Text>
|
|
||||||
</Box>
|
{/* Tanggal */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Partisipan</Text>
|
<Text fz="lg" fw="bold">Tanggal</Text>
|
||||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.partisipan}</Text>
|
<Text fz="md" c="dimmed">
|
||||||
</Box>
|
{data.tanggal ? new Date(data.tanggal).toLocaleDateString() : '-'}
|
||||||
<Box>
|
</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Lokasi</Text>
|
</Box>
|
||||||
<Text fz={"lg"}>{kegiatanDesaState.findUnique.data?.lokasi}</Text>
|
|
||||||
</Box>
|
{/* Deskripsi Singkat */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||||
<Image w={{ base: 150, md: 150, lg: 150 }} src={kegiatanDesaState.findUnique.data?.image?.link} alt="gambar" loading="lazy" />
|
<Text fz="md" c="dimmed">{data.deskripsiSingkat || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex gap={"xs"} mt={10}>
|
|
||||||
|
{/* Deskripsi Lengkap */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.deskripsiLengkap || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Kategori */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Kategori Kegiatan</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.kategoriKegiatan?.nama || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Partisipan */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Partisipan</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.partisipan || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Lokasi */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Lokasi</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.lokasi || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Gambar */}
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt={data.judul || 'Gambar Kegiatan Desa'}
|
||||||
|
w={150}
|
||||||
|
h={150}
|
||||||
|
radius="md"
|
||||||
|
fit="cover"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Tombol Hapus & Edit */}
|
||||||
|
<Flex gap="sm" mt={10}>
|
||||||
|
<Tooltip label="Hapus Kegiatan Desa" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (kegiatanDesaState.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(kegiatanDesaState.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={kegiatanDesaState.delete.loading || !kegiatanDesaState.findUnique.data}
|
variant="light"
|
||||||
color={"red"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconX size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Kegiatan Desa" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (kegiatanDesaState.findUnique.data) {
|
onClick={() => router.push(`/admin/lingkungan/gotong-royong/kegiatan-desa/${data.id}/edit`)}
|
||||||
router.push(`/admin/lingkungan/gotong-royong/kegiatan-desa/${kegiatanDesaState.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!kegiatanDesaState.findUnique.data}
|
|
||||||
color={"green"}
|
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Stack>
|
</Flex>
|
||||||
</Paper>
|
</Stack>
|
||||||
) : null}
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -130,4 +175,4 @@ function DetailKegiatanDesa() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailKegiatanDesa;
|
export default DetailKegiatanDesa;
|
||||||
|
|||||||
@@ -4,7 +4,19 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
|||||||
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
import gotongRoyongState from '@/app/admin/(dashboard)/_state/lingkungan/gotong-royong';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -12,10 +24,9 @@ import { useEffect, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function CreateKegiatanDesa() {
|
function CreateKegiatanDesa() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateKegiatanDesa = useProxy(gotongRoyongState.kegiatanDesa)
|
const stateKegiatanDesa = useProxy(gotongRoyongState.kegiatanDesa);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
@@ -26,32 +37,33 @@ function CreateKegiatanDesa() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateKegiatanDesa.create.form = {
|
stateKegiatanDesa.create.form = {
|
||||||
judul: "",
|
judul: '',
|
||||||
deskripsiSingkat: "",
|
deskripsiSingkat: '',
|
||||||
deskripsiLengkap: "",
|
deskripsiLengkap: '',
|
||||||
tanggal: new Date(),
|
tanggal: new Date(),
|
||||||
lokasi: "",
|
lokasi: '',
|
||||||
partisipan: 0,
|
partisipan: 0,
|
||||||
imageId: "",
|
imageId: '',
|
||||||
kategoriKegiatanId: "",
|
kategoriKegiatanId: '',
|
||||||
};
|
};
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn('Silakan pilih file gambar terlebih dahulu');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
return toast.error('Gagal mengunggah gambar, silakan coba lagi');
|
||||||
}
|
}
|
||||||
|
|
||||||
stateKegiatanDesa.create.form.imageId = uploaded.id;
|
stateKegiatanDesa.create.form.imageId = uploaded.id;
|
||||||
@@ -59,153 +71,176 @@ function CreateKegiatanDesa() {
|
|||||||
await stateKegiatanDesa.create.create();
|
await stateKegiatanDesa.create.create();
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/lingkungan/gotong-royong/kegiatan-desa")
|
router.push('/admin/lingkungan/gotong-royong/kegiatan-desa');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Kegiatan Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
{/* Form */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Create Kegiatan Desa</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Upload Gambar */}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Kegiatan
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
</Group>
|
||||||
|
<Text ta="center" mt="sm" size="sm" color="dimmed">
|
||||||
|
Seret gambar atau klik untuk memilih file (maks 5MB)
|
||||||
|
</Text>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||||
</div>
|
loading="lazy"
|
||||||
</Group>
|
/>
|
||||||
</Dropzone>
|
</Box>
|
||||||
|
)}
|
||||||
{/* Tampilkan preview kalau ada */}
|
|
||||||
{previewImage && (
|
|
||||||
<Box mt="sm">
|
|
||||||
<Image
|
|
||||||
src={previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
style={{
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '200px',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
}}
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Input Form */}
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Judul Kegiatan"
|
||||||
|
placeholder="Masukkan judul kegiatan"
|
||||||
value={stateKegiatanDesa.create.form.judul}
|
value={stateKegiatanDesa.create.form.judul}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateKegiatanDesa.create.form.judul = e.target.value)}
|
||||||
stateKegiatanDesa.create.form.judul = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Kegiatan</Text>}
|
|
||||||
placeholder='Masukkan judul kegiatan'
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Deskripsi Singkat"
|
||||||
|
placeholder="Masukkan deskripsi singkat"
|
||||||
value={stateKegiatanDesa.create.form.deskripsiSingkat}
|
value={stateKegiatanDesa.create.form.deskripsiSingkat}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateKegiatanDesa.create.form.deskripsiSingkat = e.target.value)}
|
||||||
stateKegiatanDesa.create.form.deskripsiSingkat = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat</Text>}
|
|
||||||
placeholder='Masukkan deskripsi singkat'
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
max={5}
|
|
||||||
step={0.1} // bisa pakai 0.1 biar support desimal
|
|
||||||
value={stateKegiatanDesa.create.form.partisipan}
|
value={stateKegiatanDesa.create.form.partisipan}
|
||||||
onChange={(val) => {
|
onChange={(e) => {
|
||||||
const value = Number(val.target.value);
|
const value = Number(e.target.value);
|
||||||
|
if (value >= 0) {
|
||||||
// Validasi manual juga boleh (jaga-jaga)
|
|
||||||
if (value >= 0 && value <= 1000) {
|
|
||||||
stateKegiatanDesa.create.form.partisipan = value;
|
stateKegiatanDesa.create.form.partisipan = value;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Partisipan</Text>}
|
label="Partisipan"
|
||||||
placeholder='Masukkan partisipan'
|
placeholder="Masukkan jumlah partisipan"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
|
label="Tanggal"
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="Contoh: 2022-01-01"
|
placeholder="Contoh: 2022-01-01"
|
||||||
value={stateKegiatanDesa.create.form.tanggal ? stateKegiatanDesa.create.form.tanggal.toISOString().split('T')[0] : ''}
|
value={
|
||||||
|
stateKegiatanDesa.create.form.tanggal
|
||||||
|
? stateKegiatanDesa.create.form.tanggal.toISOString().split('T')[0]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const dateValue = e.currentTarget.value;
|
const dateValue = e.currentTarget.value;
|
||||||
stateKegiatanDesa.create.form.tanggal = dateValue ? new Date(dateValue) : new Date();
|
stateKegiatanDesa.create.form.tanggal = dateValue ? new Date(dateValue) : new Date();
|
||||||
}}
|
}}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Lokasi"
|
||||||
|
placeholder="Masukkan lokasi kegiatan"
|
||||||
value={stateKegiatanDesa.create.form.lokasi}
|
value={stateKegiatanDesa.create.form.lokasi}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateKegiatanDesa.create.form.lokasi = e.target.value)}
|
||||||
stateKegiatanDesa.create.form.lokasi = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
|
||||||
placeholder='Masukkan lokasi'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi Lengkap</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi Lengkap
|
||||||
|
</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={stateKegiatanDesa.create.form.deskripsiLengkap}
|
value={stateKegiatanDesa.create.form.deskripsiLengkap}
|
||||||
onChange={(val) => {
|
onChange={(val) => (stateKegiatanDesa.create.form.deskripsiLengkap = val)}
|
||||||
stateKegiatanDesa.create.form.deskripsiLengkap = val;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
|
label="Kategori Kegiatan"
|
||||||
|
placeholder="Pilih kategori kegiatan"
|
||||||
value={stateKegiatanDesa.create.form.kategoriKegiatanId}
|
value={stateKegiatanDesa.create.form.kategoriKegiatanId}
|
||||||
onChange={(val) => {
|
onChange={(val) => (stateKegiatanDesa.create.form.kategoriKegiatanId = val ?? '')}
|
||||||
stateKegiatanDesa.create.form.kategoriKegiatanId = val ?? "";
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kategori Kegiatan</Text>}
|
|
||||||
placeholder="Pilih kategori produk"
|
|
||||||
data={
|
data={
|
||||||
gotongRoyongState.kategoriKegiatan.findMany.data?.map((v) => ({
|
gotongRoyongState.kategoriKegiatan.findMany.data?.map((v) => ({
|
||||||
value: v.id,
|
value: v.id,
|
||||||
label: v.nama,
|
label: v.nama,
|
||||||
})) || []
|
})) || []
|
||||||
}
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group>
|
{/* Submit */}
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import { useState } from 'react';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
|
import gotongRoyongState from '../../../_state/lingkungan/gotong-royong';
|
||||||
|
|
||||||
function KegiatanDesa() {
|
function KegiatanDesa() {
|
||||||
@@ -15,8 +31,8 @@ function KegiatanDesa() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Kegiatan Desa'
|
title="Kegiatan Desa"
|
||||||
placeholder='pencarian'
|
placeholder="Cari judul, lokasi, atau kategori kegiatan..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,71 +43,123 @@ function KegiatanDesa() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListKegiatanDesa({ search }: { search: string }) {
|
function ListKegiatanDesa({ search }: { search: string }) {
|
||||||
const listState = useProxy(gotongRoyongState.kegiatanDesa)
|
const listState = useProxy(gotongRoyongState.kegiatanDesa);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useEffect(() => {
|
|
||||||
listState.findMany.load()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
const {
|
||||||
const keyword = search.toLowerCase();
|
data,
|
||||||
return (
|
page,
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
totalPages,
|
||||||
item.lokasi.toLowerCase().includes(keyword) ||
|
loading,
|
||||||
item.kategoriKegiatan?.nama?.toLowerCase().includes(keyword)
|
load,
|
||||||
);
|
} = listState.findMany;
|
||||||
});
|
|
||||||
|
|
||||||
if (!listState.findMany.data) {
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Stack>
|
<Group justify="space-between" mb="md">
|
||||||
<JudulList
|
<Title order={4}>Daftar Kegiatan Desa</Title>
|
||||||
title='List Kegiatan Desa'
|
<Button
|
||||||
href='/admin/lingkungan/gotong-royong/kegiatan-desa/create'
|
leftSection={<IconPlus size={18} />}
|
||||||
/>
|
color="blue"
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
variant="light"
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
onClick={() =>
|
||||||
<TableThead>
|
router.push('/admin/lingkungan/gotong-royong/kegiatan-desa/create')
|
||||||
<TableTr>
|
}
|
||||||
<TableTh>Judul Kegiatan Desa</TableTh>
|
>
|
||||||
<TableTh>Kategori Kegiatan Desa</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Lokasi Kegiatan Desa</TableTh>
|
</Button>
|
||||||
<TableTh>Detail</TableTh>
|
</Group>
|
||||||
</TableTr>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
</TableThead>
|
<Table highlightOnHover>
|
||||||
<TableTbody>
|
<TableThead>
|
||||||
{filteredData.map((item) => (
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '30%' }}>Judul</TableTh>
|
||||||
|
<TableTh style={{ width: '25%' }}>Kategori</TableTh>
|
||||||
|
<TableTh style={{ width: '25%' }}>Lokasi</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
|
{item.judul}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.kategoriKegiatan?.nama}</TableTd>
|
|
||||||
<TableTd>{item.lokasi}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/lingkungan/gotong-royong/kegiatan-desa/${item.id}`)}>
|
<Text fz="sm" c="dimmed">
|
||||||
<IconDeviceImacCog size={25} />
|
{item.kategoriKegiatan?.nama || '-'}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm">{item.lokasi}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImacCog size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/lingkungan/gotong-royong/kegiatan-desa/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">
|
||||||
|
Tidak ada kegiatan desa yang cocok dengan pencarian
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KegiatanDesa;
|
export default KegiatanDesa;
|
||||||
|
|||||||
@@ -1,67 +1,116 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import { Box, ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconBook, IconLeaf, IconSchool } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "Filosofi Tri Hita",
|
label: "Filosofi Tri Hita",
|
||||||
value: "filosofi-tri-hita",
|
value: "filosofi-tri-hita",
|
||||||
href: "/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana"
|
href: "/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana",
|
||||||
|
tooltip: "Lihat filosofi Tri Hita Karana",
|
||||||
|
icon: <IconLeaf size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Nilai Konservasi Adat",
|
label: "Nilai Konservasi Adat",
|
||||||
value: "nilai-konservasi-adat",
|
value: "nilai-konservasi-adat",
|
||||||
href: "/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat"
|
href: "/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat",
|
||||||
|
tooltip: "Kelola nilai konservasi adat",
|
||||||
|
icon: <IconBook size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Bentuk Konservasi Berdasarkan Adat",
|
label: "Bentuk Konservasi Berdasarkan Adat",
|
||||||
value: "bentuk-konservasi-berdasarkan-adat",
|
value: "bentuk-konservasi-berdasarkan-adat",
|
||||||
href: "/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat"
|
href: "/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat",
|
||||||
|
tooltip: "Lihat bentuk konservasi berdasarkan adat",
|
||||||
|
icon: <IconSchool size={18} stroke={1.8} />
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find(t => t.value === value)
|
||||||
if (tab) {
|
if (tab) router.push(tab.href)
|
||||||
router.push(tab.href)
|
|
||||||
}
|
|
||||||
setActiveTab(value)
|
setActiveTab(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname)
|
||||||
if (match) {
|
if (match) setActiveTab(match.value)
|
||||||
setActiveTab(match.value)
|
|
||||||
}
|
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="md">
|
||||||
<Title order={3}>Konservasi Adat Bali</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Konservasi Adat Bali
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
<Tabs
|
||||||
))}
|
color={colors['blue-button']}
|
||||||
</TabsList>
|
variant="pills"
|
||||||
{tabs.map((e, i) => (
|
value={activeTab}
|
||||||
<TabsPanel key={i} value={e.value}>
|
onChange={handleTabChange}
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
radius="lg"
|
||||||
<></>
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
display: "inline-block",
|
||||||
|
maxWidth: "200px",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}}>
|
||||||
|
{tab.label}
|
||||||
|
</span>
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel key={i} value={tab.value}>
|
||||||
|
<Box p="md">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
|
|||||||
@@ -9,77 +9,105 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), {
|
const KonservasiAdatBaliTextEditor = dynamic(
|
||||||
ssr: false,
|
() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor),
|
||||||
});
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
function EditBentukKonservasiBerdasarkanAdat() {
|
function EditBentukKonservasiBerdasarkanAdat() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat)
|
const bentukKonservasiState = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat);
|
||||||
const [judul, setJudul] = useState('');
|
const [judul, setJudul] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
if (!bentukKonservasiState.findById.data) {
|
if (!bentukKonservasiState.findById.data) {
|
||||||
bentukKonservasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
bentukKonservasiState.findById.initialize();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bentukKonservasiState.findById.data) {
|
if (bentukKonservasiState.findById.data) {
|
||||||
setJudul(bentukKonservasiState.findById.data.judul ?? '')
|
setJudul(bentukKonservasiState.findById.data.judul ?? '');
|
||||||
setContent(bentukKonservasiState.findById.data.deskripsi ?? '')
|
setContent(bentukKonservasiState.findById.data.deskripsi ?? '');
|
||||||
}
|
}
|
||||||
}, [bentukKonservasiState.findById.data])
|
}, [bentukKonservasiState.findById.data]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (bentukKonservasiState.findById.data) {
|
if (bentukKonservasiState.findById.data) {
|
||||||
bentukKonservasiState.findById.data.judul = judul;
|
bentukKonservasiState.findById.data.judul = judul;
|
||||||
bentukKonservasiState.findById.data.deskripsi = content;
|
bentukKonservasiState.findById.data.deskripsi = content;
|
||||||
bentukKonservasiState.update.save(bentukKonservasiState.findById.data)
|
bentukKonservasiState.update.save(bentukKonservasiState.findById.data);
|
||||||
}
|
}
|
||||||
router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat')
|
router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack gap={'xs'}>
|
{/* Header */}
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant={'subtle'}
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
>
|
p="xs"
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
radius="md"
|
||||||
</Button>
|
>
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Box>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Stack gap={'xs'}>
|
Edit Bentuk Konservasi Berdasarkan Adat
|
||||||
<Title order={3}>Edit Bentuk Konservasi Berdasarkan Adat</Title>
|
</Title>
|
||||||
<Text fw={"bold"}>Judul</Text>
|
</Group>
|
||||||
<KonservasiAdatBaliTextEditor
|
|
||||||
showSubmit={false}
|
{/* Form Paper */}
|
||||||
onChange={setJudul}
|
<Paper
|
||||||
initialContent={judul}
|
bg={colors['white-1']}
|
||||||
/>
|
p="lg"
|
||||||
<Text fw={"bold"}>Content</Text>
|
radius="md"
|
||||||
<KonservasiAdatBaliTextEditor
|
shadow="sm"
|
||||||
showSubmit={false}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={setContent}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
initialContent={content}
|
>
|
||||||
/>
|
<Stack gap="md">
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fw="bold" mb={6}>
|
||||||
bg={colors['blue-button']}
|
Judul
|
||||||
onClick={submit}
|
</Text>
|
||||||
loading={bentukKonservasiState.update.loading}
|
<KonservasiAdatBaliTextEditor
|
||||||
>
|
showSubmit={false}
|
||||||
Submit
|
onChange={setJudul}
|
||||||
</Button>
|
initialContent={judul}
|
||||||
</Group>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text fw="bold" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<KonservasiAdatBaliTextEditor
|
||||||
|
showSubmit={false}
|
||||||
|
onChange={setContent}
|
||||||
|
initialContent={content}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={submit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
loading={bentukKonservasiState.update.loading}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,47 +8,80 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listBentukKonservasiBerdasarkanAdat = useProxy(stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat)
|
const listBentukKonservasiBerdasarkanAdat = useProxy(
|
||||||
|
stateKonservasiAdatBali.stateBentukKonservasiBerdasarkanAdat
|
||||||
|
);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listBentukKonservasiBerdasarkanAdat.findById.load('edit')
|
listBentukKonservasiBerdasarkanAdat.findById.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!listBentukKonservasiBerdasarkanAdat.findById.data) {
|
if (!listBentukKonservasiBerdasarkanAdat.findById.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={20} align="center">
|
||||||
<Skeleton radius={10} h={800} />
|
<Skeleton radius="md" height={600} width="100%" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
<Box p={{ base: 'md', md: 'xl' }}>
|
||||||
<Stack gap={"22"}>
|
<Paper withBorder radius="md" p={{ base: 'md', md: 'lg' }} bg={colors['white-1']}>
|
||||||
<Grid>
|
{/* Header */}
|
||||||
|
<Grid align="center" mb="lg">
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={2}>Preview Bentuk Konservasi Berdasarkan Adat</Title>
|
<Title order={3} fw={600} c="dark">
|
||||||
|
Preview Bentuk Konservasi Berdasarkan Adat
|
||||||
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit')}>
|
<Button
|
||||||
<IconEdit size={16} />
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/lingkungan/konservasi-adat-bali/bentuk-konservasi-berdasarkan-adat/edit'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Stack gap={'lg'}>
|
{/* Konten */}
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Stack gap="md">
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
<Paper radius="md" p={{ base: 'md', md: 'xl' }} bg={colors['BG-trans']} shadow="sm">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listBentukKonservasiBerdasarkanAdat.findById.data.judul }} />
|
<Box mb="md">
|
||||||
</Box>
|
<Text
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
fz={{ base: 'xl', md: '2xl' }}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listBentukKonservasiBerdasarkanAdat.findById.data.deskripsi }} />
|
fw={600}
|
||||||
</Box>
|
c="dark"
|
||||||
</Paper>
|
dangerouslySetInnerHTML={{
|
||||||
</Stack>
|
__html: listBentukKonservasiBerdasarkanAdat.findById.data.judul,
|
||||||
</Box>
|
}}
|
||||||
</Stack>
|
/>
|
||||||
</Paper>
|
</Box>
|
||||||
)
|
<Box>
|
||||||
|
<Text
|
||||||
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
|
ta="justify"
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={10}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: listBentukKonservasiBerdasarkanAdat.findById.data.deskripsi,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -9,78 +9,108 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
const KonservasiAdatBaliTextEditor = dynamic(
|
||||||
const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), {
|
() =>
|
||||||
ssr: false,
|
import('../../_lib/konservasiAdatBaliTextEditor').then(
|
||||||
});
|
(mod) => mod.KonservasiAdatBaliTextEditor
|
||||||
|
),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
function EditFilosofiTriHitaKarana() {
|
function EditFilosofiTriHitaKarana() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const filosofiTriHitaState = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita)
|
const filosofiTriHitaState = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita);
|
||||||
const [judul, setJudul] = useState('');
|
const [judul, setJudul] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
if (!filosofiTriHitaState.findById.data) {
|
if (!filosofiTriHitaState.findById.data) {
|
||||||
filosofiTriHitaState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
filosofiTriHitaState.findById.initialize();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filosofiTriHitaState.findById.data) {
|
if (filosofiTriHitaState.findById.data) {
|
||||||
setJudul(filosofiTriHitaState.findById.data.judul ?? '')
|
setJudul(filosofiTriHitaState.findById.data.judul ?? '');
|
||||||
setContent(filosofiTriHitaState.findById.data.deskripsi ?? '')
|
setContent(filosofiTriHitaState.findById.data.deskripsi ?? '');
|
||||||
}
|
}
|
||||||
}, [filosofiTriHitaState.findById.data])
|
}, [filosofiTriHitaState.findById.data]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (filosofiTriHitaState.findById.data) {
|
if (filosofiTriHitaState.findById.data) {
|
||||||
filosofiTriHitaState.findById.data.judul = judul;
|
filosofiTriHitaState.findById.data.judul = judul;
|
||||||
filosofiTriHitaState.findById.data.deskripsi = content;
|
filosofiTriHitaState.findById.data.deskripsi = content;
|
||||||
filosofiTriHitaState.update.save(filosofiTriHitaState.findById.data)
|
filosofiTriHitaState.update.save(filosofiTriHitaState.findById.data);
|
||||||
}
|
}
|
||||||
router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana')
|
router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack gap={'xs'}>
|
{/* Header */}
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant={'subtle'}
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
>
|
p="xs"
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
radius="md"
|
||||||
</Button>
|
>
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Box>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Stack gap={'xs'}>
|
Edit Filosofi Tri Hita Karana
|
||||||
<Title order={3}>Edit Filosofi Tri Hita Karana</Title>
|
</Title>
|
||||||
<Text fw={"bold"}>Judul</Text>
|
</Group>
|
||||||
<KonservasiAdatBaliTextEditor
|
|
||||||
showSubmit={false}
|
{/* Form Paper */}
|
||||||
onChange={setJudul}
|
<Paper
|
||||||
initialContent={judul}
|
bg={colors['white-1']}
|
||||||
/>
|
p="lg"
|
||||||
<Text fw={"bold"}>Content</Text>
|
radius="md"
|
||||||
<KonservasiAdatBaliTextEditor
|
shadow="sm"
|
||||||
showSubmit={false}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={setContent}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
initialContent={content}
|
>
|
||||||
/>
|
<Stack gap="md">
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fw="bold" mb={6}>
|
||||||
bg={colors['blue-button']}
|
Judul
|
||||||
onClick={submit}
|
</Text>
|
||||||
loading={filosofiTriHitaState.update.loading}
|
<KonservasiAdatBaliTextEditor
|
||||||
>
|
showSubmit={false}
|
||||||
Submit
|
onChange={setJudul}
|
||||||
</Button>
|
initialContent={judul}
|
||||||
</Group>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text fw="bold" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<KonservasiAdatBaliTextEditor
|
||||||
|
showSubmit={false}
|
||||||
|
onChange={setContent}
|
||||||
|
initialContent={content}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={submit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
loading={filosofiTriHitaState.update.loading}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
@@ -7,49 +6,73 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listFilosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita)
|
const listFilosofi = useProxy(stateKonservasiAdatBali.stateFilosofiTriHita);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listFilosofi.findById.load('edit')
|
listFilosofi.findById.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!listFilosofi.findById.data) {
|
if (!listFilosofi.findById.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={20}>
|
||||||
<Skeleton radius={10} h={800} />
|
<Skeleton radius="md" height={600} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
<Box p="md">
|
||||||
<Stack gap={"22"}>
|
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||||
<Grid>
|
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={2}>Preview Filosofi Tri Hita Karana</Title>
|
<Title order={3} fw={600}>
|
||||||
|
Preview Filosofi Tri Hita Karana
|
||||||
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana/edit')}>
|
<Button
|
||||||
<IconEdit size={16} />
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana/edit'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="md">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
<Box mb="md" px={{ base: 0, md: 20 }}>
|
||||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.judul }} />
|
<Text
|
||||||
</Box>
|
fz={{ base: 'xl', md: '2xl' }}
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
fw={600}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.deskripsi }} />
|
c="black"
|
||||||
</Box>
|
dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.judul }}
|
||||||
</Paper>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
<Box px={{ base: 0, md: 20 }}>
|
||||||
</Stack>
|
<Text
|
||||||
</Paper>
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
)
|
ta="justify"
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={10}
|
||||||
|
dangerouslySetInnerHTML={{ __html: listFilosofi.findById.data.deskripsi }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -9,77 +9,105 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
const KonservasiAdatBaliTextEditor = dynamic(() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor), {
|
const KonservasiAdatBaliTextEditor = dynamic(
|
||||||
ssr: false,
|
() => import('../../_lib/konservasiAdatBaliTextEditor').then(mod => mod.KonservasiAdatBaliTextEditor),
|
||||||
});
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
function EditNilaiKonservasiAdat() {
|
function EditNilaiKonservasiAdat() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat)
|
const nilaiKonservasiState = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat);
|
||||||
const [judul, setJudul] = useState('');
|
const [judul, setJudul] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
if (!nilaiKonservasiState.findById.data) {
|
if (!nilaiKonservasiState.findById.data) {
|
||||||
nilaiKonservasiState.findById.initialize(); // biar masuk ke `findFirst` route kamu
|
nilaiKonservasiState.findById.initialize();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nilaiKonservasiState.findById.data) {
|
if (nilaiKonservasiState.findById.data) {
|
||||||
setJudul(nilaiKonservasiState.findById.data.judul ?? '')
|
setJudul(nilaiKonservasiState.findById.data.judul ?? '');
|
||||||
setContent(nilaiKonservasiState.findById.data.deskripsi ?? '')
|
setContent(nilaiKonservasiState.findById.data.deskripsi ?? '');
|
||||||
}
|
}
|
||||||
}, [nilaiKonservasiState.findById.data])
|
}, [nilaiKonservasiState.findById.data]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (nilaiKonservasiState.findById.data) {
|
if (nilaiKonservasiState.findById.data) {
|
||||||
nilaiKonservasiState.findById.data.judul = judul;
|
nilaiKonservasiState.findById.data.judul = judul;
|
||||||
nilaiKonservasiState.findById.data.deskripsi = content;
|
nilaiKonservasiState.findById.data.deskripsi = content;
|
||||||
nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data)
|
nilaiKonservasiState.update.save(nilaiKonservasiState.findById.data);
|
||||||
}
|
}
|
||||||
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat')
|
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack gap={'xs'}>
|
{/* Header */}
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Button
|
<Button
|
||||||
variant={'subtle'}
|
variant="subtle"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
>
|
p="xs"
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
radius="md"
|
||||||
</Button>
|
>
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Box>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Stack gap={'xs'}>
|
Edit Nilai Konservasi Adat
|
||||||
<Title order={3}>Edit Nilai Konservasi Adat</Title>
|
</Title>
|
||||||
<Text fw={"bold"}>Judul</Text>
|
</Group>
|
||||||
<KonservasiAdatBaliTextEditor
|
|
||||||
showSubmit={false}
|
{/* Form Paper */}
|
||||||
onChange={setJudul}
|
<Paper
|
||||||
initialContent={judul}
|
bg={colors['white-1']}
|
||||||
/>
|
p="lg"
|
||||||
<Text fw={"bold"}>Content</Text>
|
radius="md"
|
||||||
<KonservasiAdatBaliTextEditor
|
shadow="sm"
|
||||||
showSubmit={false}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={setContent}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
initialContent={content}
|
>
|
||||||
/>
|
<Stack gap="md">
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fw="bold" mb={6}>
|
||||||
bg={colors['blue-button']}
|
Judul
|
||||||
onClick={submit}
|
</Text>
|
||||||
loading={nilaiKonservasiState.update.loading}
|
<KonservasiAdatBaliTextEditor
|
||||||
>
|
showSubmit={false}
|
||||||
Submit
|
onChange={setJudul}
|
||||||
</Button>
|
initialContent={judul}
|
||||||
</Group>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text fw="bold" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<KonservasiAdatBaliTextEditor
|
||||||
|
showSubmit={false}
|
||||||
|
onChange={setContent}
|
||||||
|
initialContent={content}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={submit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
loading={nilaiKonservasiState.update.loading}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
@@ -8,47 +7,70 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
import stateKonservasiAdatBali from '../../../_state/lingkungan/konservasi-adat-bali';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const listNilaiKonservasiAdat = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat)
|
const listNilaiKonservasiAdat = useProxy(stateKonservasiAdatBali.stateNilaiKonservasiAdat);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
listNilaiKonservasiAdat.findById.load('edit')
|
listNilaiKonservasiAdat.findById.load('edit');
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!listNilaiKonservasiAdat.findById.data) {
|
if (!listNilaiKonservasiAdat.findById.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={20}>
|
||||||
<Skeleton radius={10} h={800} />
|
<Skeleton radius="md" height={600} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'} radius={10}>
|
<Box p="md">
|
||||||
<Stack gap={"22"}>
|
<Paper withBorder p={{ base: 'md', md: 'lg' }} radius="md">
|
||||||
<Grid>
|
<Grid align="center" mb={{ base: 'md', md: 'lg' }}>
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={2}>Preview Nilai Konservasi Adat</Title>
|
<Title order={3} fw={600}>
|
||||||
|
Preview Nilai Konservasi Adat
|
||||||
|
</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }} style={{ textAlign: 'right' }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit')}>
|
<Button
|
||||||
<IconEdit size={16} />
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/admin/lingkungan/konservasi-adat-bali/nilai-konservasi-adat/edit')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Stack gap={'lg'}>
|
<Stack gap="md">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={{ base: 'md', md: 'xl' }} bg="#ECEEF8" radius="md">
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
<Box mb="md">
|
||||||
<Text fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.judul }} />
|
<Text
|
||||||
</Box>
|
fz={{ base: 'xl', md: '2xl' }}
|
||||||
<Box px={{ base: 0, md: 30 }}>
|
fw={600}
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.deskripsi }} />
|
c="black"
|
||||||
</Box>
|
dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.judul }}
|
||||||
</Paper>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
</Stack>
|
<Text
|
||||||
</Paper>
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
)
|
ta="justify"
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={10}
|
||||||
|
dangerouslySetInnerHTML={{ __html: listNilaiKonservasiAdat.findById.data.deskripsi }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconTrash, IconRecycle } from '@tabler/icons-react';
|
import { IconTrash, IconRecycle } from '@tabler/icons-react';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.ReactNode }) {
|
function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -45,49 +46,75 @@ function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.R
|
|||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="lg">
|
||||||
<Title order={3} mb="sm">Pengelolaan Sampah Bank Sampah</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs
|
Pengelolaan Sampah Bank Sampah
|
||||||
value={activeTab}
|
</Title>
|
||||||
onChange={handleTabChange}
|
|
||||||
|
<Tabs
|
||||||
|
color={colors["blue-button"]}
|
||||||
variant="pills"
|
variant="pills"
|
||||||
radius="md"
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList>
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
{tabs.map((tab) => (
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
<Tooltip
|
<TabsList
|
||||||
key={tab.value}
|
p="sm"
|
||||||
label={tab.tooltip}
|
style={{
|
||||||
position="top"
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
withArrow
|
borderRadius: "1rem",
|
||||||
transitionProps={{ transition: 'pop', duration: 300 }}
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
>
|
display: "flex",
|
||||||
<TabsTab
|
flexWrap: "nowrap",
|
||||||
value={tab.value}
|
gap: "0.5rem",
|
||||||
leftSection={tab.icon}
|
paddingInline: "0.5rem",
|
||||||
style={{
|
}}
|
||||||
padding: '10px 20px',
|
>
|
||||||
height: 'auto',
|
{tabs.map((tab, i) => (
|
||||||
minHeight: 44,
|
<Tooltip
|
||||||
}}
|
key={i}
|
||||||
|
label={tab.tooltip}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
>
|
>
|
||||||
{tab.label}
|
<TabsTab
|
||||||
</TabsTab>
|
value={tab.value}
|
||||||
</Tooltip>
|
leftSection={tab.icon}
|
||||||
))}
|
style={{
|
||||||
</TabsList>
|
fontWeight: 600,
|
||||||
<TabsPanel
|
fontSize: "0.9rem",
|
||||||
value={activeTab || ''}
|
transition: "all 0.2s ease",
|
||||||
pt="lg"
|
}}
|
||||||
style={{
|
>
|
||||||
minHeight: '60vh',
|
{tab.label}
|
||||||
}}
|
</TabsTab>
|
||||||
>
|
</Tooltip>
|
||||||
{children}
|
))}
|
||||||
</TabsPanel>
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabsPengelolaanSampahBankSampah;
|
export default LayoutTabsPengelolaanSampahBankSampah;
|
||||||
|
|||||||
@@ -4,14 +4,23 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
|
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
|
||||||
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
|
import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
interface FormProgramPenghijauan {
|
interface FormProgramPenghijauan {
|
||||||
name: string;
|
name: string;
|
||||||
deskripsi: string;
|
deskripsi: string;
|
||||||
@@ -19,19 +28,32 @@ interface FormProgramPenghijauan {
|
|||||||
icon: string;
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah' | 'truck' | 'scale' | 'clipboard' | 'trash' | 'lingkunganSehat' | 'sumberOksigen' | 'ekonomiBerkelanjutan' | 'mencegahBencana';
|
type IconKey =
|
||||||
|
| 'ekowisata'
|
||||||
|
| 'kompetisi'
|
||||||
|
| 'wisata'
|
||||||
|
| 'ekonomi'
|
||||||
|
| 'sampah'
|
||||||
|
| 'truck'
|
||||||
|
| 'scale'
|
||||||
|
| 'clipboard'
|
||||||
|
| 'trash'
|
||||||
|
| 'lingkunganSehat'
|
||||||
|
| 'sumberOksigen'
|
||||||
|
| 'ekonomiBerkelanjutan'
|
||||||
|
| 'mencegahBencana';
|
||||||
|
|
||||||
function EditProgramPenghijauan() {
|
function EditProgramPenghijauan() {
|
||||||
const stateProgramPenghijauan = useProxy(programPenghijauanState)
|
const stateProgramPenghijauan = useProxy(programPenghijauanState);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormProgramPenghijauan>({
|
const [formData, setFormData] = useState<FormProgramPenghijauan>({
|
||||||
name: '',
|
name: '',
|
||||||
deskripsi: '',
|
deskripsi: '',
|
||||||
judul: '',
|
judul: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProgramPenghijauan = async () => {
|
const loadProgramPenghijauan = async () => {
|
||||||
@@ -41,7 +63,6 @@ function EditProgramPenghijauan() {
|
|||||||
try {
|
try {
|
||||||
const data = await stateProgramPenghijauan.update.load(id);
|
const data = await stateProgramPenghijauan.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
// ⬇️ FIX PENTING: tambahkan ini
|
|
||||||
stateProgramPenghijauan.update.id = id;
|
stateProgramPenghijauan.update.id = id;
|
||||||
|
|
||||||
stateProgramPenghijauan.update.form = {
|
stateProgramPenghijauan.update.form = {
|
||||||
@@ -59,16 +80,14 @@ function EditProgramPenghijauan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program penghijauan:", error);
|
console.error('Error loading program penghijauan:', error);
|
||||||
toast.error("Gagal memuat data program penghijauan");
|
toast.error('Gagal memuat data program penghijauan');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loadProgramPenghijauan();
|
loadProgramPenghijauan();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateProgramPenghijauan.update.form = {
|
stateProgramPenghijauan.update.form = {
|
||||||
@@ -77,49 +96,74 @@ function EditProgramPenghijauan() {
|
|||||||
deskripsi: formData.deskripsi.trim(),
|
deskripsi: formData.deskripsi.trim(),
|
||||||
judul: formData.judul.trim(),
|
judul: formData.judul.trim(),
|
||||||
icon: formData.icon.trim(),
|
icon: formData.icon.trim(),
|
||||||
}
|
};
|
||||||
await stateProgramPenghijauan.update.submit();
|
await stateProgramPenghijauan.update.submit();
|
||||||
router.push("/admin/lingkungan/program-penghijauan");
|
router.push('/admin/lingkungan/program-penghijauan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating program penghijauan:", error);
|
console.error('Error updating program penghijauan:', error);
|
||||||
toast.error("Gagal memuat data program penghijauan");
|
toast.error('Gagal memperbarui program penghijauan');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Title order={3}>Edit Program Penghijauan</Title>
|
{/* Header dengan back button */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Program Penghijauan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Card utama */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Program Penghijauan</Text>}
|
label="Nama Program Penghijauan"
|
||||||
placeholder="masukkan nama program penghijauan"
|
placeholder="Masukkan nama program penghijauan"
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
name: val.target.value
|
name: val.target.value,
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.judul}
|
value={formData.judul}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Judul Deskripsi Program Penghijauan</Text>}
|
label="Judul Deskripsi Program Penghijauan"
|
||||||
placeholder="masukkan judul deskripsi program penghijauan"
|
placeholder="Masukkan judul deskripsi program penghijauan"
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
judul: val.target.value
|
judul: val.target.value,
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) => {
|
||||||
@@ -128,16 +172,35 @@ function EditProgramPenghijauan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Ikon Program Penghijauan</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Ikon Program Penghijauan
|
||||||
|
</Text>
|
||||||
<SelectIconProgramEdit
|
<SelectIconProgramEdit
|
||||||
value={formData.icon as IconKey}
|
value={formData.icon as IconKey}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFormData((prev) => ({ ...prev, icon: value }));
|
setFormData((prev) => ({ ...prev, icon: value }));
|
||||||
stateProgramPenghijauan.update.form.icon = value;
|
stateProgramPenghijauan.update.form.icon = value;
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
|
||||||
|
{/* Tombol simpan */}
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard, IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react';
|
import {
|
||||||
|
IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard,
|
||||||
|
IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale,
|
||||||
|
IconShieldFilled, IconTent, IconTrash, IconTrendingUp,
|
||||||
|
IconTrophy, IconTruck
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan';
|
import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan';
|
||||||
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailProgramPenghijauan() {
|
function DetailProgramPenghijauan() {
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const stateProgramPenghijauan = useProxy(programPenghijauanState)
|
const stateProgramPenghijauan = useProxy(programPenghijauanState)
|
||||||
@@ -50,72 +53,97 @@ function DetailProgramPenghijauan() {
|
|||||||
|
|
||||||
if (!stateProgramPenghijauan.findUnique.data) {
|
if (!stateProgramPenghijauan.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const data = stateProgramPenghijauan.findUnique.data
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Program Penghijauan</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box py={10}>
|
||||||
|
{/* Tombol kembali */}
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Konten detail */}
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: "100%", md: "60%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Program Penghijauan
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Program Penghijauan</Text>
|
<Text fz="lg" fw="bold">Nama Program</Text>
|
||||||
<Text fz={"lg"}>{stateProgramPenghijauan.findUnique.data?.name}</Text>
|
<Text fz="md" c="dimmed">{data?.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Ikon Program Penghijauan</Text>
|
<Text fz="lg" fw="bold">Ikon Program</Text>
|
||||||
{iconMap[stateProgramPenghijauan.findUnique.data?.icon] && (
|
{iconMap[data?.icon] ? (
|
||||||
<Box title={stateProgramPenghijauan.findUnique.data?.icon}>
|
<Box title={data?.icon}>
|
||||||
{React.createElement(iconMap[stateProgramPenghijauan.findUnique.data?.icon], { size: 24 })}
|
{React.createElement(iconMap[data.icon], { size: 28, color: colors['blue-button'] })}
|
||||||
</Box>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada ikon</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul Deskripsi Program Penghijauan</Text>
|
<Text fz="lg" fw="bold">Judul Deskripsi</Text>
|
||||||
<Text fz={"lg"}>{stateProgramPenghijauan.findUnique.data?.judul}</Text>
|
<Text fz="md" c="dimmed">{data?.judul || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Program Penghijauan</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: stateProgramPenghijauan.findUnique.data?.deskripsi }}></Text>
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data?.deskripsi || '-' }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
{/* Tombol aksi */}
|
||||||
|
<Group gap="sm" mt="md">
|
||||||
|
<Tooltip label="Hapus Program" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (stateProgramPenghijauan.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(stateProgramPenghijauan.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={stateProgramPenghijauan.delete.loading || !stateProgramPenghijauan.findUnique.data}
|
variant="light"
|
||||||
color={"red"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Program" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (stateProgramPenghijauan.findUnique.data) {
|
onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${data.id}/edit`)}
|
||||||
router.push(`/admin/lingkungan/program-penghijauan/${stateProgramPenghijauan.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!stateProgramPenghijauan.findUnique.data}
|
|
||||||
color={"green"}
|
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -126,7 +154,7 @@ function DetailProgramPenghijauan() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus program penghijauan ini?"
|
text="Apakah Anda yakin ingin menghapus program penghijauan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -8,59 +18,104 @@ import CreateEditor from '../../../_com/createEditor';
|
|||||||
import SelectIconProgram from '../../../_com/selectIcon';
|
import SelectIconProgram from '../../../_com/selectIcon';
|
||||||
import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan';
|
import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan';
|
||||||
|
|
||||||
|
|
||||||
function CreateProgramPenghijauan() {
|
function CreateProgramPenghijauan() {
|
||||||
const stateCreate = useProxy(programPenghijauanState)
|
const stateCreate = useProxy(programPenghijauanState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateCreate.create.form = {
|
stateCreate.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
judul: "",
|
judul: '',
|
||||||
icon: "",
|
icon: '',
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await stateCreate.create.create();
|
await stateCreate.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/lingkungan/program-penghijauan")
|
router.push('/admin/lingkungan/program-penghijauan');
|
||||||
}
|
};
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Title order={3}>Create Program Penghijauan</Title>
|
{/* Tombol back + title */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Program Penghijauan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Program Penghijauan</Text>}
|
label={<Text fz="sm" fw="bold">Nama Program Penghijauan</Text>}
|
||||||
placeholder="masukkan nama program penghijauan"
|
placeholder="Masukkan nama program penghijauan"
|
||||||
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
value={stateCreate.create.form.name || ''}
|
||||||
|
onChange={(e) => (stateCreate.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Ikon Program Penghijauan</Text>
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
<SelectIconProgram onChange={(value) => stateCreate.create.form.icon = value} />
|
Ikon Program Penghijauan
|
||||||
</Box>
|
</Text>
|
||||||
<TextInput
|
<SelectIconProgram
|
||||||
onChange={(e) => stateCreate.create.form.judul = e.currentTarget.value}
|
onChange={(value) => (stateCreate.create.form.icon = value)}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Deskripsi Program Penghijauan</Text>}
|
|
||||||
placeholder='Masukkan judul deskripsi program penghijauan'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Program Penghijauan</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateCreate.create.form.deskripsi}
|
|
||||||
onChange={(htmlContent) => stateCreate.create.form.deskripsi = htmlContent}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Judul Deskripsi Program Penghijauan</Text>}
|
||||||
|
placeholder="Masukkan judul deskripsi program penghijauan"
|
||||||
|
value={stateCreate.create.form.judul || ''}
|
||||||
|
onChange={(e) => (stateCreate.create.form.judul = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold" mb={6}>
|
||||||
|
Deskripsi Program Penghijauan
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateCreate.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) =>
|
||||||
|
(stateCreate.create.form.deskripsi = htmlContent)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="sm">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,30 +2,55 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
|
||||||
import {
|
import {
|
||||||
IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDeviceImac, IconHomeEco, IconLeaf,
|
Box,
|
||||||
IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent,
|
Button,
|
||||||
|
Center,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
|
import {
|
||||||
|
IconChartLine,
|
||||||
|
IconChristmasTreeFilled,
|
||||||
|
IconClipboardTextFilled,
|
||||||
|
IconDeviceImac,
|
||||||
|
IconHomeEco,
|
||||||
|
IconLeaf,
|
||||||
|
IconRecycle,
|
||||||
|
IconScale,
|
||||||
|
IconSearch,
|
||||||
|
IconShieldFilled,
|
||||||
|
IconTent,
|
||||||
IconTrashFilled,
|
IconTrashFilled,
|
||||||
IconTrendingUp,
|
IconTrendingUp,
|
||||||
IconTrophy,
|
IconTrophy,
|
||||||
IconTruckFilled
|
IconTruckFilled,
|
||||||
|
IconPlus,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import programPenghijauanState from '../../_state/lingkungan/program-penghijauan';
|
import programPenghijauanState from '../../_state/lingkungan/program-penghijauan';
|
||||||
|
|
||||||
|
|
||||||
function ProgramPenghijauan() {
|
function ProgramPenghijauan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Program Penghijauan'
|
title='Program Penghijauan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari program penghijauan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -41,18 +66,10 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [page])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
|
||||||
item.icon.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconMap: Record<string, React.FC<any>> = {
|
const iconMap: Record<string, React.FC<any>> = {
|
||||||
ekowisata: IconLeaf,
|
ekowisata: IconLeaf,
|
||||||
@@ -73,93 +90,104 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
|||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={650} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Paper p="md" >
|
|
||||||
<Stack>
|
|
||||||
<JudulList
|
|
||||||
title='List Program Penghijauan'
|
|
||||||
href='/admin/lingkungan/program-penghijauan/create'
|
|
||||||
/>
|
|
||||||
<Table striped withTableBorder withRowBorders>
|
|
||||||
<TableThead>
|
|
||||||
<TableTr>
|
|
||||||
<TableTh>No</TableTh>
|
|
||||||
<TableTh>Nama Program Penghijauan</TableTh>
|
|
||||||
<TableTh>Judul Deskripsi Program Penghijauan</TableTh>
|
|
||||||
<TableTh>Ikon</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
</Table>
|
|
||||||
<Text ta="center">Tidak ada data program penghijauan yang tersedia</Text>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box >
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'} h={{ base: 'auto', md: 650 }}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
{/* Header Section */}
|
||||||
title='List Program Penghijauan'
|
<Box mb="md" display="flex" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
href='/admin/lingkungan/program-penghijauan/create'
|
<Title order={4}>Daftar Program Penghijauan</Title>
|
||||||
/>
|
<Tooltip label="Tambah Program Penghijauan" withArrow>
|
||||||
<Box style={{ overflowY: 'auto' }}>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/lingkungan/program-penghijauan/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Table Section */}
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Nama Program Penghijauan</TableTh>
|
<TableTh style={{ width: '25%' }}>Nama Program</TableTh>
|
||||||
<TableTh style={{ width: '35%' }}>Judul Deskripsi Program Penghijauan</TableTh>
|
<TableTh style={{ width: '35%' }}>Judul / Deskripsi</TableTh>
|
||||||
<TableTh style={{ width: '10%' }}>Ikon</TableTh>
|
<TableTh style={{ width: '15%', textAlign: 'center' }}>Ikon</TableTh>
|
||||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
|
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item, index) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item, index) => (
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>
|
<TableTd>
|
||||||
<Text lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.judul }}/>
|
<Text fw={500} truncate="end" lineClamp={1}>{item.name}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '10%' }}>
|
<TableTd>
|
||||||
{iconMap[item.icon] && (
|
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.judul }} />
|
||||||
<Box title={item.icon}>
|
</TableTd>
|
||||||
{React.createElement(iconMap[item.icon], { size: 24 })}
|
<TableTd style={{ textAlign: 'center' }}>
|
||||||
</Box>
|
{iconMap[item.icon] && (
|
||||||
)}
|
<Box title={item.icon} mx="auto">
|
||||||
</TableTd>
|
{React.createElement(iconMap[item.icon], { size: 22 })}
|
||||||
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
</Box>
|
||||||
<Button onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${item.id}`)}>
|
)}
|
||||||
<IconDeviceImac size={25} />
|
</TableTd>
|
||||||
</Button>
|
<TableTd style={{ textAlign: 'center' }}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconDeviceImac size={16} />}
|
||||||
|
onClick={() => router.push(`/admin/lingkungan/program-penghijauan/${item.id}`)}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data program penghijauan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProgramPenghijauan;
|
export default ProgramPenghijauan;
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ function Page() {
|
|||||||
<Image
|
<Image
|
||||||
pt={{ base: 0, md: 60 }}
|
pt={{ base: 0, md: 60 }}
|
||||||
src={item.image?.link || "/perbekel.png"}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
w={{ base: 250, md: 350 }}
|
w="100%"
|
||||||
|
maw={300}
|
||||||
alt="Foto Profil PPID"
|
alt="Foto Profil PPID"
|
||||||
radius="md"
|
radius="md"
|
||||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ function ListPegawaiPPID({ search }: { search: string }) {
|
|||||||
).map((item) => (
|
).map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Box w={150}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.namaLengkap}
|
{item.namaLengkap}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Box w={150}>
|
||||||
<Badge variant="light" color="blue">
|
<Badge variant="light" color="blue">
|
||||||
{item.posisi?.nama || 'Belum diatur'}
|
{item.posisi?.nama || 'Belum diatur'}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Posisi Organisasi PPID</Title>
|
<Title order={4}>Daftar Posisi Organisasi PPID</Title>
|
||||||
<Tooltip label="Tambah Posisi Organisasi" withArrow>
|
<Tooltip label="Tambah Posisi Organisasi" withArrow>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
|
onClick={() => router.push('/admin/ppid/struktur-ppid/posisi-organisasi/create')}
|
||||||
>
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
@@ -82,51 +82,54 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '25%' }}>Nama Posisi</TableTh>
|
<TableTh style={{ width: '20%' }}>Nama Posisi</TableTh>
|
||||||
<TableTh style={{ width: '45%' }}>Deskripsi</TableTh>
|
<TableTh style={{ width: '20%' }}>Deskripsi</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Hierarki</TableTh>
|
<TableTh style={{ width: '20%' }}>Hierarki</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '25%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
<Text fw={500} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '45%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text lineClamp={2} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
<Box w={200}>
|
||||||
|
<Text lineClamp={1} fz="sm" c="dimmed" dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text>{item.hierarki || '-'}</Text>
|
<Text>{item.hierarki || '-'}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Group gap="xs">
|
<Tooltip label="Edit" withArrow>
|
||||||
<Tooltip label="Edit" withArrow>
|
<Button
|
||||||
<Button
|
variant="light"
|
||||||
variant="light"
|
color="green"
|
||||||
color="green"
|
size="sm"
|
||||||
size="sm"
|
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
||||||
onClick={() => router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`)}
|
>
|
||||||
>
|
<IconEdit size={18} />
|
||||||
<IconEdit size={18} />
|
</Button>
|
||||||
</Button>
|
</Tooltip>
|
||||||
</Tooltip>
|
</TableTd>
|
||||||
<Tooltip label="Hapus" withArrow>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Button
|
<Tooltip label="Hapus" withArrow>
|
||||||
variant="light"
|
<Button
|
||||||
color="red"
|
variant="light"
|
||||||
size="sm"
|
color="red"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
setSelectedId(item.id);
|
onClick={() => {
|
||||||
setModalHapus(true);
|
setSelectedId(item.id);
|
||||||
}}
|
setModalHapus(true);
|
||||||
>
|
}}
|
||||||
<IconTrash size={18} />
|
>
|
||||||
</Button>
|
<IconTrash size={18} />
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Group>
|
</Tooltip>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import KategoriPotensi from "./potensi/kategori-potensi";
|
|||||||
import KategoriBerita from "./berita/kategori-berita";
|
import KategoriBerita from "./berita/kategori-berita";
|
||||||
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
||||||
import MantanPerbekel from "./profile/profile-mantan-perbekel";
|
import MantanPerbekel from "./profile/profile-mantan-perbekel";
|
||||||
|
import AjukanPermohonan from "./layanan/ajukan_permohonan";
|
||||||
|
|
||||||
|
|
||||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||||
@@ -26,6 +27,7 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
|||||||
.use(KategoriPotensi)
|
.use(KategoriPotensi)
|
||||||
.use(KategoriBerita)
|
.use(KategoriBerita)
|
||||||
.use(KategoriPengumuman)
|
.use(KategoriPengumuman)
|
||||||
|
.use(AjukanPermohonan)
|
||||||
|
|
||||||
|
|
||||||
export default Desa;
|
export default Desa;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormCreate = {
|
||||||
|
nama: string;
|
||||||
|
nik: string;
|
||||||
|
alamat: string;
|
||||||
|
nomorKk: string;
|
||||||
|
kategoriId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function createAjukanPermohonan(context: Context){
|
||||||
|
const body = context.body as FormCreate;
|
||||||
|
|
||||||
|
await prisma.ajukanPermohonan.create({
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
nik: body.nik,
|
||||||
|
alamat: body.alamat,
|
||||||
|
nomorKk: body.nomorKk,
|
||||||
|
kategoriId: body.kategoriId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Ajukan permohonan berhasil dibuat',
|
||||||
|
data: {
|
||||||
|
...body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function deleteAjukanPermohonan(context: Context) {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
message: "ID tidak diberikan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.ajukanPermohonan.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
message: "Ajukan permohonan berhasil dihapus",
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
// /api/berita/findManyPaginated.ts
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function findManyAjukanPermohonan(context: Context) {
|
||||||
|
// Ambil parameter dari query
|
||||||
|
const page = Number(context.query.page) || 1;
|
||||||
|
const limit = Number(context.query.limit) || 10;
|
||||||
|
const search = (context.query.search as string) || '';
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Buat where clause
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ nama: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ nik: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ alamat: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ nomorKk: { contains: search, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.ajukanPermohonan.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
kategori: true
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'asc' },
|
||||||
|
}),
|
||||||
|
prisma.ajukanPermohonan.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil data dengan pagination",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findMany paginated:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default findManyAjukanPermohonan;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function findUniqueAjukanPermohonan(request: Request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const pathSegments = url.pathname.split("/");
|
||||||
|
const id = pathSegments[pathSegments.length - 1];
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak boleh kosong",
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof id !== "string") {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak valid",
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await prisma.ajukanPermohonan.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
kategori: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: "Ajukan permohonan tidak ditemukan",
|
||||||
|
},
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
message: "Success fetch ajukan permohonan by ID",
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Find by ID error:", e);
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message:
|
||||||
|
"Gagal mengambil ajukan permohonan: " +
|
||||||
|
(e instanceof Error ? e.message : "Unknown error"),
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import createAjukanPermohonan from "./create";
|
||||||
|
import findManyAjukanPermohonan from "./findMany";
|
||||||
|
import findUniqueAjukanPermohonan from "./findUnique";
|
||||||
|
import updateAjukanPermohonan from "./updt";
|
||||||
|
import deleteAjukanPermohonan from "./del";
|
||||||
|
|
||||||
|
|
||||||
|
const AjukanPermohonan = new Elysia({
|
||||||
|
prefix: "ajukanpermohonan",
|
||||||
|
tags: ["Desa/Layanan/AjukanPermohonan"],
|
||||||
|
})
|
||||||
|
.get("/findMany", findManyAjukanPermohonan)
|
||||||
|
.post("/create", createAjukanPermohonan, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
nik: t.String(),
|
||||||
|
alamat: t.String(),
|
||||||
|
nomorKk: t.String(),
|
||||||
|
kategoriId: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.get("/:id", findUniqueAjukanPermohonan)
|
||||||
|
.put("/:id", updateAjukanPermohonan, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
nik: t.String(),
|
||||||
|
alamat: t.String(),
|
||||||
|
nomorKk: t.String(),
|
||||||
|
kategoriId: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.delete("/del/:id", deleteAjukanPermohonan)
|
||||||
|
|
||||||
|
|
||||||
|
export default AjukanPermohonan;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormUpdate = {
|
||||||
|
id: string;
|
||||||
|
nama: string;
|
||||||
|
nik: string;
|
||||||
|
alamat: string;
|
||||||
|
nomorKk: string;
|
||||||
|
kategoriId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function updateAjukanPermohonan(context: Context) {
|
||||||
|
const id = context.params?.id;
|
||||||
|
const body = context.body as FormUpdate;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak diberikan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.ajukanPermohonan.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
kategori: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Ajukan permohonan tidak ditemukan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await prisma.ajukanPermohonan.update({
|
||||||
|
where: { id },
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success update ajukan permohonan",
|
||||||
|
data: updated,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function pelayananSuratKeteranganFindManyAll() {
|
||||||
|
try {
|
||||||
|
const data = await prisma.pelayananSuratKeterangan.findMany({
|
||||||
|
where: { isActive: true },
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
include: {
|
||||||
|
image: true,
|
||||||
|
image2: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil semua data pelayanan surat keterangan",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findManyAll:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data pelayanan surat keterangan",
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,12 @@ import pelayananSuratKeteranganFindUnique from "./findUnique";
|
|||||||
import pelayananSuratKeteranganCreate from "./create";
|
import pelayananSuratKeteranganCreate from "./create";
|
||||||
import pelayananSuratKeteranganUpdate from "./updt";
|
import pelayananSuratKeteranganUpdate from "./updt";
|
||||||
import pelayananSuratKeteranganDelete from "./del";
|
import pelayananSuratKeteranganDelete from "./del";
|
||||||
|
import pelayananSuratKeteranganFindManyAll from "./findManyAll";
|
||||||
import { t } from "elysia";
|
import { t } from "elysia";
|
||||||
|
|
||||||
const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan", tags: ["Desa/Layanan/Pelayanan Surat Keterangan"] })
|
const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan", tags: ["Desa/Layanan/Pelayanan Surat Keterangan"] })
|
||||||
.get("/find-many", pelayananSuratKeteranganFindMany)
|
.get("/find-many", pelayananSuratKeteranganFindMany)
|
||||||
|
.get("/findManyAll", pelayananSuratKeteranganFindManyAll)
|
||||||
.get("/:id", async (context) => {
|
.get("/:id", async (context) => {
|
||||||
const response = await pelayananSuratKeteranganFindUnique(new Request(context.request));
|
const response = await pelayananSuratKeteranganFindUnique(new Request(context.request));
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ type JadwalKegiatanInput = {
|
|||||||
layananJadwalKegiatan: { content: string };
|
layananJadwalKegiatan: { content: string };
|
||||||
syaratKetentuanJadwalKegiatan: { content: string };
|
syaratKetentuanJadwalKegiatan: { content: string };
|
||||||
dokumenJadwalKegiatan: { content: string };
|
dokumenJadwalKegiatan: { content: string };
|
||||||
pendaftaranJadwalKegiatan: { name: string, tanggal: string, namaOrangtua: string, nomor: string, alamat: string, catatan: string };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jadwalKegiatanCreate = async (context: Context) => {
|
const jadwalKegiatanCreate = async (context: Context) => {
|
||||||
@@ -21,16 +20,14 @@ const jadwalKegiatanCreate = async (context: Context) => {
|
|||||||
layananJadwalKegiatan,
|
layananJadwalKegiatan,
|
||||||
syaratKetentuanJadwalKegiatan,
|
syaratKetentuanJadwalKegiatan,
|
||||||
dokumenJadwalKegiatan,
|
dokumenJadwalKegiatan,
|
||||||
pendaftaranJadwalKegiatan,
|
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
const [createdInformasiJadwalKegiatan, createdDeskripsiJadwalKegiatan, createdLayananJadwalKegiatan, createdSyaratKetentuanJadwalKegiatan, createdDokumenJadwalKegiatan, createdPendaftaranJadwalKegiatan] = await Promise.all([
|
const [createdInformasiJadwalKegiatan, createdDeskripsiJadwalKegiatan, createdLayananJadwalKegiatan, createdSyaratKetentuanJadwalKegiatan, createdDokumenJadwalKegiatan] = await Promise.all([
|
||||||
prisma.informasiJadwalKegiatan.create({ data: informasiJadwalKegiatan }),
|
prisma.informasiJadwalKegiatan.create({ data: informasiJadwalKegiatan }),
|
||||||
prisma.deskripsiJadwalKegiatan.create({ data: deskripsiJadwalKegiatan }),
|
prisma.deskripsiJadwalKegiatan.create({ data: deskripsiJadwalKegiatan }),
|
||||||
prisma.layananJadwalKegiatan.create({ data: layananJadwalKegiatan }),
|
prisma.layananJadwalKegiatan.create({ data: layananJadwalKegiatan }),
|
||||||
prisma.syaratKetentuanJadwalKegiatan.create({ data: syaratKetentuanJadwalKegiatan }),
|
prisma.syaratKetentuanJadwalKegiatan.create({ data: syaratKetentuanJadwalKegiatan }),
|
||||||
prisma.dokumenJadwalKegiatan.create({ data: dokumenJadwalKegiatan }),
|
prisma.dokumenJadwalKegiatan.create({ data: dokumenJadwalKegiatan }),
|
||||||
prisma.pendaftaranJadwalKegiatan.create({ data: pendaftaranJadwalKegiatan }),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const jadwalKegiatan = await prisma.jadwalKegiatan.create({
|
const jadwalKegiatan = await prisma.jadwalKegiatan.create({
|
||||||
@@ -41,7 +38,6 @@ const jadwalKegiatanCreate = async (context: Context) => {
|
|||||||
layananJadwalKegiatanId: createdLayananJadwalKegiatan.id,
|
layananJadwalKegiatanId: createdLayananJadwalKegiatan.id,
|
||||||
syaratKetentuanJadwalKegiatanId: createdSyaratKetentuanJadwalKegiatan.id,
|
syaratKetentuanJadwalKegiatanId: createdSyaratKetentuanJadwalKegiatan.id,
|
||||||
dokumenJadwalKegiatanId: createdDokumenJadwalKegiatan.id,
|
dokumenJadwalKegiatanId: createdDokumenJadwalKegiatan.id,
|
||||||
pendaftaranJadwalKegiatanId: createdPendaftaranJadwalKegiatan.id,
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
informasijadwalkegiatan: true,
|
informasijadwalkegiatan: true,
|
||||||
@@ -49,7 +45,6 @@ const jadwalKegiatanCreate = async (context: Context) => {
|
|||||||
layananjadwalkegiatan: true,
|
layananjadwalkegiatan: true,
|
||||||
syaratketentuanjadwalkegiatan: true,
|
syaratketentuanjadwalkegiatan: true,
|
||||||
dokumenjadwalkegiatan: true,
|
dokumenjadwalkegiatan: true,
|
||||||
pendaftaranjadwalkegiatan: true,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const jadwalKegiatanDelete = async (context: Context) => {
|
|||||||
layananjadwalkegiatan: true,
|
layananjadwalkegiatan: true,
|
||||||
syaratketentuanjadwalkegiatan: true,
|
syaratketentuanjadwalkegiatan: true,
|
||||||
dokumenjadwalkegiatan: true,
|
dokumenjadwalkegiatan: true,
|
||||||
pendaftaranjadwalkegiatan: true,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export default async function jadwalKegiatanFindMany(context: Context) {
|
|||||||
{layananjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
{layananjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
{syaratketentuanjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
{syaratketentuanjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
{dokumenjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
{dokumenjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
||||||
{pendaftaranjadwalkegiatan: { content: { contains: search, mode: "insensitive" } } },
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -33,8 +32,7 @@ export default async function jadwalKegiatanFindMany(context: Context) {
|
|||||||
layananjadwalkegiatan: true,
|
layananjadwalkegiatan: true,
|
||||||
syaratketentuanjadwalkegiatan: true,
|
syaratketentuanjadwalkegiatan: true,
|
||||||
dokumenjadwalkegiatan: true,
|
dokumenjadwalkegiatan: true,
|
||||||
pendaftaranjadwalkegiatan: true,
|
},
|
||||||
},
|
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: "desc" },
|
orderBy: { createdAt: "desc" },
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default async function jadwalKegiatanFindUnique(request: Request) {
|
|||||||
layananjadwalkegiatan: true,
|
layananjadwalkegiatan: true,
|
||||||
syaratketentuanjadwalkegiatan: true,
|
syaratketentuanjadwalkegiatan: true,
|
||||||
dokumenjadwalkegiatan: true,
|
dokumenjadwalkegiatan: true,
|
||||||
pendaftaranjadwalkegiatan: true,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -30,14 +30,6 @@ const JadwalKegiatan = new Elysia({
|
|||||||
dokumenJadwalKegiatan: t.Object({
|
dokumenJadwalKegiatan: t.Object({
|
||||||
content: t.String(),
|
content: t.String(),
|
||||||
}),
|
}),
|
||||||
pendaftaranJadwalKegiatan: t.Object({
|
|
||||||
name: t.String(),
|
|
||||||
tanggal: t.String(),
|
|
||||||
namaOrangtua: t.String(),
|
|
||||||
nomor: t.String(),
|
|
||||||
alamat: t.String(),
|
|
||||||
catatan: t.String(),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.get("/find-many", jadwalKegiatanFindMany)
|
.get("/find-many", jadwalKegiatanFindMany)
|
||||||
@@ -75,14 +67,6 @@ const JadwalKegiatan = new Elysia({
|
|||||||
dokumenJadwalKegiatan: t.Object({
|
dokumenJadwalKegiatan: t.Object({
|
||||||
content: t.String(),
|
content: t.String(),
|
||||||
}),
|
}),
|
||||||
pendaftaranJadwalKegiatan: t.Object({
|
|
||||||
name: t.String(),
|
|
||||||
tanggal: t.String(),
|
|
||||||
namaOrangtua: t.String(),
|
|
||||||
nomor: t.String(),
|
|
||||||
alamat: t.String(),
|
|
||||||
catatan: t.String(),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ type JadwalKegiatanUpdateInput = {
|
|||||||
layananJadwalKegiatan: { content: string };
|
layananJadwalKegiatan: { content: string };
|
||||||
syaratKetentuanJadwalKegiatan: { content: string };
|
syaratKetentuanJadwalKegiatan: { content: string };
|
||||||
dokumenJadwalKegiatan: { content: string };
|
dokumenJadwalKegiatan: { content: string };
|
||||||
pendaftaranJadwalKegiatan: {
|
|
||||||
name: string;
|
|
||||||
tanggal: string;
|
|
||||||
namaOrangtua: string;
|
|
||||||
nomor: string;
|
|
||||||
alamat: string;
|
|
||||||
catatan: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const jadwalKegiatanUpdate = async (context: Context) => {
|
const jadwalKegiatanUpdate = async (context: Context) => {
|
||||||
@@ -50,7 +42,6 @@ const jadwalKegiatanUpdate = async (context: Context) => {
|
|||||||
layananJadwalKegiatan,
|
layananJadwalKegiatan,
|
||||||
syaratKetentuanJadwalKegiatan,
|
syaratKetentuanJadwalKegiatan,
|
||||||
dokumenJadwalKegiatan,
|
dokumenJadwalKegiatan,
|
||||||
pendaftaranJadwalKegiatan,
|
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -74,10 +65,6 @@ const jadwalKegiatanUpdate = async (context: Context) => {
|
|||||||
where: { id: existing.dokumenJadwalKegiatanId },
|
where: { id: existing.dokumenJadwalKegiatanId },
|
||||||
data: dokumenJadwalKegiatan
|
data: dokumenJadwalKegiatan
|
||||||
}),
|
}),
|
||||||
prisma.pendaftaranJadwalKegiatan.update({
|
|
||||||
where: { id: existing.pendaftaranJadwalKegiatanId },
|
|
||||||
data: pendaftaranJadwalKegiatan,
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updated = await prisma.jadwalKegiatan.update({
|
const updated = await prisma.jadwalKegiatan.update({
|
||||||
@@ -91,7 +78,6 @@ const jadwalKegiatanUpdate = async (context: Context) => {
|
|||||||
layananjadwalkegiatan: true,
|
layananjadwalkegiatan: true,
|
||||||
syaratketentuanjadwalkegiatan: true,
|
syaratketentuanjadwalkegiatan: true,
|
||||||
dokumenjadwalkegiatan: true,
|
dokumenjadwalkegiatan: true,
|
||||||
pendaftaranjadwalkegiatan: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,15 +1,53 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function kategoriKegiatanFindMany() {
|
export default async function kategoriKegiatanFindMany(context: Context) {
|
||||||
const data = await prisma.kategoriKegiatan.findMany();
|
const page = Number(context.query.page) || 1;
|
||||||
return {
|
const limit = Number(context.query.limit) || 10;
|
||||||
success: true,
|
const search = (context.query.search as string) || "";
|
||||||
data: data.map((item: any) => {
|
const skip = (page - 1) * limit;
|
||||||
return {
|
|
||||||
id: item.id,
|
// Buat where clause
|
||||||
nama: item.nama,
|
const where: any = { isActive: true };
|
||||||
}
|
|
||||||
}),
|
// Tambahkan pencarian (jika ada)
|
||||||
|
if (search) {
|
||||||
|
where.OR = [{ nama: { contains: search, mode: "insensitive" } }];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.kategoriKegiatan.findMany({
|
||||||
|
where: where,
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
}),
|
||||||
|
prisma.kategoriKegiatan.count({
|
||||||
|
where: where,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
message: "Success fetch administrasi online with pagination",
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
total,
|
||||||
};
|
};
|
||||||
}
|
} catch (e) {
|
||||||
|
console.error("Find many paginated error:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed fetch administrasi online with pagination",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default async function pengelolaanSampahFindMany(context: Context) {
|
|||||||
where,
|
where,
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'asc' },
|
||||||
}),
|
}),
|
||||||
prisma.pengelolaanSampah.count({
|
prisma.pengelolaanSampah.count({
|
||||||
where,
|
where,
|
||||||
|
|||||||
@@ -1,105 +1,83 @@
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Image, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
import { Stack, Box, Container, Grid, GridCol, Group, Paper, TextInput, Text, Image, Flex, Button } from '@mantine/core';
|
||||||
import BackButton from '../darmasaba/(pages)/desa/layanan/_com/BackButto';
|
import { IconCalendar, IconMapPin, IconSearch, IconUsersGroup } from '@tabler/icons-react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
const data1 = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
judul: 'Peran Pecalang dalam Keamanan Desa',
|
|
||||||
image: '/api/img/pecalang.png',
|
|
||||||
pengertian: 'Pecalang adalah petugas keamanan adat di Bali yang memiliki peran penting dalam menjaga ketertiban dan budaya lokal. Tugas mereka meliputi:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengamankan upacara adat dan kegiatan keagamaan.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengatur lalu lintas saat ada perayaan atau kegiatan besar.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli untuk mencegah gangguan keamanan di lingkungan desa.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berkoordinasi dengan aparat desa dan kepolisian dalam penanganan situasi darurat.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
judul: 'Patwal (Patroli Pengawal) Desa',
|
|
||||||
image: '/api/img/patwal-1.png',
|
|
||||||
pengertian: 'Selain Pecalang, Desa Darmasaba juga memiliki Patwal yang bertugas menjaga keamanan sehari-hari. Peran mereka antara lain:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli secara rutin untuk memastikan lingkungan tetap aman.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Menjaga ketertiban lalu lintas di area desa.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Melakukan tindakan preventif terhadap potensi gangguan keamanan.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Siap siaga dalam keadaan darurat untuk membantu warga.</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
judul: 'Layanan Keamanan yang Tersedia',
|
|
||||||
image: '/api/img/pospecalang.png',
|
|
||||||
pengertian: 'Jika terjadi keadaan darurat atau membutuhkan bantuan keamanan, warga dapat menghubungi:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Pos Pecalang Desa: [Masukkan Nomor Kontak].</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Patwal Desa Darmasaba: [Masukkan Nomor Kontak].</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Polsek Terdekat: 110 (Layanan Kepolisian).</ListItem>
|
|
||||||
</List>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
judul: 'Program Keamanan Desa',
|
|
||||||
image: '/api/img/rond.png',
|
|
||||||
pengertian: 'Untuk meningkatkan keamanan, Desa Darmasaba menjalankan berbagai program, seperti:',
|
|
||||||
deskripsi: <List>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Ronda Malam Warga: Kegiatan jaga malam secara bergilir oleh warga bersama Pecalang dan Patwal.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}>Sosialisasi Keamanan: Edukasi bagi warga tentang cara menjaga keamanan lingkungan.</ListItem>
|
|
||||||
<ListItem fz={{ base: 'h4', md: 'lg' }}> Pengawasan Kamera CCTV: Memantau titik- titik strategis untuk mencegah tindak kejahatan.</ListItem>
|
|
||||||
</List>
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
{/* Header */}
|
||||||
<BackButton />
|
<Container size="lg" px="md">
|
||||||
</Box>
|
<Stack align="center" gap={0} mb="xl">
|
||||||
<Box>
|
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
Program Gotong Royong
|
||||||
Keamanan Lingkungan (Pecalang / Patwal)
|
</Text>
|
||||||
</Text>
|
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||||
<Text px={{ base: 20, md: 150 }} ta={"center"} fz={{ base: "h4", md: "h3" }} >
|
Desa Darmasaba
|
||||||
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal). Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
|
</Text>
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
|
||||||
<Stack gap={'lg'}>
|
|
||||||
<SimpleGrid
|
|
||||||
pb={10}
|
|
||||||
cols={{
|
|
||||||
base: 1,
|
|
||||||
md: 3,
|
|
||||||
}}>
|
|
||||||
{data1.map((v, k) => {
|
|
||||||
return (
|
|
||||||
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
|
|
||||||
<Stack gap={'xs'}>
|
|
||||||
<Center px={10} py={20}>
|
|
||||||
<Image src={v.image} alt='' />
|
|
||||||
</Center>
|
|
||||||
<Box px={'lg'}>
|
|
||||||
<Box pb={20}>
|
|
||||||
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
|
|
||||||
{v.judul}
|
|
||||||
</Text>
|
|
||||||
<Text pb={10} fz={"h4"} ta={'justify'}>
|
|
||||||
{v.pengertian}
|
|
||||||
</Text>
|
|
||||||
<Box px={10}>
|
|
||||||
{v.deskripsi}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
{/* Tabs Menu */}
|
||||||
|
<Box px={{ base: "md", md: "xl" }} py="md" bg={colors['BG-trans']} mb="md">
|
||||||
|
<Grid align="center" justify="space-between" mb={20}>
|
||||||
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
|
<Group gap="md" wrap="wrap">
|
||||||
|
<Paper bg={colors['blue-button']} radius="xl" py={5} px={20}>
|
||||||
|
<Text c={colors['white-1']} size="sm">
|
||||||
|
Semua
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
{['Kebersihan', 'Infrastruktur', 'Sosial', 'Lingkungan'].map((kategori) => (
|
||||||
|
<Paper key={kategori} bg={colors['blue-button-trans']} radius="xl" py={5} px={20}>
|
||||||
|
<Text size="sm">
|
||||||
|
{kategori}
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
|
<TextInput
|
||||||
|
radius="lg"
|
||||||
|
placeholder="Cari Program Gotong Royong"
|
||||||
|
leftSection={<IconSearch size={18} />}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
||||||
|
<Stack gap={'xs'}>
|
||||||
|
<Image radius={20} src={'/api/img/gotong-royong.png'} w={'100%'} alt='' />
|
||||||
|
<Text fw={"bold"} fz={{ base: "h2", md: "h1" }}>Membangun Fasilitas Desa</Text>
|
||||||
|
<Group>
|
||||||
|
<Paper py={5} px={20} bg={colors['blue-button-trans']} radius={20}>
|
||||||
|
<Text c={colors['white-1']}>Sosial</Text>
|
||||||
|
</Paper>
|
||||||
|
</Group>
|
||||||
|
<Text fz={{ base: "h4", md: "h3" }}>
|
||||||
|
Program Pembangunan Fasilitas Desa Maju, Masyarakat Sejahtera.
|
||||||
|
</Text>
|
||||||
|
<Flex gap={5} align={'center'}>
|
||||||
|
<IconCalendar color={colors['blue-button-trans']} size={45} />
|
||||||
|
<Text fz={{ base: "h4", md: "h3" }}>1 April 2025</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={5} align={'center'}>
|
||||||
|
<IconMapPin color={colors['blue-button-trans']} size={45} />
|
||||||
|
<Text fz={{ base: "h4", md: "h3" }}>Banjar Desa Darmasaba</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={5} align={'center'}>
|
||||||
|
<IconUsersGroup color={colors['blue-button-trans']} size={45} />
|
||||||
|
<Text fz={{ base: "h4", md: "h3" }}>30 Partisipan</Text>
|
||||||
|
</Flex>
|
||||||
|
<Text fw={'bold'} fz={'md'}>Deskripsi : Program pembangunan Pura sebagai pusat spiritual dan budaya desa, melibatkan gotong royong masyarakat dalam pembangunan struktur utama serta ornamen tradisional.</Text>
|
||||||
|
<Group py={20} justify='center'>
|
||||||
|
<Button component={Link} href={'https://www.whatsapp.com/?lang=id'} bg={colors['blue-button']} >Daftar Sebagai Relawan</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Container, Grid, GridCol, ScrollArea, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
import { Box, Group, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -23,95 +23,52 @@ function LayoutTabsBerita({
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Get active tab from URL path
|
|
||||||
const activeTab = pathname.split('/').pop() || 'semua';
|
const activeTab = pathname.split('/').pop() || 'semua';
|
||||||
|
|
||||||
// Get initial search value from URL
|
|
||||||
const initialSearch = searchParams.get('search') || '';
|
const initialSearch = searchParams.get('search') || '';
|
||||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||||
|
|
||||||
// Update active tab state when pathname changes
|
|
||||||
const [activeTabState, setActiveTabState] = useState(activeTab);
|
const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveTabState(activeTab);
|
setActiveTabState(activeTab);
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
// Clean up timeouts on unmount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (searchTimeout !== null) {
|
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [searchTimeout]);
|
}, [searchTimeout]);
|
||||||
|
|
||||||
// Handle search input change with debounce
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
setSearchValue(value);
|
setSearchValue(value);
|
||||||
|
|
||||||
// Clear previous timeout
|
if (searchTimeout !== null) clearTimeout(searchTimeout);
|
||||||
if (searchTimeout !== null) {
|
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set new timeout
|
|
||||||
const newTimeout = window.setTimeout(() => {
|
const newTimeout = window.setTimeout(() => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
if (value) {
|
if (value) params.set('search', value);
|
||||||
params.set('search', value);
|
else params.delete('search');
|
||||||
} else {
|
|
||||||
params.delete('search');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only update URL if the search value has actually changed
|
|
||||||
if (params.toString() !== searchParams.toString()) {
|
if (params.toString() !== searchParams.toString()) {
|
||||||
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||||
}
|
}
|
||||||
}, 500); // 500ms debounce delay
|
}, 500);
|
||||||
|
|
||||||
setSearchTimeout(newTimeout);
|
setSearchTimeout(newTimeout);
|
||||||
};
|
};
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
label: "Semua",
|
|
||||||
value: "semua",
|
|
||||||
href: "/darmasaba/desa/berita/semua"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Budaya",
|
|
||||||
value: "budaya",
|
|
||||||
href: "/darmasaba/desa/berita/budaya"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Pemerintahan",
|
|
||||||
value: "pemerintahan",
|
|
||||||
href: "/darmasaba/desa/berita/pemerintahan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Ekonomi",
|
|
||||||
value: "ekonomi",
|
|
||||||
href: "/darmasaba/desa/berita/ekonomi"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Pembangunan",
|
|
||||||
value: "pembangunan",
|
|
||||||
href: "/darmasaba/desa/berita/pembangunan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Sosial",
|
|
||||||
value: "sosial",
|
|
||||||
href: "/darmasaba/desa/berita/sosial"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Teknologi",
|
|
||||||
value: "teknologi",
|
|
||||||
href: "/darmasaba/desa/berita/teknologi"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ label: "Semua", value: "semua", href: "/darmasaba/desa/berita/semua" },
|
||||||
|
{ label: "Budaya", value: "budaya", href: "/darmasaba/desa/berita/budaya" },
|
||||||
|
{ label: "Pemerintahan", value: "pemerintahan", href: "/darmasaba/desa/berita/pemerintahan" },
|
||||||
|
{ label: "Ekonomi", value: "ekonomi", href: "/darmasaba/desa/berita/ekonomi" },
|
||||||
|
{ label: "Pembangunan", value: "pembangunan", href: "/darmasaba/desa/berita/pembangunan" },
|
||||||
|
{ label: "Sosial", value: "sosial", href: "/darmasaba/desa/berita/sosial" },
|
||||||
|
{ label: "Teknologi", value: "teknologi", href: "/darmasaba/desa/berita/teknologi" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
const tab = tabs.find(t => t.value === value);
|
const tab = tabs.find(t => t.value === value);
|
||||||
@@ -127,16 +84,29 @@ function LayoutTabsBerita({
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md">
|
|
||||||
<Stack align="center" gap="0" >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Group justify='space-between' align="center">
|
||||||
Portal Berita Darmasaba
|
<Stack gap="0">
|
||||||
</Text>
|
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" >
|
||||||
<Text ta="center" px="md">
|
Portal Berita Darmasaba
|
||||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
</Text>
|
||||||
</Text>
|
<Text>
|
||||||
</Stack>
|
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||||
</Container>
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Box>
|
||||||
|
<TextInput
|
||||||
|
radius="lg"
|
||||||
|
placeholder={placeholder}
|
||||||
|
leftSection={searchIcon}
|
||||||
|
w="100%"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors['blue-button']}
|
||||||
@@ -145,33 +115,25 @@ function LayoutTabsBerita({
|
|||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||||
<Grid>
|
{/* SCROLLABLE TABS */}
|
||||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
<Box style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}>
|
||||||
<ScrollArea type="auto" offsetScrollbars>
|
<TabsList style={{ display: 'flex', flexWrap: 'nowrap', gap: '0.5rem' }}>
|
||||||
<TabsList>
|
{tabs.map((tab, index) => (
|
||||||
{tabs.map((tab, index) => (
|
<TabsTab
|
||||||
<TabsTab
|
key={index}
|
||||||
key={index}
|
value={tab.value}
|
||||||
value={tab.value}
|
onClick={() => router.push(tab.href)}
|
||||||
onClick={() => router.push(tab.href)}
|
style={{
|
||||||
>
|
flex: '0 0 auto', // Prevent shrinking
|
||||||
{tab.label}
|
minWidth: 100, // optional: makes them touch-friendly
|
||||||
</TabsTab>
|
textAlign: 'center'
|
||||||
))}
|
}}
|
||||||
</TabsList>
|
>
|
||||||
</ScrollArea>
|
{tab.label}
|
||||||
</GridCol>
|
</TabsTab>
|
||||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
))}
|
||||||
<TextInput
|
</TabsList>
|
||||||
radius="lg"
|
</Box>
|
||||||
placeholder={placeholder}
|
|
||||||
leftSection={searchIcon}
|
|
||||||
w="100%"
|
|
||||||
value={searchValue}
|
|
||||||
onChange={handleSearchChange}
|
|
||||||
/>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
@@ -180,4 +142,4 @@ function LayoutTabsBerita({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabsBerita;
|
export default LayoutTabsBerita;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
|
||||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text } from '@mantine/core';
|
|
||||||
import BackButton from '../../layanan/_com/BackButto';
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import type { SearchBarProps } from './searchBar';
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Container, Stack, Tabs, TabsList, TabsTab, Text } from '@mantine/core';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import BackButton from '../../layanan/_com/BackButto';
|
||||||
|
import type { SearchBarProps } from './searchBar';
|
||||||
|
|
||||||
// Define tabs outside the component to ensure consistency between server and client
|
// Define tabs outside the component to ensure consistency between server and client
|
||||||
const TABS = [
|
const TABS = [
|
||||||
@@ -35,7 +35,7 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [isClient, setIsClient] = useState(false);
|
const [isClient, setIsClient] = useState(false);
|
||||||
|
|
||||||
// Set default active tab to empty string to prevent hydration mismatch
|
// Set default active tab to empty string to prevent hydration mismatch
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
|||||||
// Update active tab based on current route - only on client side
|
// Update active tab based on current route - only on client side
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isClient) return;
|
if (!isClient) return;
|
||||||
|
|
||||||
const currentTab = TABS.find(tab => pathname.includes(tab.value));
|
const currentTab = TABS.find(tab => pathname.includes(tab.value));
|
||||||
if (currentTab) {
|
if (currentTab) {
|
||||||
setActiveTab(currentTab.value);
|
setActiveTab(currentTab.value);
|
||||||
@@ -75,42 +75,38 @@ function LayoutTabsGalery({ children }: HeaderSearchProps) {
|
|||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md">
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack align="center" gap="0">
|
<Stack align="center" gap="0">
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||||
Galeri Kegiatan Desa Darmasaba
|
Galeri Kegiatan Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
<Box>
|
||||||
|
<SearchBar />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={isClient ? activeTab : undefined}
|
value={isClient ? activeTab : undefined}
|
||||||
defaultValue={TABS[0].value}
|
defaultValue={TABS[0].value}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
color={colors['blue-button']}
|
color={colors['blue-button']}
|
||||||
variant="pills"
|
variant="pills"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||||
<Grid>
|
<TabsList>
|
||||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
{TABS.map((tab) => (
|
||||||
<TabsList>
|
<TabsTab
|
||||||
{TABS.map((tab) => (
|
key={tab.value}
|
||||||
<TabsTab
|
value={tab.value}
|
||||||
key={tab.value}
|
component="button"
|
||||||
value={tab.value}
|
type="button"
|
||||||
component="button"
|
>
|
||||||
type="button"
|
{tab.label}
|
||||||
>
|
</TabsTab>
|
||||||
{tab.label}
|
))}
|
||||||
</TabsTab>
|
</TabsList>
|
||||||
))}
|
|
||||||
</TabsList>
|
|
||||||
</GridCol>
|
|
||||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
|
||||||
<SearchBar />
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Container size={'xl'}>
|
<Container size={'xl'}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { TextInput } from '@mantine/core';
|
import { Group, TextInput } from '@mantine/core';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
import { usePathname, useSearchParams } from 'next/navigation';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
@@ -65,13 +65,15 @@ export function SearchBar({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<Group justify='center'>
|
||||||
|
<TextInput
|
||||||
radius="lg"
|
radius="lg"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
leftSection={searchIcon}
|
leftSection={searchIcon}
|
||||||
w="100%"
|
w={{ base: '100%', md: '50%' }}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
/>
|
/>
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,24 @@
|
|||||||
|
|
||||||
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Pagination, Paper, SimpleGrid, Spoiler, Stack, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Center,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Spoiler,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
export default function VideoContent() {
|
export default function VideoContent() {
|
||||||
const [expanded, setExpanded] = useState(false);
|
// ✅ expanded state per index
|
||||||
|
const [expandedMap, setExpandedMap] = useState<Record<number, boolean>>({});
|
||||||
const videoState = useSnapshot(stateGallery.video);
|
const videoState = useSnapshot(stateGallery.video);
|
||||||
const {
|
const { data, page, totalPages, loading } = videoState.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
} = videoState.findMany;
|
|
||||||
|
|
||||||
// Handle search and pagination changes
|
// Handle search and pagination changes
|
||||||
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
const loadData = useCallback((pageNum: number, searchTerm: string) => {
|
||||||
@@ -27,24 +32,18 @@ export default function VideoContent() {
|
|||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const urlSearch = urlParams.get('search') || '';
|
const urlSearch = urlParams.get('search') || '';
|
||||||
const urlPage = parseInt(urlParams.get('page') || '1');
|
const urlPage = parseInt(urlParams.get('page') || '1');
|
||||||
|
|
||||||
loadData(urlPage, urlSearch);
|
loadData(urlPage, urlSearch);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle search updates from the search bar
|
|
||||||
const handleSearchUpdate = (e: Event) => {
|
const handleSearchUpdate = (e: Event) => {
|
||||||
const { search } = (e as CustomEvent).detail;
|
const { search } = (e as CustomEvent).detail;
|
||||||
loadData(1, search);
|
loadData(1, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial load
|
|
||||||
handleRouteChange();
|
handleRouteChange();
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
window.addEventListener('popstate', handleRouteChange);
|
window.addEventListener('popstate', handleRouteChange);
|
||||||
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
window.addEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('popstate', handleRouteChange);
|
window.removeEventListener('popstate', handleRouteChange);
|
||||||
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
window.removeEventListener('searchUpdate', handleSearchUpdate as EventListener);
|
||||||
@@ -57,6 +56,13 @@ export default function VideoContent() {
|
|||||||
loadData(newPage, search);
|
loadData(newPage, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleExpanded = (index: number, value: boolean) => {
|
||||||
|
setExpandedMap((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[index]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const dataVideo = data || [];
|
const dataVideo = data || [];
|
||||||
|
|
||||||
if (loading && !data) {
|
if (loading && !data) {
|
||||||
@@ -72,7 +78,13 @@ export default function VideoContent() {
|
|||||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||||
{dataVideo.map((v, k) => (
|
{dataVideo.map((v, k) => (
|
||||||
<Box key={k}>
|
<Box key={k}>
|
||||||
<Paper mb={50} p="md" radius={26} bg={colors['white-trans-1']} w={{ base: '100%', md: '100%' }}>
|
<Paper
|
||||||
|
mb={50}
|
||||||
|
p="md"
|
||||||
|
radius={26}
|
||||||
|
bg={colors['white-trans-1']}
|
||||||
|
w={{ base: '100%', md: '100%' }}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Center>
|
<Center>
|
||||||
<Box
|
<Box
|
||||||
@@ -109,8 +121,8 @@ export default function VideoContent() {
|
|||||||
Hide details
|
Hide details
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
expanded={expanded}
|
expanded={expandedMap[k] || false}
|
||||||
onExpandedChange={setExpanded}
|
onExpandedChange={(val) => toggleExpanded(k, val)}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
ta="justify"
|
ta="justify"
|
||||||
@@ -137,15 +149,15 @@ export default function VideoContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Fix: HAPUS SPASI BERLEBIH DI URL
|
// ✅ Fix: convert YouTube URL ke embed
|
||||||
function convertToEmbedUrl(youtubeUrl: string): string {
|
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||||
try {
|
try {
|
||||||
const url = new URL(youtubeUrl);
|
const url = new URL(youtubeUrl);
|
||||||
const videoId = url.searchParams.get('v');
|
const videoId = url.searchParams.get('v');
|
||||||
if (!videoId) return youtubeUrl;
|
if (!videoId) return youtubeUrl;
|
||||||
return `https://www.youtube.com/embed/${videoId}`; // ✅ tanpa spasi!
|
return `https://www.youtube.com/embed/${videoId}`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error converting YouTube URL to embed:', err);
|
console.error('Error converting YouTube URL to embed:', err);
|
||||||
return youtubeUrl;
|
return youtubeUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Center, Container, Group, Image, Modal, Paper, Select, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../_com/BackButto';
|
import BackButton from '../_com/BackButto';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
|
||||||
interface LayananData {
|
interface LayananData {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -31,7 +32,12 @@ function Page() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [data, setData] = useState<LayananData | null>(null);
|
const [data, setData] = useState<LayananData | null>(null);
|
||||||
|
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const stateCreate = useProxy(stateLayananDesa.ajukanPermohonan);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
state.suratKeterangan.findManyAll.load()
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
try {
|
try {
|
||||||
@@ -48,6 +54,22 @@ function Page() {
|
|||||||
loadData();
|
loadData();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stateCreate.create.form = {
|
||||||
|
nama: '',
|
||||||
|
nik: '',
|
||||||
|
alamat: '',
|
||||||
|
nomorKk: '',
|
||||||
|
kategoriId: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await stateCreate.create.create();
|
||||||
|
resetForm();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Center h="100vh" bg={colors.Bg}>
|
<Center h="100vh" bg={colors.Bg}>
|
||||||
@@ -105,12 +127,76 @@ function Page() {
|
|||||||
size="lg"
|
size="lg"
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
gradient={{ from: '#1C6EA4', to: '#63B3ED' }}
|
||||||
|
onClick={open}
|
||||||
>
|
>
|
||||||
Ajukan Permohonan
|
Ajukan Permohonan
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
radius={0}
|
||||||
|
transitionProps={{ transition: 'fade', duration: 200 }}
|
||||||
|
>
|
||||||
|
<Paper p="md" withBorder>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Ajukan Permohonan</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Nama</Text>}
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
onChange={(val) => (stateCreate.create.form.nama = val.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label={<Text fz="sm" fw="bold">NIK</Text>}
|
||||||
|
placeholder="masukkan NIK"
|
||||||
|
onChange={(val) => (stateCreate.create.form.nik = val.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
onChange={(val) => (stateCreate.create.form.alamat = val.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label={<Text fz="sm" fw="bold">Nomor KK</Text>}
|
||||||
|
placeholder="masukkan Nomor KK"
|
||||||
|
onChange={(val) => (stateCreate.create.form.nomorKk = val.target.value)}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Kategori"
|
||||||
|
placeholder="Pilih kategori"
|
||||||
|
data={stateLayananDesa.suratKeterangan.findManyAll.data?.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}))}
|
||||||
|
value={stateCreate.create.form.kategoriId || null}
|
||||||
|
onChange={(val: string | null) => {
|
||||||
|
if (val) {
|
||||||
|
const selected = stateLayananDesa.suratKeterangan.findMany.data?.find(
|
||||||
|
(item) => item.id === val
|
||||||
|
);
|
||||||
|
if (selected) {
|
||||||
|
stateCreate.create.form.kategoriId = selected.id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stateCreate.create.form.kategoriId = '';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Modal>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function Page() {
|
|||||||
<Container w={{ base: "100%", md: "50%" }} >
|
<Container w={{ base: "100%", md: "50%" }} >
|
||||||
<Stack align="center" gap={0}>
|
<Stack align="center" gap={0}>
|
||||||
{/* Bagian Layanan */}
|
{/* Bagian Layanan */}
|
||||||
<Text fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text fz={{ base: "1.8rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
||||||
Layanan Desa Darmasaba
|
Layanan Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Container, Flex, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Container, Group, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -31,7 +31,7 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Container size="lg" px="md">
|
<Container size="lg" px="md">
|
||||||
<Stack gap="xs" >
|
<Stack gap="xs" >
|
||||||
<Flex justify={"space-between"} align={"center"}>
|
<Group justify={"space-between"} align={"center"}>
|
||||||
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
<Text fz={{ base: "2rem", md: "2rem" }} c={colors["blue-button"]} fw="bold" >
|
||||||
{detail.data?.judul}
|
{detail.data?.judul}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -40,7 +40,7 @@ function Page() {
|
|||||||
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
<Text c={colors['white-1']}>{detail.data?.CategoryPengumuman?.name}</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Group>
|
</Group>
|
||||||
</Flex>
|
</Group>
|
||||||
<Paper bg={colors["white-1"]} p="md">
|
<Paper bg={colors["white-1"]} p="md">
|
||||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: detail.data?.content }} />
|
||||||
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
|
<Text fz={"md"} c={colors["blue-button"]} fw="bold" >
|
||||||
|
|||||||
@@ -1,67 +1,76 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi';
|
import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi';
|
||||||
|
import mitraKolaborasi from '@/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Center, Grid, GridCol, Group, Image, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Center,
|
||||||
|
Grid,
|
||||||
|
GridCol,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
SimpleGrid,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const state = useProxy(kolaborasiInovasiState)
|
const kolabState = useProxy(kolaborasiInovasiState)
|
||||||
|
const mitraState = useProxy(mitraKolaborasi)
|
||||||
|
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [selectedYear, setSelectedYear] = useState<string | null>(null);
|
const [selectedYear, setSelectedYear] = useState<string | null>(null);
|
||||||
|
|
||||||
// Get unique years from the data
|
// Get unique years from kolaborasiInovasi data
|
||||||
const years = Array.from(
|
const years = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
state.findMany.data?.map(item =>
|
kolabState.findMany.data?.map(item =>
|
||||||
new Date(item.createdAt).getFullYear().toString()
|
new Date(item.createdAt).getFullYear().toString()
|
||||||
) || []
|
) || []
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.sort((a, b) => b.localeCompare(a)) // Sort descending (newest first)
|
.sort((a, b) => b.localeCompare(a))
|
||||||
.map(year => ({
|
.map(year => ({ value: year, label: year }));
|
||||||
value: year,
|
|
||||||
label: year,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = kolabState.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = state.findMany
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search, selectedYear || '')
|
mitraState.findMany.load(page, 10);
|
||||||
}, [page, search, selectedYear])
|
load(page, 10, search, selectedYear || '');
|
||||||
|
}, [page, search, selectedYear]);
|
||||||
|
|
||||||
|
const mitraData = mitraState.findMany.data || [];
|
||||||
|
const mitraLoading = mitraState.findMany.loading;
|
||||||
|
|
||||||
if (loading || !data) {
|
|
||||||
return (
|
|
||||||
<Stack py={10}>
|
|
||||||
<Skeleton height={650} />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Header Kolaborasi Inovasi */}
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }} >
|
||||||
<Grid align='center'>
|
<Grid align='center'>
|
||||||
<GridCol span={{ base: 12, md: 9 }}>
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||||
Kolaborasi Inovasi
|
Kolaborasi Inovasi
|
||||||
</Text>
|
</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 3 }}>
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius="lg"
|
||||||
placeholder='Cari Kolaborasi Inovasi'
|
placeholder='Cari Kolaborasi Inovasi'
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
@@ -71,40 +80,43 @@ function Page() {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Filter Tahun */}
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'} justify='center'>
|
<Stack gap="lg" justify="center">
|
||||||
<Group justify='flex-end'>
|
<Group justify="flex-end">
|
||||||
<Select
|
<Select
|
||||||
value={selectedYear}
|
value={selectedYear}
|
||||||
onChange={setSelectedYear}
|
onChange={setSelectedYear}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tahun</Text>}
|
label={<Text fw="bold" fz="sm">Tahun</Text>}
|
||||||
placeholder='Semua Tahun'
|
placeholder='Semua Tahun'
|
||||||
clearable
|
clearable
|
||||||
data={[
|
data={[{ value: '', label: 'Semua Tahun' }, ...years]}
|
||||||
{ value: '', label: 'Semua Tahun' },
|
|
||||||
...years
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing={'lg'}>
|
|
||||||
{data.map((v, k) => {
|
{/* List Kolaborasi Inovasi */}
|
||||||
return (
|
{loading || !data ? (
|
||||||
<Paper p={'xl'} key={k}>
|
<Stack py={10}>
|
||||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.name}</Text>
|
<Skeleton height={650} />
|
||||||
<Box pr={'lg'} pb={20}>
|
</Stack>
|
||||||
{v.slug}
|
) : (
|
||||||
</Box>
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||||
<Box pr={'lg'}>
|
{data.map((v, k) => (
|
||||||
{v.kolaborator}
|
<Paper p="xl" key={k}>
|
||||||
</Box>
|
<Text fz="h3" fw="bold" c={colors['blue-button']}>{v.name}</Text>
|
||||||
|
<Box pr="lg" pb={20}>{v.slug}</Box>
|
||||||
|
<Box pr="lg">{v.kolaborator}</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
))}
|
||||||
})}
|
</SimpleGrid>
|
||||||
</SimpleGrid>
|
)}
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)} // ini penting!
|
onChange={(newPage) => load(newPage)}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
@@ -113,17 +125,44 @@ function Page() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{/* Mitra Kolaborasi Section */}
|
||||||
<Box py={40} px={{ base: "md", md: 100 }} bg={colors['white-trans-1']}>
|
<Box py={40} px={{ base: "md", md: 100 }} bg={colors['white-trans-1']}>
|
||||||
<Stack gap={'lg'} justify='center'>
|
<Stack gap="lg" justify="center">
|
||||||
<Box px={{ base: 'md', md: 100 }} >
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
|
<Text ta="center" fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw="bold">
|
||||||
Mitra Kolaborasi
|
Mitra Kolaborasi
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={'center'} fz={'h4'}>Kami berkolaborasi dengan berbagai mitra dari berbagai sektor untuk mewujudkan visi Smart Village Darmasaba.</Text>
|
<Text ta="center" fz="h4">
|
||||||
|
Kami berkolaborasi dengan berbagai mitra dari berbagai sektor untuk mewujudkan visi Smart Village Darmasaba.
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
|
||||||
<Image src={'/api/img/logoukm-kolaborasiinvoasi.png'} alt='' w={{ base: 500, md: 650 }} loading="lazy"/>
|
{mitraLoading ? (
|
||||||
</Center>
|
<Center py={20}><Skeleton height={100} width="80%" /></Center>
|
||||||
|
) : (
|
||||||
|
<SimpleGrid cols={{ base: 2, md: 4 }} spacing="xl">
|
||||||
|
{mitraData.map((m) => (
|
||||||
|
<Paper key={m.id} p="md" shadow="sm" radius="md">
|
||||||
|
<Center mb="sm">
|
||||||
|
{m.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={`${process.env.NEXT_PUBLIC_BASE_URL || ''}${m.image.link}`}
|
||||||
|
alt={m.name}
|
||||||
|
w={150}
|
||||||
|
h={100}
|
||||||
|
fit="cover"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box w={100} h={100} bg={colors['blue-button']} style={{ borderRadius: '50%' }} />
|
||||||
|
)}
|
||||||
|
</Center>
|
||||||
|
<Text ta="center" fw="bold">{m.name}</Text>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user